mirror of
https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13.git
synced 2025-12-09 16:07:40 +00:00
527 lines
13 KiB
HTML
527 lines
13 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
<meta charset="utf-8">
|
|
|
|
<!-- Inlined data -->
|
|
<meta id="tgui:windowId" content="[tgui:windowId]">
|
|
|
|
<!-- Early setup -->
|
|
<script type="text/javascript">
|
|
// Read window id into a global
|
|
window.__windowId__ = document
|
|
.getElementById('tgui:windowId')
|
|
.getAttribute('content');
|
|
if (window.__windowId__ === '[' + 'tgui:windowId' + ']') {
|
|
window.__windowId__ = null;
|
|
}
|
|
|
|
(function () {
|
|
// Utility functions
|
|
var hasOwn = Object.prototype.hasOwnProperty;
|
|
var assign = function (target) {
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
var source = arguments[i];
|
|
for (var key in source) {
|
|
if (hasOwn.call(source, key)) {
|
|
target[key] = source[key];
|
|
}
|
|
}
|
|
}
|
|
return target;
|
|
};
|
|
|
|
// BYOND API object
|
|
// ------------------------------------------------------
|
|
|
|
var Byond = window.Byond = {};
|
|
// Trident engine version
|
|
var tridentVersion = (function () {
|
|
var groups = navigator.userAgent.match(/Trident\/(\d+).+?;/i);
|
|
var majorVersion = groups && groups[1];
|
|
return majorVersion
|
|
? parseInt(majorVersion, 10)
|
|
: null;
|
|
})();
|
|
// Basic checks to detect whether this page runs in BYOND
|
|
var isByond = tridentVersion !== null
|
|
&& location.hostname === '127.0.0.1'
|
|
&& location.pathname.indexOf('/tmp') === 0
|
|
&& location.search !== '?external';
|
|
|
|
// Version constants
|
|
Byond.IS_BYOND = isByond;
|
|
Byond.IS_LTE_IE8 = tridentVersion !== null && tridentVersion <= 4;
|
|
Byond.IS_LTE_IE9 = tridentVersion !== null && tridentVersion <= 5;
|
|
Byond.IS_LTE_IE10 = tridentVersion !== null && tridentVersion <= 6;
|
|
Byond.IS_LTE_IE11 = tridentVersion !== null && tridentVersion <= 7;
|
|
|
|
// Callbacks for asynchronous calls
|
|
Byond.__callbacks__ = [];
|
|
|
|
// Reviver for BYOND JSON
|
|
// IE8: No reviver for you!
|
|
// See: https://stackoverflow.com/questions/1288962
|
|
var byondJsonReviver;
|
|
if (!Byond.IS_LTE_IE8) {
|
|
byondJsonReviver = function (key, value) {
|
|
if (typeof value === 'object' && value !== null && value.__number__) {
|
|
return parseFloat(value.__number__);
|
|
}
|
|
return value;
|
|
};
|
|
}
|
|
|
|
// Makes a BYOND call.
|
|
// See: https://secure.byond.com/docs/ref/skinparams.html
|
|
Byond.call = function (path, params) {
|
|
// Not running in BYOND, abort.
|
|
if (!isByond) {
|
|
return;
|
|
}
|
|
// Build the URL
|
|
var url = (path || '') + '?';
|
|
var i = 0;
|
|
if (params) {
|
|
for (var key in params) {
|
|
if (hasOwn.call(params, key)) {
|
|
if (i++ > 0) {
|
|
url += '&';
|
|
}
|
|
var value = params[key];
|
|
if (value === null || value === undefined) {
|
|
value = '';
|
|
}
|
|
url += encodeURIComponent(key)
|
|
+ '=' + encodeURIComponent(value)
|
|
}
|
|
}
|
|
}
|
|
// Perform a standard call via location.href
|
|
if (url.length < 2048) {
|
|
location.href = 'byond://' + url;
|
|
return;
|
|
}
|
|
// Send an HTTP request to DreamSeeker's HTTP server.
|
|
// Allows sending much bigger payloads.
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open('GET', url);
|
|
xhr.send();
|
|
};
|
|
|
|
Byond.callAsync = function (path, params) {
|
|
if (!window.Promise) {
|
|
throw new Error('Async calls require API level of ES2015 or later.');
|
|
}
|
|
var index = Byond.__callbacks__.length;
|
|
var promise = new window.Promise(function (resolve) {
|
|
Byond.__callbacks__.push(resolve);
|
|
});
|
|
Byond.call(path, assign({}, params, {
|
|
callback: 'Byond.__callbacks__[' + index + ']',
|
|
}));
|
|
return promise;
|
|
};
|
|
|
|
Byond.topic = function (params) {
|
|
return Byond.call('', params);
|
|
};
|
|
|
|
Byond.command = function (command) {
|
|
return Byond.call('winset', {
|
|
command: command,
|
|
});
|
|
};
|
|
|
|
Byond.winget = function (id, propName) {
|
|
var isArray = propName instanceof Array;
|
|
var isSpecific = propName && propName !== '*' && !isArray;
|
|
var promise = Byond.callAsync('winget', {
|
|
id: id,
|
|
property: isArray && propName.join(',') || propName || '*',
|
|
});
|
|
if (isSpecific) {
|
|
promise = promise.then(function (props) {
|
|
return props[propName];
|
|
});
|
|
}
|
|
return promise;
|
|
};
|
|
|
|
Byond.winset = function (id, propName, propValue) {
|
|
if (typeof id === 'object' && id !== null) {
|
|
return Byond.call('winset', id);
|
|
}
|
|
var props = {};
|
|
if (typeof propName === 'string') {
|
|
props[propName] = propValue;
|
|
}
|
|
else {
|
|
assign(props, propName);
|
|
}
|
|
props.id = id;
|
|
return Byond.call('winset', props);
|
|
};
|
|
|
|
Byond.parseJson = function (json) {
|
|
try {
|
|
return JSON.parse(json, byondJsonReviver);
|
|
}
|
|
catch (err) {
|
|
throw new Error('JSON parsing error: ' + (err && err.message));
|
|
}
|
|
};
|
|
|
|
|
|
// Asset loaders
|
|
// ------------------------------------------------------
|
|
|
|
var RETRY_ATTEMPTS = 5;
|
|
var RETRY_INTERVAL = 2000;
|
|
|
|
var loadedCssByUrl = {};
|
|
var loadedJsByUrl = {};
|
|
|
|
var isStyleSheetLoaded = function (node, url) {
|
|
// Method #1 (works on IE10+)
|
|
var styleSheet = node.sheet;
|
|
if (styleSheet) {
|
|
return styleSheet.rules.length > 0;
|
|
}
|
|
// Method #2
|
|
var styleSheets = document.styleSheets;
|
|
var len = styleSheets.length;
|
|
for (var i = 0; i < len; i++) {
|
|
var styleSheet = styleSheets[i];
|
|
if (styleSheet.href.indexOf(url) !== -1) {
|
|
return styleSheet.rules.length > 0;
|
|
}
|
|
}
|
|
// All methods failed
|
|
return false;
|
|
};
|
|
|
|
var onDocumentBodyReady = function (done) {
|
|
if (document.body) {
|
|
return done();
|
|
}
|
|
setTimeout(function () {
|
|
onDocumentBodyReady(done);
|
|
});
|
|
};
|
|
|
|
var getRefNode = function () {
|
|
var refs = (document.body || document.getElementsByTagName('head')[0])
|
|
.childNodes;
|
|
return refs[refs.length - 1];
|
|
};
|
|
|
|
// Based on fg-loadcss package
|
|
// See: https://github.com/filamentgroup/loadCSS
|
|
Byond.loadCss = function (url, sync, attempt) {
|
|
if (loadedCssByUrl[url]) {
|
|
return;
|
|
}
|
|
if (!attempt) {
|
|
attempt = 1;
|
|
}
|
|
loadedCssByUrl[url] = true;
|
|
// Inject the stylesheet
|
|
var ref = getRefNode();
|
|
/** @type {HTMLLinkElement} */
|
|
var node = document.createElement('link');
|
|
node.type = 'text/css';
|
|
node.rel = 'stylesheet';
|
|
node.href = url;
|
|
// Temporarily set media to something inapplicable
|
|
// to ensure it'll fetch without blocking render
|
|
if (!sync) {
|
|
node.media = 'only x';
|
|
}
|
|
onDocumentBodyReady(function () {
|
|
ref.parentNode.insertBefore(node, ref.nextSibling);
|
|
});
|
|
// Listen for the load event
|
|
node.onload = function () {
|
|
node.onload = null;
|
|
if (isStyleSheetLoaded(node, url)) {
|
|
// Render the stylesheet
|
|
node.media = 'all';
|
|
return;
|
|
}
|
|
// Try again
|
|
node.parentNode.removeChild(node);
|
|
node = null;
|
|
loadedCssByUrl[url] = null;
|
|
if (attempt >= RETRY_ATTEMPTS) {
|
|
throw new Error("Error: Failed to load the stylesheet "
|
|
+ "'" + url + "' after " + RETRY_ATTEMPTS + " attempts.\n"
|
|
+ "It was either not found, or you're trying to load "
|
|
+ "an empty stylesheet that has no CSS rules in it.");
|
|
}
|
|
setTimeout(function () {
|
|
Byond.loadCss(url, sync, attempt + 1);
|
|
}, RETRY_INTERVAL);
|
|
};
|
|
};
|
|
|
|
Byond.loadJs = function (url, sync, attempt) {
|
|
if (loadedJsByUrl[url]) {
|
|
return;
|
|
}
|
|
if (!attempt) {
|
|
attempt = 1;
|
|
}
|
|
loadedJsByUrl[url] = true;
|
|
// Inject the stylesheet
|
|
var ref = getRefNode();
|
|
var node = document.createElement('script');
|
|
node.type = 'text/javascript';
|
|
if (sync) {
|
|
node.defer = true;
|
|
}
|
|
else {
|
|
node.async = true;
|
|
}
|
|
node.src = url;
|
|
onDocumentBodyReady(function () {
|
|
ref.parentNode.insertBefore(node, ref.nextSibling);
|
|
});
|
|
node.onerror = function () {
|
|
node.onerror = null;
|
|
node.parentNode.removeChild(node);
|
|
node = null;
|
|
loadedJsByUrl[url] = null;
|
|
if (attempt >= RETRY_ATTEMPTS) {
|
|
throw new Error("Error: Failed to load the script "
|
|
+ "'" + url + "' after " + RETRY_ATTEMPTS + " attempts.");
|
|
}
|
|
setTimeout(function () {
|
|
Byond.loadJs(url, sync, attempt + 1);
|
|
}, RETRY_INTERVAL);
|
|
};
|
|
};
|
|
})();
|
|
|
|
// Global error handling
|
|
window.onerror = function (msg, url, line, col, error) {
|
|
// Proper stacktrace
|
|
var stack = error && error.stack;
|
|
// Ghetto stacktrace
|
|
if (!stack) {
|
|
stack = msg + '\n at ' + url + ':' + line;
|
|
if (col) {
|
|
stack += ':' + col;
|
|
}
|
|
}
|
|
// Augment the stack
|
|
stack = window.__augmentStack__(stack, error);
|
|
// Print error to the page
|
|
var errorRoot = document.getElementById('FatalError');
|
|
var errorStack = document.getElementById('FatalError__stack');
|
|
if (errorRoot) {
|
|
errorRoot.className = 'FatalError FatalError--visible';
|
|
if (errorStack.textContent) {
|
|
errorStack.textContent += '\n\n' + stack;
|
|
}
|
|
else {
|
|
errorStack.textContent = stack;
|
|
}
|
|
}
|
|
// Set window geometry
|
|
var setFatalErrorGeometry = function () {
|
|
Byond.winset(window.__windowId__, {
|
|
titlebar: true,
|
|
size: '600x600',
|
|
'is-visible': true,
|
|
'can-resize': true,
|
|
});
|
|
};
|
|
setFatalErrorGeometry();
|
|
setInterval(setFatalErrorGeometry, 1000);
|
|
// Send logs to the game server
|
|
Byond.topic({
|
|
tgui: 1,
|
|
window_id: window.__windowId__,
|
|
type: 'log',
|
|
fatal: 1,
|
|
message: stack,
|
|
});
|
|
// Short-circuit further updates
|
|
window.__updateQueue__ = [];
|
|
window.update = function () {};
|
|
// Prevent default action
|
|
return true;
|
|
};
|
|
|
|
// Catch unhandled rejections
|
|
window.onunhandledrejection = window.onerror;
|
|
|
|
// Helper for augmenting stack traces on fatal errors
|
|
window.__augmentStack__ = function (stack, error) {
|
|
return stack + '\nUser Agent: ' + navigator.userAgent;
|
|
};
|
|
|
|
// Early initialization
|
|
window.__updateQueue__ = [];
|
|
window.update = function (message) {
|
|
window.__updateQueue__.push(message);
|
|
};
|
|
Byond.topic({
|
|
tgui: 1,
|
|
window_id: window.__windowId__,
|
|
type: 'ready',
|
|
});
|
|
|
|
// Necessary polyfill to make Webpack code splitting work on IE8
|
|
if (!Function.prototype.bind) (function () {
|
|
var slice = Array.prototype.slice;
|
|
Function.prototype.bind = function () {
|
|
var thatFunc = this, thatArg = arguments[0];
|
|
var args = slice.call(arguments, 1);
|
|
if (typeof thatFunc !== 'function') {
|
|
// closest thing possible to the ECMAScript 5
|
|
// internal IsCallable function
|
|
throw new TypeError('Function.prototype.bind - ' +
|
|
'what is trying to be bound is not callable');
|
|
}
|
|
return function () {
|
|
var funcArgs = args.concat(slice.call(arguments))
|
|
return thatFunc.apply(thatArg, funcArgs);
|
|
};
|
|
};
|
|
})();
|
|
</script>
|
|
|
|
<style>
|
|
.FatalError {
|
|
display: none;
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
padding: 12px;
|
|
font-size: 12px;
|
|
font-family: Consolas, monospace;
|
|
color: #ffffff;
|
|
background-color: #0000dd;
|
|
z-index: 1000;
|
|
overflow: hidden;
|
|
text-align: center;
|
|
}
|
|
|
|
.FatalError--visible {
|
|
display: block !important;
|
|
}
|
|
|
|
.FatalError__logo {
|
|
display: inline-block;
|
|
text-align: left;
|
|
font-size: 10px;
|
|
line-height: 12px;
|
|
position: relative;
|
|
margin: 16px;
|
|
top: 0;
|
|
left: 0;
|
|
animation:
|
|
FatalError__rainbow 2s linear infinite alternate,
|
|
FatalError__shadow 4s linear infinite alternate,
|
|
FatalError__tfmX 3s infinite alternate,
|
|
FatalError__tfmY 4s infinite alternate;
|
|
white-space: pre;
|
|
}
|
|
|
|
.FatalError__header {
|
|
margin-top: 12px;
|
|
}
|
|
|
|
.FatalError__stack {
|
|
text-align: left;
|
|
white-space: pre-wrap;
|
|
word-break: break-all;
|
|
margin-top: 24px;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.FatalError__footer {
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
@keyframes FatalError__rainbow {
|
|
0% { color: #ff0; }
|
|
50% { color: #0ff; }
|
|
100% { color: #f0f; }
|
|
}
|
|
|
|
@keyframes FatalError__shadow {
|
|
0% {
|
|
left: -2px;
|
|
text-shadow: 4px 0 #f0f;
|
|
}
|
|
50% {
|
|
left: 0px;
|
|
text-shadow: 0px 0 #0ff;
|
|
}
|
|
100% {
|
|
left: 2px;
|
|
text-shadow: -4px 0 #ff0;
|
|
}
|
|
}
|
|
|
|
@keyframes FatalError__tfmX {
|
|
0% { left: 15px; }
|
|
100% { left: -15px; }
|
|
}
|
|
|
|
@keyframes FatalError__tfmY {
|
|
100% { top: -15px; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- Inline assets -->
|
|
<!-- tgui:assets -->
|
|
|
|
<!-- Inline HTML -->
|
|
<!-- tgui:html -->
|
|
|
|
<!-- tgui container -->
|
|
<div id="react-root"></div>
|
|
|
|
<!-- Fatal error container -->
|
|
<div id="FatalError" class="FatalError">
|
|
<div class="FatalError__logo">
|
|
ooooo ooo . .oooooo. .oooooo..o
|
|
`888b. `8' .o8 d8P' `Y8b d8P' `Y8
|
|
8 `88b. 8 .o888oo 888 888 Y88bo.
|
|
8 `88b. 8 888 888 888 `"Y8888o.
|
|
8 `88b.8 888 888 888 `"Y88b
|
|
8 `888 888 . `88b d88' oo .d8P
|
|
o8o `8 "888" `Y8bood8P' 8""88888P'
|
|
</div>
|
|
<marquee class="FatalError__header">
|
|
A fatal exception has occurred at 002B:C562F1B7 in TGUI.
|
|
The current application will be terminated.
|
|
Please remain calm. Get to the nearest NTNet workstation
|
|
and send the copy of the following stack trace to:
|
|
www.github.com/Citadel-Station-13/Citadel-Station-13/issues. Thank you for your cooperation.
|
|
</marquee>
|
|
<div id="FatalError__stack" class="FatalError__stack"></div>
|
|
<div class="FatalError__footer">
|
|
Nanotrasen (c) 2525-2559
|
|
</div>
|
|
</div>
|
|
|
|
<noscript>
|
|
<div class="NoticeBox">
|
|
<div>Javascript is required in order to use this interface.</div>
|
|
<div>Please enable Javascript and restart the game.</div>
|
|
</div>
|
|
</noscript>
|
|
|
|
</body>
|
|
</html>
|