Set up basic WebSocket frontend

This commit is contained in:
Condorra 2022-12-23 20:17:19 +11:00
commit 0e97d58f6e
20 changed files with 16235 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
assets/main.js
dist
node_modules

39
assets/game.html Normal file
View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html style="height:100%">
<head>
<link rel="manifest" href="manifest.json" />
<link rel="icon" href="images/logo96.png" type="image/png" />
<title>Blastmud</title>
<style>
.button {
border: solid black 1px;
padding: 5px;
cursor: pointer;
text-decoration: none;
border-radius: 10px;
color: white;
background: grey;
}
</style>
<link rel="stylesheet" href="xterm.css" />
<script src="xterm.js"></script>
</head>
<body style="background: black; height: 100%">
<div style="background: black; color: white; display: flex; flex-direction: column; margin: 0px; height: 100%; width: 100%">
<h1 style="text-align: center">Play Blastmud</h1>
<div id="console" style="flex: 1">&nbsp;</div>
</div>
<div id="over18" style="position: absolute; top: 0; left: 0; background: rgba(80,80,80,0.8); width: 100%; height: 100%">
<div style="display: block; padding: 10px; background: white; width: 80%; margin-left: auto; margin-right: auto; margin-top: 10%; border-radius: 10px;">
<h1>You must be 18+ to play Blastmud</h1>
<p>This game is restricted to adults (18 years or above). It contains violence,
sex scenes, and online interactions with other adults.</p>
<div style="text-align: right">
<a class="button" href="https://google.com/">I'm under 18</a>
<a class="button" href="#" onclick="over18();">I'm 18 or over</a>
</div>
</div>
</div>
</body>
<script src="main.js"></script>
</html>

BIN
assets/images/logo144.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

BIN
assets/images/logo168.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

BIN
assets/images/logo192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

BIN
assets/images/logo48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

BIN
assets/images/logo72.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

BIN
assets/images/logo96.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

38
assets/manifest.json Normal file
View File

@ -0,0 +1,38 @@
{
"$schema": "https://json.schemastore.org/web-manifest-combined.json",
"name": "BlastMud",
"short_name": "BlastMud",
"start_url": ".",
"display": "standalone",
"background_color": "#000",
"description": "A post-apocalyptic multi user dungeon game",
"icons": [{
"src": "images/logo48.png",
"sizes": "48x48",
"type": "image/png"
}, {
"src": "images/logo72.png",
"sizes": "72x72",
"type": "image/png"
}, {
"src": "images/logo96.png",
"sizes": "96x96",
"type": "image/png"
}, {
"src": "images/logo144.png",
"sizes": "144x144",
"type": "image/png"
}, {
"src": "images/logo168.png",
"sizes": "168x168",
"type": "image/png"
}, {
"src": "images/logo192.png",
"sizes": "192x192",
"type": "image/png"
}],
"related_applications": [{
"platform": "play",
"url": "https://play.google.com/store/apps/details?id=org.blastmud.game"
}]
}

9
assets/offline.html Normal file
View File

@ -0,0 +1,9 @@
<html>
<body>
<h1>You're offline</h1>
<p>This app only works when you are online.
Check your network connection and try again.
If your network connection is okay, our servers might be
under maintenance - try again later.</p>
</body>
</html>

60
assets/service-worker.js Normal file
View File

