small server refacto
This commit is contained in:
parent
c900e81fb8
commit
78374144a8
1292
package-lock.json
generated
1292
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -16,6 +16,7 @@
|
|||||||
"connect-redis": "^6.1.3",
|
"connect-redis": "^6.1.3",
|
||||||
"express": "^4.17.3",
|
"express": "^4.17.3",
|
||||||
"express-session": "^1.17.2",
|
"express-session": "^1.17.2",
|
||||||
|
"jimp": "^0.16.1",
|
||||||
"passport": "^0.5.2",
|
"passport": "^0.5.2",
|
||||||
"passport-icynet": "^0.0.2",
|
"passport-icynet": "^0.0.2",
|
||||||
"redis": "^3.1.2",
|
"redis": "^3.1.2",
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
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 { clamp, isMobileOrTablet } from '../common/helper';
|
import { isMobileOrTablet } from '../common/helper';
|
||||||
import { CharacterPacket, CompositePacket } from '../common/types/packet';
|
import { CharacterPacket, CompositePacket } from '../common/types/packet';
|
||||||
import { IcyNetUser } from '../common/types/user';
|
import { IcyNetUser } from '../common/types/user';
|
||||||
import { ThirdPersonCamera } from './object/camera';
|
|
||||||
import { Chat } from './object/chat';
|
import { Chat } from './object/chat';
|
||||||
import { Joystick } from './object/joystick';
|
import { Joystick } from './object/joystick';
|
||||||
import { PonyModelLoader } from './object/resource/pony-loader';
|
import { PonyModelLoader } from './object/resource/pony-loader';
|
||||||
@ -24,7 +23,6 @@ export class Game {
|
|||||||
public players: (Player | PlayerEntity)[] = [];
|
public players: (Player | PlayerEntity)[] = [];
|
||||||
public player!: Player;
|
public player!: Player;
|
||||||
public me!: IcyNetUser;
|
public me!: IcyNetUser;
|
||||||
public thirdPersonCamera!: ThirdPersonCamera;
|
|
||||||
public joystick!: Joystick;
|
public joystick!: Joystick;
|
||||||
public chat!: Chat;
|
public chat!: Chat;
|
||||||
private _loading = LoadingManagerWrapper.getInstance();
|
private _loading = LoadingManagerWrapper.getInstance();
|
||||||
@ -153,7 +151,6 @@ export class Game {
|
|||||||
|
|
||||||
public toggleCamLock(): boolean {
|
public toggleCamLock(): boolean {
|
||||||
this._locked = !this._locked;
|
this._locked = !this._locked;
|
||||||
this.thirdPersonCamera?.setLock(this._locked);
|
|
||||||
this.player?.setCameraLock(this._locked);
|
this.player?.setCameraLock(this._locked);
|
||||||
return this._locked;
|
return this._locked;
|
||||||
}
|
}
|
||||||
@ -164,7 +161,6 @@ export class Game {
|
|||||||
this.renderer.scene.remove(player.container);
|
this.renderer.scene.remove(player.container);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.thirdPersonCamera?.dispose();
|
|
||||||
this.joystick?.dispose();
|
this.joystick?.dispose();
|
||||||
|
|
||||||
this.players.length = 0;
|
this.players.length = 0;
|
||||||
@ -173,7 +169,6 @@ export class Game {
|
|||||||
public update(dt: number) {
|
public update(dt: number) {
|
||||||
this.players.forEach((player) => player.update(dt));
|
this.players.forEach((player) => player.update(dt));
|
||||||
this.player?.createPacket(this.socket);
|
this.player?.createPacket(this.socket);
|
||||||
this.thirdPersonCamera?.update(dt);
|
|
||||||
this.joystick?.update(dt);
|
this.joystick?.update(dt);
|
||||||
|
|
||||||
this.player && this.world?.update(this.player.container.position);
|
this.player && this.world?.update(this.player.container.position);
|
||||||
@ -214,22 +209,10 @@ export class Game {
|
|||||||
user.position && player.container.position.fromArray(user.position);
|
user.position && player.container.position.fromArray(user.position);
|
||||||
user.rotation && player.container.rotation.fromArray(user.rotation);
|
user.rotation && player.container.rotation.fromArray(user.rotation);
|
||||||
player.setHeightSource(this.world);
|
player.setHeightSource(this.world);
|
||||||
|
player.setCamera(this.renderer);
|
||||||
|
|
||||||
this.players.push(player);
|
this.players.push(player);
|
||||||
this.player = player;
|
this.player = player;
|
||||||
this.thirdPersonCamera = new ThirdPersonCamera(
|
|
||||||
this.renderer.camera,
|
|
||||||
this.player.container,
|
|
||||||
this.renderer.canvas,
|
|
||||||
);
|
|
||||||
this.thirdPersonCamera.initialize();
|
|
||||||
this.thirdPersonCamera.registerAltMoveFunction((x, y) => {
|
|
||||||
this.player.angularVelocity.set(
|
|
||||||
0,
|
|
||||||
clamp(x * 0.5, -Math.PI, Math.PI),
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.joystick = new Joystick(player);
|
this.joystick = new Joystick(player);
|
||||||
this.joystick.initialize();
|
this.joystick.initialize();
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
import { CanvasUtils } from './canvas-utils';
|
import { CanvasUtils } from './canvas-utils';
|
||||||
import { ChatBubble } from './chat-bubble';
|
import { ChatBubble } from './chat-bubble';
|
||||||
import { Vector3, Scene, Euler } from 'three';
|
import { Vector3, Scene, Euler } from 'three';
|
||||||
|
import { clamp } from '../../common/helper';
|
||||||
|
|
||||||
const chatBuilder = new CanvasUtils({
|
const chatBuilder = new CanvasUtils({
|
||||||
rounded: true,
|
rounded: true,
|
||||||
@ -139,7 +140,7 @@ export class PlayerEntity extends PonyEntity {
|
|||||||
this.setRotation(new Euler().fromArray(packet.rotation));
|
this.setRotation(new Euler().fromArray(packet.rotation));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet.animState) {
|
if (packet.animState !== undefined) {
|
||||||
this.setWalkAnimationState(packet.animState);
|
this.setWalkAnimationState(packet.animState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -173,7 +174,7 @@ export class PlayerEntity extends PonyEntity {
|
|||||||
this._pf.copy(this._p1);
|
this._pf.copy(this._p1);
|
||||||
this._qf.copy(this._q1);
|
this._qf.copy(this._q1);
|
||||||
|
|
||||||
const t = Math.max(Math.min(this._targetFrame.time / 0.1, 1.0), 0.0);
|
const t = clamp(this._targetFrame.time / 0.1, 0.0, 1.0);
|
||||||
this._pf.lerp(this._p2, t);
|
this._pf.lerp(this._p2, t);
|
||||||
this._qf.lerp(this._q2, t);
|
this._qf.lerp(this._q2, t);
|
||||||
|
|
||||||
|
@ -2,8 +2,13 @@ import { IcyNetUser } from '../../common/types/user';
|
|||||||
import { Socket } from 'socket.io-client';
|
import { Socket } from 'socket.io-client';
|
||||||
import { PonyEntity } from './model/pony';
|
import { PonyEntity } from './model/pony';
|
||||||
import { Scene, Vector2, Vector3 } from 'three';
|
import { Scene, Vector2, Vector3 } from 'three';
|
||||||
|
import { ThirdPersonCamera } from './camera';
|
||||||
|
import { clamp } from '../../common/helper';
|
||||||
|
import { Renderer } from '../renderer';
|
||||||
|
|
||||||
export class Player extends PonyEntity {
|
export class Player extends PonyEntity {
|
||||||
|
public thirdPersonCamera!: ThirdPersonCamera;
|
||||||
|
|
||||||
public keydownMap: { [x: string]: boolean } = {};
|
public keydownMap: { [x: string]: boolean } = {};
|
||||||
private _prevKeydownMap: { [x: string]: boolean } = {};
|
private _prevKeydownMap: { [x: string]: boolean } = {};
|
||||||
|
|
||||||
@ -66,6 +71,23 @@ export class Player extends PonyEntity {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
super.dispose();
|
||||||
|
this.thirdPersonCamera?.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public setCamera(renderer: Renderer) {
|
||||||
|
this.thirdPersonCamera = new ThirdPersonCamera(
|
||||||
|
renderer.camera,
|
||||||
|
this.container,
|
||||||
|
renderer.renderer.domElement,
|
||||||
|
);
|
||||||
|
this.thirdPersonCamera.initialize();
|
||||||
|
this.thirdPersonCamera.registerAltMoveFunction((x, y) => {
|
||||||
|
this.angularVelocity.set(0, clamp(x * 0.5, -Math.PI, Math.PI), 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
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('move', this.changes);
|
||||||
@ -157,11 +179,13 @@ export class Player extends PonyEntity {
|
|||||||
|
|
||||||
this.moveCharacter(dt);
|
this.moveCharacter(dt);
|
||||||
super.update(dt);
|
super.update(dt);
|
||||||
|
this.thirdPersonCamera?.update(dt);
|
||||||
|
|
||||||
this._prevKeydownMap = { ...this.keydownMap };
|
this._prevKeydownMap = { ...this.keydownMap };
|
||||||
}
|
}
|
||||||
|
|
||||||
public setCameraLock(isLocked: boolean) {
|
public setCameraLock(isLocked: boolean) {
|
||||||
|
this.thirdPersonCamera?.setLock(isLocked);
|
||||||
this._cameraLock = isLocked;
|
this._cameraLock = isLocked;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { ShaderMaterial, UniformsLib, UniformsUtils } from 'three';
|
import { ShaderMaterial, UniformsLib, UniformsUtils } from 'three';
|
||||||
import { WorldChunk } from '../../../common/world/WorldChunk';
|
import { WorldChunk } from '../../../common/world/world-chunk';
|
||||||
import { ClientWorldTexture } from './client-world-texture';
|
import { ClientWorldTexture } from './client-world-texture';
|
||||||
|
|
||||||
// Adapted from the Lambert Material shader
|
// Adapted from the Lambert Material shader
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { ImageLoader } from 'three';
|
import { ImageLoader } from 'three';
|
||||||
import { WorldLoader } from '../../../common/world/WorldLoader';
|
import { WorldLoader } from '../../../common/world/world-loader';
|
||||||
import { CanvasUtils } from '../canvas-utils';
|
import { CanvasUtils } from '../canvas-utils';
|
||||||
import { LoadingManagerWrapper } from '../resource/loading-manager';
|
import { LoadingManagerWrapper } from '../resource/loading-manager';
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
WorldManifest,
|
WorldManifest,
|
||||||
WorldManifestRegion,
|
WorldManifestRegion,
|
||||||
} from '../../../common/world/WorldManifest';
|
} from '../../../common/world/world-manifest';
|
||||||
|
|
||||||
export class ClientWorldManifest implements WorldManifest {
|
export class ClientWorldManifest implements WorldManifest {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Object3D, Vector2, Vector3 } from 'three';
|
import { Object3D, Vector2, Vector3 } from 'three';
|
||||||
import { WorldChunk } from '../../../common/world/WorldChunk';
|
import { WorldChunk } from '../../../common/world/world-chunk';
|
||||||
import { WorldManager } from '../../../common/world/WorldManager';
|
import { WorldManager } from '../../../common/world/world-manager';
|
||||||
import { ClientWorldChunkShader } from './client-world-chunk-shader';
|
import { ClientWorldChunkShader } from './client-world-chunk-shader';
|
||||||
import { ClientWorldTexture } from './client-world-texture';
|
import { ClientWorldTexture } from './client-world-texture';
|
||||||
import { QuadtreeMesher } from './quadtree/quadtree-mesher';
|
import { QuadtreeMesher } from './quadtree/quadtree-mesher';
|
||||||
|
@ -9,7 +9,7 @@ export function debounce(func: Function, timeout = 300) {
|
|||||||
return (...args: any[]) => {
|
return (...args: any[]) => {
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
timer = setTimeout(() => {
|
timer = setTimeout(() => {
|
||||||
func.apply(this, args);
|
func.apply(null, args);
|
||||||
}, timeout);
|
}, timeout);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import { IcyNetUser } from './user';
|
|||||||
export interface PositionUpdatePacket {
|
export interface PositionUpdatePacket {
|
||||||
id?: number;
|
id?: number;
|
||||||
position?: number[];
|
position?: number[];
|
||||||
rotation?: (number | string)[];
|
rotation?: number[];
|
||||||
animState?: number;
|
animState?: number;
|
||||||
time?: number;
|
time?: number;
|
||||||
}
|
}
|
||||||
@ -12,7 +12,7 @@ export interface FullStatePacket {
|
|||||||
velocity?: number[];
|
velocity?: number[];
|
||||||
angular?: number[];
|
angular?: number[];
|
||||||
position?: number[];
|
position?: number[];
|
||||||
rotation?: (number | string)[];
|
rotation?: number[];
|
||||||
animState?: number;
|
animState?: number;
|
||||||
character?: CharacterPacket;
|
character?: CharacterPacket;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Vector3, Vector2 } from 'three';
|
import { Vector3, Vector2 } from 'three';
|
||||||
import { to1D } from '../convert';
|
import { to1D } from '../convert';
|
||||||
import { barycentricPoint } from '../helper';
|
import { barycentricPoint } from '../helper';
|
||||||
import { WorldManifestRegion } from './WorldManifest';
|
import { WorldManifestRegion } from './world-manifest';
|
||||||
|
|
||||||
export class WorldChunk {
|
export class WorldChunk {
|
||||||
constructor(
|
constructor(
|
@ -1,7 +1,7 @@
|
|||||||
import { to1D } from '../convert';
|
import { to1D } from '../convert';
|
||||||
import { WorldChunk } from './WorldChunk';
|
import { WorldChunk } from './world-chunk';
|
||||||
import { WorldLoader } from './WorldLoader';
|
import { WorldLoader } from './world-loader';
|
||||||
import { WorldManifest } from './WorldManifest';
|
import { WorldManifest } from './world-manifest';
|
||||||
|
|
||||||
export class WorldManager {
|
export class WorldManager {
|
||||||
protected _chunks!: WorldChunk[];
|
protected _chunks!: WorldChunk[];
|
||||||
@ -34,7 +34,7 @@ export class WorldManager {
|
|||||||
chunkX,
|
chunkX,
|
||||||
chunkY,
|
chunkY,
|
||||||
this.worldChunkSize,
|
this.worldChunkSize,
|
||||||
this.manifest.regionMap.find(({ x, y }) => x === chunkX && y === chunkY),
|
this.manifest.regionMap.find(({ x, y }) => x === chunkX && y === chunkY)!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,6 +46,8 @@ export class WorldManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async initialize() {}
|
||||||
|
|
||||||
getPointHeight(x: number, y: number): number {
|
getPointHeight(x: number, y: number): number {
|
||||||
const chunkX = Math.floor(x / this.worldChunkSize);
|
const chunkX = Math.floor(x / this.worldChunkSize);
|
||||||
const chunkY = Math.floor(y / this.worldChunkSize);
|
const chunkY = Math.floor(y / this.worldChunkSize);
|
3
src/server/constants.ts
Normal file
3
src/server/constants.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
|
export const ASSETS = join(__dirname, '..', '..', 'assets');
|
@ -1,9 +1,12 @@
|
|||||||
import { Server, Socket } from 'socket.io';
|
import { Server, Socket } from 'socket.io';
|
||||||
import { RequestHandler } from 'express';
|
import { RequestHandler } from 'express';
|
||||||
import { IcyNetUser } from '../common/types/user';
|
import { IcyNetUser } from '../common/types/user';
|
||||||
import { CharacterPacket, PositionUpdatePacket } from '../common/types/packet';
|
|
||||||
import { Persistence } from './object/persistence';
|
import { Persistence } from './object/persistence';
|
||||||
import { Logger } from './object/logger';
|
import { Logger } from './object/logger';
|
||||||
|
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';
|
||||||
|
|
||||||
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)}`;
|
||||||
@ -17,23 +20,19 @@ const PLACEHOLDER_USER = (socket: Socket): IcyNetUser => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class Game {
|
export class Game {
|
||||||
private _connections: Socket[] = [];
|
private _players: Player[] = [];
|
||||||
private _changedPlayers: number[] = [];
|
|
||||||
private _db = new Persistence();
|
private _db = new Persistence();
|
||||||
private _log = new Logger();
|
private _log = new Logger();
|
||||||
|
private _world!: ServerWorld;
|
||||||
|
|
||||||
constructor(private io: Server, private session: RequestHandler) {}
|
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() {
|
async initialize() {
|
||||||
|
const manifest = await ServerWorldManifest.loadManifest();
|
||||||
|
this._world = new ServerWorld(new ServerWorldLoader(), manifest);
|
||||||
|
|
||||||
await this._db.initialize();
|
await this._db.initialize();
|
||||||
|
await this._world.initialize();
|
||||||
this._log.initialize();
|
this._log.initialize();
|
||||||
|
|
||||||
this.io.use((socket, next) =>
|
this.io.use((socket, next) =>
|
||||||
@ -46,92 +45,38 @@ export class Game {
|
|||||||
process.env.SKIP_LOGIN === 'true'
|
process.env.SKIP_LOGIN === 'true'
|
||||||
? PLACEHOLDER_USER(socket)
|
? PLACEHOLDER_USER(socket)
|
||||||
: (session?.passport?.user as IcyNetUser);
|
: (session?.passport?.user as IcyNetUser);
|
||||||
const publicUserInfo = user ? this.mapPlayer(user) : null;
|
|
||||||
|
|
||||||
if (
|
if (user && this._players.find((entry) => entry.user?.id === user.id)) {
|
||||||
user &&
|
|
||||||
this._connections.find((entry) => entry.data.user?.id === user.id)
|
|
||||||
) {
|
|
||||||
socket.emit('error.duplicate');
|
socket.emit('error.duplicate');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._connections.push(socket);
|
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
socket.broadcast.emit('playerjoin', publicUserInfo);
|
const player = new Player(socket, this._db, user);
|
||||||
|
this._players.push(player);
|
||||||
|
|
||||||
this._log.writeEvent('PlayerJoin', `<${user.display_name}:${user.id}>`);
|
this._log.writeEvent('PlayerJoin', `<${user.display_name}:${user.id}>`);
|
||||||
|
|
||||||
socket.data.user = user;
|
socket.data.player = player;
|
||||||
socket.data.playerinfo = {
|
player.initialize().then(() => {
|
||||||
velocity: [0, 0, 0],
|
// Send player list to new player
|
||||||
angular: [0, 0, 0, 'XYZ'],
|
socket.emit(
|
||||||
position: [0, 0, 0],
|
'players',
|
||||||
rotation: [0, 0, 0, 'XYZ'],
|
this._players
|
||||||
animState: 0,
|
.filter((player) => player.user)
|
||||||
character: {},
|
.map((player) => ({
|
||||||
};
|
...player.getPublicUserProfile(),
|
||||||
|
...player.toSave(),
|
||||||
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) => {
|
socket.on('chat-send', (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', { sender: publicUserInfo, message });
|
this.io.emit('chat', {
|
||||||
});
|
sender: player.getPublicUserProfile(),
|
||||||
|
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 {
|
} else {
|
||||||
@ -139,35 +84,34 @@ export class Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
socket.on('disconnect', () => {
|
socket.on('disconnect', () => {
|
||||||
this._connections.splice(this._connections.indexOf(socket), 1);
|
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
this.io.emit('playerleave', publicUserInfo);
|
const player = this._players.find(
|
||||||
|
(player) => player.user.id === user.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!player) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.io.emit('playerleave', player.getPublicUserProfile());
|
||||||
|
|
||||||
this._log.writeEvent(
|
this._log.writeEvent(
|
||||||
'PlayerLeave',
|
'PlayerLeave',
|
||||||
`<${user.display_name}:${user.id}>`,
|
`<${user.display_name}:${user.id}>`,
|
||||||
);
|
);
|
||||||
this._db.upsertPony(user, socket.data.playerinfo);
|
|
||||||
|
player.dispose();
|
||||||
|
this._players.splice(this._players.indexOf(player), 1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
const playerInfo: PositionUpdatePacket[] = [];
|
this._players.forEach((player) => player.update(1000 / 60));
|
||||||
this._connections
|
this.io.emit(
|
||||||
.filter(
|
'playerupdate',
|
||||||
(conn) => conn.data.user, // && this._changedPlayers.includes(conn.data.user.id),
|
this._players.map((player) => player.toUpdatePacket()),
|
||||||
)
|
|
||||||
.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);
|
}, 1000 / 60);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
129
src/server/object/player.ts
Normal file
129
src/server/object/player.ts
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
import { Socket } from 'socket.io';
|
||||||
|
import { Vector3 } from 'three';
|
||||||
|
import {
|
||||||
|
CharacterPacket,
|
||||||
|
FullStatePacket,
|
||||||
|
PositionUpdatePacket,
|
||||||
|
} from '../../common/types/packet';
|
||||||
|
import { IcyNetUser } from '../../common/types/user';
|
||||||
|
import { DatabasePony } from '../types/database';
|
||||||
|
import { Persistence } from './persistence';
|
||||||
|
|
||||||
|
export class Player {
|
||||||
|
public position = new Vector3();
|
||||||
|
public rotation = new Vector3();
|
||||||
|
public velocity = new Vector3();
|
||||||
|
public angularVelocity = new Vector3();
|
||||||
|
public animState = 0;
|
||||||
|
public character: CharacterPacket = {};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private socket: Socket,
|
||||||
|
private db: Persistence,
|
||||||
|
public user: IcyNetUser,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async initialize() {
|
||||||
|
this.socket.on('move', (packet: FullStatePacket) => {
|
||||||
|
this.fromPacket(packet);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on('character', (info: CharacterPacket) => {
|
||||||
|
this.setCharacter(info);
|
||||||
|
|
||||||
|
this.socket.broadcast.emit('playercharacter', {
|
||||||
|
id: this.user.id,
|
||||||
|
...this.getCharacter(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const user = await this.db.upsertUser(this.user);
|
||||||
|
const pony = await this.db.getUserPony(user);
|
||||||
|
|
||||||
|
if (pony) {
|
||||||
|
this.fromSave(pony);
|
||||||
|
} else {
|
||||||
|
await this.db.upsertPony(user, this.toSave());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.socket.emit('me', {
|
||||||
|
...this.getPublicUserProfile(),
|
||||||
|
...this.toSave(),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.broadcast.emit('playerjoin', this.getPublicUserProfile());
|
||||||
|
|
||||||
|
if (pony && pony.character) {
|
||||||
|
this.socket.broadcast.emit('playercharacter', {
|
||||||
|
id: this.user.id,
|
||||||
|
character: this.getCharacter(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async dispose() {
|
||||||
|
await this.db.upsertPony(this.user, this.toSave());
|
||||||
|
}
|
||||||
|
|
||||||
|
update(dt: number) {}
|
||||||
|
|
||||||
|
fromPacket(packet: FullStatePacket | PositionUpdatePacket) {
|
||||||
|
if ((packet as FullStatePacket).angular) {
|
||||||
|
this.angularVelocity.fromArray((packet as FullStatePacket).angular!);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((packet as FullStatePacket).velocity) {
|
||||||
|
this.velocity.fromArray((packet as FullStatePacket).velocity!);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet.position) {
|
||||||
|
this.position.fromArray(packet.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet.rotation) {
|
||||||
|
this.rotation.fromArray(packet.rotation as number[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet.animState !== undefined) {
|
||||||
|
this.animState = packet.animState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fromSave(save: DatabasePony) {
|
||||||
|
save.position && this.position.fromArray(save.position as number[]);
|
||||||
|
save.rotation && this.rotation.fromArray(save.rotation as number[]);
|
||||||
|
save.character && this.setCharacter(save.character as CharacterPacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
toSave(): DatabasePony {
|
||||||
|
return {
|
||||||
|
...this.toUpdatePacket(),
|
||||||
|
character: this.getCharacter(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
toUpdatePacket(): PositionUpdatePacket {
|
||||||
|
return {
|
||||||
|
id: this.user.id,
|
||||||
|
position: this.position.toArray(),
|
||||||
|
rotation: this.rotation.toArray(),
|
||||||
|
animState: this.animState,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getPublicUserProfile(): Partial<IcyNetUser> {
|
||||||
|
return {
|
||||||
|
id: this.user.id,
|
||||||
|
username: this.user.username,
|
||||||
|
display_name: this.user.display_name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
setCharacter(packet: CharacterPacket) {
|
||||||
|
this.character = { ...this.character, ...packet };
|
||||||
|
}
|
||||||
|
|
||||||
|
getCharacter(): CharacterPacket {
|
||||||
|
return this.character;
|
||||||
|
}
|
||||||
|
}
|
32
src/server/world/server-world-loader.ts
Normal file
32
src/server/world/server-world-loader.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { WorldLoader } from '../../common/world/world-loader';
|
||||||
|
import { join } from 'path';
|
||||||
|
import jimp from 'jimp';
|
||||||
|
import { ASSETS } from '../constants';
|
||||||
|
import { to1D } from '../../common/convert';
|
||||||
|
|
||||||
|
export class ServerWorldLoader implements WorldLoader {
|
||||||
|
constructor(private _path = join(ASSETS, 'terrain', 'region')) {}
|
||||||
|
|
||||||
|
async loadHeightMap(
|
||||||
|
chunkX: number,
|
||||||
|
chunkY: number,
|
||||||
|
scale: number,
|
||||||
|
): Promise<number[]> {
|
||||||
|
console.log(`<WORLD> Loading region ${chunkX}, ${chunkY}..`);
|
||||||
|
const regionHeight = join(this._path, `height-${chunkX}-${chunkY}.png`);
|
||||||
|
const image = await jimp.read(regionHeight);
|
||||||
|
const width = image.getWidth();
|
||||||
|
const height = image.getHeight();
|
||||||
|
const heights = new Array(width * height);
|
||||||
|
|
||||||
|
for (let x = 0; x < width; x++) {
|
||||||
|
for (let y = 0; y < height; y++) {
|
||||||
|
const id = to1D(x, y, width);
|
||||||
|
const rgb = (image.getPixelColor(x, y) - 255) / 256;
|
||||||
|
heights[id] = (((rgb >> 16) & 255) * scale) / 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return heights;
|
||||||
|
}
|
||||||
|
}
|
39
src/server/world/server-world-manifest.ts
Normal file
39
src/server/world/server-world-manifest.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import {
|
||||||
|
WorldManifest,
|
||||||
|
WorldManifestRegion,
|
||||||
|
} from '../../common/world/world-manifest';
|
||||||
|
import { join } from 'path';
|
||||||
|
import { readFile } from 'fs/promises';
|
||||||
|
import { ASSETS } from '../constants';
|
||||||
|
|
||||||
|
const manifestAsset = join(ASSETS, 'terrain', 'manifest.json');
|
||||||
|
|
||||||
|
export class ServerWorldManifest implements WorldManifest {
|
||||||
|
constructor(
|
||||||
|
public worldWidth: number,
|
||||||
|
public worldHeight: number,
|
||||||
|
public worldChunkSize: number,
|
||||||
|
public worldHeightScale: number,
|
||||||
|
public textureBombingNoise: string,
|
||||||
|
public regionMap: WorldManifestRegion[],
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public static async loadManifest(): Promise<ServerWorldManifest> {
|
||||||
|
console.log(`<WORLD> Loading manifest..`);
|
||||||
|
const file = await readFile(manifestAsset, { encoding: 'utf-8' });
|
||||||
|
const json = JSON.parse(file);
|
||||||
|
const manifest = new ServerWorldManifest(
|
||||||
|
json.worldWidth,
|
||||||
|
json.worldHeight,
|
||||||
|
json.worldChunkSize,
|
||||||
|
json.worldHeightScale,
|
||||||
|
json.textureBombingNoise,
|
||||||
|
json.regionMap,
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`<WORLD> Loaded manifest: ${manifest.worldWidth} x ${manifest.worldHeight} ^ ${manifest.worldHeightScale}, ${manifest.regionMap.length} regions`,
|
||||||
|
);
|
||||||
|
return manifest;
|
||||||
|
}
|
||||||
|
}
|
8
src/server/world/server-world.ts
Normal file
8
src/server/world/server-world.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { WorldManager } from '../../common/world/world-manager';
|
||||||
|
|
||||||
|
export class ServerWorld extends WorldManager {
|
||||||
|
async initialize(): Promise<void> {
|
||||||
|
await this.loadWorld();
|
||||||
|
console.log('<WORLD> World has been loaded.');
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user