cloud-game/web/js/api.js
2024-12-03 00:38:15 +03:00

337 lines
8.6 KiB
JavaScript

import {log} from 'log';
const endpoints = {
LATENCY_CHECK: 3,
INIT: 4,
INIT_WEBRTC: 100,
OFFER: 101,
ANSWER: 102,
ICE_CANDIDATE: 103,
GAME_START: 104,
GAME_QUIT: 105,
GAME_SAVE: 106,
GAME_LOAD: 107,
GAME_SET_PLAYER_INDEX: 108,
GAME_RECORDING: 110,
GET_WORKER_LIST: 111,
GAME_ERROR_NO_FREE_SLOTS: 112,
GAME_RESET: 113,
APP_VIDEO_CHANGE: 150,
}
let transport = {
send: (packet) => {
log.warn('Default transport is used! Change it with the api.transport variable.', packet)
},
keyboard: (packet) => {
log.warn('Default transport is used! Change it with the api.transport variable.', packet)
},
mouse: (packet) => {
log.warn('Default transport is used! Change it with the api.transport variable.', packet)
}
}
const packet = (type, payload, id) => {
const packet = {t: type}
if (id !== undefined) packet.id = id
if (payload !== undefined) packet.p = payload
transport.send(packet)
}
const decodeBytes = (b) => String.fromCharCode.apply(null, new Uint8Array(b))
const keyboardPress = (() => {
// 0 1 2 3 4 5 6
// [CODE ] P MOD
const buffer = new ArrayBuffer(7)
const dv = new DataView(buffer)
return (pressed = false, e) => {
if (e.repeat) return // skip pressed key events
const key = libretro.mod
let code = libretro.map('', e.code)
let shift = e.shiftKey
// a special Esc for &$&!& Firefox
if (shift && code === 96) {
code = 27
shift = false
}
const mod = 0
| (e.altKey && key.ALT)
| (e.ctrlKey && key.CTRL)
| (e.metaKey && key.META)
| (shift && key.SHIFT)
| (e.getModifierState('NumLock') && key.NUMLOCK)
| (e.getModifierState('CapsLock') && key.CAPSLOCK)
| (e.getModifierState('ScrollLock') && key.SCROLLOCK)
dv.setUint32(0, code)
dv.setUint8(4, +pressed)
dv.setUint16(5, mod)
transport.keyboard(buffer)
}
})()
const mouse = {
MOVEMENT: 0,
BUTTONS: 1
}
const mouseMove = (() => {
// 0 1 2 3 4
// T DX DY
const buffer = new ArrayBuffer(5)
const dv = new DataView(buffer)
return (dx = 0, dy = 0) => {
dv.setUint8(0, mouse.MOVEMENT)
dv.setInt16(1, dx)
dv.setInt16(3, dy)
transport.mouse(buffer)
}
})()
const mousePress = (() => {
// 0 1
// T B
const buffer = new ArrayBuffer(2)
const dv = new DataView(buffer)
// 0: Main button pressed, usually the left button or the un-initialized state
// 1: Auxiliary button pressed, usually the wheel button or the middle button (if present)
// 2: Secondary button pressed, usually the right button
// 3: Fourth button, typically the Browser Back button
// 4: Fifth button, typically the Browser Forward button
const b2r = [1, 4, 2, 0, 0] // browser mouse button to retro button
// assumed that only one button pressed / released
return (button = 0, pressed = false) => {
dv.setUint8(0, mouse.BUTTONS)
dv.setUint8(1, pressed ? b2r[button] : 0)
transport.mouse(buffer)
}
})()
const libretro = function () {// RETRO_KEYBOARD
const retro = {
'': 0,
'Unidentified': 0,
'Unknown': 0, // ???
'First': 0, // ???
'Backspace': 8,
'Tab': 9,
'Clear': 12,
'Enter': 13, 'Return': 13,
'Pause': 19,
'Escape': 27,
'Space': 32,
'Exclaim': 33,
'Quotedbl': 34,
'Hash': 35,
'Dollar': 36,
'Ampersand': 38,
'Quote': 39,
'Leftparen': 40, '(': 40,
'Rightparen': 41, ')': 41,
'Asterisk': 42,
'Plus': 43,
'Comma': 44,
'Minus': 45,
'Period': 46,
'Slash': 47,
'Digit0': 48,
'Digit1': 49,
'Digit2': 50,
'Digit3': 51,
'Digit4': 52,
'Digit5': 53,
'Digit6': 54,
'Digit7': 55,
'Digit8': 56,
'Digit9': 57,
'Colon': 58, ':': 58,
'Semicolon': 59, ';': 59,
'Less': 60, '<': 60,
'Equal': 61, '=': 61,
'Greater': 62, '>': 62,
'Question': 63, '?': 63,
// RETROK_AT = 64,
'BracketLeft': 91, '[': 91,
'Backslash': 92, '\\': 92,
'BracketRight': 93, ']': 93,
// RETROK_CARET = 94,
// RETROK_UNDERSCORE = 95,
'Backquote': 96, '`': 96,
'KeyA': 97,
'KeyB': 98,
'KeyC': 99,
'KeyD': 100,
'KeyE': 101,
'KeyF': 102,
'KeyG': 103,
'KeyH': 104,
'KeyI': 105,
'KeyJ': 106,
'KeyK': 107,
'KeyL': 108,
'KeyM': 109,
'KeyN': 110,
'KeyO': 111,
'KeyP': 112,
'KeyQ': 113,
'KeyR': 114,
'KeyS': 115,
'KeyT': 116,
'KeyU': 117,
'KeyV': 118,
'KeyW': 119,
'KeyX': 120,
'KeyY': 121,
'KeyZ': 122,
'{': 123,
'|': 124,
'}': 125,
'Tilde': 126, '~': 126,
'Delete': 127,
'Numpad0': 256,
'Numpad1': 257,
'Numpad2': 258,
'Numpad3': 259,
'Numpad4': 260,
'Numpad5': 261,
'Numpad6': 262,
'Numpad7': 263,
'Numpad8': 264,
'Numpad9': 265,
'NumpadDecimal': 266,
'NumpadDivide': 267,
'NumpadMultiply': 268,
'NumpadSubtract': 269,
'NumpadAdd': 270,
'NumpadEnter': 271,
'NumpadEqual': 272,
'ArrowUp': 273,
'ArrowDown': 274,
'ArrowRight': 275,
'ArrowLeft': 276,
'Insert': 277,
'Home': 278,
'End': 279,
'PageUp': 280,
'PageDown': 281,
'F1': 282,
'F2': 283,
'F3': 284,
'F4': 285,
'F5': 286,
'F6': 287,
'F7': 288,
'F8': 289,
'F9': 290,
'F10': 291,
'F11': 292,
'F12': 293,
'F13': 294,
'F14': 295,
'F15': 296,
'NumLock': 300,
'CapsLock': 301,
'ScrollLock': 302,
'ShiftRight': 303,
'ShiftLeft': 304,
'ControlRight': 305,
'ControlLeft': 306,
'AltRight': 307,
'AltLeft': 308,
'MetaRight': 309,
'MetaLeft': 310,
// RETROK_LSUPER = 311,
// RETROK_RSUPER = 312,
// RETROK_MODE = 313,
// RETROK_COMPOSE = 314,
// RETROK_HELP = 315,
// RETROK_PRINT = 316,
// RETROK_SYSREQ = 317,
// RETROK_BREAK = 318,
// RETROK_MENU = 319,
'Power': 320,
// RETROK_EURO = 321,
// RETROK_UNDO = 322,
// RETROK_OEM_102 = 323,
}
const retroMod = {
NONE: 0x0000,
SHIFT: 0x01,
CTRL: 0x02,
ALT: 0x04,
META: 0x08,
NUMLOCK: 0x10,
CAPSLOCK: 0x20,
SCROLLOCK: 0x40,
}
const _map = (key = '', code = '') => {
return retro[code] || retro[key] || 0
}
return {
map: _map,
mod: retroMod,
}
}()
/**
* Server API.
*
* Requires the actual api.transport implementation.
*/
export const api = {
set transport(t) {
transport = t;
},
endpoint: endpoints,
decode: (b) => JSON.parse(decodeBytes(b)),
server: {
initWebrtc: () => packet(endpoints.INIT_WEBRTC),
sendIceCandidate: (candidate) => packet(endpoints.ICE_CANDIDATE, btoa(JSON.stringify(candidate))),
sendSdp: (sdp) => packet(endpoints.ANSWER, btoa(JSON.stringify(sdp))),
latencyCheck: (id, list) => packet(endpoints.LATENCY_CHECK, list, id),
getWorkerList: () => packet(endpoints.GET_WORKER_LIST),
},
game: {
input: {
keyboard: {
press: keyboardPress,
},
mouse: {
move: mouseMove,
press: mousePress,
}
},
load: () => packet(endpoints.GAME_LOAD),
reset: (roomId) => packet(endpoints.GAME_RESET, {room_id: roomId}),
save: () => packet(endpoints.GAME_SAVE),
setPlayerIndex: (i) => packet(endpoints.GAME_SET_PLAYER_INDEX, i),
start: (game, roomId, record, recordUser, player) => packet(endpoints.GAME_START, {
game_name: game,
room_id: roomId,
player_index: player,
record: record,
record_user: recordUser,
}),
toggleRecording: (active = false, userName = '') =>
packet(endpoints.GAME_RECORDING, {active: active, user: userName}),
quit: (roomId) => packet(endpoints.GAME_QUIT, {room_id: roomId}),
}
}