From 223f42d3d512a1f19cbb8a328e5b8abf18a65d71 Mon Sep 17 00:00:00 2001 From: Elijah R Date: Fri, 2 Feb 2024 19:40:54 -0500 Subject: [PATCH] add turns and control --- src/js/keyboard.d.ts | 3 ++ src/js/keyboard.js | 5 +-- src/ts/main.ts | 36 +++++++++++++++-- src/ts/protocol/CollabVMClient.ts | 65 ++++++++++++++++++++++++++++++- src/ts/protocol/mouse.ts | 36 +++++++++++++++++ 5 files changed, 136 insertions(+), 9 deletions(-) create mode 100644 src/js/keyboard.d.ts create mode 100644 src/ts/protocol/mouse.ts diff --git a/src/js/keyboard.d.ts b/src/js/keyboard.d.ts new file mode 100644 index 0000000..6443271 --- /dev/null +++ b/src/js/keyboard.d.ts @@ -0,0 +1,3 @@ +declare function GetKeysym(keyCode : number, key : string, location : number) : number | undefined; + +export default GetKeysym; \ No newline at end of file diff --git a/src/js/keyboard.js b/src/js/keyboard.js index 6425bc4..bec91bf 100644 --- a/src/js/keyboard.js +++ b/src/js/keyboard.js @@ -3,13 +3,10 @@ // shitty but it works so /shrug // THIS SUCKS SO BAD AND I HATE IT PLEASE REWRITE ALL OF THIS -export default function GetKeysym(keyCode, keyIdentifier, key, location) { +export default function GetKeysym(keyCode, key, location) { var keysym = keysym_from_key_identifier(key, location) || keysym_from_keycode(keyCode, location); - if (!keysym && key_identifier_sane(keyCode, keyIdentifier)) - keysym = keysym_from_key_identifier(keyIdentifier, location); - return keysym; } diff --git a/src/ts/main.ts b/src/ts/main.ts index 471b546..acab26e 100644 --- a/src/ts/main.ts +++ b/src/ts/main.ts @@ -22,6 +22,7 @@ const elements = { changeUsernameBtn: document.getElementById("changeUsernameBtn") as HTMLButtonElement, turnBtnText: document.getElementById("turnBtnText") as HTMLSpanElement, turnstatus: document.getElementById("turnstatus") as HTMLParagraphElement, + takeTurnBtn: document.getElementById("takeTurnBtn") as HTMLButtonElement, } var expectedClose = false; var turn = -1; @@ -32,6 +33,8 @@ const users : { user : User, element : HTMLTableRowElement }[] = []; +var turnInterval : number | undefined = undefined;; +var turnTimer = 0; // Active VM var VM : CollabVMClient | null = null; @@ -166,8 +169,8 @@ function sortVMList() { function sortUserList() { users.sort((a, b) => { - if (a.user.username === w.username && (a.user.turn >= b.user.turn)) return -1; - if (b.user.username === w.username && (b.user.turn >= a.user.turn)) return 1; + if (a.user.username === w.username && (a.user.turn >= b.user.turn) && b.user.turn !== 0) return -1; + if (b.user.username === w.username && (b.user.turn >= a.user.turn) && a.user.turn !== 0) return 1; if (a.user.turn === b.user.turn) return 0; if (a.user.turn === -1) return 1; if (b.user.turn === -1) return -1; @@ -269,6 +272,9 @@ function userRenamed(oldname : string, newname : string, selfrename : boolean) { function turnUpdate(status : TurnStatus) { // Clear all turn data turn = -1; + VM!.canvas.classList.remove("focused", "waiting"); + clearInterval(turnInterval); + turnTimer = 0; for (const user of users) { user.element.classList.remove("user-turn", "user-waiting"); user.element.setAttribute("data-cvm-turn", "-1"); @@ -286,15 +292,36 @@ function turnUpdate(status : TurnStatus) { } if (status.user?.username === w.username) { turn = 0; + turnTimer = status.turnTime! / 1000; elements.turnBtnText.innerHTML = "End Turn"; + VM!.canvas.classList.add("focused"); } if (status.queue.some(u => u.username === w.username)) { turn = status.queue.findIndex(u => u.username === w.username) + 1; + turnTimer = status.queueTime! / 1000; elements.turnBtnText.innerHTML = "End Turn"; + VM!.canvas.classList.add("waiting"); + } + if (turn === -1) elements.turnstatus.innerText = ""; + else { + turnInterval = setInterval(() => turnIntervalCb(), 1000); + setTurnStatus(); } sortUserList(); } +function turnIntervalCb() { + turnTimer--; + setTurnStatus(); +} + +function setTurnStatus() { + if (turn === 0) + elements.turnstatus.innerText = `Turn expires in ${turnTimer} seconds`; + else + elements.turnstatus.innerText = `Waiting for turn in ${turnTimer} seconds`; +} + function sendChat() { if (VM === null) return; VM.chat(elements.chatinput.value); @@ -313,7 +340,10 @@ elements.changeUsernameBtn.addEventListener('click', () => { var newname = prompt("Enter new username, or leave blank to be assigned a guest username", w.username); if (newname === w.username) return; VM?.rename(newname); -}) +}); +elements.takeTurnBtn.addEventListener('click', () => { + VM?.turn(turn === -1); +}); // Public API w.collabvm = { diff --git a/src/ts/protocol/CollabVMClient.ts b/src/ts/protocol/CollabVMClient.ts index fdacc0d..54fa4ed 100644 --- a/src/ts/protocol/CollabVMClient.ts +++ b/src/ts/protocol/CollabVMClient.ts @@ -4,6 +4,8 @@ import VM from "./VM.js"; import { User } from "./User.js"; import { Rank } from "./Permissions.js"; import TurnStatus from "./TurnStatus.js"; +import Mouse from "./mouse.js"; +import GetKeysym from '../../js/keyboard'; export default class CollabVMClient { // Fields @@ -14,6 +16,8 @@ export default class CollabVMClient { private connectedToVM : boolean = false; private users : User[] = []; private username : string | null = null; + private mouse : Mouse = new Mouse(); + private rank : Rank = Rank.Unregistered; // events that are used internally and not exposed private emitter; // public events @@ -31,6 +35,50 @@ export default class CollabVMClient { this.canvas.tabIndex = -1; // Get the 2D context this.ctx = this.canvas.getContext('2d')!; + // Bind canvas click + this.canvas.addEventListener('click', e => { + if (this.users.find(u => u.username === this.username)?.turn === -1) + this.turn(true); + }); + // Bind keyboard and mouse + this.canvas.addEventListener('mousedown', (e : MouseEvent) => { + if (this.users.find(u => u.username === this.username)?.turn === -1 && this.rank !== Rank.Admin) return; + this.mouse.processEvent(e, true); + this.sendmouse(this.mouse.x, this.mouse.y, this.mouse.makeMask()); + }, { + capture: true + }); + this.canvas.addEventListener('mouseup', (e : MouseEvent) => { + if (this.users.find(u => u.username === this.username)?.turn === -1 && this.rank !== Rank.Admin) return; + this.mouse.processEvent(e, false); + this.sendmouse(this.mouse.x, this.mouse.y, this.mouse.makeMask()); + }, { + capture: true + }); + this.canvas.addEventListener('mousemove', (e : MouseEvent) => { + if (this.users.find(u => u.username === this.username)?.turn === -1 && this.rank !== Rank.Admin) return; + this.mouse.processEvent(e, null); + this.sendmouse(this.mouse.x, this.mouse.y, this.mouse.makeMask()); + }, { + capture: true + }); + this.canvas.addEventListener('keydown', (e : KeyboardEvent) => { + if (this.users.find(u => u.username === this.username)?.turn === -1 && this.rank !== Rank.Admin) return; + var keysym = GetKeysym(e.keyCode, e.key, e.location); + if (keysym === undefined) return; + this.key(keysym, true); + }, { + capture: true + }); + this.canvas.addEventListener('keyup', (e : KeyboardEvent) => { + if (this.users.find(u => u.username === this.username)?.turn === -1 && this.rank !== Rank.Admin) return; + var keysym = GetKeysym(e.keyCode, e.key, e.location); + if (keysym === undefined) return; + this.key(keysym, false); + }, { + capture: true + }); + this.canvas.addEventListener('contextmenu', e => e.preventDefault()); // Create the WebSocket this.socket = new WebSocket(url, "guacamole"); // Add the event listeners @@ -61,7 +109,6 @@ export default class CollabVMClient { } case "list": { // pass msgarr to the emitter for processing by list() - console.log("got list") this.emitter.emit('list', msgArr.slice(1)); break; } @@ -194,7 +241,6 @@ export default class CollabVMClient { displayName: list[i + 1], thumbnail: th, }); - console.log("pushed", list[i]); } res(vms); }); @@ -238,5 +284,20 @@ export default class CollabVMClient { else this.send("rename"); } + // Take or drop turn + turn(taketurn : boolean) { + this.send("turn", taketurn ? "1" : "0"); + } + + // Send mouse instruction + sendmouse(x : number, y : number, mask : number) { + this.send("mouse", x.toString(), y.toString(), mask.toString()); + } + + // Send key + key(keysym : number, down : boolean) { + this.send("key", keysym.toString(), down ? "1" : "0"); + } + on = (event : string | number, cb: (...args: any) => void) => this.publicEmitter.on(event, cb); } \ No newline at end of file diff --git a/src/ts/protocol/mouse.ts b/src/ts/protocol/mouse.ts new file mode 100644 index 0000000..f26c310 --- /dev/null +++ b/src/ts/protocol/mouse.ts @@ -0,0 +1,36 @@ +export default class Mouse { + left : boolean = false; + middle : boolean = false; + right : boolean = false; + scrolldown : boolean = false; + scrollup : boolean = false; + x : number = 0; + y : number = 0; + constructor() {} + + makeMask() { + var mask = 0; + if (this.left) mask |= 1; + if (this.middle) mask |= 2; + if (this.right) mask |= 4; + if (this.scrollup) mask |= 8; + if (this.scrolldown) mask |= 16; + return mask; + } + + processEvent(e : MouseEvent, down : boolean | null = null) { + if (down !== null) switch (e.button) { + case 0: + this.left = down; + break; + case 1: + this.middle = down; + break; + case 2: + this.right = down; + break; + } + this.x = e.offsetX; + this.y = e.offsetY; + } +} \ No newline at end of file