import { Server, Socket } from 'socket.io'; import { RequestHandler } from 'express'; import { IcyNetUser } from '../common/types/user'; import { CharacterPacket, PositionUpdatePacket } from '../common/types/packet'; import { Persistence } from './object/persistence'; const PLACEHOLDER_USER = (socket: Socket): IcyNetUser => { const randomName = `player-${socket.id.substring(0, 8)}`; return { id: Math.floor(Math.random() * 1000 + 1000), username: randomName, display_name: randomName, uuid: socket.id, accessToken: 'player', }; }; export class Game { private _connections: Socket[] = []; private _changedPlayers: number[] = []; private _db = new Persistence(); constructor(private io: Server, private session: RequestHandler) {} private mapPlayer(user: IcyNetUser): any { return { id: user.id, username: user.username, display_name: user.display_name, }; } async initialize() { await this._db.initialize(); this.io.use((socket, next) => this.session(socket.request as any, {} as any, next as any), ); this.io.on('connection', (socket) => { const session = (socket.request as any).session; const user = process.env.SKIP_LOGIN === 'true' ? PLACEHOLDER_USER(socket) : (session?.passport?.user as IcyNetUser); const publicUserInfo = user ? this.mapPlayer(user) : null; if ( user && this._connections.find((entry) => entry.data.user?.id === user.id) ) { socket.emit('error.duplicate'); return; } this._connections.push(socket); if (user) { socket.broadcast.emit('playerjoin', publicUserInfo); socket.data.user = user; socket.data.playerinfo = { velocity: [0, 0, 0], angular: [0, 0, 0, 'XYZ'], position: [0, 0, 0], rotation: [0, 0, 0, 'XYZ'], animState: 0, character: {}, }; socket.on('move', (packet) => { socket.data.playerinfo = { ...socket.data.playerinfo, ...packet, }; }); socket.on('character', (info: CharacterPacket) => { socket.data.playerinfo.character = { ...socket.data.playerinfo.character, ...info, }; this.io.emit('playercharacter', { id: socket.data.user.id, ...socket.data.playerinfo.character, }); }); socket.on('chat-send', (raw) => { const message = raw.trim().substring(0, 280); this.io.emit('chat', { sender: publicUserInfo, message }); }); this._db.upsertUser(user).then((user) => { this._db.getUserPony(user).then((pony) => { if (pony) { socket.data.playerinfo.character = (pony.character as CharacterPacket) || {}; socket.data.playerinfo.position = pony.position; socket.data.playerinfo.rotation = pony.rotation; } else { this._db.upsertPony(user, socket.data.playerinfo); } socket.emit('me', { ...publicUserInfo, character: socket.data.playerinfo.character, position: pony?.position, rotation: pony?.rotation, }); socket.emit( 'players', this._connections .filter((player) => player.data.user) .map((conn) => ({ ...this.mapPlayer(conn.data.user), ...conn.data.playerinfo, })), ); if (pony) { this.io.emit('playercharacter', { id: socket.data.user.id, ...socket.data.playerinfo.character, }); } }); }); } else { socket.emit('me', null); } socket.on('disconnect', () => { this._connections.splice(this._connections.indexOf(socket), 1); if (user) { this.io.emit('playerleave', publicUserInfo); this._db.upsertPony(user, socket.data.playerinfo); } }); }); setInterval(() => { const playerInfo: PositionUpdatePacket[] = []; this._connections .filter( (conn) => conn.data.user, // && this._changedPlayers.includes(conn.data.user.id), ) .forEach((conn) => playerInfo.push({ position: conn.data.playerinfo.position, rotation: conn.data.playerinfo.rotation, animState: conn.data.playerinfo.animState, id: conn.data.user.id, }), ); this.io.emit('playerupdate', playerInfo); this._changedPlayers.length = 0; }, 1000 / 60); } }