@ -0,0 +1,60 @@
/*
Copyright 2015, 2019 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
const OFFLINE_VERSION = 1;
const CACHE_NAME = 'offline';
const OFFLINE_URL = 'offline.html';
self.addEventListener('install', (event) => {
event.waitUntil((async () => {
const cache = await caches.open(CACHE_NAME);
await cache.add(new Request(OFFLINE_URL, {cache: 'reload'}));
})());
});
self.addEventListener('activate', (event) => {
event.waitUntil((async () => {
// Enable navigation preload if it's supported.
// See https://developers.google.com/web/updates/2017/02/navigation-preload
if ('navigationPreload' in self.registration) {
await self.registration.navigationPreload.enable();
}
})());
// Tell the active service worker to take control of the page immediately.
self.clients.claim();
});
self.addEventListener('fetch', (event) => {
// We only want to call event.respondWith() if this is a navigation request
// for an HTML page.
if (event.request.mode === 'navigate') {
event.respondWith((async () => {
try {
// First, try to use the navigation preload response if it's supported.
const preloadResponse = await event.preloadResponse;
if (preloadResponse && preloadResponse.type !== 'error') {
return preloadResponse;
}
const networkResponse = await fetch(event.request);
return networkResponse;
} catch (error) {
// catch is only triggered if an exception is thrown, which is likely
// due to a network error.
// If fetch() returns a valid HTTP response with a response code in
// the 4xx or 5xx range, the catch() will NOT be called.
console.log('Fetch failed; returning offline page instead.', error);
const cache = await caches.open(CACHE_NAME);
const cachedResponse = await cache.match(OFFLINE_URL);
return cachedResponse;
}
})());
}
});

191
assets/xterm.css Normal file
View File

@ -0,0 +1,191 @@
/**
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
* https://github.com/chjj/term.js
* @license MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* Originally forked from (with the author's permission):
* Fabrice Bellard's javascript vt100 for jslinux:
* http://bellard.org/jslinux/
* Copyright (c) 2011 Fabrice Bellard
* The original design remains. The terminal itself
* has been extended to include xterm CSI codes, among
* other features.
*/
/**
* Default styles for xterm.js
*/
.xterm {
cursor: text;
position: relative;
user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
}
.xterm.focus,
.xterm:focus {
outline: none;
}
.xterm .xterm-helpers {
position: absolute;
top: 0;
/**
* The z-index of the helpers must be higher than the canvases in order for
* IMEs to appear on top.
*/
z-index: 5;
}
.xterm .xterm-helper-textarea {
padding: 0;
border: 0;
margin: 0;
/* Move textarea out of the screen to the far left, so that the cursor is not visible */
position: absolute;
opacity: 0;
left: -9999em;
top: 0;
width: 0;
height: 0;
z-index: -5;
/** Prevent wrapping so the IME appears against the textarea at the correct position */
white-space: nowrap;
overflow: hidden;
resize: none;
}
.xterm .composition-view {
/* TODO: Composition position got messed up somewhere */
background: #000;
color: #FFF;
display: none;
position: absolute;
white-space: nowrap;
z-index: 1;
}
.xterm .composition-view.active {
display: block;
}
.xterm .xterm-viewport {
/* On OS X this is required in order for the scroll bar to appear fully opaque */
background-color: #000;
overflow-y: scroll;
cursor: default;
position: absolute;
right: 0;
left: 0;
top: 0;
bottom: 0;
}
.xterm .xterm-screen {
position: relative;
}
.xterm .xterm-screen canvas {
position: absolute;
left: 0;
top: 0;
}
.xterm .xterm-scroll-area {
visibility: hidden;
}
.xterm-char-measure-element {
display: inline-block;
visibility: hidden;
position: absolute;
top: 0;
left: -9999em;
line-height: normal;
}
.xterm.enable-mouse-events {
/* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
cursor: default;
}
.xterm.xterm-cursor-pointer,
.xterm .xterm-cursor-pointer {
cursor: pointer;
}
.xterm.column-select.focus {
/* Column selection mode */
cursor: crosshair;
}
.xterm .xterm-accessibility,
.xterm .xterm-message {
position: absolute;
left: 0;
top: 0;
bottom: 0;
z-index: 10;
color: transparent;
}
.xterm .live-region {
position: absolute;
left: -9999px;
width: 1px;
height: 1px;
overflow: hidden;
}
.xterm-dim {
opacity: 0.5;
}
.xterm-underline-1 { text-decoration: underline; }
.xterm-underline-2 { text-decoration: double underline; }
.xterm-underline-3 { text-decoration: wavy underline; }
.xterm-underline-4 { text-decoration: dotted underline; }
.xterm-underline-5 { text-decoration: dashed underline; }
.xterm-strikethrough {
text-decoration: line-through;
}
.xterm-screen .xterm-decoration-container .xterm-decoration {
z-index: 6;
position: absolute;
}
.xterm-decoration-overview-ruler {
z-index: 7;
position: absolute;
top: 0;
right: 0;
pointer-events: none;
}
.xterm-decoration-top {
z-index: 2;
position: relative;
}

