diff --git a/src/client/game.ts b/src/client/game.ts index d0de0bd..26c7bc8 100644 --- a/src/client/game.ts +++ b/src/client/game.ts @@ -1,7 +1,11 @@ import { Socket } from 'socket.io-client'; import { Color, DoubleSide, MeshBasicMaterial, Vector3 } from 'three'; import { isMobileOrTablet } from '../common/helper'; -import { CharacterPacket, CompositePacket } from '../common/types/packet'; +import { + CharacterPacket, + CompositePacket, + PositionUpdatePacket, +} from '../common/types/packet'; import { IcyNetUser } from '../common/types/user'; import { Chat } from './object/chat'; import { Joystick } from './object/joystick'; @@ -18,6 +22,10 @@ import { ClientWorld } from './object/world/client-world'; import { ClientWorldManifest } from './object/world/client-world-manifest'; import { ClientWorldLoader } from './object/world/client-world-loader'; import { LoadingManagerWrapper } from './object/resource/loading-manager'; +import { + ClientToServerEvents, + ServerToClientEvents, +} from '../common/types/socket'; export class Game { public players: (Player | PlayerEntity)[] = []; @@ -35,7 +43,9 @@ export class Game { private videoTest = new VideoPlayer(24, 12); - constructor(public socket: Socket) {} + constructor( + public socket: Socket, + ) {} async initialize(): Promise { this._loading.initialize(); @@ -75,7 +85,7 @@ export class Game { // end of this.chat.registerSendFunction((message) => { - this.socket.emit('chat-send', message); + this.socket.emit('set.chat', message); }); this.renderer.registerUpdateFunction((dt: number) => { @@ -187,7 +197,7 @@ export class Game { ); }); - this.socket.on('me', (user) => { + this.socket.on('set.me', (user) => { if (!user) { this._loading.showError('Error: You need to log in!'); window.location.href = '/login'; @@ -223,10 +233,10 @@ export class Game { this.joystick.show(); } - this.socket.emit('character', this.character); + this.socket.emit('set.character', this.character); }); - this.socket.on('playerjoin', (user) => { + this.socket.on('player.join', (user) => { if (user.id === this.me.id) { return; } @@ -239,7 +249,7 @@ export class Game { this.players.push(newplayer); }); - this.socket.on('playerleave', (user) => { + this.socket.on('player.leave', (user) => { const findPlayer = this.players.find((item) => item.user.id === user.id); if (findPlayer) { this.chat.addMessage(`${user.display_name} has left the game.`, null, { @@ -252,7 +262,7 @@ export class Game { } }); - this.socket.on('players', (list: CompositePacket[]) => { + this.socket.on('player.list', (list: CompositePacket[]) => { list.forEach((player) => { if (player.id === this.me.id) { return; @@ -273,8 +283,8 @@ export class Game { ); }); - this.socket.on('playerupdate', (data) => { - data.forEach((item) => { + this.socket.on('player.update', (data) => { + data.forEach((item: PositionUpdatePacket) => { const player = this.players.find( (player) => player.user.id === item.id, ); @@ -288,7 +298,7 @@ export class Game { }); }); - this.socket.on('playercharacter', (data) => { + this.socket.on('player.character', (data) => { const player = this.players.find((player) => player.user.id === data.id); if ( player && @@ -299,7 +309,7 @@ export class Game { } }); - this.socket.on('chat', (event) => { + this.socket.on('player.chat', (event) => { const player = this.players.find( (item) => item.user.id === event.sender.id, ); @@ -325,7 +335,7 @@ export class Game { }); } - private experimentalPlayerCmd(message: string, sender: IcyNetUser) { + private experimentalPlayerCmd(message: string, sender: Partial) { if (this.me.id === sender.id) { if (message.startsWith('!color')) { const [cmd, color] = message.split(' '); @@ -341,7 +351,7 @@ export class Game { this.character = { ...this.character, color }; this.player.setColor(color); - this.socket.emit('character', this.character); + this.socket.emit('set.character', this.character); } if (message.startsWith('!eyecolor')) { @@ -358,8 +368,8 @@ export class Game { this.character = { ...this.character, eyeColor }; this.player.setEyeColor(eyeColor); - this.socket.emit('character', this.character); - localStorage.setItem('character', JSON.stringify(this.character)); + this.socket.emit('set.character', this.character); + localStorage.setItem('set.character', JSON.stringify(this.character)); } if (message.startsWith('!party')) { diff --git a/src/client/object/player.ts b/src/client/object/player.ts index c775e68..78a41b7 100644 --- a/src/client/object/player.ts +++ b/src/client/object/player.ts @@ -90,7 +90,7 @@ export class Player extends PonyEntity { public createPacket(socket: Socket): void { if (Object.keys(this.changes).length) { - socket.emit('move', this.changes); + socket.emit('set.move', this.changes); this.changes = {}; } } diff --git a/src/common/types/socket.ts b/src/common/types/socket.ts new file mode 100644 index 0000000..0398108 --- /dev/null +++ b/src/common/types/socket.ts @@ -0,0 +1,27 @@ +import { + CharacterPacket, + CompositePacket, + FullStatePacket, + PositionUpdatePacket, +} from './packet'; +import { IcyNetUser } from './user'; + +export interface ServerToClientEvents { + 'set.me': (player: CompositePacket | null) => void; + 'error.duplicate': () => void; + 'player.list': (players: Partial) => void; + 'player.chat': (data: { + sender: Partial; + message: string; + }) => void; + 'player.join': (player: IcyNetUser) => void; + 'player.leave': (player: Partial) => void; + 'player.update': (packet: Partial) => void; + 'player.character': (packet: CharacterPacket & { id: number }) => void; +} + +export interface ClientToServerEvents { + 'set.chat': (message: string) => void; + 'set.character': (packet: CharacterPacket) => void; + 'set.move': (packet: FullStatePacket) => void; +} diff --git a/src/server/game.ts b/src/server/game.ts index 4da67e9..8d5bfad 100644 --- a/src/server/game.ts +++ b/src/server/game.ts @@ -7,6 +7,12 @@ import { ServerWorld } from './world/server-world'; import { ServerWorldLoader } from './world/server-world-loader'; import { ServerWorldManifest } from './world/server-world-manifest'; import { Player } from './object/player'; +import { + ServerToClientEvents, + ClientToServerEvents, +} from '../common/types/socket'; +import { InterServerEvents, SocketData } from './types/socket'; +import { CompositePacket } from '../common/types/packet'; const PLACEHOLDER_USER = (socket: Socket): IcyNetUser => { const randomName = `player-${socket.id.substring(0, 8)}`; @@ -25,7 +31,15 @@ export class Game { private _log = new Logger(); private _world!: ServerWorld; - constructor(private io: Server, private session: RequestHandler) {} + constructor( + private io: Server< + ClientToServerEvents, + ServerToClientEvents, + InterServerEvents, + SocketData + >, + private session: RequestHandler, + ) {} async initialize() { const manifest = await ServerWorldManifest.loadManifest(); @@ -61,26 +75,26 @@ export class Game { player.initialize().then(() => { // Send player list to new player socket.emit( - 'players', + 'player.list', this._players .filter((player) => player.user) .map((player) => ({ ...player.getPublicUserProfile(), - ...player.toSave(), + ...(player.toSave() as CompositePacket), })), ); }); - socket.on('chat-send', (raw) => { + socket.on('set.chat', (raw) => { const message = raw.trim().substring(0, 280); this._log.writeChat(user, message); - this.io.emit('chat', { + this.io.emit('player.chat', { sender: player.getPublicUserProfile(), message, }); }); } else { - socket.emit('me', null); + socket.emit('set.me', null); } socket.on('disconnect', () => { @@ -93,7 +107,7 @@ export class Game { return; } - this.io.emit('playerleave', player.getPublicUserProfile()); + this.io.emit('player.leave', player.getPublicUserProfile()); this._log.writeEvent( 'PlayerLeave', @@ -109,7 +123,7 @@ export class Game { setInterval(() => { this._players.forEach((player) => player.update(1000 / 60)); this.io.emit( - 'playerupdate', + 'player.update', this._players.map((player) => player.toUpdatePacket()), ); }, 1000 / 60); diff --git a/src/server/index.ts b/src/server/index.ts index 20cb90f..4963016 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -9,9 +9,14 @@ import http from 'http'; import { join } from 'path'; import { Server } from 'socket.io'; import { IcyNetUser } from '../common/types/user'; +import { + ServerToClientEvents, + ClientToServerEvents, +} from '../common/types/socket'; import { config } from './config'; import { Game } from './game'; +import { InterServerEvents, SocketData } from './types/socket'; const RedisStore = connectRedis(session); const redisClient = config.redis?.enabled ? redis.createClient() : undefined; @@ -63,7 +68,12 @@ if (process.env.NODE_ENV === 'production') { } const server = http.createServer(app); -const io = new Server(server); +const io = new Server< + ClientToServerEvents, + ServerToClientEvents, + InterServerEvents, + SocketData +>(server); const checkAuth: RequestHandler = (req, res, next) => { if (req.isAuthenticated()) { diff --git a/src/server/minigame/minigame.ts b/src/server/minigame/minigame.ts new file mode 100644 index 0000000..7146274 --- /dev/null +++ b/src/server/minigame/minigame.ts @@ -0,0 +1,5 @@ +export class Minigame { + public readonly name = 'game'; + public readonly requiredPlayers = 2; + public readonly maxPlayers = 2; +} diff --git a/src/server/object/persistence.ts b/src/server/object/persistence.ts index 62abe1e..cc154de 100644 --- a/src/server/object/persistence.ts +++ b/src/server/object/persistence.ts @@ -73,10 +73,11 @@ export class Persistence { const keys = this.filterPonyKeys(updateData); const insert = Array.from({ length: keys.length }, () => '?').join(','); - await this.db.run( + const res = await this.db.run( `INSERT INTO Pony (${keys.join(',')}) VALUES (${insert})`, keys.map((key) => (updateData as any)[key]), ); + updateData.id = res.lastID; return this.deserializePony(updateData); } @@ -127,7 +128,7 @@ export class Persistence { 'display_name', 'position', 'rotation', - 'character', + 'set.character', 'created_at', 'user_id', ]; diff --git a/src/server/object/player.ts b/src/server/object/player.ts index 5b9b8a2..945a844 100644 --- a/src/server/object/player.ts +++ b/src/server/object/player.ts @@ -10,6 +10,7 @@ import { DatabasePony } from '../types/database'; import { Persistence } from './persistence'; export class Player { + public id!: number; public position = new Vector3(); public rotation = new Vector3(); public velocity = new Vector3(); @@ -24,14 +25,14 @@ export class Player { ) {} async initialize() { - this.socket.on('move', (packet: FullStatePacket) => { + this.socket.on('set.move', (packet: FullStatePacket) => { this.fromPacket(packet); }); - this.socket.on('character', (info: CharacterPacket) => { + this.socket.on('set.character', (info: CharacterPacket) => { this.setCharacter(info); - this.socket.broadcast.emit('playercharacter', { + this.socket.broadcast.emit('player.character', { id: this.user.id, ...this.getCharacter(), }); @@ -46,15 +47,15 @@ export class Player { await this.db.upsertPony(user, this.toSave()); } - this.socket.emit('me', { + this.socket.emit('set.me', { ...this.getPublicUserProfile(), ...this.toSave(), }); - this.socket.broadcast.emit('playerjoin', this.getPublicUserProfile()); + this.socket.broadcast.emit('player.join', this.getPublicUserProfile()); if (pony && pony.character) { - this.socket.broadcast.emit('playercharacter', { + this.socket.broadcast.emit('player.character', { id: this.user.id, character: this.getCharacter(), }); diff --git a/src/server/types/socket.ts b/src/server/types/socket.ts new file mode 100644 index 0000000..c6895ea --- /dev/null +++ b/src/server/types/socket.ts @@ -0,0 +1,7 @@ +import { Player } from '../object/player'; + +export interface SocketData { + player: Player; +} + +export interface InterServerEvents {}