This commit is contained in:
Evert Prants 2022-05-15 13:54:15 +03:00
parent 78374144a8
commit f9d6329e4a
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
9 changed files with 109 additions and 34 deletions

View File

@ -1,7 +1,11 @@
import { Socket } from 'socket.io-client'; import { Socket } from 'socket.io-client';
import { Color, DoubleSide, MeshBasicMaterial, Vector3 } from 'three'; import { Color, DoubleSide, MeshBasicMaterial, Vector3 } from 'three';
import { isMobileOrTablet } from '../common/helper'; 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 { IcyNetUser } from '../common/types/user';
import { Chat } from './object/chat'; import { Chat } from './object/chat';
import { Joystick } from './object/joystick'; 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 { ClientWorldManifest } from './object/world/client-world-manifest';
import { ClientWorldLoader } from './object/world/client-world-loader'; import { ClientWorldLoader } from './object/world/client-world-loader';
import { LoadingManagerWrapper } from './object/resource/loading-manager'; import { LoadingManagerWrapper } from './object/resource/loading-manager';
import {
ClientToServerEvents,
ServerToClientEvents,
} from '../common/types/socket';
export class Game { export class Game {
public players: (Player | PlayerEntity)[] = []; public players: (Player | PlayerEntity)[] = [];
@ -35,7 +43,9 @@ export class Game {
private videoTest = new VideoPlayer(24, 12); private videoTest = new VideoPlayer(24, 12);
constructor(public socket: Socket) {} constructor(
public socket: Socket<ServerToClientEvents, ClientToServerEvents>,
) {}
async initialize(): Promise<void> { async initialize(): Promise<void> {
this._loading.initialize(); this._loading.initialize();
@ -75,7 +85,7 @@ export class Game {
// end of // end of
this.chat.registerSendFunction((message) => { this.chat.registerSendFunction((message) => {
this.socket.emit('chat-send', message); this.socket.emit('set.chat', message);
}); });
this.renderer.registerUpdateFunction((dt: number) => { 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) { if (!user) {
this._loading.showError('Error: You need to log in!'); this._loading.showError('Error: You need to log in!');
window.location.href = '/login'; window.location.href = '/login';
@ -223,10 +233,10 @@ export class Game {
this.joystick.show(); 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) { if (user.id === this.me.id) {
return; return;
} }
@ -239,7 +249,7 @@ export class Game {
this.players.push(newplayer); 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); const findPlayer = this.players.find((item) => item.user.id === user.id);
if (findPlayer) { if (findPlayer) {
this.chat.addMessage(`${user.display_name} has left the game.`, null, { 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) => { list.forEach((player) => {
if (player.id === this.me.id) { if (player.id === this.me.id) {
return; return;
@ -273,8 +283,8 @@ export class Game {
); );
}); });
this.socket.on('playerupdate', (data) => { this.socket.on('player.update', (data) => {
data.forEach((item) => { data.forEach((item: PositionUpdatePacket) => {
const player = this.players.find( const player = this.players.find(
(player) => player.user.id === item.id, (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); const player = this.players.find((player) => player.user.id === data.id);
if ( if (
player && player &&
@ -299,7 +309,7 @@ export class Game {
} }
}); });
this.socket.on('chat', (event) => { this.socket.on('player.chat', (event) => {
const player = this.players.find( const player = this.players.find(
(item) => item.user.id === event.sender.id, (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<IcyNetUser>) {
if (this.me.id === sender.id) { if (this.me.id === sender.id) {
if (message.startsWith('!color')) { if (message.startsWith('!color')) {
const [cmd, color] = message.split(' '); const [cmd, color] = message.split(' ');
@ -341,7 +351,7 @@ export class Game {
this.character = { ...this.character, color }; this.character = { ...this.character, color };
this.player.setColor(color); this.player.setColor(color);
this.socket.emit('character', this.character); this.socket.emit('set.character', this.character);
} }
if (message.startsWith('!eyecolor')) { if (message.startsWith('!eyecolor')) {
@ -358,8 +368,8 @@ export class Game {
this.character = { ...this.character, eyeColor }; this.character = { ...this.character, eyeColor };
this.player.setEyeColor(eyeColor); this.player.setEyeColor(eyeColor);
this.socket.emit('character', this.character); this.socket.emit('set.character', this.character);
localStorage.setItem('character', JSON.stringify(this.character)); localStorage.setItem('set.character', JSON.stringify(this.character));
} }
if (message.startsWith('!party')) { if (message.startsWith('!party')) {

View File

@ -90,7 +90,7 @@ export class Player extends PonyEntity {
public createPacket(socket: Socket): void { public createPacket(socket: Socket): void {
if (Object.keys(this.changes).length) { if (Object.keys(this.changes).length) {
socket.emit('move', this.changes); socket.emit('set.move', this.changes);
this.changes = {}; this.changes = {};
} }
} }

View File

@ -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<CompositePacket[]>) => void;
'player.chat': (data: {
sender: Partial<IcyNetUser>;
message: string;
}) => void;
'player.join': (player: IcyNetUser) => void;
'player.leave': (player: Partial<IcyNetUser>) => void;
'player.update': (packet: Partial<PositionUpdatePacket[]>) => 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;
}

View File

@ -7,6 +7,12 @@ import { ServerWorld } from './world/server-world';
import { ServerWorldLoader } from './world/server-world-loader'; import { ServerWorldLoader } from './world/server-world-loader';
import { ServerWorldManifest } from './world/server-world-manifest'; import { ServerWorldManifest } from './world/server-world-manifest';
import { Player } from './object/player'; 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 PLACEHOLDER_USER = (socket: Socket): IcyNetUser => {
const randomName = `player-${socket.id.substring(0, 8)}`; const randomName = `player-${socket.id.substring(0, 8)}`;
@ -25,7 +31,15 @@ export class Game {
private _log = new Logger(); private _log = new Logger();
private _world!: ServerWorld; private _world!: ServerWorld;
constructor(private io: Server, private session: RequestHandler) {} constructor(
private io: Server<
ClientToServerEvents,
ServerToClientEvents,
InterServerEvents,
SocketData
>,
private session: RequestHandler,
) {}
async initialize() { async initialize() {
const manifest = await ServerWorldManifest.loadManifest(); const manifest = await ServerWorldManifest.loadManifest();
@ -61,26 +75,26 @@ export class Game {
player.initialize().then(() => { player.initialize().then(() => {
// Send player list to new player // Send player list to new player
socket.emit( socket.emit(
'players', 'player.list',
this._players this._players
.filter((player) => player.user) .filter((player) => player.user)
.map((player) => ({ .map((player) => ({
...player.getPublicUserProfile(), ...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); const message = raw.trim().substring(0, 280);
this._log.writeChat(user, message); this._log.writeChat(user, message);
this.io.emit('chat', { this.io.emit('player.chat', {
sender: player.getPublicUserProfile(), sender: player.getPublicUserProfile(),
message, message,
}); });
}); });
} else { } else {
socket.emit('me', null); socket.emit('set.me', null);
} }
socket.on('disconnect', () => { socket.on('disconnect', () => {
@ -93,7 +107,7 @@ export class Game {
return; return;
} }
this.io.emit('playerleave', player.getPublicUserProfile()); this.io.emit('player.leave', player.getPublicUserProfile());
this._log.writeEvent( this._log.writeEvent(
'PlayerLeave', 'PlayerLeave',
@ -109,7 +123,7 @@ export class Game {
setInterval(() => { setInterval(() => {
this._players.forEach((player) => player.update(1000 / 60)); this._players.forEach((player) => player.update(1000 / 60));
this.io.emit( this.io.emit(
'playerupdate', 'player.update',
this._players.map((player) => player.toUpdatePacket()), this._players.map((player) => player.toUpdatePacket()),
); );
}, 1000 / 60); }, 1000 / 60);

View File

@ -9,9 +9,14 @@ import http from 'http';
import { join } from 'path'; import { join } from 'path';
import { Server } from 'socket.io'; import { Server } from 'socket.io';
import { IcyNetUser } from '../common/types/user'; import { IcyNetUser } from '../common/types/user';
import {
ServerToClientEvents,
ClientToServerEvents,
} from '../common/types/socket';
import { config } from './config'; import { config } from './config';
import { Game } from './game'; import { Game } from './game';
import { InterServerEvents, SocketData } from './types/socket';
const RedisStore = connectRedis(session); const RedisStore = connectRedis(session);
const redisClient = config.redis?.enabled ? redis.createClient() : undefined; const redisClient = config.redis?.enabled ? redis.createClient() : undefined;
@ -63,7 +68,12 @@ if (process.env.NODE_ENV === 'production') {
} }
const server = http.createServer(app); 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) => { const checkAuth: RequestHandler = (req, res, next) => {
if (req.isAuthenticated()) { if (req.isAuthenticated()) {

View File

@ -0,0 +1,5 @@
export class Minigame {
public readonly name = 'game';
public readonly requiredPlayers = 2;
public readonly maxPlayers = 2;
}

View File

@ -73,10 +73,11 @@ export class Persistence {
const keys = this.filterPonyKeys(updateData); const keys = this.filterPonyKeys(updateData);
const insert = Array.from({ length: keys.length }, () => '?').join(','); 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})`, `INSERT INTO Pony (${keys.join(',')}) VALUES (${insert})`,
keys.map((key) => (updateData as any)[key]), keys.map((key) => (updateData as any)[key]),
); );
updateData.id = res.lastID;
return this.deserializePony(updateData); return this.deserializePony(updateData);
} }
@ -127,7 +128,7 @@ export class Persistence {
'display_name', 'display_name',
'position', 'position',
'rotation', 'rotation',
'character', 'set.character',
'created_at', 'created_at',
'user_id', 'user_id',
]; ];

View File

@ -10,6 +10,7 @@ import { DatabasePony } from '../types/database';
import { Persistence } from './persistence'; import { Persistence } from './persistence';
export class Player { export class Player {
public id!: number;
public position = new Vector3(); public position = new Vector3();
public rotation = new Vector3(); public rotation = new Vector3();
public velocity = new Vector3(); public velocity = new Vector3();
@ -24,14 +25,14 @@ export class Player {
) {} ) {}
async initialize() { async initialize() {
this.socket.on('move', (packet: FullStatePacket) => { this.socket.on('set.move', (packet: FullStatePacket) => {
this.fromPacket(packet); this.fromPacket(packet);
}); });
this.socket.on('character', (info: CharacterPacket) => { this.socket.on('set.character', (info: CharacterPacket) => {
this.setCharacter(info); this.setCharacter(info);
this.socket.broadcast.emit('playercharacter', { this.socket.broadcast.emit('player.character', {
id: this.user.id, id: this.user.id,
...this.getCharacter(), ...this.getCharacter(),
}); });
@ -46,15 +47,15 @@ export class Player {
await this.db.upsertPony(user, this.toSave()); await this.db.upsertPony(user, this.toSave());
} }
this.socket.emit('me', { this.socket.emit('set.me', {
...this.getPublicUserProfile(), ...this.getPublicUserProfile(),
...this.toSave(), ...this.toSave(),
}); });
this.socket.broadcast.emit('playerjoin', this.getPublicUserProfile()); this.socket.broadcast.emit('player.join', this.getPublicUserProfile());
if (pony && pony.character) { if (pony && pony.character) {
this.socket.broadcast.emit('playercharacter', { this.socket.broadcast.emit('player.character', {
id: this.user.id, id: this.user.id,
character: this.getCharacter(), character: this.getCharacter(),
}); });

View File

@ -0,0 +1,7 @@
import { Player } from '../object/player';
export interface SocketData {
player: Player;
}
export interface InterServerEvents {}