26
assets/xterm.js Normal file
View File

@ -0,0 +1,26 @@
/*
Copyright 2021 Erik Bremen
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.*/

15572
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

11
package.json Normal file
View File

@ -0,0 +1,11 @@
{
"dependencies": {
"xterm": "^5.1.0",
"xterm-readline": "^1.1.0"
},
"devDependencies": {
"@webpack-cli/generators": "^3.0.1",
"ts-loader": "^9.4.2",
"workbox-webpack-plugin": "^6.5.4"
}
}

158
src/Logo.svg Normal file
View File

@ -0,0 +1,158 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="40mm"
height="40mm"
viewBox="0 0 40 40"
version="1.1"
id="svg8"
inkscape:version="1.0.2 (e86c870879, 2021-01-15)"
sodipodi:docname="Logo.svg">
<defs
id="defs2">
<linearGradient
inkscape:collect="always"
id="linearGradient841">
<stop
style="stop-color:#ff2f1b;stop-opacity:0.94338685"
offset="0"
id="stop837" />
<stop
style="stop-color:#ff981b;stop-opacity:1"
offset="1"
id="stop839" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient841"
id="linearGradient843"
x1="0.09372035"
y1="32.445991"
x2="40.099368"
y2="32.445991"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="3.959798"
inkscape:cx="33.612988"
inkscape:cy="79.88823"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
units="mm"
width="10in"
inkscape:window-width="1920"
inkscape:window-height="1080"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="opacity:0.996;fill:#bdbdbf;fill-opacity:1;stroke:none;stroke-width:3.26501"
id="rect845"
width="40.060905"
height="39.918461"
x="0.038461588"
y="0.040780049" />
<path
style="fill:#000096;fill-rule:evenodd;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1"
d="m 0.01924099,11.770051 c 0,0 4.23471871,-9.0904555 7.97328241,0.07001 3.7385636,9.160462 10.9886006,0.01079 10.9886006,0.01079 0,0 5.966783,-14.2485618 11.666,0.158241 5.699215,14.406802 9.288732,-1.270883 9.288732,-1.270883 L 40.044468,-0.00924326 0.03846159,0.04078005 Z"
id="path833" />
<path
style="fill:url(#linearGradient843);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 40.086823,30.112199 c 0,0 -4.210076,7.636771 -7.973427,-0.04911 -3.763352,-7.685876 -10.98857,0.0043 -10.98857,0.0043 0,0 -5.92816,11.969217 -11.6663624,-0.118701 -5.738201,-12.087915 -9.28523872,1.078198 -9.28523872,1.078198 l -0.07950453,9.022855 40.00564765,-0.0905 z"
id="path835" />
<g
id="g893">
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.865;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 2.4325,0 V 40.079086"
id="path847" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.865;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 8.1530386,0 V 40.079086"
id="path849" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.865;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 13.873575,0 V 40.079086"
id="path851" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.865;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 19.594114,0 V 40.079086"
id="path853" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.865;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 25.314652,0 V 40.079086"
id="path855" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.865;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 31.035191,0 V 40.079086"
id="path857" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.865;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 36.755726,0 V 40.079086"
id="path859" />
</g>
<g
id="g909"
transform="rotate(89.785681,20.14727,20.064174)">
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.865;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 2.4325,0 V 40.079086"
id="path895" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.865;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 8.1530386,0 V 40.079086"
id="path897" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.865;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 13.873575,0 V 40.079086"
id="path899" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.865;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 19.594114,0 V 40.079086"
id="path901" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.865;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 25.314652,0 V 40.079086"
id="path903" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.865;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 31.035191,0 V 40.079086"
id="path905" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.865;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 36.755726,0 V 40.079086"
id="path907" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.0 KiB

