From c4c25f554c9f2e9556f14c9c49f87c7e77d3210c Mon Sep 17 00:00:00 2001 From: Evert Prants Date: Sun, 25 Jun 2023 14:51:16 +0300 Subject: [PATCH] multiplayer start --- .../client/src/components/GameWrapper.vue | 2 +- packages/client/src/game/core/gameplay.ts | 56 +++++-- packages/client/src/game/types/events.ts | 1 + .../editor/src/components/EditorSidebar.vue | 1 + packages/engine/package.json | 12 +- packages/engine/src/components/level.ts | 14 +- .../engine/src/components/physicsworld.ts | 15 ++ .../engine/src/gameobjects/physics.object.ts | 2 +- packages/engine/src/index.ts | 5 + packages/engine/src/net/chat-type.enum.ts | 4 + packages/engine/src/net/error-type.enum.ts | 3 + packages/engine/src/net/index.ts | 5 + packages/engine/src/net/packet-type.enum.ts | 19 +++ packages/engine/src/net/packet.ts | 97 +++++++++++ packages/engine/src/net/socket.ts | 155 ++++++++++++++++++ packages/engine/src/types/events.ts | 31 +++- packages/engine/src/utils/character.ts | 11 +- packages/engine/src/utils/index.ts | 1 + packages/engine/src/utils/random.ts | 3 + pnpm-lock.yaml | 26 ++- 20 files changed, 441 insertions(+), 22 deletions(-) create mode 100644 packages/engine/src/net/chat-type.enum.ts create mode 100644 packages/engine/src/net/error-type.enum.ts create mode 100644 packages/engine/src/net/index.ts create mode 100644 packages/engine/src/net/packet-type.enum.ts create mode 100644 packages/engine/src/net/packet.ts create mode 100644 packages/engine/src/net/socket.ts create mode 100644 packages/engine/src/utils/random.ts diff --git a/packages/client/src/components/GameWrapper.vue b/packages/client/src/components/GameWrapper.vue index c3cf90a..dabd93d 100644 --- a/packages/client/src/components/GameWrapper.vue +++ b/packages/client/src/components/GameWrapper.vue @@ -18,7 +18,7 @@ const resize = () => onMounted(() => { editorRef.value.mount(wrapperRef.value); // TODO: for dev - editorRef.value.loadLevel('https://lunasqu.ee/freeblox/test-level.json'); + // editorRef.value.loadLevel('https://lunasqu.ee/freeblox/test-level.json'); window.addEventListener('resize', resize); }); diff --git a/packages/client/src/game/core/gameplay.ts b/packages/client/src/game/core/gameplay.ts index 8a95d2f..73c39e0 100644 --- a/packages/client/src/game/core/gameplay.ts +++ b/packages/client/src/game/core/gameplay.ts @@ -4,10 +4,15 @@ import { instanceCharacterObject, getCharacterController, Humanoid, + GameSocket, + EventEmitter, + Renderer, + randomUUID, } from '@freeblox/engine'; -import { Vector3 } from 'three'; +import { Quaternion, Vector3 } from 'three'; import { ThirdPersonCamera } from './camera'; +import { GameEvents, SpawnEvent } from '../types/events'; /** * Gameplay manager. @@ -32,10 +37,22 @@ export class GameplayComponent extends EngineComponent { private move = new Vector3(); private look = new Vector3(); + private server = new GameSocket(this.events); + + public uuid = randomUUID(); + + constructor( + protected renderer: Renderer, + protected events: EventEmitter + ) { + super(renderer, events); + } override initialize(): void { this.world = this.renderer.scene.getObjectByName('World') as World; this.cleanUpEvents = this.bindEvents(); + this.server.track(this.world); + this.server.connect('ws://localhost:8256', this.uuid, 'testing'); } override update(delta: number): void { @@ -55,9 +72,14 @@ export class GameplayComponent extends EngineComponent { this.character?.localToWorld(this.look); this.character?.setVelocity(this.move); + const look = this.move.clone().normalize(); if (this.move.length()) { - this.character?.setLook(this.move.clone().normalize()); + this.character?.setLook(look); } + this.events.emit('sendPlayer', { + velocity: this.move, + lookAt: look, + }); if (this.jump) { this.jump = false; this.character?.jump(); @@ -68,12 +90,22 @@ export class GameplayComponent extends EngineComponent { this.cleanUpEvents?.(); } - public async loadCharacter(name: string, uuid?: string) { - const char = await instanceCharacterObject(name); - if (uuid) char.uuid = uuid; + public async loadCharacter( + name: string, + pos?: Vector3, + rot?: Quaternion, + uuid?: string + ) { + const char = await instanceCharacterObject(name, pos, uuid); const ctrl = getCharacterController(char); this.world.add(char); this.characters.push(ctrl); + + if (rot) char.quaternion.copy(rot); + + this.events.emit('sceneJoin', char); + + if (uuid !== this.uuid) return; this.character = ctrl; this.controls = new ThirdPersonCamera( @@ -82,7 +114,6 @@ export class GameplayComponent extends EngineComponent { this.renderer.renderer.domElement ); this.controls.initialize(); - this.events.emit('sceneJoin', char); } private bindEvents() { @@ -122,8 +153,13 @@ export class GameplayComponent extends EngineComponent { } }; - const worldLoadedEvent = () => { - this.loadCharacter('Diamond'); + const createCharacterEvent = (event: SpawnEvent) => { + this.loadCharacter( + event.playerName, + event.position, + event.rotation, + event.playerId + ); }; const physicsLoadedEvent = () => { @@ -132,12 +168,12 @@ export class GameplayComponent extends EngineComponent { window.addEventListener('keydown', keyDownEvent); window.addEventListener('keyup', keyUpEvent); - this.events.addListener('loadComplete', worldLoadedEvent); + this.events.addListener('spawnCharacter', createCharacterEvent); this.events.addListener('physicsComplete', physicsLoadedEvent); return () => { window.removeEventListener('keydown', keyDownEvent); window.removeEventListener('keyup', keyUpEvent); - this.events.removeEventListener('loadComplete', worldLoadedEvent); + this.events.removeEventListener('spawnCharacter', createCharacterEvent); this.events.removeEventListener('physicsComplete', physicsLoadedEvent); }; } diff --git a/packages/client/src/game/types/events.ts b/packages/client/src/game/types/events.ts index c43b262..18f4159 100644 --- a/packages/client/src/game/types/events.ts +++ b/packages/client/src/game/types/events.ts @@ -1,4 +1,5 @@ import { EngineEvents } from '@freeblox/engine'; +import { Vector3 } from 'three'; export type Events = {}; diff --git a/packages/editor/src/components/EditorSidebar.vue b/packages/editor/src/components/EditorSidebar.vue index d7d34f1..5e029f6 100644 --- a/packages/editor/src/components/EditorSidebar.vue +++ b/packages/editor/src/components/EditorSidebar.vue @@ -159,6 +159,7 @@ const explorerOperation = ( }; register('initialized', () => createSceneMap()); +register('loadComplete', () => createSceneMap()); register('sceneJoin', () => createSceneMap()); register('sceneLeave', () => createSceneMap()); register('selected', (event) => updateSelectionMap(event)); diff --git a/packages/engine/package.json b/packages/engine/package.json index 0508af5..2702633 100644 --- a/packages/engine/package.json +++ b/packages/engine/package.json @@ -10,6 +10,12 @@ "dev": "tsc --watch", "prepare": "npm run build" }, + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, "keywords": [ "game", "engine", @@ -19,11 +25,15 @@ "license": "MIT", "devDependencies": { "@types/three": "^0.152.1", + "@types/uuid": "^9.0.2", "typescript": "^5.0.4" }, "dependencies": { "@dimforge/rapier3d": "^0.11.2", + "buffer": "^6.0.3", "reflect-metadata": "^0.1.13", - "three": "^0.153.0" + "smart-buffer": "^4.2.0", + "three": "^0.153.0", + "uuid": "^9.0.0" } } diff --git a/packages/engine/src/components/level.ts b/packages/engine/src/components/level.ts index ae27d42..33fd4f2 100644 --- a/packages/engine/src/components/level.ts +++ b/packages/engine/src/components/level.ts @@ -161,8 +161,18 @@ export class LevelComponent extends EngineComponent { }); }; - const instanceEvent = (event: InstanceEvent) => - this.createObject(event.type, event.parent); + const instanceEvent = (event: InstanceEvent) => { + const parent = + typeof event.parent === 'string' + ? this.world.getObjectByProperty('uuid', event.parent) + : event.parent; + const object = this.createObject(event.type, parent, !!event.data); + + if (event.data && object) { + object.deserialize(event.data); + this.events.emit('sceneJoin', object); + } + }; const resetEvent = () => { this.world.clear(); diff --git a/packages/engine/src/components/physicsworld.ts b/packages/engine/src/components/physicsworld.ts index 6013c17..da302dd 100644 --- a/packages/engine/src/components/physicsworld.ts +++ b/packages/engine/src/components/physicsworld.ts @@ -6,6 +6,7 @@ import { Humanoid } from '../gameobjects/humanoid.object'; import { PhysicsObject } from '../gameobjects/physics.object'; import { GameObject } from '../types/game-object'; import type Rapier from '@dimforge/rapier3d'; +import { ServerTransformEvent } from '..'; export class PhysicsWorldComponent extends EngineComponent { public name = PhysicsWorldComponent.name; @@ -58,14 +59,28 @@ export class PhysicsWorldComponent extends EngineComponent { this.removePhysics(object); }; + const serverTransformEvent = (event: ServerTransformEvent) => { + const object = this.trackedObjects.find( + (obj) => event.object === obj.uuid + ) as PhysicsObject; + if (!object) return; + object.rigidBody?.setTranslation(event.position, false); + object.rigidBody?.setRotation(event.quaternion, false); + event.velocity && object.rigidBody?.setLinvel(event.velocity, false); + event.angularVelocity && + object.rigidBody?.setAngvel(event.angularVelocity, false); + }; + this.events.addListener('loadComplete', worldLoadEvent); this.events.addListener('sceneJoin', sceneJoinEvent); this.events.addListener('sceneLeave', sceneLeaveEvent); + this.events.addListener('serverTransform', serverTransformEvent); return () => { this.events.removeEventListener('loadComplete', worldLoadEvent); this.events.removeEventListener('sceneJoin', sceneJoinEvent); this.events.removeEventListener('sceneLeave', sceneLeaveEvent); + this.events.removeEventListener('serverTransform', serverTransformEvent); }; } diff --git a/packages/engine/src/gameobjects/physics.object.ts b/packages/engine/src/gameobjects/physics.object.ts index 1316250..2d879df 100644 --- a/packages/engine/src/gameobjects/physics.object.ts +++ b/packages/engine/src/gameobjects/physics.object.ts @@ -10,7 +10,7 @@ export class PhysicsObject extends PhysicalObject implements PhysicsTicking { isTickingObject = true; protected collider?: Rapier.Collider; - protected rigidBody?: Rapier.RigidBody; + public rigidBody?: Rapier.RigidBody; protected physicsWorldRef?: Rapier.World; @EditorProperty({ type: Boolean }) diff --git a/packages/engine/src/index.ts b/packages/engine/src/index.ts index 275d7be..1d5242b 100644 --- a/packages/engine/src/index.ts +++ b/packages/engine/src/index.ts @@ -1,4 +1,9 @@ +import { Buffer as BufferPolyfill } from 'buffer'; +declare var Buffer: typeof BufferPolyfill; +globalThis.Buffer = BufferPolyfill; + export * from './core'; +export * from './net'; export * from './utils'; export * from './types'; export * from './components'; diff --git a/packages/engine/src/net/chat-type.enum.ts b/packages/engine/src/net/chat-type.enum.ts new file mode 100644 index 0000000..9e8f362 --- /dev/null +++ b/packages/engine/src/net/chat-type.enum.ts @@ -0,0 +1,4 @@ +export enum ChatType { + MESSAGE = 0, + COMMAND, +} diff --git a/packages/engine/src/net/error-type.enum.ts b/packages/engine/src/net/error-type.enum.ts new file mode 100644 index 0000000..7d41f64 --- /dev/null +++ b/packages/engine/src/net/error-type.enum.ts @@ -0,0 +1,3 @@ +export enum ErrorType { + AUTH_FAIL = 0, +} diff --git a/packages/engine/src/net/index.ts b/packages/engine/src/net/index.ts new file mode 100644 index 0000000..fee5c33 --- /dev/null +++ b/packages/engine/src/net/index.ts @@ -0,0 +1,5 @@ +export * from './packet-type.enum'; +export * from './chat-type.enum'; +export * from './error-type.enum'; +export * from './packet'; +export * from './socket'; diff --git a/packages/engine/src/net/packet-type.enum.ts b/packages/engine/src/net/packet-type.enum.ts new file mode 100644 index 0000000..522f283 --- /dev/null +++ b/packages/engine/src/net/packet-type.enum.ts @@ -0,0 +1,19 @@ +export enum PacketType { + AUTH = 0, + KEEPALIVE, + STREAM_START, + STREAM_ASSET, + STREAM_OBJECT, + STREAM_DESTROY, + STREAM_EVENT, + STREAM_FINISH, + STREAM_TRANSFORM, + STREAM_CHAT, + PLAYER_LIST, + PLAYER_JOIN, + PLAYER_QUIT, + PLAYER_CHARACTER, + PLAYER_MOVEMENT, + PLAYER_CHAT, + ERROR, +} diff --git a/packages/engine/src/net/packet.ts b/packages/engine/src/net/packet.ts new file mode 100644 index 0000000..cc1ab8e --- /dev/null +++ b/packages/engine/src/net/packet.ts @@ -0,0 +1,97 @@ +import { PacketType } from './packet-type.enum'; +import { SmartBuffer } from 'smart-buffer'; +import { Instancable } from '../types/instancable'; +import { Quaternion, Vector3 } from 'three'; + +export class Packet { + private buffer = new SmartBuffer(); + constructor(public packet?: PacketType) {} + + write(data: any, type: Instancable | string) { + switch (type) { + case 'string': + case String: + this.buffer.writeStringNT(data); + break; + case 'bool': + case Boolean: + this.buffer.writeUInt8(data ? 1 : 0); + break; + case 'float': + case Number: + this.buffer.writeFloatLE(data); + break; + case 'uint8': + this.buffer.writeUInt8(data); + break; + case 'int32': + this.buffer.writeInt32LE(data); + break; + case 'uint32': + this.buffer.writeUInt32LE(data); + break; + case 'vec3': + this.buffer.writeFloatLE(data.x); + this.buffer.writeFloatLE(data.y); + this.buffer.writeFloatLE(data.z); + break; + case 'quat': + this.buffer.writeFloatLE(data.x); + this.buffer.writeFloatLE(data.y); + this.buffer.writeFloatLE(data.z); + this.buffer.writeFloatLE(data.w); + break; + } + return this; + } + + read(type: Instancable | string) { + switch (type) { + case 'string': + case String: + return this.buffer.readStringNT() as T; + case 'bool': + case Boolean: + return this.buffer.readUInt8() as T; + case 'float': + case Number: + return this.buffer.readFloatLE() as T; + case 'uint8': + return this.buffer.readUInt8() as T; + case 'int32': + return this.buffer.readInt32LE() as T; + case 'uint32': + return this.buffer.readUInt32LE() as T; + case 'vec3': + return new Vector3( + this.buffer.readFloatLE(), + this.buffer.readFloatLE(), + this.buffer.readFloatLE() + ) as T; + case 'quat': + return new Quaternion( + this.buffer.readFloatLE(), + this.buffer.readFloatLE(), + this.buffer.readFloatLE(), + this.buffer.readFloatLE() + ) as T; + } + } + + writeHeader() { + if (this.packet === undefined) return; + this.buffer.insertUInt8(this.packet, 0); + } + + toBuffer() { + this.writeHeader(); + return this.buffer.toBuffer(); + } + + static from(buffer: Buffer) { + const packet = new Packet(); + packet.buffer = SmartBuffer.fromBuffer(buffer); + packet.packet = packet.buffer.readUInt8(); + return packet; + } +} diff --git a/packages/engine/src/net/socket.ts b/packages/engine/src/net/socket.ts new file mode 100644 index 0000000..ed928d9 --- /dev/null +++ b/packages/engine/src/net/socket.ts @@ -0,0 +1,155 @@ +import { Quaternion, Vector3 } from 'three'; +import { Packet, PacketType } from '.'; +import { World } from '../gameobjects/world.object'; +import { Disposable } from '../types/disposable'; +import { EngineEvents, PlayerEvent } from '../types/events'; +import { EventEmitter } from '../utils/events'; + +export class GameSocket implements Disposable { + private ws?: WebSocket; + private host?: string; + private token!: string; + private playerId!: string; + private world!: World; + private cleanUpEvents?: () => void; + constructor(private events: EventEmitter) {} + + track(world: World) { + this.world = world; + } + + connect(host: string, id: string, token: string) { + this.host = host; + this.playerId = id; + this.token = token; + this.ws = new WebSocket(host); + this.ws.binaryType = 'arraybuffer'; + this.bindSocket(this.ws!); + console.log('connected to', this.host); + this.cleanUpEvents = this.bindEvents(); + } + + disconnect() { + this.ws?.close(); + this.events.emit('serverDisconnect'); + console.log('disconnected from', this.host); + this.cleanUpEvents?.(); + } + + dispose(): void { + this.cleanUpEvents?.(); + } + + private onOpen() { + const packet = new Packet(PacketType.AUTH) + .write(this.playerId, String) + .write(this.token, String) + .write(0, 'uint8') + .toBuffer(); + this.ws?.send(packet); + } + + private handlePacket(incoming: Packet) { + switch (incoming.packet) { + case PacketType.ERROR: { + const error = incoming.read(String); + alert(error); + break; + } + case PacketType.STREAM_START: + console.log('starting world loading'); + break; + case PacketType.STREAM_OBJECT: { + const objectUUID = incoming.read(String); + const parentUUID = incoming.read(String); + const objectType = incoming.read(String); + const objectData = JSON.parse(incoming.read(String) as string); + this.events.emit('instance', { + type: objectType as string, + parent: parentUUID, + data: objectData, + }); + break; + } + case PacketType.STREAM_TRANSFORM: { + const objectUUID = incoming.read(String)!; + const objectPos = incoming.read('vec3')!; + const objectQuat = incoming.read('quat')!; + const objectLinvel = incoming.read('vec3')!; + const objectAngvel = incoming.read('vec3')!; + this.events.emit('serverTransform', { + object: objectUUID, + position: objectPos, + quaternion: objectQuat, + velocity: objectLinvel, + angularVelocity: objectAngvel, + }); + break; + } + case PacketType.STREAM_FINISH: { + this.events.emit('loadComplete'); + break; + } + case PacketType.PLAYER_LIST: { + const playerCount = incoming.read('uint32')!; + const players = Array.from({ length: playerCount }, () => null).map( + () => incoming.read(String)?.split(':') + ); + console.log('player list', players); + break; + } + case PacketType.PLAYER_JOIN: { + const playerId = incoming.read(String)!; + const playerName = incoming.read(String)!; + console.log('player joined', playerId, playerName); + break; + } + case PacketType.PLAYER_CHARACTER: { + const playerId = incoming.read(String)!; + const playerName = incoming.read(String)!; + const position = incoming.read('vec3')!; + const rotation = incoming.read('quat')!; + this.events.emit('spawnCharacter', { + playerId, + playerName, + position, + rotation, + }); + } + } + } + + private bindSocket(ws: WebSocket) { + ws.addEventListener('message', (event) => { + const packet = Packet.from(Buffer.from(event.data)); + this.handlePacket(packet); + }); + + ws.addEventListener('close', (event) => { + this.disconnect(); + }); + + ws.addEventListener('error', (event) => {}); + + ws.addEventListener('open', (event) => { + this.onOpen(); + }); + } + + private bindEvents() { + const sendPlayerEvent = (event: PlayerEvent) => { + if (this.ws?.readyState !== WebSocket.OPEN) return; + this.ws.send( + new Packet(PacketType.PLAYER_MOVEMENT) + .write(event.velocity, 'vec3') + .write(event.lookAt, 'vec3') + .toBuffer() + ); + }; + + this.events.on('sendPlayer', sendPlayerEvent); + return () => { + this.events.removeEventListener('sendPlayer', sendPlayerEvent); + }; + } +} diff --git a/packages/engine/src/types/events.ts b/packages/engine/src/types/events.ts index 8ce08f7..392903a 100644 --- a/packages/engine/src/types/events.ts +++ b/packages/engine/src/types/events.ts @@ -4,7 +4,9 @@ import { Object3D, ColorRepresentation, Vector3, + Quaternion, } from 'three'; +import { SerializedObject } from './game-object'; export interface MousePositionEvent { position: Vector2; @@ -63,7 +65,28 @@ export interface ReparentEvent { export interface InstanceEvent { type: string; - parent?: Object3D; + parent?: Object3D | string; + data?: SerializedObject; +} + +export interface ServerTransformEvent { + object: string; + position: Vector3; + quaternion: Quaternion; + velocity?: Vector3; + angularVelocity?: Vector3; +} + +export interface PlayerEvent { + velocity: Vector3; + lookAt: Vector3; +} + +export interface SpawnEvent { + playerId: string; + playerName: string; + position: Vector3; + rotation: Quaternion; } export type EngineEvents = { @@ -84,4 +107,10 @@ export type EngineEvents = { physicsComplete: () => void; initialized: () => void; reset: () => void; + + spawnCharacter: (event: SpawnEvent) => void; + sendPlayer: (event: PlayerEvent) => void; + serverConnect: () => void; + serverTransform: (obj: ServerTransformEvent) => void; + serverDisconnect: () => void; }; diff --git a/packages/engine/src/utils/character.ts b/packages/engine/src/utils/character.ts index 55accb6..dc60040 100644 --- a/packages/engine/src/utils/character.ts +++ b/packages/engine/src/utils/character.ts @@ -1,4 +1,4 @@ -import { AnimationClip, Bone, Object3D, SkinnedMesh } from 'three'; +import { AnimationClip, Bone, Object3D, SkinnedMesh, Vector3 } from 'three'; import { assetManager } from '../assets'; import { Group } from '../gameobjects/group.object'; import { MeshPart } from '../gameobjects/mesh.object'; @@ -36,7 +36,11 @@ export const loadBaseCharacter = async () => { return cachedMeta; }; -export const instanceCharacterObject = async (name: string) => { +export const instanceCharacterObject = async ( + name: string, + pos = new Vector3(0, 1, 0), + uuid?: string +) => { const base = await loadBaseCharacter(); const cloned = SkeletonUtils.clone(base.root!); @@ -79,8 +83,9 @@ export const instanceCharacterObject = async (name: string) => { const controller = new Humanoid(); controller.position.set(0, 4.75, 0); controller.archivable = false; + if (uuid) controller.uuid = uuid; baseObject.add(controller); - baseObject.position.set(0, 1, 0); + baseObject.position.copy(pos); return baseObject; }; diff --git a/packages/engine/src/utils/index.ts b/packages/engine/src/utils/index.ts index 5f29dcd..12170b2 100644 --- a/packages/engine/src/utils/index.ts +++ b/packages/engine/src/utils/index.ts @@ -2,3 +2,4 @@ export * from './debounce'; export * from './events'; export * from './read-metadata'; export * from './character'; +export * from './random'; diff --git a/packages/engine/src/utils/random.ts b/packages/engine/src/utils/random.ts new file mode 100644 index 0000000..fc64089 --- /dev/null +++ b/packages/engine/src/utils/random.ts @@ -0,0 +1,3 @@ +import { v4 } from 'uuid'; + +export const randomUUID = () => v4(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 72171ef..c5be957 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -169,16 +169,28 @@ importers: '@dimforge/rapier3d': specifier: ^0.11.2 version: 0.11.2 + buffer: + specifier: ^6.0.3 + version: 6.0.3 reflect-metadata: specifier: ^0.1.13 version: 0.1.13 + smart-buffer: + specifier: ^4.2.0 + version: 4.2.0 three: specifier: ^0.153.0 version: 0.153.0 + uuid: + specifier: ^9.0.0 + version: 9.0.0 devDependencies: '@types/three': specifier: ^0.152.1 version: 0.152.1 + '@types/uuid': + specifier: ^9.0.2 + version: 9.0.2 typescript: specifier: ^5.0.4 version: 5.0.4 @@ -1633,6 +1645,10 @@ packages: lil-gui: 0.17.0 dev: true + /@types/uuid@9.0.2: + resolution: {integrity: sha512-kNnC1GFBLuhImSnV7w4njQkUiJi0ZXUycu1rUaouPqiKlXkh77JKgdRnTAp1x5eBwcIwbtI+3otwzuIDEuDoxQ==} + dev: true + /@types/webxr@0.5.2: resolution: {integrity: sha512-szL74BnIcok9m7QwYtVmQ+EdIKwbjPANudfuvDrAF8Cljg9MKUlIoc1w5tjj9PMpeSH3U1Xnx//czQybJ0EfSw==} dev: true @@ -2233,7 +2249,6 @@ packages: /base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - dev: true /big-integer@1.6.51: resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==} @@ -2353,6 +2368,13 @@ packages: ieee754: 1.2.1 dev: true + /buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: false + /builtin-modules@3.3.0: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} @@ -3814,7 +3836,6 @@ packages: /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - dev: true /ignore-walk@6.0.3: resolution: {integrity: sha512-C7FfFoTA+bI10qfeydT8aZbvr91vAEU+2W5BZUlzPec47oNb07SsOfwYrtxuvOYdUApPP/Qlh4DtAO51Ekk2QA==} @@ -6104,7 +6125,6 @@ packages: /smart-buffer@4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - dev: true /smob@1.4.0: resolution: {integrity: sha512-MqR3fVulhjWuRNSMydnTlweu38UhQ0HXM4buStD/S3mc/BzX3CuM9OmhyQpmtYCvoYdl5ris6TI0ZqH355Ymqg==}