Bun, Inferno->React migration (#22529)

Re-creation of https://github.com/Aurorastation/Aurora.3/pull/21046 to
skip merge conflict hell. Brings us modern TGUI.

**ALTERNATE TITLE: TGUI HELLSCAPE PR
ABANDON ALL HOPE YE WHO ENTER HERE**

- [x] Migrate build tools (javascript -> typescript, bun for package
management).
- [x] Upgrade all TGUI dependencies and associated root files to
TG-congruent versions (axios, babel, dompurify, eslint, highlight,
marked, prettier, sass, source-map, stacktrace-parser, typescript).
- [x] InfernoJS -> React migrations
- [x] React cleanup and polish (migrate all remaining .js files to
appropriate .ts or .tsx filetype, all remaining hooks, linting, error
corrections, etc.)
- [ ] Test all remaining TGUI interfaces
This commit is contained in:
Batrachophreno
2026-06-05 15:55:22 +02:00
committed by GitHub
parent a52729c105
commit 0d92359da7
930 changed files with 23130 additions and 50520 deletions
+17
View File
@@ -0,0 +1,17 @@
# This action is a wrapper around `oven-sh/setup-bun` to use the version specified in
# `dependencies.sh`.
name: Setup Bun
description: Install Bun using the version specified in `dependencies.sh`
runs:
using: composite
steps:
- name: Configure Bun version
shell: bash
run: |
source dependencies.sh
echo "BUN_VERSION_REQUIRED=$BUN_VERSION" >> $GITHUB_ENV
- name: Install Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: ${{ env.BUN_VERSION_REQUIRED }}
+26
View File
@@ -0,0 +1,26 @@
# This action is a wrapper around `actions/setup-node`, to use the version specified in
# `dependencies.sh`.
name: Setup Node
description: Install Node using the version specified in `dependencies.sh`; additionally, restores the Yarn cache if one exists
inputs:
restore-yarn-cache:
description: "If `true`, restores the Yarn cache alongside installing node."
required: false
type: boolean
default: false
runs:
using: composite
steps:
- name: Configure Node version
shell: bash
run: |
source dependencies.sh
echo "NODE_VERSION_REQUIRED=$NODE_VERSION_LTS" >> $GITHUB_ENV
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION_REQUIRED }}
cache: ${{ fromJSON(inputs.restore-yarn-cache) && 'yarn' || '' }}
cache-dependency-path: ${{ fromJSON(inputs.restore-yarn-cache) && 'tgui/yarn.lock' || '' }}
+4 -8
View File
@@ -281,18 +281,14 @@ jobs:
- name: Set ENV variables
run: bash dependencies.sh
#Setup NodeJS using YARN as cache
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION_LTS }}
cache: "yarn"
cache-dependency-path: tgui/yarn.lock
#Setup bun
- name: Setup Bun
uses: ./.github/actions/setup_bun
#Lint TGUI
- name: Check Tgui
run: |
tools/build/build --ci lint tgui-test
tools/build/build.sh --ci lint tgui-test
###########################################
############### MISC LINTING ##############
+31
View File
@@ -0,0 +1,31 @@
name: "Build TGUI"
on:
workflow_dispatch:
jobs:
update-tgui:
concurrency: tgui
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-24.04
steps:
- name: Install Bun
uses: ./.github/actions/setup_bun
- name: Update TGUI
run: |
tools/bootstrap/javascript tools/build/build.sh tgui
- name: Commit TGUI
run: |
git pull origin master
git config --local user.email "action@github.com"
git config --local user.name "AuroraBuildBot"
git add --force tgui/public/*
git commit -m "Automatic TGUI compile [ci skip]" -a || true
- name: Push
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.BOT_TOKEN_AURORA }}
+3
View File
@@ -73,3 +73,6 @@ define_sanity_output.txt
# Tracy dumps
*.utracy
# JavaScript tools
**/node_modules
+5 -2
View File
@@ -7,6 +7,9 @@
"vscode.typescript-language-features",
"redhat.vscode-yaml",
"esbenp.prettier-vscode",
"rome.rome"
]
"rome.rome",
"dbaeumer.vscode-eslint",
"editorconfig.editorconfig",
"esbenp.prettier-vscode",
"biomejs.biome"
}
+10 -8
View File
@@ -1,25 +1,27 @@
{
"eslint.nodePath": "./tgui/.yarn/sdks",
"eslint.workingDirectories": ["./tgui"],
"prettier.prettierPath": "./tgui/.yarn/sdks/prettier/index.cjs",
"typescript.tsdk": "./tgui/.yarn/sdks/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"search.exclude": {
"**/.yarn": true,
"**/.pnp.*": true
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
"source.organizeImports.biome": "explicit",
"source.fixAll.biome": "explicit"
},
"editor.insertSpaces": false,
"files.eol": "\n",
"files.insertFinalNewline": true,
"files.trimTrailingWhitespace": true,
"gitlens.advanced.blame.customArguments": ["-w"],
"[javascript][typescript][typescriptreact][javascriptreact][html][scss][css][json][jsonc][markdown][yaml]": {
"editor.rulers": [80],
"[javascript][typescript][javascriptreact][typescriptreact][css][json][jsonc]": {
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true,
"editor.rulers": [80]
},
"[yaml][markdown][html][scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
"editor.formatOnSave": true,
"editor.rulers": [80]
},
"workbench.editorAssociations": {
"*.dmi": "dmiEditor.dmiEditor"
+4 -34
View File
@@ -16,8 +16,7 @@
},
"problemMatcher": [
"$dreammaker",
"$tsc",
"$eslint-stylish"
"$tsc"
],
"group": {
"kind": "build",
@@ -61,8 +60,7 @@
"command": ".\\bin\\tgui-build.cmd"
},
"problemMatcher": [
"$tsc",
"$eslint-stylish"
"$tsc"
],
"group": "build",
"label": "tgui: build"
@@ -74,38 +72,11 @@
"command": ".\\bin\\tgui-dev.cmd"
},
"problemMatcher": [
"$tsc",
"$eslint-stylish"
"$tsc"
],
"group": "build",
"label": "tgui: dev server"
},
{
"type": "shell",
"command": "bin/tgui-bench",
"windows": {
"command": ".\\bin\\tgui-bench.cmd"
},
"problemMatcher": [
"$tsc",
"$eslint-stylish"
],
"group": "build",
"label": "tgui: bench"
},
{
"type": "shell",
"command": "bin/tgui-sonar",
"windows": {
"command": ".\\bin\\tgui-sonar.cmd"
},
"problemMatcher": [
"$tsc",
"$eslint-stylish"
],
"group": "build",
"label": "tgui: sonar"
},
{
"type": "shell",
"command": "bin/tgfont",
@@ -113,8 +84,7 @@
"command": ".\\bin\\tgfont.cmd"
},
"problemMatcher": [
"$tsc",
"$eslint-stylish"
"$tsc"
],
"group": "build",
"label": "tgui: rebuild tgfont"
+9 -9
View File
@@ -155,6 +155,7 @@
#include "code\__DEFINES\targeting.dm"
#include "code\__DEFINES\technomancer.dm"
#include "code\__DEFINES\text.dm"
#include "code\__DEFINES\tgs.config.dm"
#include "code\__DEFINES\tgs.dm"
#include "code\__DEFINES\tgui.dm"
#include "code\__DEFINES\time.dm"
@@ -184,6 +185,7 @@
#include "code\__DEFINES\dcs\signals\signals_record.dm"
#include "code\__DEFINES\dcs\signals\signals_spatial_grid.dm"
#include "code\__DEFINES\dcs\signals\signals_subsystem.dm"
#include "code\__DEFINES\dcs\signals\signals_tgui.dm"
#include "code\__DEFINES\dcs\signals\signals_tools.dm"
#include "code\__DEFINES\dcs\signals\signals_turf.dm"
#include "code\__DEFINES\dcs\signals\signals_weather.dm"
@@ -404,6 +406,7 @@
#include "code\controllers\subsystems\stickyban.dm"
#include "code\controllers\subsystems\sun.dm"
#include "code\controllers\subsystems\sunlight.dm"
#include "code\controllers\subsystems\tgui.dm"
#include "code\controllers\subsystems\throwing.dm"
#include "code\controllers\subsystems\ticker.dm"
#include "code\controllers\subsystems\time_track.dm"
@@ -448,7 +451,6 @@
#include "code\controllers\subsystems\processing\electronics.dm"
#include "code\controllers\subsystems\processing\fast_process.dm"
#include "code\controllers\subsystems\processing\modifiers.dm"
#include "code\controllers\subsystems\processing\nanoui.dm"
#include "code\controllers\subsystems\processing\ntsl2.dm"
#include "code\controllers\subsystems\processing\obj_tab_items.dm"
#include "code\controllers\subsystems\processing\odyssey.dm"
@@ -457,7 +459,6 @@
#include "code\controllers\subsystems\processing\projectiles.dm"
#include "code\controllers\subsystems\processing\psi.dm"
#include "code\controllers\subsystems\processing\shuttle.dm"
#include "code\controllers\subsystems\processing\tgui.dm"
#include "code\datums\ai_law_sets.dm"
#include "code\datums\ai_laws.dm"
#include "code\datums\beam.dm"
@@ -465,6 +466,7 @@
#include "code\datums\callback.dm"
#include "code\datums\cargo.dm"
#include "code\datums\category.dm"
#include "code\datums\chat_payload.dm"
#include "code\datums\computerfiles.dm"
#include "code\datums\datum.dm"
#include "code\datums\datumvars.dm"
@@ -3167,13 +3169,6 @@
#include "code\modules\multiz\zmimic\mimic_common.dm"
#include "code\modules\multiz\zmimic\mimic_movable.dm"
#include "code\modules\multiz\zmimic\mimic_turf.dm"
#include "code\modules\nano\nanoexternal.dm"
#include "code\modules\nano\nanomapgen.dm"
#include "code\modules\nano\nanoui.dm"
#include "code\modules\nano\modules\follow_menu.dm"
#include "code\modules\nano\modules\human_appearance.dm"
#include "code\modules\nano\modules\nano_module.dm"
#include "code\modules\nano\modules\narrate_panel.dm"
#include "code\modules\ntsl2\guide.dm"
#include "code\modules\ntsl2\ntsl2_program.dm"
#include "code\modules\ntsl2\ntsl2_types.dm"
@@ -3936,8 +3931,11 @@
#include "code\modules\tgui\modules\armor_values.dm"
#include "code\modules\tgui\modules\faction_select.dm"
#include "code\modules\tgui\modules\flavor_text.dm"
#include "code\modules\tgui\modules\follow_menu.dm"
#include "code\modules\tgui\modules\hivenet_manifest.dm"
#include "code\modules\tgui\modules\human_appearance.dm"
#include "code\modules\tgui\modules\ipc_diagnostic.dm"
#include "code\modules\tgui\modules\narrate_panel.dm"
#include "code\modules\tgui\modules\neural_configuration.dm"
#include "code\modules\tgui\states\admin.dm"
#include "code\modules\tgui\states\always.dm"
@@ -3960,6 +3958,8 @@
#include "code\modules\tgui\states\self.dm"
#include "code\modules\tgui\states\zlevel.dm"
#include "code\modules\tgui_input\alert.dm"
#include "code\modules\tgui_input\checkboxes.dm"
#include "code\modules\tgui_input\keycombo.dm"
#include "code\modules\tgui_input\list.dm"
#include "code\modules\tgui_input\number.dm"
#include "code\modules\tgui_input\text.dm"
+68
View File
@@ -0,0 +1,68 @@
{
"$schema": "https://biomejs.dev/schemas/2.1.2/schema.json",
"assist": {
"actions": {
"source": {
"organizeImports": "on"
}
},
"enabled": true
},
"css": {
"formatter": {
"quoteStyle": "double"
}
},
"files": {
"ignoreUnknown": false,
"includes": [
"**",
"!**/node_modules",
"!tgui/public",
"tgui/public/tgui.html",
"!tgui/packages/tgui-setup/helpers.js"
]
},
"formatter": {
"enabled": true,
"indentStyle": "tab",
"lineEnding": "lf"
},
"javascript": {
"formatter": {
"indentStyle": "space",
"quoteStyle": "single"
}
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"a11y": "off",
"correctness": {
"noUnusedImports": "warn",
"noUnusedVariables": "off",
"useExhaustiveDependencies": "off",
"noUnusedFunctionParameters": "off",
"useHookAtTopLevel": "off"
},
"security": {
"noDangerouslySetInnerHtml": "warn"
},
"style": {
"noNonNullAssertion": "off",
"useSelfClosingElements": "error"
},
"suspicious": {
"noArrayIndexKey": "off",
"noExplicitAny": "off",
"noImplicitAnyLet": "off"
}
}
},
"vcs": {
"clientKind": "git",
"enabled": false,
"useIgnoreFile": false
}
}
+32
View File
@@ -0,0 +1,32 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"devDependencies": {
"@biomejs/biome": "^2.1.2",
"prettier": "^3.6.2",
},
},
},
"packages": {
"@biomejs/biome": ["@biomejs/biome@2.4.15", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.15", "@biomejs/cli-darwin-x64": "2.4.15", "@biomejs/cli-linux-arm64": "2.4.15", "@biomejs/cli-linux-arm64-musl": "2.4.15", "@biomejs/cli-linux-x64": "2.4.15", "@biomejs/cli-linux-x64-musl": "2.4.15", "@biomejs/cli-win32-arm64": "2.4.15", "@biomejs/cli-win32-x64": "2.4.15" }, "bin": { "biome": "bin/biome" } }, "sha512-j5VH3a/h/HXTKBM50MDMxRCzkeLv9S2XJcW2WgnZT1+xyisi+0bISrXR82gCX+8S9lvK0skEvHJRN+3Ktr2hlw=="],
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.15", "", { "os": "darwin", "cpu": "arm64" }, "sha512-rF3PPqLq1yoST79zaQbDjVJwsuIeci/O+9bgNmC5QpgOqz6aqYuzA4abyAGx+mgyiDXn4A049xAN8gijbuR1Qg=="],
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.15", "", { "os": "darwin", "cpu": "x64" }, "sha512-/5KHXYMfSJs1fNXiX30xFtI8JcCFV6zaVVLxOa0M2sfqBKHkpQhRTv94yxQWxeTY2lzo2OuTlNvPC+hDQt2wcQ=="],
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-owaAMZD/T4LrD0ELNCk0Km3qrRHuM0X6EAyVE1FSqGY0rbLoiDLrO4Us2tllm6cAeB2Ioa9C2C08NZPdr8+0Ug=="],
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-ZPcxznxm0pogHBLZhYntyR3sR+MrZjqJIKEr7ZqVen0Rl+P/4upVmfYXjftizi9RoqZntg33fv/1fbdhbYXpEQ=="],
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.15", "", { "os": "linux", "cpu": "x64" }, "sha512-0jj7THz12GbUOLmMibktK6DZjqz2zV64KFxyBtcFTKPiiOIY0a7vns1elpO1dERvxpsZ5ik0oFfz0oGwFde1+g=="],
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.15", "", { "os": "linux", "cpu": "x64" }, "sha512-CNq/9W38SYSH023lfcQ4KKU8K0YX8T//FZUhcgtMMRABDojx5XsMV7jlweAvGSl389wJQB29Qo6Zb/a+jdvt+w=="],
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.15", "", { "os": "win32", "cpu": "arm64" }, "sha512-ouhkYdlhp/1GghEJPdWwD/Vi3gQ1nFxuSpMolWsbq3Lsq3QUR4jl6UdhhscdCugKU5vOEuMiJhvKj66O0OCq+w=="],
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.15", "", { "os": "win32", "cpu": "x64" }, "sha512-zBrGq5mx5wwpnow4+2BxUvleDM+GNd4sLbPaMapsSLQLD0NGRCquqPBTgN+7XkUteHvj7M+BstuI8tmnV7+HgQ=="],
"prettier": ["prettier@3.8.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw=="],
}
}
@@ -9,6 +9,8 @@
#define COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON "atom_init_success_on"
///from base of atom/examine(): (/mob, list/examine_text)
#define COMSIG_ATOM_EXAMINE "atom_examine"
///from base of atom/examine_tags(): (/mob, list/examine_tags)
#define COMSIG_ATOM_EXAMINE_TAGS "atom_examine_tags"
///from base of atom/Entered(): (atom/movable/arrived, atom/old_loc, list/atom/old_locs)
#define COMSIG_ATOM_ENTERED "atom_entered"
/// Sent from the atom that just Entered src. From base of atom/Entered(): (/atom/destination, atom/old_loc, list/atom/old_locs)
@@ -0,0 +1,2 @@
/// Window is fully visible and we can make fragile calls
#define COMSIG_TGUI_WINDOW_VISIBLE "tgui_window_visible"
+4
View File
@@ -24,6 +24,7 @@
#define SPAN_COLOR(color, str) SPAN_STYLE("color: [color]", "[str]")
#define SPAN_CAUTION(str) ("<span class='caution'>" + str + "</span>")
#define SPAN_STORYTELLER(str) ("<span class='storyteller'>" + str + "</span>")
#define SPAN_BOLDANNOUNCE(str) ("<span class='boldannounce'>" + str + "</span>")
#define SPAN_MACHINE_WARNING(str) ("<span class='machine-warning'>" + str + "</span>")
#define SPAN_MACHINE_DANGER(str) ("<span class='machine-danger'>" + str + "</span>")
@@ -42,6 +43,9 @@
#define SPAN_BOLDNOTICE(str) ("<span class='boldnotice'>" + str + "</span>")
#define SPAN_BOLDWARNING(X) "<span class='boldwarning'>[X]</span>"
// Spans that use embedded tgui components:
#define SPAN_TOOLTIP(tip, main_text) ("<span data-component=\"Tooltip\" data-content=\"" + tip + "\" class=\"tooltip\">" + main_text + "</span>")
/*
#####################
Font sizes
-1
View File
@@ -36,7 +36,6 @@
#define SS_PRIORITY_STATPANELS 25 // Statpanels.
#define SS_PRIORITY_LIGHTING 25 // Queued lighting engine updates.
#define SS_PRIORITY_MACHINERY 25 // Machinery + powernet ticks.
#define SS_PRIORITY_NANOUI 25 // UI updates.
#define SS_PRIORITY_ELECTRONICS 20 // Integrated Electronics processing.
#define SS_PRIORITY_CALAMITY 20 // Singularity, Tesla, Nar'sie, blob, etc.
#define SS_PRIORITY_EVENT 20
+1
View File
@@ -277,6 +277,7 @@
#define FIRE_PRIORITY_SPACEDRIFT 30
#define FIRE_PRIORITY_DEFAULT 50
#define FIRE_PRIORITY_MOBS 100
#define FIRE_PRIORITY_TGUI 110
#define FIRE_PRIORITY_STATPANEL 390
#define FIRE_PRIORITY_CHAT 400
#define FIRE_PRIORITY_RUNECHAT 410
+1 -1
View File
@@ -50,7 +50,7 @@
#define CHAT_MESSAGE_WIDTH 112
// Setting this much higher than 1024 could allow spammers to DOS the server easily.
#define MAX_MESSAGE_LEN 1024
#define MAX_MESSAGE_LEN 4096
#define MAX_PAPER_MESSAGE_LEN 3072
#define MAX_BOOK_MESSAGE_LEN 9216
#define MAX_LNAME_LEN 64
+11
View File
@@ -0,0 +1,11 @@
#define TGS_EXTERNAL_CONFIGURATION
#define TGS_DEFINE_AND_SET_GLOBAL(Name, Value) GLOBAL_VAR_INIT(##Name, ##Value); GLOBAL_PROTECT(##Name)
#define TGS_READ_GLOBAL(Name) GLOB.##Name
#define TGS_WRITE_GLOBAL(Name, Value) GLOB.##Name = ##Value
#define TGS_WORLD_ANNOUNCE(message) to_chat(world, SPAN_BOLDANNOUNCE("[html_encode(##message)]"))
#define TGS_INFO_LOG(message) log_world("TGS Info: [##message]")
#define TGS_WARNING_LOG(message) log_world("TGS Warn: [##message]")
#define TGS_ERROR_LOG(message) stack_trace("TGS Error: [##message]")
#define TGS_NOTIFY_ADMINS(event) message_admins(##event)
#define TGS_CLIENT_COUNT GLOB.clients.len
#define TGS_PROTECT_DATUM(Path) GENERAL_PROTECT_DATUM(##Path)
+132 -44
View File
@@ -1,55 +1,68 @@
// tgstation-server DMAPI
// The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in IETF RFC 2119.
#define TGS_DMAPI_VERSION "6.6.2"
#define TGS_DMAPI_VERSION "7.3.3"
// All functions and datums outside this document are subject to change with any version and should not be relied on.
// CONFIGURATION
/// Create this define if you want to do TGS configuration outside of this file.
/// Consumers SHOULD create this define if you want to do TGS configuration outside of this file.
#ifndef TGS_EXTERNAL_CONFIGURATION
// Comment this out once you've filled in the below.
//#error TGS API unconfigured
// Consumers MUST comment this out once you've filled in the below and are not using [TGS_EXTERNAL_CONFIGURATION].
// #error TGS API unconfigured
// Uncomment this if you wish to allow the game to interact with TGS 3..
// Consumers MUST uncomment this if you wish to allow the game to interact with TGS version 3.
// This will raise the minimum required security level of your game to TGS_SECURITY_TRUSTED due to it utilizing call()().
//#define TGS_V3_API
// Required interfaces (fill in with your codebase equivalent):
/// Create a global variable named `Name` and set it to `Value`.
#define TGS_DEFINE_AND_SET_GLOBAL(Name, Value) var/global/##Name = ##Value
#define TGS_DEFINE_AND_SET_GLOBAL(Name, Value)
/// Read the value in the global variable `Name`.
#define TGS_READ_GLOBAL(Name) global.##Name
#define TGS_READ_GLOBAL(Name)
/// Set the value in the global variable `Name` to `Value`.
#define TGS_WRITE_GLOBAL(Name, Value) global.##Name = ##Value
#define TGS_WRITE_GLOBAL(Name, Value)
/// Disallow ANYONE from reflecting a given `path`, security measure to prevent in-game use of DD -> TGS capabilities.
#define TGS_PROTECT_DATUM(Path)
/// Display an announcement `message` from the server to all players.
#define TGS_WORLD_ANNOUNCE(message) world << "<b>TGS Notification</b>: [html_encode(##message)]"
#define TGS_WORLD_ANNOUNCE(message)
/// Notify current in-game administrators of a string `event`.
#define TGS_NOTIFY_ADMINS(event) message_admins(##event)
#define TGS_NOTIFY_ADMINS(event)
/// Write an info `message` to a server log.
#define TGS_INFO_LOG(message) log_tgs("[##message]")
#define TGS_INFO_LOG(message)
/// Write an warning `message` to a server log.
#define TGS_WARNING_LOG(message) log_tgs("[##message]", SEVERITY_WARNING)
#define TGS_WARNING_LOG(message)
/// Write an error `message` to a server log.
#define TGS_ERROR_LOG(message) log_tgs("[##message]", SEVERITY_ERROR)
#define TGS_ERROR_LOG(message)
/// Get the number of connected /clients.
#define TGS_CLIENT_COUNT clients.len
#define TGS_CLIENT_COUNT
#endif
#ifndef TGS_FILE2TEXT_NATIVE
#ifdef file2text
#error Your codebase is re-defining the BYOND proc file2text. The DMAPI requires the native version to read the result of world.Export(). You SHOULD fix this by adding "#define TGS_FILE2TEXT_NATIVE file2text" before your override of file2text to allow the DMAPI to use the native version. This will only be used for world.Export(), not regular file accesses
#endif
#define TGS_FILE2TEXT_NATIVE file2text
#endif
// SpacemanDMM compatibility
#ifndef CAN_BE_REDEFINED
#define CAN_BE_REDEFINED(X)
#endif
// EVENT CODES
/// Before a reboot mode change, extras parameters are the current and new reboot mode enums.
@@ -73,12 +86,12 @@
#define TGS_EVENT_REPO_MERGE_PULL_REQUEST 3
/// Before the repository makes a sychronize operation. Parameters: Absolute repostiory path.
#define TGS_EVENT_REPO_PRE_SYNCHRONIZE 4
/// Before a BYOND install operation begins. Parameters: [/datum/tgs_version] of the installing BYOND.
#define TGS_EVENT_BYOND_INSTALL_START 5
/// When a BYOND install operation fails. Parameters: Error message
#define TGS_EVENT_BYOND_INSTALL_FAIL 6
/// When the active BYOND version changes. Parameters: (Nullable) [/datum/tgs_version] of the current BYOND, [/datum/tgs_version] of the new BYOND.
#define TGS_EVENT_BYOND_ACTIVE_VERSION_CHANGE 7
/// Before a engine install operation begins. Parameters: Version string of the installing engine.
#define TGS_EVENT_ENGINE_INSTALL_START 5
/// When a engine install operation fails. Parameters: Error message
#define TGS_EVENT_ENGINE_INSTALL_FAIL 6
/// When the active engine version changes. Parameters: (Nullable) Version string of the current engine, version string of the new engine.
#define TGS_EVENT_ENGINE_ACTIVE_VERSION_CHANGE 7
/// When the compiler starts running. Parameters: Game directory path, origin commit SHA.
#define TGS_EVENT_COMPILE_START 8
/// When a compile is cancelled. No parameters.
@@ -108,7 +121,7 @@
// #define TGS_EVENT_DREAM_DAEMON_LAUNCH 22
/// After a single submodule update is performed. Parameters: Updated submodule name.
#define TGS_EVENT_REPO_SUBMODULE_UPDATE 23
/// After CodeModifications are applied, before DreamMaker is run. Parameters: Game directory path, origin commit sha, byond version.
/// After CodeModifications are applied, before DreamMaker is run. Parameters: Game directory path, origin commit sha, version string of the used engine.
#define TGS_EVENT_PRE_DREAM_MAKER 24
/// Whenever a deployment folder is deleted from disk. Parameters: Game directory path.
#define TGS_EVENT_DEPLOYMENT_CLEANUP 25
@@ -122,6 +135,7 @@
/// The watchdog will restart on reboot.
#define TGS_REBOOT_MODE_RESTART 2
// Note that security levels are currently meaningless in OpenDream
/// DreamDaemon Trusted security level.
#define TGS_SECURITY_TRUSTED 0
/// DreamDaemon Safe security level.
@@ -136,20 +150,26 @@
/// DreamDaemon invisible visibility level.
#define TGS_VISIBILITY_INVISIBLE 2
/// The Build Your Own Net Dream engine.
#define TGS_ENGINE_TYPE_BYOND 0
/// The OpenDream engine.
#define TGS_ENGINE_TYPE_OPENDREAM 1
//REQUIRED HOOKS
/**
* Call this somewhere in [/world/proc/New] that is always run. This function may sleep!
* Consumers MUST call this somewhere in [/world/proc/New] that is always run. This function may sleep!
*
* * event_handler - Optional user defined [/datum/tgs_event_handler].
* * minimum_required_security_level: The minimum required security level to run the game in which the DMAPI is integrated. Can be one of [TGS_SECURITY_ULTRASAFE], [TGS_SECURITY_SAFE], or [TGS_SECURITY_TRUSTED].
* * http_handler - Optional user defined [/datum/tgs_http_handler].
*/
/world/proc/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE)
/world/proc/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE, datum/tgs_http_handler/http_handler)
CAN_BE_REDEFINED(TRUE)
return
/**
* Call this when your initializations are complete and your game is ready to play before any player interactions happen.
* Consumers MUST call this when world initializations are complete and the game is ready to play before any player interactions happen.
*
* This may use [/world/var/sleep_offline] to make this happen so ensure no changes are made to it while this call is running.
* Afterwards, consider explicitly setting it to what you want to avoid this BYOND bug: http://www.byond.com/forum/post/2575184
@@ -159,12 +179,10 @@
CAN_BE_REDEFINED(TRUE)
return
/// Put this at the start of [/world/proc/Topic].
/// Consumers MUST run this macro at the start of [/world/proc/Topic].
#define TGS_TOPIC var/tgs_topic_return = TgsTopic(args[1]); if(tgs_topic_return) return tgs_topic_return
/**
* Call this as late as possible in [world/proc/Reboot] (BEFORE ..()).
*/
/// Consumers MUST call this as late as possible in [world/proc/Reboot] (BEFORE ..()).
/world/proc/TgsReboot()
CAN_BE_REDEFINED(TRUE)
return
@@ -261,7 +279,7 @@
/// The [/datum/tgs_chat_channel] the user was from.
var/datum/tgs_chat_channel/channel
/// User definable handler for TGS events.
/// User definable handler for TGS events This abstract version SHOULD be overridden to be used.
/datum/tgs_event_handler
/// If the handler receieves [TGS_EVENT_HEALTH_CHECK] events.
var/receive_health_checks = FALSE
@@ -275,7 +293,41 @@
set waitfor = FALSE
return
/// User definable chat command.
/// User definable handler for HTTP calls. This abstract version MUST be overridden to be used.
/datum/tgs_http_handler
/**
* User definable callback for executing HTTP GET requests.
* MUST perform BYOND sleeps while the request is in flight.
* MUST return a [/datum/tgs_http_result].
* SHOULD log its own errors
*
* url - The full URL to execute the GET request for including query parameters.
*/
/datum/tgs_http_handler/proc/PerformGet(url)
CRASH("[type]/PerformGet not implemented!")
/// Result of a [/datum/tgs_http_handler] call. MUST NOT be overridden.
/datum/tgs_http_result
/// HTTP response as text
var/response_text
/// Boolean request success flag. Set for any 2XX response code.
var/success
/**
* Create a [/datum/tgs_http_result].
*
* * response_text - HTTP response as text. Must be provided in New().
* * success - Boolean request success flag. Set for any 2XX response code. Must be provided in New().
*/
/datum/tgs_http_result/New(response_text, success)
if(response_text && !istext(response_text))
CRASH("response_text was not text!")
src.response_text = response_text
src.success = success
/// User definable chat command. This abstract version MUST be overridden to be used.
/datum/tgs_chat_command
/// The string to trigger this command on a chat bot. e.g `@bot name ...` or `!tgs name ...`.
var/name = ""
@@ -288,29 +340,36 @@
/**
* Process command activation. Should return a [/datum/tgs_message_content] to respond to the issuer with.
* MUST be implemented
*
* sender - The [/datum/tgs_chat_user] who issued the command.
* params - The trimmed string following the command `/datum/tgs_chat_command/var/name].
* * sender - The [/datum/tgs_chat_user] who issued the command.
* * params - The trimmed string following the command `/datum/tgs_chat_command/var/name].
*/
/datum/tgs_chat_command/proc/Run(datum/tgs_chat_user/sender, params)
CRASH("[type] has no implementation for Run()")
/// User definable chat message.
/// User definable chat message. MUST NOT be overridden.
/datum/tgs_message_content
/// The tring content of the message. Must be provided in New().
/// The string content of the message. Must be provided in New().
var/text
/// The [/datum/tgs_chat_embed] to embed in the message. Not supported on all chat providers.
var/datum/tgs_chat_embed/structure/embed
/**
* Create a [/datum/tgs_message_content].
*
* * text - The string content of the message.
*/
/datum/tgs_message_content/New(text)
..()
if(!istext(text))
TGS_ERROR_LOG("[/datum/tgs_message_content] created with no text!")
text = null
src.text = text
/// User definable chat embed. Currently mirrors Discord chat embeds. See https://discord.com/developers/docs/resources/channel#embed-object-embed-structure for details.
/// User definable chat embed. Currently mirrors Discord chat embeds. See https://discord.com/developers/docs/resources/message#embed-object for details.
/datum/tgs_chat_embed/structure
var/title
var/description
@@ -322,13 +381,13 @@
/// Colour must be #AARRGGBB or #RRGGBB hex string.
var/colour
/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-image-structure for details.
/// See https://discord.com/developers/docs/resources/message#embed-object-embed-image-structure for details.
var/datum/tgs_chat_embed/media/image
/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-thumbnail-structure for details.
/// See https://discord.com/developers/docs/resources/message#embed-object-embed-thumbnail-structure for details.
var/datum/tgs_chat_embed/media/thumbnail
/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-image-structure for details.
/// See https://discord.com/developers/docs/resources/message#embed-object-embed-video-structure for details.
var/datum/tgs_chat_embed/media/video
var/datum/tgs_chat_embed/footer/footer
@@ -337,7 +396,7 @@
var/list/datum/tgs_chat_embed/field/fields
/// Common datum for similar discord embed medias.
/// Common datum for similar Discord embed medias.
/datum/tgs_chat_embed/media
/// Must be set in New().
var/url
@@ -345,48 +404,58 @@
var/height
var/proxy_url
/// Create a [/datum/tgs_chat_embed].
/datum/tgs_chat_embed/media/New(url)
..()
if(!istext(url))
CRASH("[/datum/tgs_chat_embed/media] created with no url!")
src.url = url
/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-footer-structure for details.
/// See https://discord.com/developers/docs/resources/message#embed-object-embed-footer-structure for details.
/datum/tgs_chat_embed/footer
/// Must be set in New().
var/text
var/icon_url
var/proxy_icon_url
/// Create a [/datum/tgs_chat_embed/footer].
/datum/tgs_chat_embed/footer/New(text)
..()
if(!istext(text))
CRASH("[/datum/tgs_chat_embed/footer] created with no text!")
src.text = text
/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-provider-structure for details.
/// See https://discord.com/developers/docs/resources/message#embed-object-embed-provider-structure for details.
/datum/tgs_chat_embed/provider
var/name
var/url
/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-author-structure for details. Must have name set in New().
/// See https://discord.com/developers/docs/resources/message#embed-object-embed-author-structure for details. Must have name set in New().
/datum/tgs_chat_embed/provider/author
var/icon_url
var/proxy_icon_url
/// Create a [/datum/tgs_chat_embed/footer].
/datum/tgs_chat_embed/provider/author/New(name)
..()
if(!istext(name))
CRASH("[/datum/tgs_chat_embed/provider/author] created with no name!")
src.name = name
/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-field-structure for details. Must have name and value set in New().
/// See https://discord.com/developers/docs/resources/message#embed-object-embed-field-structure for details.
/datum/tgs_chat_embed/field
/// Must be set in New().
var/name
/// Must be set in New().
var/value
var/is_inline
/// Create a [/datum/tgs_chat_embed/field].
/datum/tgs_chat_embed/field/New(name, value)
..()
if(!istext(name))
CRASH("[/datum/tgs_chat_embed/field] created with no name!")
@@ -429,6 +498,7 @@
/**
* Send a message to connected chats. This function may sleep!
* If TGS is offline when called, the message may be placed in a queue to be sent and this function will return immediately. Your message will be sent when TGS reconnects to the game.
*
* message - The [/datum/tgs_message_content] to send.
* admin_only: If [TRUE], message will be sent to admin connected chats. Vice-versa applies.
@@ -439,6 +509,7 @@
/**
* Send a private message to a specific user. This function may sleep!
* If TGS is offline when called, the message may be placed in a queue to be sent and this function will return immediately. Your message will be sent when TGS reconnects to the game.
*
* message - The [/datum/tgs_message_content] to send.
* user: The [/datum/tgs_chat_user] to PM.
@@ -449,6 +520,7 @@
/**
* Send a message to connected chats that are flagged as game-related in TGS. This function may sleep!
* If TGS is offline when called, the message may be placed in a queue to be sent and this function will return immediately. Your message will be sent when TGS reconnects to the game.
*
* message - The [/datum/tgs_message_content] to send.
* channels - Optional list of [/datum/tgs_chat_channel]s to restrict the message to.
@@ -462,6 +534,11 @@
CAN_BE_REDEFINED(TRUE)
return
/// Returns the running engine type
/world/proc/TgsEngine()
CAN_BE_REDEFINED(TRUE)
return
/// Returns the current [/datum/tgs_version] of the DMAPI being used if it was activated, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping!
/world/proc/TgsApiVersion()
CAN_BE_REDEFINED(TRUE)
@@ -497,10 +574,21 @@
CAN_BE_REDEFINED(TRUE)
return
/**
* Trigger an event in TGS. Requires TGS version >= 6.3.0. Returns [TRUE] if the event was triggered successfully, [FALSE] otherwise. This function may sleep!
*
* event_name - The name of the event to trigger
* parameters - Optional list of string parameters to pass as arguments to the event script. The first parameter passed to a script will always be the running game's directory followed by these parameters.
* wait_for_completion - If set, this function will not return until the event has run to completion.
*/
/world/proc/TgsTriggerEvent(event_name, list/parameters, wait_for_completion = FALSE)
CAN_BE_REDEFINED(TRUE)
return
/*
The MIT License
Copyright (c) 2017-2023 Jordan Brown
Copyright (c) 2017-2024 Jordan Brown
Permission is hereby granted, free of charge,
to any person obtaining a copy of this software and
+10
View File
@@ -24,6 +24,16 @@
/// Window is free and ready to receive data
#define TGUI_WINDOW_READY 2
/// Though not the maximum renderable ByondUis within tgui, this is the maximum that the server will manage per-UI
#define TGUI_MANAGED_BYONDUI_LIMIT 10
// These are defines instead of being inline, as they're being sent over
// from tgui-core, so can't be easily played with
#define TGUI_MANAGED_BYONDUI_TYPE_RENDER "renderByondUi"
#define TGUI_MANAGED_BYONDUI_TYPE_UNMOUNT "unmountByondUi"
#define TGUI_MANAGED_BYONDUI_PAYLOAD_ID "renderByondUi"
/// Get a window id based on the provided pool index
#define TGUI_WINDOW_ID(index) "tgui-window-[index]"
/// Get a pool index of the provided window id
+28
View File
@@ -912,3 +912,31 @@
/proc/endswith(input_text, ending)
var/input_length = LAZYLEN(ending)
return !!findtext(input_text, ending, -input_length)
///Returns a string based on the weight class define used as argument
/proc/weight_class_to_text(w_class)
switch(w_class)
if(WEIGHT_CLASS_TINY)
. = "tiny"
if(WEIGHT_CLASS_SMALL)
. = "small"
if(WEIGHT_CLASS_NORMAL)
. = "normal-sized"
if(WEIGHT_CLASS_BULKY)
. = "bulky"
if(WEIGHT_CLASS_HUGE)
. = "huge"
if(WEIGHT_CLASS_GIGANTIC)
. = "gigantic"
else
. = ""
/proc/weight_class_to_tooltip(w_class)
switch(w_class)
if(WEIGHT_CLASS_TINY to WEIGHT_CLASS_SMALL)
return "This item can fit into pockets, boxes and backpacks."
if(WEIGHT_CLASS_NORMAL)
return "This item can fit into backpacks."
if(WEIGHT_CLASS_BULKY to WEIGHT_CLASS_GIGANTIC)
return "This item is too large to fit into any standard storage."
return ""
+72 -21
View File
@@ -10,31 +10,82 @@ SUBSYSTEM_DEF(chat)
flags = SS_TICKER | SS_NO_INIT
wait = 1
priority = FIRE_PRIORITY_CHAT
init_order = INIT_ORDER_CHAT
init_order = INITSTAGE_MAX
var/list/payload_by_client = list()
/// Assosciates a ckey with a list of messages to send to them.
var/list/list/datum/chat_payload/client_to_payloads = list()
/// Associates a ckey with an assosciative list of their last CHAT_RELIABILITY_HISTORY_SIZE messages.
var/list/list/datum/chat_payload/client_to_reliability_history = list()
/// Assosciates a ckey with their next sequence number.
var/list/client_to_sequence_number = list()
/datum/controller/subsystem/chat/proc/generate_payload(client/target, message_data)
var/sequence = client_to_sequence_number[target.ckey]
client_to_sequence_number[target.ckey] += 1
var/datum/chat_payload/payload = new
payload.sequence = sequence
payload.content = message_data
if(!(target.ckey in client_to_reliability_history))
client_to_reliability_history[target.ckey] = list()
var/list/client_history = client_to_reliability_history[target.ckey]
client_history["[sequence]"] = payload
if(length(client_history) > 5)
var/oldest = text2num(client_history[1])
for(var/index in 2 to length(client_history))
var/test = text2num(client_history[index])
if(test < oldest)
oldest = test
client_history -= "[oldest]"
return payload
/datum/controller/subsystem/chat/proc/send_payload_to_client(client/target, datum/chat_payload/payload)
target.tgui_panel.window.send_message("chat/message", payload.into_message())
SEND_TEXT(target, payload.get_content_as_html())
/datum/controller/subsystem/chat/fire()
for(var/key in payload_by_client)
var/client/client = key
var/payload = payload_by_client[key]
payload_by_client -= key
if(client)
// Send to tgchat
client.tgui_panel?.window.send_message("chat/message", payload)
// Send to old chat
for(var/message in payload)
to_target(client, message_to_html(message))
for(var/ckey in client_to_payloads)
var/client/target = GLOB.directory[ckey]
if(isnull(target)) // verify client still exists
LAZYREMOVE(client_to_payloads, ckey)
continue
for(var/datum/chat_payload/payload as anything in client_to_payloads[ckey])
send_payload_to_client(target, payload)
LAZYREMOVE(client_to_payloads, ckey)
if(MC_TICK_CHECK)
return
/datum/controller/subsystem/chat/proc/queue(target, message)
if(islist(target))
for(var/_target in target)
var/client/client = CLIENT_FROM_VAR(_target)
if(client)
LAZYADD(payload_by_client[client], list(message))
/datum/controller/subsystem/chat/proc/queue(queue_target, list/message_data)
var/list/targets = islist(queue_target) ? queue_target : list(queue_target)
for(var/target in targets)
var/client/client = CLIENT_FROM_VAR(target)
if(isnull(client))
continue
LAZYADDASSOCLIST(client_to_payloads, client.ckey, generate_payload(client, message_data))
/datum/controller/subsystem/chat/proc/send_immediate(send_target, list/message_data)
var/list/targets = islist(send_target) ? send_target : list(send_target)
for(var/target in targets)
var/client/client = CLIENT_FROM_VAR(target)
if(isnull(client))
continue
send_payload_to_client(client, generate_payload(client, message_data))
/datum/controller/subsystem/chat/proc/handle_resend(client/client, sequence)
var/list/client_history = client_to_reliability_history[client.ckey]
sequence = "[sequence]"
if(isnull(client_history) || !(sequence in client_history))
return
var/client/client = CLIENT_FROM_VAR(target)
if(client)
LAZYADD(payload_by_client[client], list(message))
var/datum/chat_payload/payload = client_history[sequence]
if(payload.resends > 3)
return // we tried but byond said no
payload.resends += 1
send_payload_to_client(client, client_history[sequence])
@@ -50,12 +50,16 @@ SUBSYSTEM_DEF(nightlight)
/datum/controller/subsystem/nightlight/proc/get_apc_list(var/whitelisted_only = 1)
var/list/obj/structure/machinery/power/apc/lighting_apcs = list()
for (var/A in get_sorted_areas())
var/area/B = A
if (B.no_light_control || (!(B.allow_nightmode) && whitelisted_only))
for (var/obj/structure/machinery/power/apc/APC in SSmachinery.apc_units)
if (QDELETED(APC) || APC.aidisabled)
continue
if (B.apc && !B.apc.aidisabled)
lighting_apcs += B.apc
var/area/apc_area = APC.area || get_area(APC)
if (!apc_area)
continue
if (apc_area.no_light_control || (!(apc_area.allow_nightmode) && whitelisted_only))
continue
lighting_apcs += APC
CHECK_TICK
@@ -1,241 +0,0 @@
PROCESSING_SUBSYSTEM_DEF(nanoui)
// Subsystem stuff.
name = "NanoUI"
flags = SS_NO_INIT
priority = SS_PRIORITY_NANOUI
stat_tag = "A"
// NanoUI stuff.
var/list/open_nanouis = list()
/**
* Get an open /nanoui ui for the current user, src_object and ui_key and try to update it with data
*
* * user - /mob The mob who opened/owns the ui
* * src_object - /obj|/mob The obj or mob which the ui belongs to
* * ui_key - A string key used for the ui
* * data - List, the data to be passed to the ui, if it exists
* * force_open - Boolean, the ui is being forced to (re)open, so close ui if it exists (instead of updating)
*
* Returns the `/nanoui` found ui, for null if none exists
*/
/datum/controller/subsystem/processing/nanoui/proc/try_update_ui(mob/user, src_object, ui_key, datum/nanoui/ui, data, force_open = FALSE)
if (!ui) // no ui has been passed, so we'll search for one
ui = get_open_ui(user, src_object, ui_key)
if (ui)
// The UI is already open
if (!force_open)
ui.push_data(data)
return ui
else
ui.reinitialise(new_initial_data=data)
return ui
return null
/**
* Get an open /nanoui ui for the current user, src_object and ui_key
*
* * user - The `/mob` who opened/owns the ui
* * src_object - The `/obj` or `/mob` which the ui belongs to
* * ui_key - A string key used for the ui
*/
/datum/controller/subsystem/processing/nanoui/proc/get_open_ui(mob/user, src_object, ui_key)
var/src_object_key = REF(src_object)
if (!LAZYLEN(open_nanouis[src_object_key]) || !LAZYLEN(open_nanouis[src_object_key][ui_key]))
return null
for (var/datum/nanoui/ui in open_nanouis[src_object_key][ui_key])
if (ui.user == user)
return ui
//testing("nanomanager/get_open_ui mob [user.name] [src_object:name] [ui_key] - ui not found")
return null
/**
* Update all `/nanoui` uis attached to src_object
*
* * src_object - The `/obj` or `/mob` which the uis are attached to
*
* Returns the number of UIs updated
*/
/datum/controller/subsystem/processing/nanoui/proc/update_uis(src_object)
var/src_object_key = REF(src_object)
if (!LAZYLEN(open_nanouis[src_object_key]))
return 0
. = 0
var/list/obj_uis = open_nanouis[src_object_key]
for (var/ui_key in obj_uis)
for (var/thing in obj_uis[ui_key])
var/datum/nanoui/ui = thing
if(ui && ui.src_object && ui.user && ui.src_object.ui_host())
ui.process(1)
.++
/**
* Close all `/nanoui` uis attached to src_object
*
* * src_object - The `/obj` or `/mob` which the uis are attached to
*
* Returns the number of UIs closed
*/
/datum/controller/subsystem/processing/nanoui/proc/close_uis(src_object)
var/src_object_key = REF(src_object)
if (!open_nanouis[src_object_key] || !islist(open_nanouis[src_object_key]))
return 0
. = 0
var/list/obj_uis = open_nanouis[src_object_key]
for (var/ui_key in obj_uis)
for (var/thing in obj_uis[ui_key])
var/datum/nanoui/ui = thing
if(ui && ui.src_object && ui.user && ui.src_object.ui_host())
ui.close()
.++
/**
* Update /nanoui uis belonging to user
*
* * user - The `/mob` who owns the uis
* * src_object - An `/obj` or `/mob` that, if is provided, only update uis which are attached to it (optional)
* * ui_key - A string, if ui_key is provided, only update uis with a matching ui_key (optional)
*
* Returns the number of UIs updated
*/
/datum/controller/subsystem/processing/nanoui/proc/update_user_uis(mob/user, src_object, ui_key)
if (!LAZYLEN(user.open_nanouis))
return 0 // has no open uis
. = 0
for (var/thing in user.open_nanouis)
var/datum/nanoui/ui = thing
if (NULL_OR_EQUAL(src_object, ui.src_object) && NULL_OR_EQUAL(ui_key, ui.ui_key))
ui.process(1)
.++
/datum/controller/subsystem/processing/nanoui/proc/close_user_uis(mob/user, src_object, ui_key)
if (!LAZYLEN(user.open_nanouis))
return 0
for (var/thing in user.open_nanouis)
var/datum/nanoui/ui = thing
if (NULL_OR_EQUAL(src_object, ui.src_object) && NULL_OR_EQUAL(ui_key, ui.ui_key))
ui.close()
.++
//testing("nanomanager/close_user_uis mob [user.name] closed [open_nanouis.len] of [.] uis")
/**
* Add a /nanoui ui to the list of open uis
* This is called by the /nanoui open() proc
*
* * ui - The `/nanoui` ui to add
*/
/datum/controller/subsystem/processing/nanoui/proc/ui_opened(datum/nanoui/ui)
var/src_object_key = REF(ui.src_object)
LAZYINITLIST(open_nanouis[src_object_key])
LAZYADD(ui.user.open_nanouis, ui)
LAZYADD(open_nanouis[src_object_key][ui.ui_key], ui)
START_PROCESSING(SSnanoui, ui)
//testing("nanomanager/ui_opened mob [ui.user.name] [ui.src_object:name] [ui.ui_key] - user.open_nanouis [ui.user.open_nanouis.len] | uis [uis.len] | processing_uis [processing_uis.len]")
/**
* Remove a /nanoui ui from the list of open uis
* This is called by the /nanoui close() proc
*
* * ui - A `/nanoui` to remove
*
* Returns FALSE if no ui was removed, TRUE if removed successfully
*/
/datum/controller/subsystem/processing/nanoui/proc/ui_closed(datum/nanoui/ui)
var/src_object_key = REF(ui.src_object)
var/ui_key = ui.ui_key
var/list/obj_uis = open_nanouis[src_object_key]
if (!LAZYLEN(obj_uis) || !obj_uis[ui_key])
return 0 // Wasn't open.
STOP_PROCESSING(SSnanoui, ui)
if(ui.user) // Sanity check in case a user has been deleted (say a blown up borg watching the alarm interface)
LAZYREMOVE(ui.user.open_nanouis, ui)
obj_uis[ui_key] -= ui
if (!LAZYLEN(obj_uis[ui_key]))
obj_uis -= ui_key
if (!LAZYLEN(obj_uis))
open_nanouis -= src_object_key
//testing("nanomanager/ui_closed mob [ui.user.name] [ui.src_object:name] [ui.ui_key] - user.open_nanouis [ui.user.open_nanouis.len] | uis [uis.len] | processing_uis [processing_uis.len]")
return 1
/**
* This is called on user logout
* Closes/clears all uis attached to the user's `/mob`
*
* * user - The user's `/mob`
*/
/datum/controller/subsystem/processing/nanoui/proc/user_logout(mob/user)
return close_user_uis(user)
/**
* This is called when a player transfers from one mob to another
* Transfers all open UIs to the new mob
*
* * oldMob - The user's old `/mob`
* * newMob - The user's new `/mob`
*/
/datum/controller/subsystem/processing/nanoui/proc/user_transferred(mob/oldMob, mob/newMob)
//testing("nanomanager/user_transferred from mob [oldMob.name] to mob [newMob.name]")
if (!oldMob || !LAZYLEN(oldMob.open_nanouis) || !LAZYLEN(open_nanouis))
//testing("nanomanager/user_transferred mob [oldMob.name] has no open uis")
return 0 // has no open uis
for (var/thing in oldMob.open_nanouis)
var/datum/nanoui/ui = thing
ui.user = newMob
LAZYADD(newMob.open_nanouis, ui)
oldMob.open_nanouis = null
return 1 // success
/datum/asset/nanoui
var/list/common = list()
var/list/common_dirs = list(
"nano/css/",
"nano/js/",
"nano/images/",
"nano/images/status_icons/",
"nano/templates/"
)
var/list/uncommon_dirs = list()
/datum/asset/nanoui/register()
// Crawl the directories to find files.
for (var/path in common_dirs)
var/list/filenames = flist(path)
for(var/filename in filenames)
if(copytext(filename, length(filename)) != "/") // Ignore directories.
if(fexists(path + filename))
common[filename] = fcopy_rsc(path + filename)
SSassets.transport.register_asset(filename, common[filename])
for (var/path in uncommon_dirs)
var/list/filenames = flist(path)
for(var/filename in filenames)
if(copytext(filename, length(filename)) != "/") // Ignore directories.
if(fexists(path + filename))
SSassets.transport.register_asset(filename, fcopy_rsc(path + filename))
/datum/asset/nanoui/send(client, uncommon)
if(!islist(uncommon))
uncommon = list(uncommon)
SSassets.transport.send_assets(client, uncommon)
SSassets.transport.send_assets(client, common)
@@ -10,11 +10,11 @@
*
*/
PROCESSING_SUBSYSTEM_DEF(tgui)
SUBSYSTEM_DEF(tgui)
name = "tgui"
wait = 9
flags = SS_NO_INIT
priority = SS_PRIORITY_NANOUI
priority = FIRE_PRIORITY_TGUI
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
/// A list of UIs scheduled to process
@@ -24,54 +24,39 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
/// The HTML base used for all UIs.
var/basehtml
/datum/controller/subsystem/processing/tgui/PreInit()
basehtml = file2text('tgui/public/tgui.html')
// Inject inline polyfills
var/polyfill = file2text('tgui/public/tgui-polyfill.min.js')
polyfill = "<script>\n[polyfill]\n</script>"
basehtml = replacetextEx(basehtml, "<!-- tgui:inline-polyfill -->", polyfill)
basehtml = replacetextEx(basehtml, "<!-- tgui:nt-copyright -->", "NanoTrasen © 2457-[text2num(time2text(world.realtime, "YYYY")) + 442]")
/datum/controller/subsystem/tgui/PreInit()
basehtml = file2text("tgui/public/tgui.html")
basehtml = replacetext(basehtml, "tgui:stylesheet", MAP_STYLESHEET)
// Inject inline helper functions
var/helpers = file2text("tgui/public/helpers.min.js")
helpers = "<script type='text/javascript'>\n[helpers]\n</script>"
basehtml = replacetextEx(basehtml, "<!-- tgui:helpers -->", helpers)
/datum/controller/subsystem/processing/tgui/OnConfigLoad()
var/storage_iframe = GLOB.config.storage_cdn_iframe
if(storage_iframe && storage_iframe != /datum/configuration::storage_cdn_iframe)
basehtml = replacetextEx(basehtml, "tgui:storagecdn", storage_iframe)
return
// Inject inline ntos-error styles
var/ntos_error = file2text("tgui/public/ntos-error.min.css")
ntos_error = "<style type='text/css'>\n[ntos_error]\n</style>"
basehtml = replacetextEx(basehtml, "<!-- tgui:ntos-error -->", ntos_error)
if(GLOB.config.asset_transport == "webroot")
var/datum/asset_transport/webroot/webroot = SSassets.transport
basehtml = replacetextEx(basehtml, "<!-- tgui:nt-copyright -->", "Nanotrasen (c) 2395-[num2text(GLOB.game_year)]")
var/datum/asset_cache_item/item = webroot.register_asset("iframe.html", file("tgui/public/iframe.html"))
basehtml = replacetext(basehtml, "tgui:storagecdn", webroot.get_asset_url("iframe.html", item))
return
if(!storage_iframe)
return
basehtml = replacetextEx(basehtml, "tgui:storagecdn", storage_iframe)
/datum/controller/subsystem/processing/tgui/Shutdown()
/datum/controller/subsystem/tgui/Shutdown()
close_all_uis()
/datum/controller/subsystem/processing/tgui/stat_entry(msg)
/datum/controller/subsystem/tgui/stat_entry(msg)
msg = "P:[length(all_uis)]"
return ..()
/datum/controller/subsystem/processing/tgui/fire(resumed = FALSE)
CAN_BE_REDEFINED(TRUE)
/datum/controller/subsystem/tgui/fire(resumed = FALSE)
if(!resumed)
src.current_run = all_uis.Copy()
// Cache for sanic speed (lists are references anyways)
var/list/current_run = src.current_run
var/seconds_per_tick = wait * 0.1
while(current_run.len)
var/datum/tgui/ui = current_run[current_run.len]
current_run.len--
// TODO: Move user/src_object check to process()
if(ui?.user && ui.src_object)
ui.process(seconds_per_tick)
ui.process(wait * 0.1)
else
ui.close(0)
if(MC_TICK_CHECK)
@@ -84,9 +69,9 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
* Returns null if pool was exhausted.
*
* required user mob
* return datum/tgui
* return datum/tgui_window
*/
/datum/controller/subsystem/processing/tgui/proc/request_pooled_window(mob/user)
/datum/controller/subsystem/tgui/proc/request_pooled_window(mob/user)
if(!user.client)
return null
var/list/windows = user.client.tgui_windows
@@ -122,7 +107,7 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
*
* required user mob
*/
/datum/controller/subsystem/processing/tgui/proc/force_close_all_windows(mob/user)
/datum/controller/subsystem/tgui/proc/force_close_all_windows(mob/user)
log_tgui(user, context = "SStgui/force_close_all_windows")
if(user.client)
user.client.tgui_windows = list()
@@ -138,14 +123,12 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
* required user mob
* required window_id string
*/
/datum/controller/subsystem/processing/tgui/proc/force_close_window(mob/user, window_id)
/datum/controller/subsystem/tgui/proc/force_close_window(mob/user, window_id)
log_tgui(user, context = "SStgui/force_close_window")
// Close all tgui datums based on window_id.
for(var/datum/tgui/ui in user.tgui_open_uis)
if(ui.window && ui.window.id == window_id)
ui.close(can_be_suspended = FALSE)
// Unset machine just to be sure.
user.unset_machine()
// Close window directly just to be sure.
user << browse(null, "window=[window_id]")
@@ -161,7 +144,7 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
*
* return datum/tgui The found UI.
*/
/datum/controller/subsystem/processing/tgui/proc/try_update_ui(
/datum/controller/subsystem/tgui/proc/try_update_ui(
mob/user,
datum/src_object,
datum/tgui/ui)
@@ -191,7 +174,8 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
*
* return datum/tgui The found UI.
*/
/datum/controller/subsystem/processing/tgui/proc/get_open_ui(mob/user, datum/src_object)
/datum/controller/subsystem/tgui/proc/get_open_ui(mob/user, datum/src_object)
// No UIs opened for this src_object
if(!LAZYLEN(src_object?.open_uis))
return null
for(var/datum/tgui/ui in src_object.open_uis)
@@ -209,15 +193,15 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
*
* return int The number of UIs updated.
*/
/datum/controller/subsystem/processing/tgui/proc/update_uis(datum/src_object)
/datum/controller/subsystem/tgui/proc/update_uis(datum/src_object)
// No UIs opened for this src_object
if(!LAZYLEN(src_object?.open_uis))
return 0
var/count = 0
var/seconds_per_tick = wait * 0.1
for(var/datum/tgui/ui in src_object.open_uis)
// Check if UI is valid.
if(ui?.src_object && ui.user && ui.src_object.ui_host(ui.user))
INVOKE_ASYNC(ui, TYPE_PROC_REF(/datum/tgui, process), seconds_per_tick, TRUE)
INVOKE_ASYNC(ui, TYPE_PROC_REF(/datum/tgui, process), wait * 0.1, TRUE)
count++
return count
@@ -230,7 +214,7 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
*
* return int The number of UIs closed.
*/
/datum/controller/subsystem/processing/tgui/proc/close_uis(datum/src_object)
/datum/controller/subsystem/tgui/proc/close_uis(datum/src_object)
// No UIs opened for this src_object
if(!LAZYLEN(src_object?.open_uis))
return 0
@@ -249,7 +233,7 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
*
* return int The number of UIs closed.
*/
/datum/controller/subsystem/processing/tgui/proc/close_all_uis()
/datum/controller/subsystem/tgui/proc/close_all_uis()
var/count = 0
for(var/datum/tgui/ui in all_uis)
// Check if UI is valid.
@@ -268,14 +252,13 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
*
* return int The number of UIs updated.
*/
/datum/controller/subsystem/processing/tgui/proc/update_user_uis(mob/user, datum/src_object)
/datum/controller/subsystem/tgui/proc/update_user_uis(mob/user, datum/src_object)
var/count = 0
if(length(user?.tgui_open_uis) == 0)
return count
var/seconds_per_tick = wait * 0.1
for(var/datum/tgui/ui in user.tgui_open_uis)
if(isnull(src_object) || ui.src_object == src_object)
ui.process(seconds_per_tick, force = 1)
ui.process(wait * 0.1, force = 1)
count++
return count
@@ -289,7 +272,7 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
*
* return int The number of UIs closed.
*/
/datum/controller/subsystem/processing/tgui/proc/close_user_uis(mob/user, datum/src_object)
/datum/controller/subsystem/tgui/proc/close_user_uis(mob/user, datum/src_object)
var/count = 0
if(length(user?.tgui_open_uis) == 0)
return count
@@ -306,7 +289,7 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
*
* required ui datum/tgui The UI to be added.
*/
/datum/controller/subsystem/processing/tgui/proc/on_open(datum/tgui/ui)
/datum/controller/subsystem/tgui/proc/on_open(datum/tgui/ui)
ui.user?.tgui_open_uis |= ui
LAZYOR(ui.src_object.open_uis, ui)
all_uis |= ui
@@ -320,7 +303,7 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
*
* return bool If the UI was removed or not.
*/
/datum/controller/subsystem/processing/tgui/proc/on_close(datum/tgui/ui)
/datum/controller/subsystem/tgui/proc/on_close(datum/tgui/ui)
// Remove it from the list of processing UIs.
all_uis -= ui
current_run -= ui
@@ -340,7 +323,7 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
*
* return int The number of UIs closed.
*/
/datum/controller/subsystem/processing/tgui/proc/on_logout(mob/user)
/datum/controller/subsystem/tgui/proc/on_logout(mob/user)
close_user_uis(user)
/**
@@ -353,7 +336,7 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
*
* return bool If the UIs were transferred.
*/
/datum/controller/subsystem/processing/tgui/proc/on_transfer(mob/source, mob/target)
/datum/controller/subsystem/tgui/proc/on_transfer(mob/source, mob/target)
// The old mob had no open UIs.
if(length(source?.tgui_open_uis) == 0)
return FALSE
+16
View File
@@ -0,0 +1,16 @@
/// Stores information about a chat payload
/datum/chat_payload
/// Sequence number of this payload
var/sequence = 0
/// Message we are sending
var/list/content
/// Resend count
var/resends = 0
/// Converts the chat payload into a JSON string
/datum/chat_payload/proc/into_message()
return "{\"sequence\":[sequence],\"content\":[json_encode(content)]}"
/// Returns an HTML-encoded message from our contents.
/datum/chat_payload/proc/get_content_as_html()
return message_to_html(content)
-5
View File
@@ -47,11 +47,6 @@
lan = null
return lan
/datum/component/local_network_member/nano_host()
if(parent)
return parent.nano_host()
. = ..()
/datum/component/local_network_member/proc/get_new_tag(mob/user)
var/new_ident = input(user, "Enter a new ident tag.", "[parent]", id_tag) as null|text
if(new_ident && parent && user.Adjacent(parent) && CanInteract(user, GLOB.physical_state))
-1
View File
@@ -94,7 +94,6 @@
current.remove_vampire_powers()
current.mind = null
SSnanoui.user_transferred(current, new_character)
SStgui.on_transfer(current, new_character)
if(current.client && GLOB.ticket_panels[current.client])
var/datum/ticket_panel/tp = GLOB.ticket_panels[current.client]
+10 -3
View File
@@ -6,7 +6,8 @@
wires = list(
WIRE_POWER,
WIRE_IDSCAN, WIRE_AI,
WIRE_PANIC, WIRE_ALARM
WIRE_PANIC, WIRE_ALARM,
WIRE_RCON
)
add_duds(3)
..()
@@ -24,6 +25,7 @@
. += A.locked ? "The air alarm is locked." : "The air alarm is unlocked."
. += (A.shorted || (A.stat & (NOPOWER|BROKEN))) ? "The Air Alarm is offline." : "The Air Alarm is working properly!"
. += A.aidisabled ? "The 'AI control allowed' light is off." : "The 'AI control allowed' light is on."
. += A.rcon_setting ? "The remote control link is enabled." : "The remote control link is disabled."
/datum/wires/alarm/on_cut(wire, mend, source)
var/obj/structure/machinery/alarm/A = holder
@@ -51,6 +53,12 @@
A.post_alert(2)
A.update_icon()
if(WIRE_RCON)
if(!mend)
A.rcon_setting = FALSE
else
A.rcon_setting = TRUE
/datum/wires/alarm/on_pulse(wire)
var/obj/structure/machinery/alarm/A = holder
switch(wire)
@@ -67,7 +75,6 @@
A.shorted = 0
A.update_icon()
if (WIRE_AI)
if (A.aidisabled == 0)
A.aidisabled = 1
@@ -83,7 +90,7 @@
A.mode = 1 // AALARM_MODE_SCRUB
A.apply_mode()
if(WIRE_ALARM)
if(WIRE_ALARM, WIRE_RCON)
if (A.alarm_area.atmosalert(0, A))
A.post_alert(0)
A.update_icon()
+2 -2
View File
@@ -31,7 +31,7 @@
if(WIRE_TRANSMIT)
R.set_broadcasting(!R.get_broadcasting() && !is_cut(WIRE_SIGNAL))
SSnanoui.update_uis(holder)
SStgui.update_uis(holder)
/datum/wires/radio/on_cut(wire, mend, source)
var/obj/item/radio/R = holder
@@ -45,4 +45,4 @@
if(WIRE_TRANSMIT)
R.set_broadcasting(mend && !is_cut(WIRE_SIGNAL))
SSnanoui.update_uis(holder)
SStgui.update_uis(holder)
+38
View File
@@ -87,6 +87,15 @@
if(src.desc)
. += src.desc
var/list/tags_list = examine_tags(user)
if(length(tags_list))
var/tag_string = list()
for (var/atom_tag in tags_list)
tag_string += (isnull(tags_list[atom_tag]) ? atom_tag : SPAN_TOOLTIP(tags_list[atom_tag], atom_tag))
// some regex to ensure that we don't add another "and" if the final element's main text (not tooltip) has one
tag_string = english_list(tag_string, and_text = (findtext(tag_string[length(tag_string)], regex(@">.*?and .*?<"))) ? " " : " and ")
. += "It is a [tag_string] [examine_descriptor(user)]."
// Returns a SPAN_* based on health, if configured.
var/list/condition_hints = src.condition_hints()
if(length(condition_hints))
@@ -145,6 +154,35 @@
if(H.glasses)
H.glasses.glasses_examine_atom(src, H)
/// What this atom should be called in examine tags
/atom/proc/examine_descriptor(mob/user)
return "object"
/**
* A list of "tags" displayed after atom's description in examine.
* This should return an assoc list of tags -> tooltips for them. If item is null, then no tooltip is assigned.
*
* * TGUI tooltips (not the main text) in chat cannot use HTML stuff at all, so
* trying something like `<b><big>ffff</big></b>` will not work for tooltips.
*
* For example:
* ```byond
* . = list()
* .["small"] = "It is a small item."
* .["fireproof"] = "It is made of fire-retardant materials."
* .["and conductive"] = "It's made of conductive materials and whatnot. Blah blah blah." // having "and " in the end tag's main text/key works too!
* ```
* will result in
*
* It is a *small*, *fireproof* *and conductive* item.
*
* where "item" is pulled from [/atom/proc/examine_descriptor]
*/
/atom/proc/examine_tags(mob/user)
. = list()
SEND_SIGNAL(src, COMSIG_ATOM_EXAMINE_TAGS, user, .)
/**
* Used to check if "examine_fluff" from the HTML link in examine() is true, i.e. if it was clicked.
*/
+15 -16
View File
@@ -344,23 +344,22 @@
I.forceMove(T)
/obj/item/get_examine_text(mob/user, distance, is_adjacent, infix, suffix, get_extended = FALSE)
var/size
switch(src.w_class)
if (WEIGHT_CLASS_HUGE to INFINITY)
size = "huge"
if (WEIGHT_CLASS_BULKY to WEIGHT_CLASS_HUGE)
size = "bulky"
if (WEIGHT_CLASS_NORMAL to WEIGHT_CLASS_BULKY)
size = "normal-sized"
if (WEIGHT_CLASS_SMALL to WEIGHT_CLASS_NORMAL)
size = "small"
if (0 to WEIGHT_CLASS_SMALL)
size = "tiny"
//Changed this switch to ranges instead of tiered values, to cope with granularity and also
//things outside its range ~Nanako
/obj/item/examine_descriptor(mob/user)
return "item"
. = ..(user, distance, is_adjacent, "It is a [size] item.", get_extended = get_extended)
/obj/item/examine_tags(mob/user)
var/list/parent_tags = ..()
parent_tags.Insert(1, weight_class_to_text(w_class)) // To make size display first, otherwise it looks goofy
. = parent_tags
.[weight_class_to_text(w_class)] = weight_class_to_tooltip(w_class)
if (siemens_coefficient == 0)
.["insulated"] = "It is made from a robust electrical insulator and will block any electricity passing through it!"
else if (siemens_coefficient <= 0.5)
.["partially insulated"] = "It is made from a poor insulator that will dampen (but not fully block) electric shocks passing through it."
/obj/item/get_examine_text(mob/user, distance, is_adjacent, infix, suffix, get_extended = FALSE)
. = ..(user, distance, is_adjacent, get_extended = get_extended)
var/datum/component/armor/armor_component = GetComponent(/datum/component/armor)
if(armor_component && !armor_component.hidden)
. += FONT_SMALL(SPAN_NOTICE("\[?\] This item has armor values. <a href='byond://?src=[REF(src)];examine_armor=1'>\[Show Armor Values\]</a>"))
@@ -42,7 +42,7 @@
else if(crystal_type == CRYSTAL_TYPE_BLUECRYSTAL)
I.hidden_uplink.bluecrystals += amount
I.hidden_uplink.update_tgui_data()
SSnanoui.update_uis(I.hidden_uplink)
SStgui.update_uis(I.hidden_uplink)
use(amount)
to_chat(user, SPAN_NOTICE("You slot \the [src] into \the [I] and charge its internal uplink."))
+16 -4
View File
@@ -6,16 +6,20 @@
pass_flags_self = PASSSTRUCTURE
should_use_health = TRUE
var/material_alteration = MATERIAL_ALTERATION_ALL // Overrides for material shit. Set them manually if you don't want colors etc. See wood chairs/office chairs.
/// Overrides for material shit. Set them manually if you don't want colors etc. See wood chairs/office chairs.
var/material_alteration = MATERIAL_ALTERATION_ALL
var/climbable
var/parts
var/list/climbers
var/list/footstep_sound //footstep sounds when stepped on
/// Footstep sounds when stepped on
var/list/footstep_sound
var/material/material
var/build_amt = 2 // used by some structures to determine into how many pieces they should disassemble into or be made with
/// Used by some structures to determine into how many pieces they should disassemble into or be made with
var/build_amt = 2
var/slowdown = 0 //amount that pulling mobs have their movement delayed by
/// Amount that pulling mobs have their movement delayed by
var/slowdown = 0
/obj/structure/Initialize(mapload)
. = ..()
@@ -40,6 +44,14 @@
material = null
return ..()
/obj/structure/examine_descriptor(mob/user)
return "structure"
/obj/structure/examine_tags(atom/source, mob/user, list/examine_list)
. = ..()
if(climbable)
.["climbable"] = "It can be climbed on, either by dragging your mob onto it or middle-clicking it."
/obj/structure/attackby(obj/item/attacking_item, mob/user, params)
. = ..()
if(user?.a_intent == I_HURT && maxhealth)
+4 -1
View File
@@ -258,6 +258,9 @@ Class Procs:
return FALSE
qdel(src)
/obj/structure/machinery/examine_descriptor(mob/user)
return "machine"
// /obj/structure/machinery/proc/process_all()
// /* Uncomment this if/when you need component processing
// if(processing_flags & MACHINERY_PROCESS_COMPONENTS)
@@ -643,5 +646,5 @@ Class Procs:
/obj/structure/machinery/ui_status(mob/user, datum/ui_state/state)
. = ..()
if(. < UI_INTERACTIVE)
if(user.machine)
if(user?.machine)
user.unset_machine()
+24 -29
View File
@@ -118,9 +118,8 @@ pixel_x = 10;
var/breach_detection = 1
//var/skipprocess = 0 //Experimenting
var/alarm_frequency = 1437
var/remote_control = 0
var/rcon_setting = 2
var/rcon_time = 0
/// If true, RCON is enabled. If false, RCON has been disabled.
var/rcon_setting = TRUE
var/locked = 1
var/aidisabled = 0
var/shorted = 0
@@ -475,18 +474,6 @@ pixel_x = 10;
mode=AALARM_MODE_FILL
apply_mode()
//atmos computer remote controll stuff
switch(rcon_setting)
if(RCON_NO)
remote_control = 0
if(RCON_AUTO)
if(danger_level == 2)
remote_control = 1
else
remote_control = 0
if(RCON_YES)
remote_control = 1
return
/obj/structure/machinery/alarm/proc/handle_heating_cooling(datum/gas_mixture/environment, seconds_per_tick)
@@ -698,6 +685,22 @@ pixel_x = 10;
frequency.post_signal(src, alert_signal)
/obj/structure/machinery/alarm/proc/is_alarming()
return max(danger_level, alarm_area?.atmosalm) > 0
/obj/structure/machinery/alarm/proc/is_remote_user(mob/user)
if(!user)
return FALSE
for(var/datum/tgui/open_tgui in user.tgui_open_uis)
if(open_tgui.interface == "AtmosAlarmControl")
return TRUE
return FALSE
/obj/structure/machinery/alarm/proc/is_unlocked_for(mob/user)
if(is_remote_user(user) && rcon_setting)
return TRUE
return !locked || issilicon(user)
/obj/structure/machinery/alarm/attack_ai(mob/user)
if(!ai_can_interact(user))
return
@@ -751,7 +754,8 @@ pixel_x = 10;
data["target_temperature"] = round(target_temperature - T0C, 0.1)
// Access
data["locked"] = locked && !issilicon(user)
data["locked"] = !is_unlocked_for(user)
data["remote_view"] = is_remote_user(user)
data["shorted"] = shorted
data["rcon"] = rcon_setting
@@ -848,14 +852,6 @@ pixel_x = 10;
// Actions available without unlocking
switch(action)
if("rcon")
var/new_rcon = text2num(params["value"])
switch(new_rcon)
if(RCON_NO) rcon_setting = RCON_NO
if(RCON_AUTO) rcon_setting = RCON_AUTO
if(RCON_YES) rcon_setting = RCON_YES
return TRUE
if("temperature")
var/list/tlv = TLV["temperature"]
var/max_temperature = min(tlv[3] - T0C, MAX_TEMPERATURE)
@@ -871,7 +867,7 @@ pixel_x = 10;
return TRUE
// Actions that require the alarm to be unlocked
if(locked && !issilicon(usr))
if(!is_unlocked_for(usr))
return
switch(action)
@@ -962,11 +958,10 @@ pixel_x = 10;
apply_mode()
return TRUE
/// Snowflake proc to make it so users of the Atmosphere Control app (having the UI open) have full access to an alarm.
/// Lets Atmosphere Control users remotely view alarms.
/obj/structure/machinery/alarm/ui_status(mob/user)
for(var/datum/tgui/open_tgui in user.tgui_open_uis)
if(open_tgui.interface == "AtmosAlarmControl")
return UI_INTERACTIVE
if(is_remote_user(user) && rcon_setting)
return UI_INTERACTIVE
return ..()
/**
@@ -141,7 +141,7 @@
var/list/sensor_information = list()
var/datum/radio_frequency/radio_connection
var/ui_type = "AtmosControl"
ui_type = "AtmosControl"
circuit = /obj/item/circuitboard/air_management
/obj/structure/machinery/computer/general_air_control/Destroy()
@@ -32,6 +32,9 @@
/// The access cable inserted into this computer, if any.
var/obj/item/access_cable/inserted_cable
/// Contains the name of the TGUI interface file opened when using this computer. If null,
var/ui_type = ""
/obj/structure/machinery/computer/Initialize()
. = ..()
overlay_layer = layer
@@ -45,11 +48,6 @@
inserted_cable = null
return ..()
/obj/structure/machinery/computer/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = TRUE)
if(!operable() || !is_station_level(z) || user.stat)
user.unset_machine()
return
/obj/structure/machinery/computer/emp_act(severity)
. = ..()
@@ -248,10 +248,7 @@
/obj/structure/machinery/computer/scan_consolenew/attackby(obj/item/attacking_item, mob/user)
if (istype(attacking_item, /obj/item/disk/data)) //INSERT SOME diskS
if (!src.disk)
user.drop_from_inventory(attacking_item, src)
src.disk = attacking_item
to_chat(user, "You insert [attacking_item].")
SSnanoui.update_uis(src) // update all UIs attached to src
to_chat(user, "You try to insert [attacking_item], but the drive seems jammed.")
return TRUE
else
return ..()
@@ -274,7 +274,6 @@
signal.data["dock_status"] = get_docking_status()
post_signal(signal)
//this is mostly for NanoUI
/datum/computer/file/embedded_program/docking/proc/get_docking_status()
switch (dock_state)
if (STATE_UNDOCKED) return "undocked"
@@ -97,7 +97,7 @@
var/categories = CAT_NORMAL
/// What we're requesting payment for right now
var/datum/data/vending_product/currently_vending = null
/// Status screen messages like "insufficient funds", displayed in NanoUI
/// Status screen messages like "insufficient funds", displayed in UI
var/status_message = ""
/// Set to 1 if status_message is an error
var/status_error = 0
@@ -770,7 +770,6 @@
sel_key = params["vendItem"]
var/datum/data/vending_product/R = product_records[key]
// This should not happen unless the request from NanoUI was bad
if(!(R.category & categories))
return
+3
View File
@@ -26,6 +26,9 @@
/// If the turf should generate details. Default: TRUE
var/has_edge_icon = TRUE
/turf/simulated/floor/examine_descriptor(mob/user)
return "floor"
/turf/simulated/floor/disassembly_hints(mob/user, distance, is_adjacent)
. += ..()
if(flooring)
+3
View File
@@ -62,6 +62,9 @@
state = SPAN_NOTICE("\The [src] seems completely intact.")
. = state
/turf/simulated/wall/examine_descriptor(mob/user)
return "wall"
/turf/simulated/wall/mechanics_hints(mob/user, distance, is_adjacent)
. += ..()
if(locate(/obj/effect/overlay/wallrot) in src)
+3
View File
@@ -3,6 +3,9 @@
icon = 'icons/turf/flooring/tiles.dmi'
icon_state = "tiled_preview"
/turf/unsimulated/floor/examine_descriptor(mob/user)
return "floor"
/turf/unsimulated/floor/monotile
icon_state = "monotile_preview"
+3
View File
@@ -7,6 +7,9 @@
blocks_air = TRUE
pass_flags_self = PASSCLOSEDTURF
/turf/unsimulated/wall/examine_descriptor(mob/user)
return "wall"
/turf/unsimulated/wall/fakeglass
name = "window"
icon = 'icons/turf/walls.dmi'
-8
View File
@@ -265,14 +265,6 @@
else //Delayed to avoid wingets from Login calls.
addtimer(CALLBACK(src, VERB_REF(fit_viewport), 1 SECONDS))
/client/verb/refresh_tgui()
set name = "Refresh TGUI"
set category = "OOC.Debug"
for(var/window_id in tgui_windows)
var/datum/tgui_window/window = tgui_windows[window_id]
window.reinitialize()
/client/verb/fix_stat_panel()
set name = "Fix-Stat-Panel"
set hidden = TRUE
-3
View File
@@ -180,7 +180,6 @@ GLOBAL_LIST_INIT(admin_verbs_server, list(
/datum/admins/proc/toggle_round_spookyness,
/datum/admins/proc/toggle_space_ninja,
/client/proc/toggle_random_events,
/client/proc/nanomapgen_DumpImage,
/client/proc/toggle_recursive_explosions,
/client/proc/restart_controller,
/client/proc/cmd_ss_panic,
@@ -355,7 +354,6 @@ GLOBAL_LIST_INIT(admin_verbs_hideable, list(
/datum/admins/proc/adjump,
/datum/admins/proc/toggle_space_ninja,
/client/proc/toggle_random_events,
/client/proc/nanomapgen_DumpImage,
/client/proc/play_local_sound,
/client/proc/play_sound,
/client/proc/play_server_sound,
@@ -475,7 +473,6 @@ GLOBAL_LIST_INIT(admin_verbs_dev, list( //will need to be altered - Ryan784
/client/proc/jumptoship,
/client/proc/jumptosector,
/client/proc/cmd_dev_say,
/client/proc/nanomapgen_DumpImage,
/client/proc/admin_ghost,
/client/proc/air_report,
/client/proc/enable_debug_verbs,
-15
View File
@@ -463,21 +463,6 @@
usr << browse(HTML_SKELETON(replacetext(SSatoms.InitLog(), "\n", "<br>")), "window=initlog")
/client/proc/reload_nanoui_resources()
set category = "Debug"
set name = "Reload NanoUI Resources"
set desc = "Force the client to redownload NanoUI Resources"
// Close open NanoUIs.
SSnanoui.close_user_uis(usr)
// Re-load the assets.
var/datum/asset/assets = get_asset_datum(/datum/asset/nanoui)
assets.register()
// Clear the user's cache so they get resent.
usr.client.sent_assets = list()
/**
* Used to generate lag and load the MC to test how things work under live server stress
*/
@@ -6,7 +6,6 @@
S["UI_style"] >> pref.UI_style
S["UI_style_color"] >> pref.UI_style_color
S["UI_style_alpha"] >> pref.UI_style_alpha
S["tgui_fancy"] >> pref.tgui_fancy
S["tgui_lock"] >> pref.tgui_lock
S["ooccolor"] >> pref.ooccolor
S["clientfps"] >> pref.clientfps
@@ -21,7 +20,6 @@
S["UI_style"] << pref.UI_style
S["UI_style_color"] << pref.UI_style_color
S["UI_style_alpha"] << pref.UI_style_alpha
S["tgui_fancy"] << pref.tgui_fancy
S["tgui_lock"] << pref.tgui_lock
S["ooccolor"] << pref.ooccolor
S["clientfps"] << pref.clientfps
@@ -39,7 +37,6 @@
"UI_style",
"UI_style_color",
"UI_style_alpha",
"tgui_fancy",
"tgui_lock",
"ooccolor",
"clientfps",
@@ -63,7 +60,6 @@
"UI_style",
"UI_style_color",
"UI_style_alpha",
"tgui_fancy",
"tgui_lock",
"ooccolor",
"clientfps",
@@ -83,7 +79,6 @@
"UI_style_alpha" = pref.UI_style_alpha,
"UI_style_color" = pref.UI_style_color,
"UI_style" = pref.UI_style,
"tgui_fancy" = pref.tgui_fancy,
"tgui_lock" = pref.tgui_lock,
"ooccolor" = pref.ooccolor,
"clientfps" = pref.clientfps,
@@ -100,7 +95,6 @@
pref.UI_style_color = sanitize_hexcolor(pref.UI_style_color, initial(pref.UI_style_color))
pref.UI_style_alpha = sanitize_integer(text2num(pref.UI_style_alpha), 0, 255, initial(pref.UI_style_alpha))
pref.clientfps = sanitize_integer(text2num(pref.clientfps), 0, 1000, initial(pref.clientfps))
pref.tgui_fancy = sanitize_bool(pref.tgui_fancy, TRUE)
pref.tgui_lock = sanitize_bool(pref.tgui_lock, FALSE)
pref.tgui_inputs = sanitize_bool(pref.tgui_inputs, TRUE)
pref.tgui_buttons_large = sanitize_bool(pref.tgui_buttons_large, FALSE)
@@ -116,7 +110,6 @@
dat += "-Color: <a href='byond://?src=[REF(src)];select_color=1'><b>[pref.UI_style_color]</b></a> [HTML_RECT(pref.UI_style_color)] - <a href='byond://?src=[REF(src)];reset=ui'>reset</a><br>"
dat += "-Alpha(transparency): <a href='byond://?src=[REF(src)];select_alpha=1'><b>[pref.UI_style_alpha]</b></a> - <a href='byond://?src=[REF(src)];reset=alpha'>reset</a><br>"
dat += "<b>Tooltip Style:</b> <a href='byond://?src=[REF(src)];select_tooltip_style=1'><b>[pref.tooltip_style]</b></a><br>"
dat += "<b>TGUI Fancy:</b> <a href='byond://?src=[REF(src)];select_tguif=1'><b>[pref.tgui_fancy ? "ON" : "OFF"]</b></a><br>"
dat += "<b>TGUI Lock:</b> <a href='byond://?src=[REF(src)];select_tguil=1'><b>[pref.tgui_lock ? "ON" : "OFF"]</b></a><br>"
dat += "<b>TGUI Inputs:</b> <a href='byond://?src=[REF(src)];tgui_inputs=1'><b>[pref.tgui_inputs ? "ON" : "OFF"]</b></a><br>"
dat += "<b>TGUI Input Large Buttons:</b> <a href='byond://?src=[REF(src)];tgui_inputs_large=1'><b>[pref.tgui_buttons_large ? "ON" : "OFF"]</b></a><br>"
@@ -152,10 +145,6 @@
pref.UI_style_alpha = UI_style_alpha_new
return TOPIC_REFRESH
else if(href_list["select_tguif"])
pref.tgui_fancy = !pref.tgui_fancy
return TOPIC_REFRESH
else if(href_list["select_tguil"])
pref.tgui_lock = !pref.tgui_lock
return TOPIC_REFRESH
-1
View File
@@ -26,7 +26,6 @@ GLOBAL_LIST_EMPTY_TYPED(preferences_datums, /datum/preferences)
var/sfx_toggles = ASFX_DEFAULT
var/UI_style_color = "#ffffff"
var/UI_style_alpha = 255
var/tgui_fancy = TRUE
var/tgui_lock = FALSE
var/tgui_inputs = TRUE
var/tgui_buttons_large = FALSE
+10
View File
@@ -77,6 +77,16 @@
QDEL_LIST(accessories)
return ..()
/obj/item/clothing/examine_descriptor(mob/user)
return "clothing"
/obj/item/clothing/examine_tags(mob/user)
. = ..()
if(item_flags & ITEM_FLAG_THICK_MATERIAL)
.["thick"] = "Stops or slows minor piercing effects, such as from injectors."
if(item_flags & ITEM_FLAG_INJECTION_PORT)
.["port-enabled"] = "Has a dedicated injection port for syringes and hypo-sprays to be used on the wearer."
//Updates the icons of the mob wearing the clothing item, if any.
/obj/item/clothing/proc/update_clothing_icon()
return
+1
View File
@@ -5,6 +5,7 @@
icon_state = "hardhat_yellow"
item_state = "hardhat_yellow"
light_overlay = "hardhat_light"
item_flags = ITEM_FLAG_THICK_MATERIAL
contained_sprite = TRUE
action_button_name = "Toggle Headlamp"
light_range = 4 //luminosity when on
-3
View File
@@ -325,9 +325,6 @@
if(istype(exosuit) && exosuit.head && exosuit.head.radio && exosuit.head.radio.is_functional())
return ..()
/obj/item/radio/exosuit/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/ui_state/state = GLOB.mech_state)
. = ..()
/mob/living/heavy_vehicle/proc/become_remote()
for(var/mob/user in pilots)
eject(user, FALSE)
-1
View File
@@ -5,7 +5,6 @@
//TGUI
remove_all_indicators()
SSnanoui.user_logout(src) // this is used to clean up (remove) this user's Nano UIs
GLOB.player_list -= src
disconnect_time = world.realtime
log_access("Logout: [key_name(src)]")
@@ -45,8 +45,7 @@ if one is destroyed the second will take over. If all relays are gone it stops w
possible to enable/disable Software Downloading, P2P file transfers and Communication (IC version of IRC, PDA messages for more than two people)
5. Software
Software would almost exclusively use NanoUI modules. Few exceptions are text editor (uses similar screen as TCS IDE used for editing and classic HTML for previewing as Nano looks differently)
and similar programs which for some reason require HTML UI. Most software will be highly dependent on NTNet to work as laptops are not physically connected to the station's network.
Software would almost exclusively use TGUI modules. Most software will be highly dependent on NTNet to work as laptops are not physically connected to the station's network.
What i plan to add:
Note: XXXXDB programs will use ingame_manuals to display basic help for players, similar to how books, etc. do
@@ -416,13 +416,10 @@
/obj/item/modular_computer/proc/update_uis()
if(active_program) //Should we update program ui or computer ui?
SSnanoui.update_uis(active_program)
SStgui.update_uis(src)
if(active_program.NM)
SSnanoui.update_uis(active_program.NM)
if(active_program)
SStgui.update_uis(active_program)
else
SStgui.update_uis(src)
SSnanoui.update_uis(src)
/obj/item/modular_computer/proc/check_update_ui_need()
var/ui_update_needed = FALSE
@@ -458,18 +455,6 @@
if(ui_update_needed)
update_uis()
// Used by camera monitor program
/obj/item/modular_computer/check_eye(var/mob/user)
if(active_program)
return active_program.check_eye(user)
return ..()
// Used by camera monitor program
/obj/item/modular_computer/grants_equipment_vision(var/mob/user)
if(active_program)
return active_program.grants_equipment_vision(user)
return ..()
/obj/item/modular_computer/get_cell()
return battery_module ? battery_module.get_cell() : DEVICE_NO_CELL
@@ -37,9 +37,9 @@
/obj/item/modular_computer/proc/get_header_data(list/data)
LAZYINITLIST(data)
data["PC_batteryicon"] = get_battery_icon()
data["PC_batteryicon"] = battery_module ? get_battery_icon() : null
data["PC_showbatteryicon"] = !!battery_module
data["PC_batterypercent"] = battery_module ? "[round(battery_module.battery.percent())] %" : "N/C"
data["PC_batterypercent"] = battery_module ? "[round(battery_module.battery.percent())] %" : null
data["PC_apclinkicon"] = (tesla_link?.enabled && apc_powered) ? "charging.gif" : ""
data["PC_device_theme"] = active_program ? active_program.tgui_theme : "scc"
data["PC_ntneticon"] = get_ntnet_status_icon()
@@ -206,5 +206,15 @@
return UI_INTERACTIVE
. = ..()
if(. < UI_INTERACTIVE)
if(user.machine)
if(user?.machine)
user.unset_machine()
/obj/item/modular_computer/check_eye(mob/user)
if(active_program)
return active_program.check_eye(user)
return ..()
/obj/item/modular_computer/grants_equipment_vision(mob/user)
if(active_program)
return active_program.grants_equipment_vision(user)
return ..()
@@ -19,6 +19,9 @@
/obj/item/modular_computer/silicon/ui_host()
. = computer_host
/obj/item/modular_computer/silicon/ui_state(mob/user)
return GLOB.self_state
/obj/item/modular_computer/silicon/Initialize(mapload)
if(istype(loc, /mob/living/silicon))
computer_host = loc
@@ -84,11 +84,7 @@ ABSTRACT_TYPE(/datum/computer_file/program)
var/tgui_theme = "scc"
/// If this TGUI should autoupdate or not.
var/ui_auto_update = TRUE
// TO BE DEPRECATED:
var/datum/nano_module/NM // If the program uses NanoModule, put it here and it will be automagically opened. Otherwise implement ui_interact.
var/nanomodule_path // Path to nanomodule, make sure to set this if implementing new program.
var/ui_auto_update = TRUE // Path to nanomodule, make sure to set this if implementing new program.
/datum/computer_file/program/New(obj/item/modular_computer/comp)
..()
@@ -136,7 +132,6 @@ ABSTRACT_TYPE(/datum/computer_file/program)
var/datum/computer_file/program/temp = ..(rename, computer)
temp.required_access_run = required_access_run
temp.required_access_download = required_access_download
temp.nanomodule_path = nanomodule_path
temp.filedesc = filedesc
temp.program_icon_state = program_icon_state
temp.requires_ntnet = requires_ntnet
@@ -200,6 +195,14 @@ ABSTRACT_TYPE(/datum/computer_file/program)
return computer.get_ntnet_status(specific_action)
return FALSE
/// Allows active programs to preserve non-standard user views, such as camera monitors.
/datum/computer_file/program/proc/check_eye(mob/user)
return -1
/// Allows active programs to grant equipment vision while controlling the user's view.
/datum/computer_file/program/proc/grants_equipment_vision(mob/user)
return FALSE
// Called by Process() on device that runs us, once every tick.
/datum/computer_file/program/proc/process_tick()
return TRUE
@@ -307,31 +310,12 @@ ABSTRACT_TYPE(/datum/computer_file/program)
else // Should never happen - So fail silently
return FALSE
// This attempts to retrieve header data for NanoUIs. If implementing completely new device of different type than existing ones
// This attempts to retrieve header data for UIs. If implementing completely new device of different type than existing ones
// always include the device here in this proc. This proc basically relays the request to whatever is running the program.
/datum/computer_file/program/proc/get_header_data()
if(computer)
return computer.get_header_data()
/datum/computer_file/program/Topic(href, href_list)
if(..())
return TRUE
if(computer)
return computer.Topic(href, href_list)
// Relays the call to nano module, if we have one
/datum/computer_file/program/proc/check_eye(var/mob/user)
if(NM)
return NM.check_eye(user)
else
return -1
/// Relays the call to nano module, if we have one
/datum/computer_file/program/proc/grants_equipment_vision(var/mob/user)
if(NM)
return NM.grants_equipment_vision(user)
/datum/computer_file/program/proc/message_dead(var/message)
for(var/mob/M in GLOB.player_list)
if(M.stat == DEAD && (M.client && M.client.prefs.toggles & CHAT_GHOSTEARS))
@@ -5,8 +5,6 @@
// When implementing new program based device, use this to run the program.
/datum/computer_file/program/proc/run_program(var/mob/user)
if(can_run(user, 1) || !requires_access_to_run)
if(nanomodule_path)
NM = new nanomodule_path(src, new /datum/topic_manager/program(src), src)
if(requires_ntnet && network_destination)
generate_network_log("Connection opened to [network_destination].")
program_state = PROGRAM_STATE_ACTIVE
@@ -18,9 +16,6 @@
program_state = PROGRAM_STATE_KILLED
if(network_destination)
generate_network_log("Connection to [network_destination] closed.")
if(NM)
qdel(NM)
NM = null
return TRUE
// Is called when program service is being activated
@@ -1,39 +1,5 @@
/obj/item/modular_computer/initial_data()
/obj/item/modular_computer/proc/initial_data()
return list("_PC" = get_header_data())
/datum/computer_file/program/initial_data()
/datum/computer_file/program/proc/initial_data()
return list("_PC" = get_header_data())
/obj/item/modular_computer/update_layout()
return TRUE
/datum/computer_file/program/update_layout()
return TRUE
/datum/nano_module/program
available_to_ai = FALSE
var/datum/computer_file/program/program // Program-Based computer program that runs this nano module. Defaults to null.
/datum/nano_module/program/New(var/host, var/topic_manager, var/program)
..()
src.program = program
/datum/nano_module/program/Destroy()
program = null
return ..()
/datum/topic_manager/program
var/datum/program
/datum/topic_manager/program/New(var/datum/program)
..()
src.program = program
/datum/topic_manager/program/Destroy()
program = null
return ..()
// Calls forwarded to PROGRAM itself should begin with "PRG_"
// Calls forwarded to COMPUTER running the program should begin with "PC_"
/datum/topic_manager/program/Topic(href, href_list)
return program && program.Topic(href, href_list)
@@ -9,7 +9,6 @@
available_on_ntnet = TRUE
usage_flags = PROGRAM_ALL
tgui_id = "CargoOrder"
ui_auto_update = FALSE
var/page = "main" //main - Main Menu, order - Order Page, item_details - Item Details Page, tracking - Tracking Page
var/selected_category = null // Category that is currently selected
@@ -11,7 +11,6 @@
size = 8
color = LIGHT_COLOR_BLUE
tgui_id = "IDCardModification"
ui_auto_update = FALSE
var/is_centcom = FALSE
var/show_assignments = FALSE
@@ -12,7 +12,6 @@
network_destination = "station long-range communication array"
color = LIGHT_COLOR_BLUE
tgui_id = "CommandCommunications"
ui_auto_update = FALSE
var/datum/comm_message_listener/message_core
var/intercept = FALSE
var/can_call_shuttle = FALSE //If calling the shuttle should be available from this console
@@ -272,7 +271,12 @@ GLOBAL_VAR_INIT(last_message_id, 0)
/datum/comm_message_listener/proc/Add(var/list/message)
messages[++messages.len] = message
/datum/comm_message_listener/proc/Remove(var/list/message)
/datum/comm_message_listener/proc/Remove(var/message)
if(isnum(message))
for(var/list/stored_message in messages)
if(stored_message["id"] == message)
message = stored_message
break
messages -= list(message)
/*
Command action procs
@@ -48,7 +48,7 @@
// Opens the interface for the given air alarm.
if("alarm")
var/obj/structure/machinery/alarm/alarm = locate(params["alarm"]) in (monitored_alarms.len ? monitored_alarms : SSmachinery.processing)
if(alarm)
if(alarm?.rcon_setting)
alarm.ui_interact(usr)
return TRUE
// Manually clear and repopulate the alarm list.
@@ -65,6 +65,8 @@
var/alarms = list()
for(var/obj/structure/machinery/alarm/alarm in monitored_alarms)
if(!alarm.rcon_setting)
continue
alarms += list(list(
"deck" = alarm.alarm_area.horizon_deck,
"dept" = alarm.alarm_area.department,
@@ -77,4 +79,3 @@
data["alarms"] = alarms
return data
@@ -23,7 +23,9 @@
..()
lstate = SSnightlight.is_active() ? "dark" : "full"
/datum/computer_file/program/lighting_control/proc/update_lighting()
/datum/computer_file/program/lighting_control/proc/update_lighting(var/new_context = context, var/new_lstate = lstate)
context = new_context
lstate = new_lstate
// whether to only select areas explicitly marked for nightlighting
var/wl_only = context == "all" ? 0 : 1
@@ -48,7 +50,8 @@
lstate = params["mode"]
. = TRUE
if ("set")
update_lighting()
update_lighting(params["context"] || context, params["mode"] || lstate)
. = TRUE
/datum/computer_file/program/lighting_control/ui_data(mob/user)
var/list/data = initial_data()
@@ -14,7 +14,6 @@
color = LIGHT_COLOR_YELLOW
tgui_id = "RCON"
tgui_theme = "hephaestus"
ui_auto_update = FALSE
/datum/computer_file/program/rcon_console/New()
..()
@@ -162,9 +162,10 @@
return FALSE
set_current(C)
user.set_machine(ui_host())
var/obj/host = ui_host()
if(host)
user.set_machine(host)
user.reset_view(current_camera)
check_eye(user)
if(ishuman(user))
var/mob/living/carbon/human/H = user
@@ -172,6 +173,22 @@
return TRUE
/datum/computer_file/program/camera_monitor/check_eye(mob/user)
if(user.stat || user.blinded)
return -1
if(!current_camera)
return 0
var/viewflag = current_camera.check_eye(user)
if(viewflag < 0)
reset_current()
return viewflag
/datum/computer_file/program/camera_monitor/grants_equipment_vision(mob/user)
if(user.stat || user.blinded || !current_camera)
return FALSE
return current_camera.grants_equipment_vision(user)
/datum/computer_file/program/camera_monitor/proc/set_current(var/obj/structure/machinery/camera/C)
if(current_camera == C)
return
@@ -192,44 +209,14 @@
L.tracking_cancelled()
current_camera = null
/datum/computer_file/program/camera_monitor/check_eye(var/mob/user as mob)
var/obj/item/modular_computer/MC = user.machine
if(istype(MC) && ui_host() == MC)
if(!MC.working || user.blinded || user.stat)
user.unset_machine()
return -1
if(!current_camera)
return 0
var/viewflag = current_camera.check_eye(user)
if ( viewflag < 0 ) //camera doesn't work
reset_current()
return viewflag
/datum/computer_file/program/camera_monitor/grants_equipment_vision(mob/user)
var/obj/item/modular_computer/MC = user.machine
if(istype(MC) && ui_host() == MC)
if(!MC.working || user.blinded || user.stat)
return FALSE
if(!current_camera)
return FALSE
var/viewflag = current_camera.check_eye(user)
if (viewflag < 0) //camera doesn't work
return FALSE
return TRUE
// ERT Variant of the program
/datum/computer_file/program/camera_monitor/ert
filename = "ntcammon"
filedesc = "Advanced Camera Monitoring"
extended_desc = "This program allows remote access to station's camera system. Some camera networks may have additional access requirements. This version has an integrated database with additional encrypted keys."
size = 14
nanomodule_path = /datum/nano_module/camera_monitor/ert
available_on_ntnet = FALSE
/datum/nano_module/camera_monitor/ert
name = "Advanced Camera Monitoring Program"
available_to_ai = FALSE
// The ERT variant has access to ERT and crescent cams, but still checks for accesses. ERT members should be able to use it.
/datum/computer_file/program/camera_monitor/ert/modify_networks_list(var/list/networks)
..()
@@ -242,13 +229,9 @@
filedesc = "Journalism Camera Monitoring"
extended_desc = "This program allows remote access to station's camera system. Some camera networks may have additional access requirements. This version has has a connection to news networks."
size = 2
nanomodule_path = /datum/nano_module/camera_monitor/news
required_access_download = null
usage_flags = PROGRAM_ALL
/datum/nano_module/camera_monitor/news
name = "Journalism Camera Monitoring Program"
/datum/computer_file/program/camera_monitor/news/modify_networks_list(var/list/networks)
networks = list()
networks.Add(list(list("tag" = NETWORK_NEWS, "has_access" = 1)))
@@ -128,7 +128,6 @@
set_current(C)
user.machine = ui_host()
user.reset_view(current_camera)
check_eye(user)
return TRUE
/datum/computer_file/program/penal_mechs/proc/set_current(var/obj/structure/machinery/camera/C)
@@ -150,11 +149,3 @@
if(istype(L))
L.tracking_cancelled()
current_camera = null
/datum/computer_file/program/penal_mechs/check_eye(var/mob/user)
if(!current_camera)
return FALSE
var/viewflag = current_camera.check_eye(user)
if(viewflag < 0) //camera doesn't work
reset_current()
return viewflag
@@ -12,6 +12,7 @@
undeletable = TRUE
tgui_id = "FileManager"
var/open_file
var/open_file_is_usb = FALSE
var/error
/datum/computer_file/program/filemanager/ui_data(mob/user)
@@ -27,7 +28,11 @@
if(!computer || !computer.hard_drive)
data["error"] = "I/O ERROR: Unable to access hard drive."
else
HDD = computer.hard_drive
HDD = open_file_is_usb ? computer.portable_drive : computer.hard_drive
data["file_is_usb"] = open_file_is_usb
if(!HDD)
data["error"] = "I/O ERROR: Unable to open file."
return data
file = HDD.find_file_by_name(open_file)
script = file
if(!istype(file))
@@ -45,6 +50,7 @@
else
data["scriptdata"] = null
data["filedata"] = null
data["file_is_usb"] = FALSE
data["filename"] = null
HDD = computer.hard_drive
RHDD = computer.portable_drive
@@ -85,10 +91,23 @@
return
if(F.can_access_file(usr))
open_file = params["PRG_openfile"]
open_file_is_usb = FALSE
if("PRG_usbopenfile")
. = TRUE
var/obj/item/computer_hardware/hard_drive/RHDD = computer.portable_drive
if(!RHDD)
return
var/datum/computer_file/F = RHDD.find_file_by_name(params["PRG_usbopenfile"])
if(!F)
return
if(F.can_access_file(usr))
open_file = params["PRG_usbopenfile"]
open_file_is_usb = TRUE
if("PRG_newtextfile")
. = TRUE
var/newname = sanitize(input(usr, "Enter file name or leave blank to cancel:", "File rename"))
var/newname = sanitize_filename(tgui_input_text(usr, "Enter file name or leave blank to cancel:", "File rename"))
if(!newname)
return TRUE
var/obj/item/computer_hardware/hard_drive/HDD = computer.hard_drive
@@ -97,7 +116,9 @@
var/datum/computer_file/data/F = new/datum/computer_file/data()
F.filename = newname
F.filetype = "TXT"
HDD.store_file(F)
if(!HDD.store_file(F))
qdel(F)
error = "I/O ERROR: Unable to create file. The hard drive may be full, read-only, or contain a conflicting file."
if("PRG_deletefile")
. = TRUE
@@ -122,6 +143,7 @@
if("PRG_closefile")
. = TRUE
open_file = null
open_file_is_usb = FALSE
error = null
if("PRG_clone")
@@ -132,8 +154,13 @@
var/datum/computer_file/F = HDD.find_file_by_name(params["PRG_clone"])
if(!F || !istype(F))
return TRUE
if(istype(F, /datum/computer_file/program))
error = "I/O ERROR: Executable programs cannot be cloned from File Manager."
return TRUE
var/datum/computer_file/C = F.clone(1)
HDD.store_file(C)
if(!HDD.store_file(C))
qdel(C)
error = "I/O ERROR: Unable to clone file. The hard drive may be full, read-only, or contain a conflicting file."
if("PRG_rename")
. = TRUE
@@ -143,7 +170,7 @@
var/datum/computer_file/file = HDD.find_file_by_name(params["PRG_rename"])
if(!file || !istype(file))
return TRUE
var/newname = sanitize(input(usr, "Enter new file name:", "File rename", file.filename))
var/newname = sanitize_filename(tgui_input_text(usr, "Enter new file name or leave blank to cancel:", "File rename", file.filename))
if(file && newname)
file.filename = newname
@@ -163,7 +190,7 @@
var/oldtext = html_decode(F.stored_data)
oldtext = replacetext(oldtext, "\[editorbr\]", "\n")
var/newtext = sanitize(replacetext(input(usr, "Editing file [open_file]. You may use most tags used in paper formatting:", "Text Editor", oldtext) as message|null, "\n", "\[editorbr\]"), MAX_TEXTFILE_LENGTH)
var/newtext = sanitize_filename(replacetext(tgui_input_text(usr, "Editing file [open_file]. You may use most tags used in paper formatting:", "Text Editor", oldtext), "\n", "\[editorbr\]"), MAX_TEXTFILE_LENGTH)
if(!newtext)
return
@@ -185,10 +212,10 @@
return TRUE
var/obj/item/computer_hardware/hard_drive/HDD = computer.hard_drive
if(!HDD)
return TRUE
return FALSE
if(!computer.nano_printer)
error = "Missing Hardware: Your computer does not have required hardware to complete this operation."
return TRUE
return FALSE
var/datum/computer_file/data/F = HDD.find_file_by_name(open_file)
var/datum/computer_file/script/S = F
if(!F)
@@ -196,21 +223,21 @@
if(istype(F))
if(!computer.nano_printer.print_text(F.stored_data))
error = "Hardware error: Printer was unable to print the file. It may be out of paper."
return TRUE
return FALSE
else if(istype(S))
if(!computer.nano_printer.print_text(S.code))
error = "Hardware error: Printer was unable to print the file. It may be out of paper."
return TRUE
return FALSE
if("PRG_copytousb")
. = TRUE
var/obj/item/computer_hardware/hard_drive/HDD = computer.hard_drive
var/obj/item/computer_hardware/hard_drive/portable/RHDD = computer.portable_drive
if(!HDD || !RHDD)
return TRUE
return FALSE
var/datum/computer_file/F = HDD.find_file_by_name(params["PRG_copytousb"])
if(!F || !istype(F))
return TRUE
return FALSE
var/is_usr_tech_support = FALSE
if(ishuman(usr))
var/mob/living/carbon/human/H = usr
@@ -270,7 +297,7 @@
return
if(F.password)
return
F.password = sanitize(input(usr, "Enter an encryption key:", "Encrypt File"))
F.password = sanitize_filename(tgui_input_text(usr, "Enter an encryption key:", "Encrypt File"))
if("PRG_decrypt")
. = TRUE
-63
View File
@@ -1,63 +0,0 @@
/datum/nano_module
var/name
var/datum/host
var/available_to_ai = TRUE
var/datum/topic_manager/topic_manager
/datum/nano_module/New(var/datum/host, var/topic_manager)
..()
src.host = host
src.topic_manager = topic_manager
/datum/nano_module/Destroy()
host = null
topic_manager = null
return ..()
/datum/nano_module/ui_host()
return host ? host.ui_host() : src
/datum/nano_module/proc/can_still_topic(var/datum/ui_state/state = GLOB.default_state)
return CanUseTopic(usr, state) == STATUS_INTERACTIVE
/datum/nano_module/proc/check_eye(var/mob/user)
return -1
/datum/nano_module/proc/grants_equipment_vision(var/mob/user)
return FALSE
/datum/nano_module/proc/check_access(var/mob/user, var/access)
if(!access)
return 1
if(!istype(user))
return 0
var/obj/item/card/id/I = user.GetIdCard()
if(!I)
return 0
if(access in I.access)
return 1
return 0
/datum/nano_module/Topic(href, href_list)
if(topic_manager && topic_manager.Topic(href, href_list))
return TRUE
. = ..()
/datum/nano_module/nano_host()
return host ? host : src
/datum/nano_module/proc/get_host_z()
return GET_Z(nano_host())
/datum/proc/initial_data()
return list()
/datum/proc/update_layout()
return FALSE
/datum/proc/nano_host()
return src
-37
View File
@@ -1,37 +0,0 @@
// This file contains all Nano procs/definitions for external classes/objects
/**
* Called when a Nano UI window is closed
* This is how Nano handles closed windows
* It must be a verb so that it can be called using winset
*/
/client/verb/nanoclose(var/uiref as text)
set hidden = 1 // hide this verb from the user's panel
set name = "nanoclose"
var/datum/nanoui/ui = locate(uiref)
if (istype(ui))
ui.close()
if(ui.ref)
var/href = "close=1"
src.Topic(href, params2list(href), ui.ref) // this will direct to the atom's Topic() proc via client.Topic()
else if (ui.on_close_logic)
// no atomref specified (or not found)
// so just reset the user mob's machine var
if(src && src.mob)
src.mob.unset_machine()
/**
* The ui_interact proc is used to open and update Nano UIs
* If ui_interact is not used then the UI will not update correctly
* ui_interact is currently defined for /atom/movable
*
* @param user /mob The mob who is interacting with this ui
* @param ui_key string A string key to use for this ui. Allows for multiple unique uis on one obj/mob (defaut value "main")
* @param ui /datum/nanoui This parameter is passed by the nanoui process() proc when updating an open ui
* @param force_open boolean Force the UI to (re)open, even if it's already open
*/
/datum/proc/nanoui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/nanoui/master_ui = null, var/datum/ui_state/state = GLOB.default_state)
return
-90
View File
@@ -1,90 +0,0 @@
// This file is a modified version of https://raw2.github.com/Baystation12/OldCode-BS12/master/code/TakePicture.dm
#define NANOMAP_ICON_SIZE 4
#define NANOMAP_MAX_ICON_DIMENSION 1024
#define NANOMAP_TILES_PER_IMAGE (NANOMAP_MAX_ICON_DIMENSION / NANOMAP_ICON_SIZE)
#define NANOMAP_TERMINALERR 5
#define NANOMAP_INPROGRESS 2
#define NANOMAP_BADOUTPUT 2
#define NANOMAP_SUCCESS 1
#define NANOMAP_WATCHDOGSUCCESS 4
#define NANOMAP_WATCHDOGTERMINATE 3
//Call these procs to dump your world to a series of image files (!!)
//NOTE: Does not explicitly support non 32x32 icons or stuff with large pixel_* values, so don't blame me if it doesn't work perfectly
/client/proc/nanomapgen_DumpImage()
set name = "Generate NanoUI Map"
set category = "Server"
if(holder)
nanomapgen_DumpTile(1, 1, text2num(input(usr,"Enter the Z level to generate")))
/client/proc/nanomapgen_DumpTile(var/startX = 1, var/startY = 1, var/currentZ = 1, var/endX = -1, var/endY = -1)
if (endX < 0 || endX > world.maxx)
endX = world.maxx
if (endY < 0 || endY > world.maxy)
endY = world.maxy
if (currentZ < 0 || currentZ > world.maxz)
to_chat(usr, "NanoMapGen: <B>ERROR: currentZ ([currentZ]) must be between 1 and [world.maxz]</B>")
sleep(3)
return NANOMAP_TERMINALERR
if (startX > endX)
to_chat(usr, "NanoMapGen: <B>ERROR: startX ([startX]) cannot be greater than endX ([endX])</B>")
sleep(3)
return NANOMAP_TERMINALERR
if (startY > endX)
to_chat(usr, "NanoMapGen: <B>ERROR: startY ([startY]) cannot be greater than endY ([endY])</B>")
sleep(3)
return NANOMAP_TERMINALERR
var/icon/Tile = icon(file("nano/mapbase1024.png"))
if (Tile.Width() != NANOMAP_MAX_ICON_DIMENSION || Tile.Height() != NANOMAP_MAX_ICON_DIMENSION)
log_world("ERROR: NanoMapGen: <B>ERROR: BASE IMAGE DIMENSIONS ARE NOT [NANOMAP_MAX_ICON_DIMENSION]x[NANOMAP_MAX_ICON_DIMENSION]</B>")
sleep(3)
return NANOMAP_TERMINALERR
LOG_DEBUG("NanoMapGen: <B>GENERATE MAP ([startX],[startY],[currentZ]) to ([endX],[endY],[currentZ])</B>")
to_chat(usr, "NanoMapGen: <B>GENERATE MAP ([startX],[startY],[currentZ]) to ([endX],[endY],[currentZ])</B>")
var/count = 0;
for(var/WorldX = startX, WorldX <= endX, WorldX++)
for(var/WorldY = startY, WorldY <= endY, WorldY++)
var/atom/Turf = locate(WorldX, WorldY, currentZ)
var/icon/TurfIcon = new(Turf.icon, Turf.icon_state, dir = Turf.dir)
TurfIcon.Scale(NANOMAP_ICON_SIZE, NANOMAP_ICON_SIZE)
Tile.Blend(TurfIcon, ICON_OVERLAY, ((WorldX - 1) * NANOMAP_ICON_SIZE), ((WorldY - 1) * NANOMAP_ICON_SIZE))
count++
if (count % 8000 == 0)
LOG_DEBUG("NanoMapGen: <B>[count] tiles done</B>")
sleep(1)
var/mapFilename = "new_Aurora[currentZ]"
LOG_DEBUG("NanoMapGen: <B>sending [mapFilename] to client</B>")
usr << browse(Tile, "window=picture;file=[mapFilename];display=0")
LOG_DEBUG("NanoMapGen: <B>Done.</B>")
to_chat(usr, "NanoMapGen: <B>Done. File [mapFilename] uploaded to your cache.</B>")
if (Tile.Width() != NANOMAP_MAX_ICON_DIMENSION || Tile.Height() != NANOMAP_MAX_ICON_DIMENSION)
return NANOMAP_BADOUTPUT
return NANOMAP_SUCCESS
-537
View File
@@ -1,537 +0,0 @@
/**********************************************************
NANO UI FRAMEWORK
nanoui class (or whatever Byond calls classes)
nanoui is used to open and update nano browser uis
**********************************************************/
/datum/nanoui
// the user who opened this ui
var/mob/user
// the object this ui "belongs" to
var/datum/src_object
// the title of this ui
var/title
// the key of this ui, this is to allow multiple (different) uis for each src_object
var/ui_key
// window_id is used as the window name/identifier for browse and onclose
var/window_id
// the browser window width
var/width = 0
// the browser window height
var/height = 0
// whether to use extra logic when window closes
var/on_close_logic = 1
// an extra ref to use when the window is closed, usually null
var/atom/ref = null
// options for modifying window behaviour
var/window_options = "focus=0;can_close=1;can_minimize=1;can_maximize=0;can_resize=1;titlebar=1;" // window option is set using window_id
// the list of stylesheets to apply to this ui
var/list/stylesheets = list()
// the list of javascript scripts to use for this ui
var/list/scripts = list()
// a list of templates which can be used with this ui
var/templates[0]
// the layout key for this ui (this is used on the frontend, leave it as "default" unless you know what you're doing)
var/layout_key = "default"
// this sets whether to re-render the ui layout with each update (default 0, turning on will break the map ui if it's in use)
var/auto_update_layout = 0
// this sets whether to re-render the ui content with each update (default 1)
var/auto_update_content = 1
// the default state to use for this ui (this is used on the frontend, leave it as "default" unless you know what you're doing)
var/state_key = "default"
// show the map ui, this is used by the default layout
var/show_map = 0
// the map z level to display
var/map_z_level = 3
// initial data, containing the full data structure, must be sent to the ui (the data structure cannot be extended later on)
var/list/initial_data[0]
// set to 1 to update the ui automatically every master_controller tick
var/is_auto_updating = 0
// the current status/visibility of the ui
var/status = STATUS_INTERACTIVE
// Relationship between a master interface and its children. Used in update_status
var/datum/nanoui/master_ui
var/list/datum/nanoui/children = list()
var/datum/ui_state/state = null
/**
* Create a new nanoui instance.
*
* @param nuser /mob The mob who has opened/owns this ui
* @param nsrc_object /obj|/mob The obj or mob which this ui belongs to
* @param nui_key string A string key to use for this ui. Allows for multiple unique uis on one src_oject
* @param ntemplate string The filename of the template file from /nano/templates (e.g. "my_template.tmpl")
* @param ntitle string The title of this ui
* @param nwidth int the width of the ui window
* @param nheight int the height of the ui window
* @param nref /atom A custom ref to use if "on_close_logic" is set to 1
*
* @return /nanoui new nanoui object
*/
/datum/nanoui/New(nuser, nsrc_object, nui_key, ntemplate_filename, ntitle = 0, nwidth = 0, nheight = 0, var/atom/nref = null, var/datum/nanoui/master_ui = null, var/datum/ui_state/state = GLOB.default_state)
user = nuser
src_object = nsrc_object
ui_key = nui_key
window_id = "[ui_key][REF(src_object)]"
src.master_ui = master_ui
if(master_ui)
master_ui.children += src
src.state = state
// add the passed template filename as the "main" template, this is required
add_template("main", ntemplate_filename)
if (ntitle)
title = sanitize(ntitle)
if (nwidth)
width = nwidth
if (nheight)
height = nheight
if (nref)
ref = nref
add_common_assets()
var/datum/asset/assets = get_asset_datum(/datum/asset/nanoui)
assets.send(user, assets)
/**
* Use this proc to add assets which are common to (and required by) all nano uis
*
* @return nothing
*/
/datum/nanoui/proc/add_common_assets()
add_script("https://cdnjs.cloudflare.com/ajax/libs/jquery/1.10.2/jquery.min.js")
add_script("https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.0/jquery-ui.min.js")
add_script("libraries.min.js") // A JS file comprising of jQuery, doT.js and jQuery Timer libraries (compressed together)
add_script("nano_utility.js") // The NanoUtility JS, this is used to store utility functions.
add_script("nano_template.js") // The NanoTemplate JS, this is used to render templates.
add_script("nano_state_manager.js") // The NanoStateManager JS, it handles updates from the server and passes data to the current state
add_script("nano_state.js") // The NanoState JS, this is the base state which all states must inherit from
add_script("nano_state_default.js") // The NanoStateDefault JS, this is the "default" state (used by all UIs by default), which inherits from NanoState
add_script("nano_base_callbacks.js") // The NanoBaseCallbacks JS, this is used to set up (before and after update) callbacks which are common to all UIs
add_script("nano_base_helpers.js") // The NanoBaseHelpers JS, this is used to set up template helpers which are common to all UIs
add_stylesheet("shared.css") // this CSS sheet is common to all UIs
add_stylesheet("icons.css") // this CSS sheet is common to all UIs
/**
* Set the current status (also known as visibility) of this ui.
*
* @param state int The status to set, see the defines at the top of this file
* @param push_update int (bool) Push an update to the ui to update it's status (an update is always sent if the status has changed to red (0))
*
* @return nothing
*/
/datum/nanoui/proc/set_status(state, push_update)
if (state != status) // Only update if it is different
if (status == STATUS_DISABLED)
status = state
if (push_update)
update()
else
status = state
if (push_update || status == 0)
push_data(null, 1) // Update the UI, force the update in case the status is 0, data is null so that previous data is used
/**
* Update the status (visibility) of this ui based on the user's status
*
* @param push_update int (bool) Push an update to the ui to update it's status. This is set to 0/false if an update is going to be pushed anyway (to avoid unnessary updates)
*
* @return nothing
*/
/datum/nanoui/proc/update_status(var/push_update = 0)
var/obj/host = src_object.ui_host()
var/new_status = host.CanUseTopic(user, state)
if(master_ui)
new_status = min(new_status, master_ui.status)
set_status(new_status, push_update)
if(new_status == STATUS_CLOSE)
close()
/**
* Set the ui to auto update (every master_controller tick)
*
* @param state int (bool) Set auto update to 1 or 0 (true/false)
*
* @return nothing
*/
/datum/nanoui/proc/set_auto_update(nstate = 1)
is_auto_updating = nstate
/**
* Set the initial data for the ui. This is vital as the data structure set here cannot be changed when pushing new updates.
*
* @param data /list The list of data for this ui
*
* @return nothing
*/
/datum/nanoui/proc/set_initial_data(list/data)
initial_data = data
/**
* Get config data to sent to the ui.
*
* @return /list config data
*/
/datum/nanoui/proc/get_config_data()
var/name = "[src_object]"
name = sanitize(name)
var/list/config_data = list(
"title" = title,
"srcObject" = list("name" = name),
"stateKey" = state_key,
"status" = status,
"autoUpdateLayout" = auto_update_layout,
"autoUpdateContent" = auto_update_content,
"showMap" = show_map,
"mapName" = "Aurora",
"mapZLevel" = map_z_level,
"mapZLevels" = SSatlas.current_map.map_levels,
"user" = list("name" = user.name)
)
return config_data
/**
* Get data to sent to the ui.
*
* @param data /list The list of general data for this ui (can be null to use previous data sent)
*
* @return /list data to send to the ui
*/
/datum/nanoui/proc/get_send_data(var/list/data)
var/list/config_data = get_config_data()
var/list/send_data = list("config" = config_data)
if (!isnull(data))
send_data["data"] = data
return send_data
/**
* Set the browser window options for this ui
*
* @param nwindow_options string The new window options
*
* @return nothing
*/
/datum/nanoui/proc/set_window_options(nwindow_options)
window_options = nwindow_options
/**
* Add a CSS stylesheet to this UI
* These must be added before the UI has been opened, adding after that will have no effect
*
* @param file string The name of the CSS file from /nano/css (e.g. "my_style.css")
*
* @return nothing
*/
/datum/nanoui/proc/add_stylesheet(file)
stylesheets.Add(file)
/**
* Add a JavsScript script to this UI
* These must be added before the UI has been opened, adding after that will have no effect
*
* @param file string The name of the JavaScript file from /nano/js (e.g. "my_script.js")
*
* @return nothing
*/
/datum/nanoui/proc/add_script(file)
scripts.Add(file)
/**
* Add a template for this UI
* Templates are combined with the data sent to the UI to create the rendered view
* These must be added before the UI has been opened, adding after that will have no effect
*
* @param key string The key which is used to reference this template in the frontend
* @param filename string The name of the template file from /nano/templates (e.g. "my_template.tmpl")
*
* @return nothing
*/
/datum/nanoui/proc/add_template(key, filename)
templates[key] = filename
/**
* Set the layout key for use in the frontend Javascript
* The layout key is the basic layout key for the page
* Two files are loaded on the client based on the layout key varable:
* -> a template in /nano/templates with the filename "layout_<layout_key>.tmpl
* -> a CSS stylesheet in /nano/css with the filename "layout_<layout_key>.css
*
* @param nlayout string The layout key to use
*
* @return nothing
*/
/datum/nanoui/proc/set_layout_key(nlayout_key)
layout_key = lowertext(nlayout_key)
/**
* Set the ui to update the layout (re-render it) on each update, turning this on will break the map ui (if it's being used)
*
* @param state int (bool) Set update to 1 or 0 (true/false) (default 0)
*
* @return nothing
*/
/datum/nanoui/proc/set_auto_update_layout(nstate)
auto_update_layout = nstate
/**
* Set the ui to update the main content (re-render it) on each update
*
* @param state int (bool) Set update to 1 or 0 (true/false) (default 1)
*
* @return nothing
*/
/datum/nanoui/proc/set_auto_update_content(nstate)
auto_update_content = nstate
/**
* Set the state key for use in the frontend Javascript
*
* @param nstate_key string The key of the state to use
*
* @return nothing
*/
/datum/nanoui/proc/set_state_key(nstate_key)
state_key = nstate_key
/**
* Toggle showing the map ui
*
* @param nstate_key boolean 1 to show map, 0 to hide (default is 0)
*
* @return nothing
*/
/datum/nanoui/proc/set_show_map(nstate)
show_map = nstate
/**
* Toggle showing the map ui
*
* @param nstate_key boolean 1 to show map, 0 to hide (default is 0)
*
* @return nothing
*/
/datum/nanoui/proc/set_map_z_level(nz)
map_z_level = nz
/**
* Set whether or not to use the "old" on close logic (mainly unset_machine())
*
* @param state int (bool) Set on_close_logic to 1 or 0 (true/false)
*
* @return nothing
*/
/datum/nanoui/proc/use_on_close_logic(state)
on_close_logic = state
/**
* Return the HTML for this UI
*
* @return string HTML for the UI
*/
/datum/nanoui/proc/get_html()
// before the UI opens, add the layout files based on the layout key
add_stylesheet("layout_[layout_key].css")
add_template("layout", "layout_[layout_key].tmpl")
var/head_content = ""
for (var/filename in scripts)
head_content += "<script type='text/javascript' src='[filename]'></script> "
for (var/filename in stylesheets)
head_content += "<link rel='stylesheet' type='text/css' href='[filename]'> "
var/template_data_json = "{}" // An empty JSON object
if (templates.len > 0)
template_data_json = strip_improper(json_encode(templates))
var/list/send_data = get_send_data(initial_data)
var/initial_data_json = replacetext(replacetext(json_encode(send_data), "&#34;", "&amp;#34;"), "'", "&#39;")
initial_data_json = strip_improper(initial_data_json);
var/url_parameters_json = json_encode(list("src" = "[REF(src)]"))
return {"
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<head>
<script type='text/javascript'>
function receiveUpdateData(jsonString)
{
// We need both jQuery and NanoStateManager to be able to receive data
// At the moment any data received before those libraries are loaded will be lost
if (typeof NanoStateManager != 'undefined' && typeof jQuery != 'undefined')
{
NanoStateManager.receiveUpdateData(jsonString);
}
}
</script>
[head_content]
</head>
<body scroll=auto data-template-data='[template_data_json]' data-url-parameters='[url_parameters_json]' data-initial-data='[initial_data_json]'>
<div id='uiLayout'>
</div>
<noscript>
<div id='uiNoScript'>
<h2>JAVASCRIPT REQUIRED</h2>
<p>Your Internet Explorer's Javascript is disabled (or broken).<br/>
Enable Javascript and then open this UI again.</p>
</div>
</noscript>
</body>
</html>
"}
/**
* Open this UI
*
* @return nothing
*/
/datum/nanoui/proc/open()
if(!user.client)
return
// An attempted fix to UIs sometimes locking up spamming runtime errors due to src_object being null for whatever reason.
// This hard-deletes the UI, preventing the device that uses the UI from being locked up permanently.
if(!src_object)
del(src)
var/window_size = ""
if (width && height)
window_size = "size=[width]x[height];"
update_status(0)
if(status == STATUS_CLOSE)
return
user << browse(get_html(), "window=[window_id];[window_size][window_options]")
winset(user, "mapwindow.map", "focus=true") // return keyboard focus to map
on_close_winset()
//onclose(user, window_id)
SSnanoui.ui_opened(src)
/**
* Reinitialise this UI, potentially with a different template and/or initial data
*
* @return nothing
*/
/datum/nanoui/proc/reinitialise(template, new_initial_data)
if(template)
add_template("main", template)
if(new_initial_data)
set_initial_data(new_initial_data)
open()
/**
* Close this UI
*
* @return nothing
*/
/datum/nanoui/proc/close()
is_auto_updating = 0
SSnanoui.ui_closed(src)
user << browse(null, "window=[window_id]")
for(var/datum/nanoui/child in children)
child.close()
children.Cut()
state = null
master_ui = null
if(istype(user))
user.unset_machine()
/**
* Set the UI window to call the nanoclose verb when the window is closed
* This allows Nano to handle closed windows
*
* @return nothing
*/
/datum/nanoui/proc/on_close_winset()
if(!user.client)
return
var/params = "[REF(src)]"
winset(user, window_id, "on-close=\"nanoclose [params]\"")
/**
* Push data to an already open UI window
*
* @return nothing
*/
/datum/nanoui/proc/push_data(data, force_push = 0)
update_status(0)
if (status == STATUS_DISABLED && !force_push)
return // Cannot update UI, no visibility
var/list/send_data = get_send_data(data)
//to_chat(user, list2json(data) // used for debugging)
send_output(user, list2params(list(json_encode(send_data))),"[window_id].browser:receiveUpdateData")
/**
* This Topic() proc is called whenever a user clicks on a link within a Nano UI
* If the UI status is currently STATUS_INTERACTIVE then call the src_object Topic()
* If the src_object Topic() returns 1 (true) then update all UIs attached to src_object
*
* @return nothing
*/
/datum/nanoui/Topic(href, href_list)
update_status(0) // update the status
if (status != STATUS_INTERACTIVE || user != usr) // If UI is not interactive or usr calling Topic is not the UI user
return
// This is used to toggle the nano map ui
var/map_update = 0
if(href_list["showMap"])
set_show_map(text2num(href_list["showMap"]))
map_update = 1
if(href_list["mapZLevel"])
var/map_z = (text2num(href_list["mapZLevel"]))
if(map_z in SSatlas.current_map.map_levels)
set_map_z_level(map_z)
map_update = 1
else
return
if ((src_object && src_object.Topic(href, href_list, state)) || map_update)
SSnanoui.update_uis(src_object) // update all UIs attached to src_object
/**
* Process this UI, updating the entire UI or just the status (aka visibility)
* This process proc is called by the master_controller
*
* @param update string For this UI to update
*
* @return nothing
*/
/datum/nanoui/process(update = 0)
if (QDELETED(src_object) || QDELETED(user))
close()
return
if (status && (update || is_auto_updating))
update() // Update the UI (update_status() is called whenever a UI is updated)
else
update_status(1) // Not updating UI, so lets check here if status has changed
/**
* Update the UI
*
* @return nothing
*/
/datum/nanoui/proc/update(var/force_open = 0)
src_object.ui_interact(user, ui_key, src, force_open, master_ui, state)
+33 -39
View File
@@ -399,17 +399,34 @@
ui_interact(user)
/obj/item/organ/internal/vaurca/preserve/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
/obj/item/organ/internal/vaurca/preserve/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "Tank", ui_x=400, ui_y=180)
ui.open()
/obj/item/organ/internal/vaurca/preserve/ui_host(mob/user)
. = ..()
if(istype(loc,/obj/item/transfer_valve))
return loc
/obj/item/organ/internal/vaurca/preserve/ui_data(mob/user)
var/mob/living/carbon/location = null
if(istype(loc, /obj/item/rig))
if(istype(loc.loc, /mob/living/carbon))
location = loc.loc
else if(istype(loc, /mob/living/carbon))
location = loc
var/using_internal
if(istype(location))
if(location.internal==src)
using_internal = 1
// this is the data which will be sent to the ui
var/data[0]
data["tankPressure"] = round(SAFE_XGM_PRESSURE(air_contents))
var/list/data = list()
data["tankPressure"] = round(XGM_PRESSURE(air_contents))
data["releasePressure"] = round(distribute_pressure ? distribute_pressure : 0)
data["defaultReleasePressure"] = round(TANK_DEFAULT_RELEASE_PRESSURE)
data["maxReleasePressure"] = round(TANK_MAX_RELEASE_PRESSURE)
@@ -437,36 +454,13 @@
if(H.head && (H.head.item_flags & ITEM_FLAG_AIRTIGHT))
data["maskConnected"] = 1
// update the ui if it exists, returns null if no ui is passed/found
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
// the ui does not exist, so we'll create a new() one
// for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm
ui = new(user, src, ui_key, "tanks.tmpl", "Tank", 500, 300)
// when the ui is first opened this is the data it will use
ui.set_initial_data(data)
// open the new ui window
ui.open()
// auto update every Master Controller tick
ui.set_auto_update(1)
return data
/obj/item/organ/internal/vaurca/preserve/Topic(href, href_list)
..()
if (usr.stat|| usr.restrained())
return 0
if (src.loc != usr)
return 0
if (href_list["dist_p"])
if (href_list["dist_p"] == "reset")
src.distribute_pressure = TANK_DEFAULT_RELEASE_PRESSURE
else if (href_list["dist_p"] == "max")
src.distribute_pressure = TANK_MAX_RELEASE_PRESSURE
else
var/cp = text2num(href_list["dist_p"])
src.distribute_pressure += cp
src.distribute_pressure = min(max(round(src.distribute_pressure), 0), TANK_MAX_RELEASE_PRESSURE)
if (href_list["stat"])
/obj/item/organ/internal/vaurca/preserve/ui_act(action,params)
. = ..()
if(.)
return
if(action=="toggleReleaseValve")
if(istype(loc,/mob/living/carbon))
var/mob/living/carbon/location = loc
if(location.internal == src)
@@ -476,7 +470,6 @@
if (location.internals)
location.internals.icon_state = "internal0"
else
var/can_open_valve
if(location.wear_mask && (location.wear_mask.item_flags & ITEM_FLAG_AIRTIGHT))
can_open_valve = 1
@@ -491,11 +484,12 @@
if (location.internals)
location.internals.icon_state = "internal1"
else
to_chat(usr, SPAN_NOTICE("You need something to connect to \the [src]."))
src.add_fingerprint(usr)
return 1
to_chat(usr, SPAN_WARNING("You need something to connect to \the [src]."))
. = TRUE
update_icon()
if(action=="setReleasePressure")
distribute_pressure = min(max(round(text2num(params["release_pressure"])), 0), TANK_MAX_RELEASE_PRESSURE)
. = TRUE
/obj/item/organ/internal/vaurca/preserve/remove_air(amount)
return air_contents.remove(amount)
@@ -30,7 +30,7 @@
/obj/structure/machinery/computer/ship/engines/ui_interact(mob/user, datum/tgui/ui)
if(!connected)
display_reconnect_dialog(user, "ship control systems")
balloon_alert(user, "no connection!")
return
ui = SStgui.try_update_ui(user, src, ui)
+64 -62
View File
@@ -117,6 +117,10 @@
return 1
/obj/structure/machinery/computer/ship/helm/ui_interact(mob/user, datum/tgui/ui)
if(!connected)
balloon_alert(user, "no connection!")
return
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "Helm", capitalize_first_letters(name))
@@ -126,55 +130,52 @@
/obj/structure/machinery/computer/ship/helm/ui_data(mob/user)
var/list/data = list()
if(!connected)
display_reconnect_dialog(user, "helm")
else
var/turf/T = get_turf(connected)
var/obj/effect/overmap/visitable/sector/current_sector = locate() in T
var/turf/T = get_turf(connected)
var/obj/effect/overmap/visitable/sector/current_sector = locate() in T
data["sector"] = current_sector ? current_sector.name : "Deep Space"
data["sector_info"] = current_sector ? current_sector.desc : "Not Available"
data["landed"] = connected.get_landed_info()
data["ship_coord_x"] = connected.x
data["ship_coord_y"] = connected.y
data["dest"] = dy && dx
data["autopilot_x"] = dx
data["autopilot_y"] = dy
data["speedlimit"] = speedlimit ? speedlimit*1000 : "Halted"
data["accel"] = get_acceleration()
data["heading"] = connected.get_heading() ? dir2angle(connected.get_heading()) : 0
data["direction"] = dir2angle(connected.dir)
data["autopilot"] = autopilot
data["manual_control"] = viewing_overmap(user)
data["canburn"] = connected.can_burn()
data["canturn"] = connected.can_turn()
data["cancombatroll"] = connected.can_combat_roll()
data["cancombatturn"] = connected.can_combat_turn()
data["accellimit"] = accellimit*1000
data["sector"] = current_sector ? current_sector.name : "Deep Space"
data["sector_info"] = current_sector ? current_sector.desc : "Not Available"
data["landed"] = connected.get_landed_info()
data["ship_coord_x"] = connected.x
data["ship_coord_y"] = connected.y
data["dest"] = dy && dx
data["autopilot_x"] = dx
data["autopilot_y"] = dy
data["speedlimit"] = speedlimit ? speedlimit*1000 : "Halted"
data["accel"] = get_acceleration()
data["heading"] = connected.get_heading() ? dir2angle(connected.get_heading()) : 0
data["direction"] = dir2angle(connected.dir)
data["autopilot"] = autopilot
data["manual_control"] = viewing_overmap(user)
data["canburn"] = connected.can_burn()
data["canturn"] = connected.can_turn()
data["cancombatroll"] = connected.can_combat_roll()
data["cancombatturn"] = connected.can_combat_turn()
data["accellimit"] = accellimit*1000
var/speed = round(connected.get_speed()*1000, 0.01)
data["speed"] = speed
if(connected.get_speed() < SHIP_SPEED_SLOW)
data["speed_slow"] = TRUE
if(connected.get_speed() > SHIP_SPEED_FAST)
data["speed_fast"] = TRUE
var/list/speed_xy = connected.get_speed_xy()
data["ship_speed_x"] = speed_xy[1]
data["ship_speed_y"] = speed_xy[2]
var/speed = round(connected.get_speed()*1000, 0.01)
data["speed"] = speed
if(connected.get_speed() < SHIP_SPEED_SLOW)
data["speed_slow"] = TRUE
if(connected.get_speed() > SHIP_SPEED_FAST)
data["speed_fast"] = TRUE
var/list/speed_xy = connected.get_speed_xy()
data["ship_speed_x"] = speed_xy[1]
data["ship_speed_y"] = speed_xy[2]
data["ETAnext"] = get_eta()
data["ETAnext"] = get_eta()
var/list/locations[0]
for (var/key in known_sectors)
var/datum/computer_file/data/waypoint/R = known_sectors[key]
var/list/rdata[0]
rdata["name"] = R.fields["name"]
rdata["x"] = R.fields["x"]
rdata["y"] = R.fields["y"]
rdata["reference"] = "[REF(R)]"
locations.Add(list(rdata))
var/list/locations[0]
for (var/key in known_sectors)
var/datum/computer_file/data/waypoint/R = known_sectors[key]
var/list/rdata[0]
rdata["name"] = R.fields["name"]
rdata["x"] = R.fields["x"]
rdata["y"] = R.fields["y"]
rdata["reference"] = "[REF(R)]"
locations.Add(list(rdata))
data["locations"] = locations
data["locations"] = locations
return data
@@ -367,6 +368,10 @@
ui_interact(user)
/obj/structure/machinery/computer/ship/navigation/ui_interact(mob/user, datum/tgui/ui)
if(!connected)
balloon_alert(user, "no connection!")
return
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "Nav", capitalize_first_letters(name), ui_x=470, ui_y=320)
@@ -376,25 +381,22 @@
/obj/structure/machinery/computer/ship/navigation/ui_data(mob/user)
var/list/data = list()
if(!connected)
display_reconnect_dialog(user, "navigation")
else
var/turf/T = get_turf(connected)
var/obj/effect/overmap/visitable/sector/current_sector = locate() in T
var/turf/T = get_turf(connected)
var/obj/effect/overmap/visitable/sector/current_sector = locate() in T
data["sector"] = current_sector ? current_sector.name : "Deep Space"
data["sector_info"] = current_sector ? current_sector.desc : "Not Available"
data["ship_coord_x"] = connected.x
data["ship_coord_y"] = connected.y
data["speed"] = round(connected.get_speed()*1000, 0.01)
data["accel"] = round(connected.get_acceleration()*1000, 0.01)
var/list/speed_xy = connected.get_speed_xy()
data["ship_speed_x"] = speed_xy[1]
data["ship_speed_y"] = speed_xy[2]
data["direction"] = dir2angle(connected.dir)
data["heading"] = connected.get_heading() ? dir2angle(connected.get_heading()) : 0
data["ETAnext"] = get_eta()
data["viewing"] = viewing_overmap(user)
data["sector"] = current_sector ? current_sector.name : "Deep Space"
data["sector_info"] = current_sector ? current_sector.desc : "Not Available"
data["ship_coord_x"] = connected.x
data["ship_coord_y"] = connected.y
data["speed"] = round(connected.get_speed()*1000, 0.01)
data["accel"] = round(connected.get_acceleration()*1000, 0.01)
var/list/speed_xy = connected.get_speed_xy()
data["ship_speed_x"] = speed_xy[1]
data["ship_speed_y"] = speed_xy[2]
data["direction"] = dir2angle(connected.dir)
data["heading"] = connected.get_heading() ? dir2angle(connected.get_heading()) : 0
data["ETAnext"] = get_eta()
data["viewing"] = viewing_overmap(user)
return data
@@ -22,11 +22,6 @@ somewhere on that shuttle. Subtypes of these can be then used to perform ship ov
. += "Consoles like these are typically access-locked."
. += "You can remove this lock with <b>wirecutters</b>, but it would take awhile! Alternatively, you can also use a cryptographic sequencer (emag) for instant removal."
/obj/structure/machinery/computer/ship/proc/display_reconnect_dialog(var/mob/user, var/flavor)
var/datum/browser/popup = new (user, "[src]", "[src]")
popup.set_content("<center><strong><font color = 'red'>Error</strong></font><br>Unable to connect to [flavor].<br><a href='byond://?src=[REF(src)];sync=1'>Reconnect</a></center>")
popup.open()
/obj/structure/machinery/computer/ship/attackby(obj/item/attacking_item, mob/user)
if(attacking_item.tool_behaviour == TOOL_CABLECOIL) // Repair from hotwire
var/obj/item/stack/cable_coil/C = attacking_item
@@ -5,7 +5,6 @@
// Powernet sensors are devices which relay information about connected powernet. This information may be relayed
// via two procs. Proc return_reading_text will return fully HTML styled string which contains all information. This
// may be used in PDAs or similar applications. Second proc, return_reading_data will return list containing needed data.
// This is used in NanoUI, for example.
/obj/structure/machinery/power/sensor
name = "Powernet Sensor"
@@ -70,4 +70,3 @@
/obj/structure/machinery/alarm/monitor/isolation
req_one_access = list(ACCESS_RESEARCH, ACCESS_ATMOSPHERICS, ACCESS_ENGINE_EQUIP)
locked = 0
remote_control = 1
+31
View File
@@ -0,0 +1,31 @@
## /TG/ Chat
/TG/ Chat, which will be referred to as TgChat from this point onwards, is a system in which we can send messages to clients in a controlled and semi-reliable manner. The standard way of sending messages to BYOND clients simply dumps whatever you output to them directly into their chat window, however BYOND allows us to load our own code on the client to change this behaviour in a way that allows us to do some pretty neat things.
### Message Format
TgChat handles sending messages from the server to the client through the use of JSON payloads, of which the format will change depending on the type of message and the intended client endpoint. An example of the payload for chat messages is as follows:
```json
{
"sequence": 0,
"content": {
"type": ". . .", // ?optional
"text": ". . .", // ?optional !atleast-one
"html": ". . .", // ?optional !atleast-one
"avoidHighlighting": 0 // ?optional
}
}
```
### Reliability
In the past there have been issues where BYOND will silently and without reason lose a message we sent to the client, to detect this and recover from it seamlessly TgChat also has a baked in reliability layer. This reliability layer is very primitive, and simply keeps track of received sequence numbers. Should the client receive an unexpected sequence number TgChat asks the server to resend any missing packets.
### Ping System
TgChat supports a round trip time ping measurement, which is displayed to the client so they can know how long it takes for their commands and inputs to reach the server. This is done by sending the client a ping request, `ping/soft`, which tells the client to send a ping to the server. When the server receives said ping it sends a reply, `ping/reply`, to the client with a payload containing the current DateTime which the client can reference against the initial ping request.
### Chat Tabs, Local Storage, and Highlighting
To make organizing and managing chat easier and more functional for both players and admins, TgChat has the ability to filter out messages based on their primary tag, such as individual departmental radios, to a dedicated chat tab for easier reading and comprehension. These tabs can also be configured to highlist messages based on a simple keyword search. You can set a multitude of different keywords to search for and they will be highlighting for instant alerting of the client. Said tabs, highlighting rules, and your chat history will persist thanks to use of local storage on the client. Using local storage TgChat can ensure that your preferences are saved and maintained between client restarts and switching between other /TG/ servers. Local Storage is also used to keep your chat history aswell, should you need to scroll through your chat logs.
+3 -18
View File
@@ -35,23 +35,8 @@
if(text) message["text"] = text
if(html) message["html"] = html
if(avoid_highlighting) message["avoidHighlighting"] = avoid_highlighting
var/message_blob = TGUI_CREATE_MESSAGE("chat/message", message)
var/message_html = message_to_html(message)
if(islist(target))
for(var/_target in target)
var/client/client = CLIENT_FROM_VAR(_target)
if(client)
// Send to tgchat
client.tgui_panel?.window.send_raw_message(message_blob)
// Send to old chat
to_target(client, message_html)
return
var/client/client = CLIENT_FROM_VAR(target)
if(client)
// Send to tgchat
client.tgui_panel?.window.send_raw_message(message_blob)
// Send to old chat
to_target(client, message_html)
// send it immediately
SSchat.send_immediate(target, message)
/**
* Sends the message to the recipient (target).
@@ -74,7 +59,7 @@
trailing_newline = TRUE,
confidential = FALSE
)
if(isnull(Master) || !SSchat?.initialized)
if(isnull(Master) || !SSchat?.initialized || !MC_RUNNING(SSchat.init_stage))
to_chat_immediate(target, html, type, text, avoid_highlighting)
return
+1 -1
View File
@@ -1,6 +1,6 @@
The MIT License
Copyright (c) 2017-2023 Jordan Brown
Copyright (c) 2017-2024 Jordan Brown
Permission is hereby granted, free of charge,
to any person obtaining a copy of this software and
+4 -4
View File
@@ -1,13 +1,13 @@
# DMAPI Internals
This folder should be placed on it's own inside a codebase that wishes to use the TGS DMAPI. Warranty void if modified.
This folder should be placed on its own inside a codebase that wishes to use the TGS DMAPI. Warranty void if modified.
- [includes.dm](./includes.dm) is the file that should be included by DM code, it handles including the rest.
- The [core](./core) folder includes all code not directly part of any API version.
- The other versioned folders contain code for the different DMAPI versions.
- [v3210](./v3210) contains the final TGS3 API.
- [v4](./v4) is the legacy DMAPI 4 (Used in TGS 4.0.X versions).
- [v5](./v5) is the current DMAPI version used by TGS >=4.1.
- [v3210](./v3210) contains the final TGS3 API.
- [v4](./v4) is the legacy DMAPI 4 (Used in TGS 4.0.X versions).
- [v5](./v5) is the current DMAPI version used by TGS >=4.1.
- [LICENSE](./LICENSE) is the MIT license for the DMAPI.
APIs communicate with TGS in two ways. All versions implement TGS -> DM communication using /world/Topic. DM -> TGS communication, called the bridge method, is different for each version.
+2 -2
View File
@@ -2,8 +2,8 @@
This folder contains all DMAPI code not directly involved in an API.
- [_definitions.dm](./definitions.dm) contains defines needed across DMAPI internals.
- [\_definitions.dm](./definitions.dm) contains defines needed across DMAPI internals.
- [byond_world_export.dm](./byond_world_export.dm) contains the default `/datum/tgs_http_handler` implementation which uses `world.Export()`.
- [core.dm](./core.dm) contains the implementations of the `/world/proc/TgsXXX()` procs. Many map directly to the `/datum/tgs_api` functions. It also contains the /datum selection and setup code.
- [datum.dm](./datum.dm) contains the `/datum/tgs_api` declarations that all APIs must implement.
- [tgs_version.dm](./tgs_version.dm) contains the `/datum/tgs_version` definition
-
@@ -0,0 +1,22 @@
/datum/tgs_http_handler/byond_world_export
/datum/tgs_http_handler/byond_world_export/PerformGet(url)
// This is an infinite sleep until we get a response
var/export_response = world.Export(url)
TGS_DEBUG_LOG("byond_world_export: Export complete")
if(!export_response)
TGS_ERROR_LOG("byond_world_export: Failed request: [url]")
return new /datum/tgs_http_result(null, FALSE)
var/content = export_response["CONTENT"]
if(!content)
TGS_ERROR_LOG("byond_world_export: Failed request, missing content!")
return new /datum/tgs_http_result(null, FALSE)
var/response_json = TGS_FILE2TEXT_NATIVE(content)
if(!response_json)
TGS_ERROR_LOG("byond_world_export: Failed request, failed to load content!")
return new /datum/tgs_http_result(null, FALSE)
return new /datum/tgs_http_result(response_json, TRUE)
+23 -5
View File
@@ -1,7 +1,7 @@
/world/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE)
/world/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE, datum/tgs_http_handler/http_handler = null)
var/current_api = TGS_READ_GLOBAL(tgs)
if(current_api)
TGS_ERROR_LOG("API datum already set ([REF(current_api)] ([current_api]))! Was TgsNew() called more than once?")
TGS_ERROR_LOG("API datum already set ([current_api])! Was TgsNew() called more than once?")
return
if(!(minimum_required_security_level in list(TGS_SECURITY_ULTRASAFE, TGS_SECURITY_SAFE, TGS_SECURITY_TRUSTED)))
@@ -42,11 +42,11 @@
var/datum/tgs_version/max_api_version = TgsMaximumApiVersion();
if(version.suite != null && version.minor != null && version.patch != null && version.deprecated_patch != null && version.deprefixed_parameter > max_api_version.deprefixed_parameter)
TGS_ERROR_LOG("Detected unknown API version! Defaulting to latest. Update the DMAPI to fix this problem.")
TGS_ERROR_LOG("Detected unknown Interop API version! Defaulting to latest. Update the DMAPI to fix this problem.")
api_datum = /datum/tgs_api/latest
if(!api_datum)
TGS_ERROR_LOG("Found unsupported API version: [raw_parameter]. If this is a valid version please report this, backporting is done on demand.")
TGS_ERROR_LOG("Found unsupported Interop API version: [raw_parameter]. If this is a valid version please report this, backporting is done on demand.")
return
TGS_INFO_LOG("Activating API for version [version.deprefixed_parameter]")
@@ -55,7 +55,10 @@
TGS_ERROR_LOG("Invalid parameter for event_handler: [event_handler]")
event_handler = null
var/datum/tgs_api/new_api = new api_datum(event_handler, version)
if(!http_handler)
http_handler = new /datum/tgs_http_handler/byond_world_export
var/datum/tgs_api/new_api = new api_datum(event_handler, version, http_handler)
TGS_WRITE_GLOBAL(tgs, new_api)
@@ -107,6 +110,13 @@
if(api)
return api.ApiVersion()
/world/TgsEngine()
#ifdef OPENDREAM
return TGS_ENGINE_TYPE_OPENDREAM
#else
return TGS_ENGINE_TYPE_BYOND
#endif
/world/TgsInstanceName()
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
@@ -159,3 +169,11 @@
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
return api.Visibility()
/world/TgsTriggerEvent(event_name, list/parameters, wait_for_completion = FALSE)
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
if(!istype(parameters, /list))
parameters = list()
return api.TriggerEvent(event_name, parameters, wait_for_completion)
+6 -3
View File
@@ -6,8 +6,8 @@ TGS_DEFINE_AND_SET_GLOBAL(tgs, null)
var/list/warned_deprecated_command_runs
/datum/tgs_api/New(datum/tgs_event_handler/event_handler, datum/tgs_version/version)
. = ..()
/datum/tgs_api/New(datum/tgs_event_handler/event_handler, datum/tgs_version/version, datum/tgs_http_handler/http_handler)
..()
src.event_handler = event_handler
src.version = version
@@ -17,7 +17,7 @@ TGS_DEFINE_AND_SET_GLOBAL(tgs, null)
world.sleep_offline = FALSE // https://www.byond.com/forum/post/2894866
del(world)
world.sleep_offline = FALSE // just in case, this is BYOND after all...
sleep(1)
sleep(world.tick_lag)
TGS_DEBUG_LOG("BYOND DIDN'T TERMINATE THE WORLD!!! TICK IS: [world.time], sleep_offline: [world.sleep_offline]")
/datum/tgs_api/latest
@@ -69,3 +69,6 @@ TGS_PROTECT_DATUM(/datum/tgs_api)
/datum/tgs_api/proc/Visibility()
return TGS_UNIMPLEMENTED
/datum/tgs_api/proc/TriggerEvent(event_name, list/parameters, wait_for_completion)
return FALSE
+1
View File
@@ -1,4 +1,5 @@
/datum/tgs_version/New(raw_parameter)
..()
src.raw_parameter = raw_parameter
deprefixed_parameter = replacetext(raw_parameter, "/tg/station 13 Server v", "")
var/list/version_bits = splittext(deprefixed_parameter, ".")
+1
View File
@@ -1,4 +1,5 @@
#include "core\_definitions.dm"
#include "core\byond_world_export.dm"
#include "core\core.dm"
#include "core\datum.dm"
#include "core\tgs_version.dm"
+3 -3
View File
@@ -181,7 +181,7 @@
var/json = json_encode(data)
while(requesting_new_port && !override_requesting_new_port)
sleep(1)
sleep(world.tick_lag)
//we need some port open at this point to facilitate return communication
if(!world.port)
@@ -209,7 +209,7 @@
requesting_new_port = FALSE
while(export_lock)
sleep(1)
sleep(world.tick_lag)
export_lock = TRUE
last_interop_response = null
@@ -217,7 +217,7 @@
text2file(json, server_commands_json_path)
for(var/I = 0; I < EXPORT_TIMEOUT_DS && !last_interop_response; ++I)
sleep(1)
sleep(world.tick_lag)
if(!last_interop_response)
TGS_ERROR_LOG("Failed to get export result for: [json]")
+2 -2
View File
@@ -2,8 +2,8 @@
This DMAPI implements bridge requests using HTTP GET requests to TGS. It has no security restrictions.
- [__interop_version.dm](./__interop_version.dm) contains the version of the API used between the DMAPI and TGS.
- [_defines.dm](./_defines.dm) contains constant definitions.
- [\_\_interop_version.dm](./__interop_version.dm) contains the version of the API used between the DMAPI and TGS.
- [\_defines.dm](./_defines.dm) contains constant definitions.
- [api.dm](./api.dm) contains the bulk of the API code.
- [bridge.dm](./bridge.dm) contains functions related to making bridge requests.
- [chunking.dm](./chunking.dm) contains common function for splitting large raw data sets into chunks BYOND can natively process.
+1 -1
View File
@@ -1 +1 @@
"5.6.2"
"5.10.1"
+13 -1
View File
@@ -8,16 +8,17 @@
#define DMAPI5_TOPIC_REQUEST_LIMIT 65528
#define DMAPI5_TOPIC_RESPONSE_LIMIT 65529
#define DMAPI5_BRIDGE_COMMAND_PORT_UPDATE 0
#define DMAPI5_BRIDGE_COMMAND_STARTUP 1
#define DMAPI5_BRIDGE_COMMAND_PRIME 2
#define DMAPI5_BRIDGE_COMMAND_REBOOT 3
#define DMAPI5_BRIDGE_COMMAND_KILL 4
#define DMAPI5_BRIDGE_COMMAND_CHAT_SEND 5
#define DMAPI5_BRIDGE_COMMAND_CHUNK 6
#define DMAPI5_BRIDGE_COMMAND_EVENT 7
#define DMAPI5_PARAMETER_ACCESS_IDENTIFIER "accessIdentifier"
#define DMAPI5_PARAMETER_CUSTOM_COMMANDS "customCommands"
#define DMAPI5_PARAMETER_TOPIC_PORT "topicPort"
#define DMAPI5_CHUNK "chunk"
#define DMAPI5_CHUNK_PAYLOAD "payload"
@@ -34,6 +35,7 @@
#define DMAPI5_BRIDGE_PARAMETER_VERSION "version"
#define DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE "chatMessage"
#define DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL "minimumSecurityLevel"
#define DMAPI5_BRIDGE_PARAMETER_EVENT_INVOCATION "eventInvocation"
#define DMAPI5_BRIDGE_RESPONSE_NEW_PORT "newPort"
#define DMAPI5_BRIDGE_RESPONSE_RUNTIME_INFORMATION "runtimeInformation"
@@ -80,6 +82,8 @@
#define DMAPI5_TOPIC_COMMAND_WATCHDOG_REATTACH 8
#define DMAPI5_TOPIC_COMMAND_SEND_CHUNK 9
#define DMAPI5_TOPIC_COMMAND_RECEIVE_CHUNK 10
#define DMAPI5_TOPIC_COMMAND_RECEIVE_BROADCAST 11
#define DMAPI5_TOPIC_COMMAND_COMPLETE_EVENT 12
#define DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE "commandType"
#define DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND "chatCommand"
@@ -89,7 +93,9 @@
#define DMAPI5_TOPIC_PARAMETER_NEW_INSTANCE_NAME "newInstanceName"
#define DMAPI5_TOPIC_PARAMETER_CHAT_UPDATE "chatUpdate"
#define DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION "newServerVersion"
#define DMAPI5_TOPIC_PARAMETER_BROADCAST_MESSAGE "broadcastMessage"
#define DMAPI5_TOPIC_RESPONSE_CLIENT_COUNT "clientCount"
#define DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE "commandResponse"
#define DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE_MESSAGE "commandResponseMessage"
#define DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES "chatResponses"
@@ -114,3 +120,9 @@
#define DMAPI5_CUSTOM_CHAT_COMMAND_NAME "name"
#define DMAPI5_CUSTOM_CHAT_COMMAND_HELP_TEXT "helpText"
#define DMAPI5_CUSTOM_CHAT_COMMAND_ADMIN_ONLY "adminOnly"
#define DMAPI5_EVENT_ID "eventId"
#define DMAPI5_EVENT_INVOCATION_NAME "eventName"
#define DMAPI5_EVENT_INVOCATION_PARAMETERS "parameters"
#define DMAPI5_EVENT_INVOCATION_NOTIFY_COMPLETION "notifyCompletion"
+96 -30
View File
@@ -8,8 +8,12 @@
var/reboot_mode = TGS_REBOOT_MODE_NORMAL
/// List of chat messages list()s that attempted to be sent during a topic call. To be bundled in the result of the call
var/list/intercepted_message_queue
/// List of chat messages list()s that attempted to be sent during a topic call. To be bundled in the result of the call
var/list/offline_message_queue
var/list/custom_commands
var/list/test_merges
@@ -17,15 +21,23 @@
var/list/chat_channels
var/initialized = FALSE
var/initial_bridge_request_received = FALSE
var/datum/tgs_version/interop_version
var/chunked_requests = 0
var/list/chunked_topics = list()
var/list/pending_events = list()
var/detached = FALSE
/datum/tgs_api/v5/New()
var/datum/tgs_http_handler/http_handler
/datum/tgs_api/v5/New(datum/tgs_event_handler/event_handler, datum/tgs_version/version, datum/tgs_http_handler/http_handler)
. = ..()
TGS_DEBUG_LOG("V5 API created")
interop_version = version
src.http_handler = http_handler
TGS_DEBUG_LOG("V5 API created: [json_encode(args)]")
/datum/tgs_api/v5/ApiVersion()
return new /datum/tgs_version(
@@ -38,8 +50,14 @@
access_identifier = world.params[DMAPI5_PARAM_ACCESS_IDENTIFIER]
var/datum/tgs_version/api_version = ApiVersion()
version = null
var/list/bridge_response = Bridge(DMAPI5_BRIDGE_COMMAND_STARTUP, list(DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL = minimum_required_security_level, DMAPI5_BRIDGE_PARAMETER_VERSION = api_version.raw_parameter, DMAPI5_PARAMETER_CUSTOM_COMMANDS = ListCustomCommands()))
version = null // we want this to be the TGS version, not the interop version
// sleep once to prevent an issue where world.Export on the first tick can hang indefinitely
TGS_DEBUG_LOG("Starting Export bug prevention sleep tick. time:[world.time] sleep_offline:[world.sleep_offline]")
sleep(world.tick_lag)
TGS_DEBUG_LOG("Export bug prevention sleep complete")
var/list/bridge_response = Bridge(DMAPI5_BRIDGE_COMMAND_STARTUP, list(DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL = minimum_required_security_level, DMAPI5_BRIDGE_PARAMETER_VERSION = api_version.raw_parameter, DMAPI5_PARAMETER_CUSTOM_COMMANDS = ListCustomCommands(), DMAPI5_PARAMETER_TOPIC_PORT = GetTopicPort()))
if(!istype(bridge_response))
TGS_ERROR_LOG("Failed initial bridge request!")
return FALSE
@@ -53,7 +71,8 @@
TGS_INFO_LOG("DMAPI validation, exiting...")
TerminateWorld()
version = new /datum/tgs_version(runtime_information[DMAPI5_RUNTIME_INFORMATION_SERVER_VERSION])
initial_bridge_request_received = TRUE
version = new /datum/tgs_version(runtime_information[DMAPI5_RUNTIME_INFORMATION_SERVER_VERSION]) // reassigning this because it can change if TGS updates
security_level = runtime_information[DMAPI5_RUNTIME_INFORMATION_SECURITY_LEVEL]
visibility = runtime_information[DMAPI5_RUNTIME_INFORMATION_VISIBILITY]
instance_name = runtime_information[DMAPI5_RUNTIME_INFORMATION_INSTANCE_NAME]
@@ -78,7 +97,7 @@
if(revInfo)
tm.commit = revisionData[DMAPI5_REVISION_INFORMATION_COMMIT_SHA]
tm.origin_commit = revisionData[DMAPI5_REVISION_INFORMATION_ORIGIN_COMMIT_SHA]
tm.timestamp = entry[DMAPI5_REVISION_INFORMATION_TIMESTAMP]
tm.timestamp = revisionData[DMAPI5_REVISION_INFORMATION_TIMESTAMP]
else
TGS_WARNING_LOG("Failed to decode [DMAPI5_TEST_MERGE_REVISION] from test merge #[tm.number]!")
@@ -102,15 +121,22 @@
initialized = TRUE
return TRUE
/datum/tgs_api/v5/proc/GetTopicPort()
#if defined(OPENDREAM) && defined(OPENDREAM_TOPIC_PORT_EXISTS)
return "[world.opendream_topic_port]"
#else
return null
#endif
/datum/tgs_api/v5/proc/RequireInitialBridgeResponse()
TGS_DEBUG_LOG("RequireInitialBridgeResponse()")
var/logged = FALSE
while(!version)
while(!initial_bridge_request_received)
if(!logged)
TGS_DEBUG_LOG("RequireInitialBridgeResponse: Starting sleep")
logged = TRUE
sleep(1)
sleep(world.tick_lag)
TGS_DEBUG_LOG("RequireInitialBridgeResponse: Passed")
@@ -183,17 +209,7 @@
var/datum/tgs_chat_channel/channel = I
ids += channel.id
message2 = UpgradeDeprecatedChatMessage(message2)
if (!length(channels))
return
var/list/data = message2._interop_serialize()
data[DMAPI5_CHAT_MESSAGE_CHANNEL_IDS] = ids
if(intercepted_message_queue)
intercepted_message_queue += list(data)
else
Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = data))
SendChatMessageRaw(message2, ids)
/datum/tgs_api/v5/ChatTargetedBroadcast(datum/tgs_message_content/message2, admin_only)
var/list/channels = list()
@@ -202,32 +218,82 @@
if (!channel.is_private_channel && ((channel.is_admin_channel && admin_only) || (!channel.is_admin_channel && !admin_only)))
channels += channel.id
SendChatMessageRaw(message2, channels)
/datum/tgs_api/v5/ChatPrivateMessage(datum/tgs_message_content/message2, datum/tgs_chat_user/user)
SendChatMessageRaw(message2, list(user.channel.id))
/datum/tgs_api/v5/proc/SendChatMessageRaw(datum/tgs_message_content/message2, list/channel_ids)
message2 = UpgradeDeprecatedChatMessage(message2)
if (!length(channels))
if (!length(channel_ids))
return
var/list/data = message2._interop_serialize()
data[DMAPI5_CHAT_MESSAGE_CHANNEL_IDS] = channels
data[DMAPI5_CHAT_MESSAGE_CHANNEL_IDS] = channel_ids
if(intercepted_message_queue)
intercepted_message_queue += list(data)
else
Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = data))
return
/datum/tgs_api/v5/ChatPrivateMessage(datum/tgs_message_content/message2, datum/tgs_chat_user/user)
message2 = UpgradeDeprecatedChatMessage(message2)
var/list/data = message2._interop_serialize()
data[DMAPI5_CHAT_MESSAGE_CHANNEL_IDS] = list(user.channel.id)
if(intercepted_message_queue)
intercepted_message_queue += list(data)
if(offline_message_queue)
offline_message_queue += list(data)
return
if(detached)
offline_message_queue = list(data)
WaitForReattach(FALSE)
data = offline_message_queue
offline_message_queue = null
for(var/queued_message in data)
SendChatDataRaw(queued_message)
else
Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = data))
SendChatDataRaw(data)
/datum/tgs_api/v5/proc/SendChatDataRaw(list/data)
Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = data))
/datum/tgs_api/v5/ChatChannelInfo()
RequireInitialBridgeResponse()
WaitForReattach(TRUE)
return chat_channels.Copy()
/datum/tgs_api/v5/TriggerEvent(event_name, list/parameters, wait_for_completion)
RequireInitialBridgeResponse()
WaitForReattach(TRUE)
if(interop_version.minor < 9)
TGS_WARNING_LOG("Interop version too low for custom events!")
return FALSE
var/str_parameters = list()
for(var/i in parameters)
str_parameters += "[i]"
var/list/response = Bridge(DMAPI5_BRIDGE_COMMAND_EVENT, list(DMAPI5_BRIDGE_PARAMETER_EVENT_INVOCATION = list(DMAPI5_EVENT_INVOCATION_NAME = event_name, DMAPI5_EVENT_INVOCATION_PARAMETERS = str_parameters, DMAPI5_EVENT_INVOCATION_NOTIFY_COMPLETION = wait_for_completion)))
if(!response)
return FALSE
var/event_id = response[DMAPI5_EVENT_ID]
if(!event_id)
return FALSE
TGS_DEBUG_LOG("Created event ID: [event_id]")
if(!wait_for_completion)
return TRUE
TGS_DEBUG_LOG("Waiting for completion of event ID: [event_id]")
while(!pending_events[event_id])
sleep(world.tick_lag)
TGS_DEBUG_LOG("Completed wait on event ID: [event_id]")
pending_events -= event_id
return TRUE
/datum/tgs_api/v5/proc/DecodeChannels(chat_update_json)
TGS_DEBUG_LOG("DecodeChannels()")
var/list/chat_channels_json = chat_update_json[DMAPI5_CHAT_UPDATE_CHANNELS]
+18 -11
View File
@@ -48,7 +48,9 @@
var/json = CreateBridgeData(command, data, TRUE)
var/encoded_json = url_encode(json)
var/url = "http://127.0.0.1:[server_port]/Bridge?[DMAPI5_BRIDGE_DATA]=[encoded_json]"
var/api_prefix = interop_version.minor >= 8 ? "api/" : ""
var/url = "http://127.0.0.1:[server_port]/[api_prefix]Bridge?[DMAPI5_BRIDGE_DATA]=[encoded_json]"
return url
/datum/tgs_api/v5/proc/CreateBridgeData(command, list/data, needs_auth)
@@ -63,7 +65,7 @@
if(detached)
// Wait up to one minute
for(var/i in 1 to 600)
sleep(1)
sleep(world.tick_lag)
if(!detached && (!require_channels || length(chat_channels)))
break
@@ -75,20 +77,25 @@
/datum/tgs_api/v5/proc/PerformBridgeRequest(bridge_request)
WaitForReattach(FALSE)
// This is an infinite sleep until we get a response
var/export_response = world.Export(bridge_request)
if(!export_response)
TGS_ERROR_LOG("Failed bridge request: [bridge_request]")
TGS_DEBUG_LOG("Bridge request start")
var/datum/tgs_http_result/result = http_handler.PerformGet(bridge_request)
TGS_DEBUG_LOG("Bridge request complete")
if(isnull(result))
TGS_ERROR_LOG("Failed bridge request, handler returned null!")
return
var/response_json = file2text(export_response["CONTENT"])
if(!response_json)
TGS_ERROR_LOG("Failed bridge request, missing content!")
if(!istype(result) || result.type != /datum/tgs_http_result)
TGS_ERROR_LOG("Failed bridge request, handler returned non-[/datum/tgs_http_result]!")
return
var/list/bridge_response = json_decode(response_json)
if(!result.success)
TGS_DEBUG_LOG("Failed bridge request, HTTP request failed!")
return
var/list/bridge_response = json_decode(result.response_text)
if(!bridge_response)
TGS_ERROR_LOG("Failed bridge request, bad json: [response_json]")
TGS_ERROR_LOG("Failed bridge request, bad json: [result.response_text]")
return
var/error = bridge_response[DMAPI5_RESPONSE_ERROR_MESSAGE]
+33 -5
View File
@@ -94,7 +94,7 @@
if(DMAPI5_TOPIC_COMMAND_CHANGE_PORT)
var/new_port = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_PORT]
if (!isnum(new_port) || !(new_port > 0))
return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_PORT]]")
return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_PORT]")
if(event_handler != null)
event_handler.HandleEvent(TGS_EVENT_PORT_SWAP, new_port)
@@ -141,7 +141,7 @@
if(DMAPI5_TOPIC_COMMAND_SERVER_PORT_UPDATE)
var/new_port = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_PORT]
if (!isnum(new_port) || !(new_port > 0))
return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_PORT]]")
return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_PORT]")
server_port = new_port
return TopicResponse()
@@ -149,7 +149,9 @@
if(DMAPI5_TOPIC_COMMAND_HEALTHCHECK)
if(event_handler && event_handler.receive_health_checks)
event_handler.HandleEvent(TGS_EVENT_HEALTH_CHECK)
return TopicResponse()
var/list/health_check_response = TopicResponse()
health_check_response[DMAPI5_TOPIC_RESPONSE_CLIENT_COUNT] = TGS_CLIENT_COUNT
return health_check_response;
if(DMAPI5_TOPIC_COMMAND_WATCHDOG_REATTACH)
detached = FALSE
@@ -157,7 +159,7 @@
var/error_message = null
if (new_port != null)
if (!isnum(new_port) || !(new_port > 0))
error_message = "Invalid [DMAPI5_TOPIC_PARAMETER_NEW_PORT]]"
error_message = "Invalid [DMAPI5_TOPIC_PARAMETER_NEW_PORT]"
else
server_port = new_port
@@ -165,7 +167,7 @@
if (!istext(new_version_string))
if(error_message != null)
error_message += ", "
error_message += "Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION]]"
error_message += "Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION]"
else
var/datum/tgs_version/new_version = new(new_version_string)
if (event_handler)
@@ -175,6 +177,11 @@
var/list/reattach_response = TopicResponse(error_message)
reattach_response[DMAPI5_PARAMETER_CUSTOM_COMMANDS] = ListCustomCommands()
reattach_response[DMAPI5_PARAMETER_TOPIC_PORT] = GetTopicPort()
for(var/eventId in pending_events)
pending_events[eventId] = TRUE
return reattach_response
if(DMAPI5_TOPIC_COMMAND_SEND_CHUNK)
@@ -267,4 +274,25 @@
return chunk_to_send
if(DMAPI5_TOPIC_COMMAND_RECEIVE_BROADCAST)
var/message = topic_parameters[DMAPI5_TOPIC_PARAMETER_BROADCAST_MESSAGE]
if (!istext(message))
return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_BROADCAST_MESSAGE]")
TGS_WORLD_ANNOUNCE(message)
return TopicResponse()
if(DMAPI5_TOPIC_COMMAND_COMPLETE_EVENT)
var/event_id = topic_parameters[DMAPI5_EVENT_ID]
if (!istext(event_id))
return TopicResponse("Invalid or missing [DMAPI5_EVENT_ID]")
TGS_DEBUG_LOG("Completing event ID [event_id]...")
pending_events[event_id] = TRUE
return TopicResponse()
return TopicResponse("Unknown command: [command]")
/datum/tgs_api/v5/proc/WorldBroadcast(message)
set waitfor = FALSE
TGS_WORLD_ANNOUNCE(message)
+15 -1
View File
@@ -8,16 +8,17 @@
#undef DMAPI5_TOPIC_REQUEST_LIMIT
#undef DMAPI5_TOPIC_RESPONSE_LIMIT
#undef DMAPI5_BRIDGE_COMMAND_PORT_UPDATE
#undef DMAPI5_BRIDGE_COMMAND_STARTUP
#undef DMAPI5_BRIDGE_COMMAND_PRIME
#undef DMAPI5_BRIDGE_COMMAND_REBOOT
#undef DMAPI5_BRIDGE_COMMAND_KILL
#undef DMAPI5_BRIDGE_COMMAND_CHAT_SEND
#undef DMAPI5_BRIDGE_COMMAND_CHUNK
#undef DMAPI5_BRIDGE_COMMAND_EVENT
#undef DMAPI5_PARAMETER_ACCESS_IDENTIFIER
#undef DMAPI5_PARAMETER_CUSTOM_COMMANDS
#undef DMAPI5_PARAMETER_TOPIC_PORT
#undef DMAPI5_CHUNK
#undef DMAPI5_CHUNK_PAYLOAD
@@ -34,6 +35,7 @@
#undef DMAPI5_BRIDGE_PARAMETER_VERSION
#undef DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE
#undef DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL
#undef DMAPI5_BRIDGE_PARAMETER_EVENT_INVOCATION
#undef DMAPI5_BRIDGE_RESPONSE_NEW_PORT
#undef DMAPI5_BRIDGE_RESPONSE_RUNTIME_INFORMATION
@@ -78,6 +80,10 @@
#undef DMAPI5_TOPIC_COMMAND_SERVER_PORT_UPDATE
#undef DMAPI5_TOPIC_COMMAND_HEALTHCHECK
#undef DMAPI5_TOPIC_COMMAND_WATCHDOG_REATTACH
#undef DMAPI5_TOPIC_COMMAND_SEND_CHUNK
#undef DMAPI5_TOPIC_COMMAND_RECEIVE_CHUNK
#undef DMAPI5_TOPIC_COMMAND_RECEIVE_BROADCAST
#undef DMAPI5_TOPIC_COMMAND_COMPLETE_EVENT
#undef DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE
#undef DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND
@@ -87,7 +93,9 @@
#undef DMAPI5_TOPIC_PARAMETER_NEW_INSTANCE_NAME
#undef DMAPI5_TOPIC_PARAMETER_CHAT_UPDATE
#undef DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION
#undef DMAPI5_TOPIC_PARAMETER_BROADCAST_MESSAGE
#undef DMAPI5_TOPIC_RESPONSE_CLIENT_COUNT
#undef DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE
#undef DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE_MESSAGE
#undef DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES
@@ -112,3 +120,9 @@
#undef DMAPI5_CUSTOM_CHAT_COMMAND_NAME
#undef DMAPI5_CUSTOM_CHAT_COMMAND_HELP_TEXT
#undef DMAPI5_CUSTOM_CHAT_COMMAND_ADMIN_ONLY
#undef DMAPI5_EVENT_ID
#undef DMAPI5_EVENT_INVOCATION_NAME
#undef DMAPI5_EVENT_INVOCATION_PARAMETERS
#undef DMAPI5_EVENT_INVOCATION_NOTIFY_COMPLETION
+1 -1
View File
@@ -42,7 +42,7 @@
*
* required user mob The mob interacting with the UI.
*
* return list Statuic Data to be sent to the UI.
* return list Static Data to be sent to the UI.
*/
/datum/proc/ui_static_data(mob/user)
return list()

Some files were not shown because too many files have changed in this diff Show More