63
src/index.ts Normal file
View File

@ -0,0 +1,63 @@
import {Terminal} from 'xterm';
import { Readline } from "xterm-readline";
let isScreened = false, sendLessExplicit = false;
const term = new Terminal();
const rl = new Readline();
term.options.scrollback = 1000;
term.loadAddon(rl);
term.open(document.getElementById('console'));
let lineHandler = (l: string) => { console.log("Default handler", l); };
async function readForever() {
while (true) {
const l = await rl.read("");
lineHandler(l);
}
}
readForever();
function connectTerm() {
lineHandler = () => {};
term.writeln("\x1b[0mConnecting to server...");
const wsurl = document.location.href.replace(/^https:\/\/(.*)\/game(.html)?(\?.*)?(\#.*)?/, 'wss:\/\/$1/wsgame');
let webSocket = new WebSocket(wsurl);
webSocket.addEventListener('open', (event) => {
lineHandler = (l: string) => { console.log("Send handler", l); webSocket.send(l); }
term.writeln("\x1b[0mConnected");
});
webSocket.addEventListener('close', (event) => {
lineHandler = connectTerm;
term.writeln("\x1b[0mDisconnected; use r (followed by enter) to reconnect.");
});
webSocket.addEventListener('error', (event) => {
term.writeln("\x1b[0mNetwork error with connection.");
});
webSocket.addEventListener('message', (msg) => {
term.write(msg.data);
})
}
function over18() {
document.getElementById("over18").style.display = 'none';
isScreened = true;
window.localStorage['over18'] = true;
term.focus();
connectTerm();
}
if ('serviceWorker' in navigator) navigator.serviceWorker.register('service-worker.js');
const params: {[key:string]: string} =
location.search.substr(1).split('&')
.reduce((o, s) => { const [k, v] = s.split('='); o[k] = v; return o;},
{} as {[key:string]: string});
if (params["source"] && params["source"] === "android") {
isScreened = true;
sendLessExplicit = true;
}
if (isScreened || window.localStorage['over18']) {
over18();
}

11
tsconfig.json Normal file
View File

@ -0,0 +1,11 @@
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"noImplicitAny": true,
"module": "es6",
"target": "es5",
"allowJs": true,
"moduleResolution": "node"
},
"files": ["src/index.ts"]
}

4
update-deps.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
npx webpack
cp dist/main.js ./assets/main.js

50
webpack.config.js Normal file
View File

@ -0,0 +1,50 @@
// Generated using webpack-cli https://github.com/webpack/webpack-cli
const path = require('path');
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
const isProduction = process.env.NODE_ENV !== 'test';
const config = {
entry: './src/index.ts',
output: {
path: path.resolve(__dirname, 'dist'),
},
plugins: [
// Add your plugins here
// Learn more about plugins from https://webpack.js.org/configuration/plugins/
],
module: {
rules: [
{
test: /\.(ts|tsx)$/i,
loader: 'ts-loader',
exclude: ['/node_modules/'],
},
{
test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
type: 'asset',
},
// Add your rules for custom modules here
// Learn more about loaders from https://webpack.js.org/loaders/
],
},
resolve: {
extensions: ['.tsx', '.ts', '.jsx', '.js', '...'],
},
};
module.exports = () => {
if (isProduction) {
config.mode = 'production';
config.plugins.push(new WorkboxWebpackPlugin.GenerateSW());
} else {
config.mode = 'development';
}
return config;
};