diff --git a/src/app.module.ts b/src/app.module.ts index 587ba4e..262b15a 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -2,11 +2,12 @@ import { Module } from '@nestjs/common'; import { EventsModule } from './events/events.module'; import { ConfigModule } from '@nestjs/config'; import { world } from './config/world.config'; +import { server } from './config/server.config'; @Module({ imports: [ ConfigModule.forRoot({ - load: [world], + load: [server, world], }), EventsModule, ], diff --git a/src/config/server.config.ts b/src/config/server.config.ts new file mode 100644 index 0000000..7c7a957 --- /dev/null +++ b/src/config/server.config.ts @@ -0,0 +1,7 @@ +import { registerAs } from '@nestjs/config'; + +export const server = registerAs('server', () => ({ + gatewayPort: Number(process.env.GATEWAY_PORT || 8256), + statusPort: Number(process.env.PORT || 8255), + corsOrigin: process.env.CORS_ORIGIN || '*', +})); diff --git a/src/events/events.gateway.ts b/src/events/events.gateway.ts index 23b121d..7aa5d95 100644 --- a/src/events/events.gateway.ts +++ b/src/events/events.gateway.ts @@ -1,3 +1,4 @@ +import { Logger } from '@nestjs/common'; import { ConnectedSocket, MessageBody, @@ -8,6 +9,7 @@ import { WebSocketGateway, WebSocketServer, } from '@nestjs/websockets'; +import { Request } from 'express'; import { Packet } from 'src/net/packet'; import { PlayerStoreService } from 'src/player/player-store.service'; import { PlayerSocket } from 'src/types/data-socket'; @@ -17,14 +19,12 @@ import { getRandomId } from 'src/utils/random'; import { WorldService } from 'src/world/world.service'; import { Server } from 'ws'; -@WebSocketGateway(8256, { - cors: { - origin: '*', - }, -}) +@WebSocketGateway() export class EventsGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect { + private readonly logger = new Logger(EventsGateway.name); + constructor( private readonly players: PlayerStoreService, private readonly world: WorldService, @@ -59,6 +59,9 @@ export class EventsGateway .toBuffer(), ); } + this.logger.log( + `Client ${client.id} (${client.ip}) has logged in as ${player.name} (${player.id})`, + ); await this.world.initializePlayer(player); } @@ -68,14 +71,19 @@ export class EventsGateway } afterInit(server: Server) { - console.log('server init'); + this.logger.log( + `>>> Game server is listening on port ${server.options.port} <<<`, + ); } - handleConnection(client: PlayerSocket, ...args: any[]) { + handleConnection(client: PlayerSocket, request: Request) { client.id = getRandomId(); + client.ip = request.socket.remoteAddress; + this.logger.log(`Client ${client.id} connected from ${client.ip}.`); } handleDisconnect(client: PlayerSocket) { + this.logger.log(`Client ${client.id} (${client.ip}) disconnected.`); this.world.playerQuit(client); } } diff --git a/src/game/humanoid.object.ts b/src/game/humanoid.object.ts index 6f86a13..8b2e4e5 100644 --- a/src/game/humanoid.object.ts +++ b/src/game/humanoid.object.ts @@ -21,6 +21,7 @@ export class Humanoid extends GameObject implements PhysicsTicking { private _appliedGravity = new Vector3(0, 0, 0); private _grounded = true; private _lookAt = new Vector3(0, 0, 1); + private _lookAtTarget = new Vector3(0, 0, 1); private _currentLookAt = new Vector3(0, 0, 1); private _animState = 0; @@ -108,7 +109,7 @@ export class Humanoid extends GameObject implements PhysicsTicking { } setLook(vector: Vector3) { - this._lookAt.lerp(vector, 0.15); + this._lookAtTarget.copy(vector); } jump() { @@ -147,6 +148,7 @@ export class Humanoid extends GameObject implements PhysicsTicking { // Apply look vector this._currentLookAt.copy(this.parent!.position); + this._lookAt.lerp(this._lookAtTarget, 0.15); this._currentLookAt.add(this._lookAt); this.parent?.lookAt(this._currentLookAt); diff --git a/src/main.ts b/src/main.ts index 749ff8b..200111c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,12 +1,24 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { WsAdapter } from './net/ws-adapter'; +import { ConfigService } from '@nestjs/config'; +import { Logger } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); - app.useWebSocketAdapter(new WsAdapter(app)); + const config = app.get(ConfigService); + const appPort = config.get('server.statusPort'); + const gatewayPort = config.get('server.gatewayPort'); + const corsOrigin = config.get('server.corsOrigin'); - await app.listen(3000); - console.log(`Application is running on: ${await app.getUrl()}`); + app.enableCors({ + origin: corsOrigin, + credentials: true, + }); + + app.useWebSocketAdapter(new WsAdapter(app, gatewayPort, corsOrigin)); + await app.listen(appPort); + + Logger.log(`Server status API: ${await app.getUrl()}`, 'bootstrap'); } bootstrap(); diff --git a/src/net/ws-adapter.ts b/src/net/ws-adapter.ts index bf7c900..7874dff 100644 --- a/src/net/ws-adapter.ts +++ b/src/net/ws-adapter.ts @@ -6,10 +6,18 @@ import { mergeMap, filter } from 'rxjs/operators'; import { Packet } from './packet'; export class WsAdapter implements WebSocketAdapter { - constructor(private app: INestApplicationContext) {} + constructor( + private app: INestApplicationContext, + private port: number, + private cors: string, + ) {} create(port: number, options: any = {}): any { - return new WebSocket.Server({ port, ...options }); + return new WebSocket.Server({ + port: this.port || port, + ...options, + cors: { origin: this.cors }, + }); } bindClientConnect( diff --git a/src/player/player-store.service.ts b/src/player/player-store.service.ts index 88aad30..db30e74 100644 --- a/src/player/player-store.service.ts +++ b/src/player/player-store.service.ts @@ -98,7 +98,9 @@ export class PlayerStoreService { if (player.controller) { player.controller.setVelocity(velocity); - player.controller.setLook(lookAt); + if (!(velocity.x === 0 && velocity.y === 0 && velocity.z === 0)) { + player.controller.setLook(lookAt); + } if (jump) player.controller.jump(); return true; } diff --git a/src/types/data-socket.ts b/src/types/data-socket.ts index c7a790e..03a9ee7 100644 --- a/src/types/data-socket.ts +++ b/src/types/data-socket.ts @@ -2,4 +2,5 @@ import { WebSocket } from 'ws'; export interface PlayerSocket extends WebSocket { id: string; + ip: string; } diff --git a/src/types/packet-type.enum.ts b/src/types/packet-type.enum.ts index b983936..de8ea21 100644 --- a/src/types/packet-type.enum.ts +++ b/src/types/packet-type.enum.ts @@ -9,7 +9,7 @@ export enum PacketType { */ STREAM_START, /** - * [][Asset Name][Asset Type][Buffer] + * [][Asset Path][Asset Name][Asset Type][Buffer] */ STREAM_ASSET, /** @@ -37,7 +37,7 @@ export enum PacketType { */ STREAM_CHAT, /** - * [][Player ID:Player Name] + * [][Player count][Player ID:Player Name] */ PLAYER_LIST, /** @@ -57,9 +57,13 @@ export enum PacketType { */ PLAYER_MOVEMENT, /** - * [][Chat Type][Chat Message] + * [][Player ID][Chat Type][Chat Message] */ PLAYER_CHAT, + /** + * [][Player ID][Object UUID][Event][JSON] + */ + PLAYER_EVENT, /** * [][Error type] */ diff --git a/src/world/physics.service.ts b/src/world/physics.service.ts index c8c5a8c..9981997 100644 --- a/src/world/physics.service.ts +++ b/src/world/physics.service.ts @@ -7,6 +7,9 @@ import { Object3D, Vector3 } from 'three'; import { GameObject } from 'src/game/game-object'; import { PhysicsTicking } from 'src/physics'; +const PHYSICS_STEPPING = 1000 / 60; +const PHYSICS_STEPPING_S = 1 / 60; + @Injectable() export class PhysicsService { private world!: World; @@ -23,16 +26,16 @@ export class PhysicsService { if (!this.running) return; this.physicsTicker = setInterval(() => { this.physicsWorld.step(); - for (const object of this.trackedObjects) object.tick(0.016); + for (const object of this.trackedObjects) object.tick(PHYSICS_STEPPING_S); this.tickCallback?.(); - }, 16); + }, PHYSICS_STEPPING); } async start(world: World, cb?: () => void) { this.world = world; await RAPIER.init(); this.physicsWorld = new RAPIER.World(new Vector3(0, this.world.gravity, 0)); - this.physicsWorld.timestep = 0.016; + this.physicsWorld.timestep = PHYSICS_STEPPING_S; this.running = true; this.tickCallback = cb; this.initializePhysicsScene(); @@ -54,10 +57,7 @@ export class PhysicsService { .write(object.uuid, String) .write(body.translation(), 'vec3') .write(body.rotation(), 'quat') - .write( - object instanceof Humanoid ? body.nextTranslation() : body.linvel(), - 'vec3', - ) + .write(body.linvel(), 'vec3') .write(body.angvel(), 'vec3') .toBuffer(), ); diff --git a/src/world/world.service.ts b/src/world/world.service.ts index e556205..754a3d3 100644 --- a/src/world/world.service.ts +++ b/src/world/world.service.ts @@ -32,11 +32,11 @@ export class WorldService implements OnModuleInit { this.logger.log('Loading world file from environment'); this.loadWorld() .then(() => { - this.logger.log('World file loaded'); + this.logger.log('World file loaded!'); this.physics.start(this.world, () => { - if (this.broadcastTick >= 0.25) { + this.broadcastWorldState(); + if (this.broadcastTick >= 0.5) { this.broadcastTick = 0; - this.broadcastWorldState(); return; } this.broadcastTick += 0.016; @@ -71,6 +71,9 @@ export class WorldService implements OnModuleInit { } public async initializePlayer(player: Player) { + this.logger.debug( + `Initializing player ${player.name} (${player.id}), sending world file..`, + ); // Streaming start player.send( new Packet(PacketType.STREAM_START) @@ -121,6 +124,10 @@ export class WorldService implements OnModuleInit { this.players .getPlayerCharacterPackets(player) .forEach((packet) => player.send(packet)); + + this.logger.log( + `PLAYER ${player.name} (${player.id}) has joined the game!`, + ); } public removeObject(object: Object3D) { @@ -197,6 +204,7 @@ export class WorldService implements OnModuleInit { ); this.removeObject(player.controller); this.removeObject(player.character); + this.logger.log(`PLAYER ${player.name} (${player.id}) has left the game.`); } private recursiveCreate(entry: SerializedObject, setParent?: Object3D) {