freeblox/packages/engine/src/net/socket.ts

156 lines
4.5 KiB
TypeScript

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<EngineEvents>) {}
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<Vector3>('vec3')!;
const objectQuat = incoming.read<Quaternion>('quat')!;
const objectLinvel = incoming.read<Vector3>('vec3')!;
const objectAngvel = incoming.read<Vector3>('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<number>('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<Vector3>('vec3')!;
const rotation = incoming.read<Quaternion>('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);
};
}
}