710 lines
20 KiB
JavaScript
710 lines
20 KiB
JavaScript
let MAIN = null;
|
|
let MENU = null;
|
|
let SCENE = null;
|
|
|
|
let CONNECTED = false;
|
|
let SOCKET = null;
|
|
let CONTEXT = {
|
|
Scene: null,
|
|
Auth: null,
|
|
Data: null,
|
|
};
|
|
|
|
const Status = {
|
|
Ok: 0,
|
|
Error: 1,
|
|
NotImplement: 2,
|
|
|
|
BadHandle: 1,
|
|
BadSecret: 2,
|
|
BadCode: 3,
|
|
};
|
|
|
|
const OpCode = {
|
|
Register :0x0010,
|
|
Authenticate :0x0011,
|
|
Resume :0x0012,
|
|
Deauthenticate :0x0013,
|
|
};
|
|
|
|
class Message {
|
|
constructor(code, data) {
|
|
this.code = code;
|
|
this.data = data;
|
|
}
|
|
}
|
|
|
|
const PACK = {
|
|
u8:(value) => {
|
|
return new Uint8Array([ value & 0xFF ]);
|
|
},
|
|
u16:(value) => {
|
|
return new Uint8Array([ (value >> 8) & 0xFF, value & 0xFF ]);
|
|
},
|
|
u32:(value) => {
|
|
return new Uint8Array([
|
|
(value >> 24) & 0xFF,
|
|
(value >> 16) & 0xFF,
|
|
(value >> 8) & 0xFF,
|
|
value & 0xFF
|
|
]);
|
|
},
|
|
};
|
|
|
|
const UI = {
|
|
text:(value) => {
|
|
return document.createTextNode(value);
|
|
},
|
|
|
|
button:(text, callback) => {
|
|
let button = document.createElement("button");
|
|
button.innerText = text;
|
|
if(callback !== null) { button.addEventListener("click", callback); }
|
|
return button;
|
|
},
|
|
|
|
textbox:(id, placeholder) => {
|
|
let input = document.createElement("input");
|
|
input.setAttribute("type", "text");
|
|
if(id !== null) { input.setAttribute("id", id); }
|
|
input.setAttribute("placeholder", placeholder);
|
|
return input;
|
|
},
|
|
|
|
password:(id) => {
|
|
let input = document.createElement("input");
|
|
input.setAttribute("type", "password");
|
|
if(id !== null) { input.setAttribute("id", id); }
|
|
return input;
|
|
},
|
|
|
|
label:(name, id) => {
|
|
let label = document.createElement("label");
|
|
label.setAttribute("for", id);
|
|
label.innerText = name;
|
|
return label;
|
|
},
|
|
|
|
div:(children) => {
|
|
let div = document.createElement("div");
|
|
for(child of children) { div.appendChild(child); }
|
|
return div;
|
|
},
|
|
|
|
table:(header, rows) => {
|
|
let table = document.createElement("table");
|
|
let tbody = document.createElement("tbody");
|
|
|
|
if(header !== null) {
|
|
let row = document.createElement("tr");
|
|
for(head of header) {
|
|
let cell = document.createElement("th");
|
|
cell.innerText = head;
|
|
row.appendChild(cell);
|
|
}
|
|
tbody.appendChild(row);
|
|
}
|
|
|
|
for(row of rows) {
|
|
let tr = document.createElement("tr");
|
|
for(node of row) {
|
|
let cell = document.createElement("td");
|
|
if(Array.isArray(node)) { for(item of node) { cell.appendChild(item); } }
|
|
else { cell.appendChild(node); }
|
|
tr.appendChild(cell);
|
|
}
|
|
tbody.appendChild(tr);
|
|
}
|
|
|
|
table.appendChild(tbody);
|
|
return table;
|
|
},
|
|
|
|
mainnav:(left_children, right_children) => {
|
|
let header = document.createElement("nav");
|
|
let left = document.createElement("section");
|
|
|
|
if(CONTEXT.Auth === null) {
|
|
left.appendChild(UI.button("Register", () => { LOAD(SCENES.Register) }));
|
|
left.appendChild(UI.button("Log In", () => { LOAD(SCENES.Authenticate) }));
|
|
}
|
|
|
|
for(child of left_children) { left.appendChild(child); }
|
|
|
|
let right = document.createElement("section");
|
|
for(child of right_children) { right.appendChild(child); }
|
|
|
|
header.appendChild(left);
|
|
header.appendChild(right);
|
|
|
|
MAIN.appendChild(header);
|
|
},
|
|
|
|
mainmenu:() => {
|
|
if(SOCKET !== null) {
|
|
MENU.appendChild(UI.button("Online", () => { LOAD(SCENES.Online); }));
|
|
if(CONTEXT.Auth !== null) {
|
|
MENU.appendChild(UI.button("Continue", () => { LOAD(SCENES.Continue); }));
|
|
MENU.appendChild(UI.button("Join", () => { LOAD(SCENES.Join); }));
|
|
}
|
|
MENU.appendChild(UI.button("Live", () => { LOAD(SCENES.Live); }));
|
|
MENU.appendChild(UI.button("History", () => { LOAD(SCENES.History); }));
|
|
MENU.appendChild(UI.button("Guide", () => { LOAD(SCENES.Guide); }));
|
|
MENU.appendChild(UI.button("About", () => { LOAD(SCENES.About); }));
|
|
|
|
if(CONTEXT.Auth !== null) {
|
|
MENU.appendChild(UI.button("Logout", () => {
|
|
MESSAGE_COMPOSE([
|
|
PACK.u16(OpCode.Deauthenticate),
|
|
]);
|
|
CONTEXT.Auth = null;
|
|
LOAD(SCENE);
|
|
}));
|
|
}
|
|
}
|
|
},
|
|
};
|
|
|
|
const SCENES = {
|
|
Init:{
|
|
load:() => {
|
|
LOAD_OFFLINE();
|
|
CONTEXT.Scene = SCENES.Online;
|
|
RECONNECT();
|
|
return true;
|
|
},
|
|
},
|
|
|
|
Offline:{
|
|
load:() => {
|
|
MENU.appendChild(UI.button("Reconnect", () => { RECONNECT(); }))
|
|
return true;
|
|
},
|
|
},
|
|
|
|
Register:{
|
|
load:() => {
|
|
if(CONTEXT.Auth !== null) return false;
|
|
UI.mainmenu();
|
|
UI.mainnav([], []);
|
|
|
|
let container = document.createElement("section");
|
|
let form = document.createElement("div");
|
|
form.appendChild(UI.table(null, [
|
|
[ UI.label("Handle", "handle"), UI.textbox("handle", "") ],
|
|
[ UI.label("Secret", "secret"), UI.password("secret") ],
|
|
[ UI.label("Code", "code"), UI.password("code") ],
|
|
]));
|
|
|
|
let button = UI.button("Register", (event) => {
|
|
let handle = document.getElementById("handle");
|
|
let secret = document.getElementById("secret");
|
|
let code = document.getElementById("code");
|
|
|
|
handle.removeAttribute("class");
|
|
secret.removeAttribute("class");
|
|
code.removeAttribute("class");
|
|
event.target.removeAttribute("class");
|
|
|
|
if(handle.value.length > 0 && secret.value.length > 0 && code.value.length > 0) {
|
|
event.target.setAttribute("disabled", "");
|
|
|
|
let enc = new TextEncoder();
|
|
let enc_handle = enc.encode(handle.value);
|
|
let enc_secret = enc.encode(secret.value);
|
|
let enc_code = enc.encode(code.value);
|
|
|
|
MESSAGE_COMPOSE([
|
|
PACK.u16(OpCode.Register),
|
|
PACK.u16(enc_handle.length),
|
|
enc_handle,
|
|
PACK.u16(enc_secret.length),
|
|
enc_secret,
|
|
PACK.u16(enc_code.length),
|
|
enc_code,
|
|
]);
|
|
} else {
|
|
if(handle.value.length == 0) { handle.setAttribute("class", "error"); }
|
|
if(secret.value.length == 0) { secret.setAttribute("class", "error"); }
|
|
if(code.value.length == 0) { code.setAttribute("class", "error"); }
|
|
}
|
|
});
|
|
button.setAttribute("id", "submit");
|
|
form.appendChild(button);
|
|
|
|
container.appendChild(form);
|
|
MAIN.appendChild(container);
|
|
MAIN.setAttribute("class", "form");
|
|
|
|
return true;
|
|
},
|
|
message:(code, data) => {
|
|
if(code == OpCode.Register && data !== null) {
|
|
let submit = document.getElementById("submit");
|
|
switch(data.status) {
|
|
case Status.Ok: {
|
|
CONTEXT.Auth = data;
|
|
LOAD(SCENES.Online);
|
|
} break;
|
|
default: {
|
|
submit.removeAttribute("disabled");
|
|
switch(data.status) {
|
|
case Status.BadHandle: {
|
|
document.getElementById("handle").setAttribute("class", "error");
|
|
submit.setAttribute("class", "error");
|
|
} break;
|
|
case Status.BadSecret: {
|
|
document.getElementById("secret").setAttribute("class", "error");
|
|
submit.setAttribute("class", "error");
|
|
} break;
|
|
case Status.BadCode: {
|
|
document.getElementById("code").setAttribute("class", "error");
|
|
submit.setAttribute("class", "error");
|
|
} break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
},
|
|
|
|
Authenticate:{
|
|
load:() => {
|
|
if(CONTEXT.Auth !== null) return false;
|
|
UI.mainmenu();
|
|
UI.mainnav([], []);
|
|
|
|
let container = document.createElement("section");
|
|
let form = document.createElement("div");
|
|
form.appendChild(UI.table(null, [
|
|
[ UI.label("Handle", "handle"), UI.textbox("handle", "") ],
|
|
[ UI.label("Secret", "secret"), UI.password("secret") ],
|
|
]));
|
|
|
|
let button = UI.button("Register", (event) => {
|
|
let handle = document.getElementById("handle");
|
|
let secret = document.getElementById("secret");
|
|
|
|
handle.removeAttribute("class");
|
|
secret.removeAttribute("class");
|
|
event.target.removeAttribute("class");
|
|
|
|
if(handle.value.length > 0 && secret.value.length > 0) {
|
|
event.target.setAttribute("disabled", "");
|
|
|
|
let enc = new TextEncoder();
|
|
let enc_handle = enc.encode(handle.value);
|
|
let enc_secret = enc.encode(secret.value);
|
|
|
|
MESSAGE_COMPOSE([
|
|
PACK.u16(OpCode.Authenticate),
|
|
PACK.u16(enc_handle.length),
|
|
enc_handle,
|
|
PACK.u16(enc_secret.length),
|
|
enc_secret,
|
|
]);
|
|
} else {
|
|
if(handle.value.length == 0) { handle.setAttribute("class", "error"); }
|
|
if(secret.value.length == 0) { secret.setAttribute("class", "error"); }
|
|
}
|
|
});
|
|
button.setAttribute("id", "submit");
|
|
form.appendChild(button);
|
|
|
|
container.appendChild(form);
|
|
MAIN.appendChild(container);
|
|
MAIN.setAttribute("class", "form");
|
|
|
|
return true;
|
|
},
|
|
message:(code, data) => {
|
|
if(code == OpCode.Authenticate && data !== null) {
|
|
let submit = document.getElementById("submit");
|
|
switch(data.status) {
|
|
case Status.Ok: {
|
|
CONTEXT.Auth = data;
|
|
LOAD(SCENES.Online);
|
|
} break;
|
|
case Status.Error: {
|
|
submit.removeAttribute("disabled");
|
|
|
|
document.getElementById("handle").setAttribute("class", "error");
|
|
document.getElementById("secret").setAttribute("class", "error");
|
|
submit.setAttribute("class", "error");
|
|
}
|
|
}
|
|
}
|
|
},
|
|
},
|
|
|
|
Online:{
|
|
load:() => {
|
|
UI.mainmenu();
|
|
|
|
let left_buttons = [ ];
|
|
if(CONTEXT.Auth !== null) {
|
|
left_buttons.push(UI.button("Start", null));
|
|
}
|
|
|
|
UI.mainnav(
|
|
left_buttons,
|
|
[
|
|
UI.div([UI.text("0 - 0 of 0")]),
|
|
UI.button("◀", null),
|
|
UI.button("▶", null),
|
|
]
|
|
);
|
|
|
|
let table = document.createElement("table");
|
|
table.setAttribute("id", "content");
|
|
MAIN.appendChild(table);
|
|
|
|
MAIN.setAttribute("class", "list");
|
|
|
|
SCENE.refresh();
|
|
return true;
|
|
},
|
|
refresh:() => {
|
|
let request = new Uint8Array();
|
|
|
|
//SERVER.send()
|
|
|
|
SCENE.message(0, 0, null);
|
|
},
|
|
message:(code, data) => {
|
|
let table = document.getElementById("content");
|
|
MAIN.removeChild(table);
|
|
|
|
let rows = [
|
|
[ UI.text("Player1"), UI.text("Player2"), UI.text("0"), UI.text("Ha1-D ◈ Ba1-M"), UI.text("0"), [ UI.button("Join", null), UI.button("Spectate", null) ] ],
|
|
];
|
|
|
|
MAIN.appendChild(UI.table(
|
|
[ "Dawn", "Dusk", "Turn", "Move", "Spectators", "" ],
|
|
rows,
|
|
));
|
|
}
|
|
},
|
|
|
|
Continue:{
|
|
load:() => {
|
|
if(CONTEXT.Auth === null) return false;
|
|
UI.mainmenu();
|
|
|
|
let left_buttons = [ ];
|
|
if(CONTEXT.Auth !== null) {
|
|
left_buttons.push(UI.button("Start", null));
|
|
}
|
|
|
|
UI.mainnav(
|
|
left_buttons,
|
|
[
|
|
UI.div([UI.text("0 - 0 of 0")]),
|
|
UI.button("◀", null),
|
|
UI.button("▶", null),
|
|
]
|
|
);
|
|
|
|
let table = document.createElement("table");
|
|
table.setAttribute("id", "content");
|
|
MAIN.appendChild(table);
|
|
|
|
MAIN.setAttribute("class", "list");
|
|
|
|
SCENE.refresh();
|
|
return true;
|
|
},
|
|
refresh:() => {
|
|
|
|
},
|
|
},
|
|
|
|
Join:{
|
|
load:() => {
|
|
if(CONTEXT.Auth === null) return false;
|
|
UI.mainmenu();
|
|
|
|
let left_buttons = [ ];
|
|
if(CONTEXT.Auth !== null) {
|
|
left_buttons.push(UI.button("Start", null));
|
|
}
|
|
|
|
UI.mainnav(
|
|
left_buttons,
|
|
[
|
|
UI.div([UI.text("0 - 0 of 0")]),
|
|
UI.button("◀", null),
|
|
UI.button("▶", null),
|
|
]
|
|
);
|
|
|
|
let table = document.createElement("table");
|
|
table.setAttribute("id", "content");
|
|
MAIN.appendChild(table);
|
|
|
|
MAIN.setAttribute("class", "list");
|
|
|
|
SCENE.refresh();
|
|
return true;
|
|
},
|
|
refresh:() => {
|
|
|
|
},
|
|
},
|
|
|
|
Live:{
|
|
load:() => {
|
|
UI.mainmenu();
|
|
|
|
let left_buttons = [ ];
|
|
if(CONTEXT.Auth !== null) {
|
|
left_buttons.push(UI.button("Start", null));
|
|
}
|
|
|
|
UI.mainnav(
|
|
left_buttons,
|
|
[
|
|
UI.div([UI.text("0 - 0 of 0")]),
|
|
UI.button("◀", null),
|
|
UI.button("▶", null),
|
|
]
|
|
);
|
|
|
|
let table = document.createElement("table");
|
|
table.setAttribute("id", "content");
|
|
MAIN.appendChild(table);
|
|
|
|
MAIN.setAttribute("class", "list");
|
|
|
|
SCENE.refresh();
|
|
return true;
|
|
},
|
|
refresh:() => {
|
|
|
|
},
|
|
},
|
|
|
|
History:{
|
|
load:() => {
|
|
UI.mainmenu();
|
|
|
|
UI.mainnav(
|
|
[ ],
|
|
[
|
|
UI.div([UI.text("0 - 0 of 0")]),
|
|
UI.button("◀", null),
|
|
UI.button("▶", null),
|
|
]
|
|
);
|
|
|
|
let table = document.createElement("table");
|
|
table.setAttribute("id", "content");
|
|
MAIN.appendChild(table);
|
|
|
|
MAIN.setAttribute("class", "list");
|
|
|
|
SCENE.refresh();
|
|
return true;
|
|
},
|
|
refresh:() => {
|
|
|
|
},
|
|
},
|
|
|
|
Guide:{
|
|
load:() => {
|
|
UI.mainmenu();
|
|
UI.mainnav([], []);
|
|
return true;
|
|
},
|
|
refresh:() => {
|
|
|
|
},
|
|
},
|
|
|
|
About:{
|
|
load:() => {
|
|
UI.mainmenu();
|
|
UI.mainnav([], []);
|
|
return true;
|
|
},
|
|
refresh:() => {
|
|
|
|
},
|
|
},
|
|
|
|
Game:{
|
|
load:() => {
|
|
MENU.appendChild(UI.button("Back", () => { LOAD(SCENES.Online) }));
|
|
MENU.appendChild(UI.button("Retire", () => { }));
|
|
return true;
|
|
},
|
|
unload:() => {
|
|
|
|
},
|
|
refresh:() => {
|
|
|
|
},
|
|
},
|
|
};
|
|
|
|
function RECONNECT() {
|
|
if(SOCKET === null) {
|
|
console.log("Websocket connecting..");
|
|
SOCKET = new WebSocket("wss://omen.kirisame.com:38612");
|
|
SOCKET.binaryType = "arraybuffer";
|
|
SOCKET.addEventListener("error", (event) => {
|
|
SOCKET = null;
|
|
LOAD(SCENES.Offline)
|
|
});
|
|
SOCKET.addEventListener("open", (event) => {
|
|
if(SOCKET.readyState === WebSocket.OPEN) {
|
|
console.log("Websocket connected.");
|
|
|
|
SOCKET.addEventListener("message", MESSAGE);
|
|
|
|
SOCKET.addEventListener("close", (event) => {
|
|
console.log("Websocket closed.");
|
|
SOCKET = null;
|
|
RECONNECT();
|
|
});
|
|
|
|
RESUME();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function RESUME() {
|
|
LOAD(CONTEXT.Scene);
|
|
}
|
|
|
|
function REBUILD() {
|
|
MENU = document.createElement("nav");
|
|
let title = document.createElement("header");
|
|
title.innerText = "Omen";
|
|
MENU.appendChild(title);
|
|
|
|
MAIN = document.createElement("main");
|
|
|
|
document.body.appendChild(MENU);
|
|
document.body.appendChild(MAIN);
|
|
}
|
|
|
|
function MESSAGE(event) {
|
|
console.log("Message received.");
|
|
|
|
if(SCENE.message !== undefined) {
|
|
let bytes = new Uint8Array(event.data);
|
|
let code = 0;
|
|
let index = 2;
|
|
let data = null;
|
|
|
|
if(bytes.length >= 2) {
|
|
code = (bytes[0] << 8) + bytes[1];
|
|
}
|
|
|
|
switch(code) {
|
|
case OpCode.Register: {
|
|
console.log("Register response.");
|
|
|
|
if(bytes.length == 28) {
|
|
console.log("Good size");
|
|
data = {
|
|
status:(bytes[2] << 8) + bytes[3],
|
|
token:new Uint8Array(),
|
|
secret:new Uint8Array(),
|
|
};
|
|
index += 2;
|
|
|
|
for(let i = 0; i < 8; ++i) {
|
|
data.token += bytes[index++];
|
|
}
|
|
for(let i = 0; i < 16; ++i) {
|
|
data.secret += bytes[index++];
|
|
}
|
|
} else {
|
|
console.log("Register bad length:" + bytes.length);
|
|
return;
|
|
}
|
|
} break;
|
|
|
|
case OpCode.Authenticate: {
|
|
console.log("Authenticate response.");
|
|
|
|
if(bytes.length == 28) {
|
|
console.log("Good size");
|
|
data = {
|
|
status:(bytes[2] << 8) + bytes[3],
|
|
token:new Uint8Array(),
|
|
secret:new Uint8Array(),
|
|
};
|
|
index += 2;
|
|
|
|
for(let i = 0; i < 8; ++i) {
|
|
data.token += bytes[index++];
|
|
}
|
|
for(let i = 0; i < 16; ++i) {
|
|
data.secret += bytes[index++];
|
|
}
|
|
} else {
|
|
console.log("Authenticate bad length:" + bytes.length);
|
|
return;
|
|
}
|
|
} break;
|
|
|
|
case OpCode.Resume: {
|
|
|
|
} break;
|
|
|
|
case OpCode.Deauthenticate: {
|
|
|
|
} break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
SCENE.message(code, data);
|
|
}
|
|
}
|
|
|
|
function MESSAGE_COMPOSE(data) {
|
|
if(SOCKET !== null) {
|
|
let length = 0;
|
|
for(let i = 0; i < data.length; ++i) {
|
|
length += data[i].length;
|
|
}
|
|
|
|
let raw = new Uint8Array(length);
|
|
length = 0;
|
|
for(let i = 0; i < data.length; ++i) {
|
|
raw.set(data[i], length);
|
|
length += data[i].length;
|
|
}
|
|
|
|
SOCKET.send(raw);
|
|
}
|
|
}
|
|
|
|
function LOAD(scene) {
|
|
if(SCENE.unload !== undefined) { SCENE.unload(); }
|
|
while(document.body.lastChild !== null) { document.body.removeChild(document.body.lastChild); }
|
|
REBUILD();
|
|
SCENE = scene;
|
|
CONTEXT.Scene = SCENE;
|
|
if(!SCENE.load()) { LOAD(SCENES.Online); }
|
|
}
|
|
|
|
function LOAD_OFFLINE() {
|
|
if(SCENE.unload !== undefined) { SCENE.unload(); }
|
|
while(document.body.lastChild !== null) { document.body.removeChild(document.body.lastChild); }
|
|
REBUILD();
|
|
SCENE = SCENES.Offline;
|
|
if(!SCENE.load()) { LOAD(SCENES.Online); }
|
|
}
|
|
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
SCENE = SCENES.Offline;
|
|
LOAD(SCENES.Init);
|
|
});
|