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",
|
||||
"express": "^4.17.3",
|
||||
"express-session": "^1.17.2",
|
||||
"jimp": "^0.16.1",
|
||||
"passport": "^0.5.2",
|
||||
"passport-icynet": "^0.0.2",
|
||||
"redis": "^3.1.2",
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { Socket } from 'socket.io-client';
|
||||
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 { IcyNetUser } from '../common/types/user';
|
||||
import { ThirdPersonCamera } from './object/camera';
|
||||
import { Chat } from './object/chat';
|
||||
import { Joystick } from './object/joystick';
|
||||
import { PonyModelLoader } from './object/resource/pony-loader';
|
||||
@ -24,7 +23,6 @@ export class Game {
|
||||
public players: (Player | PlayerEntity)[] = [];
|
||||
public player!: Player;
|
||||
public me!: IcyNetUser;
|
||||
public thirdPersonCamera!: ThirdPersonCamera;
|
||||
public joystick!: Joystick;
|
||||
public chat!: Chat;
|
||||
private _loading = LoadingManagerWrapper.getInstance();
|
||||
@ -153,7 +151,6 @@ export class Game {
|
||||
|
||||
public toggleCamLock(): boolean {
|
||||
this._locked = !this._locked;
|
||||
this.thirdPersonCamera?.setLock(this._locked);
|
||||
this.player?.setCameraLock(this._locked);
|
||||
return this._locked;
|
||||
}
|
||||
@ -164,7 +161,6 @@ export class Game {
|
||||
this.renderer.scene.remove(player.container);
|
||||
});
|
||||
|
||||
this.thirdPersonCamera?.dispose();
|
||||
this.joystick?.dispose();
|
||||
|
||||
this.players.length = 0;
|
||||
@ -173,7 +169,6 @@ export class Game {
|
||||
public update(dt: number) {
|
||||
this.players.forEach((player) => player.update(dt));
|
||||
this.player?.createPacket(this.socket);
|
||||
this.thirdPersonCamera?.update(dt);
|
||||
this.joystick?.update(dt);
|
||||
|
||||
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.rotation && player.container.rotation.fromArray(user.rotation);
|
||||
player.setHeightSource(this.world);
|
||||
player.setCamera(this.renderer);
|
||||
|
||||
this.players.push(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.initialize();
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
import { CanvasUtils } from './canvas-utils';
|
||||
import { ChatBubble } from './chat-bubble';
|
||||
import { Vector3, Scene, Euler } from 'three';
|
||||
import { clamp } from '../../common/helper';
|
||||
|
||||
const chatBuilder = new CanvasUtils({
|
||||
rounded: true,
|
||||
@ -139,7 +140,7 @@ export class PlayerEntity extends PonyEntity {
|
||||
this.setRotation(new Euler().fromArray(packet.rotation));
|
||||
}
|
||||
|
||||
if (packet.animState) {
|
||||
if (packet.animState !== undefined) {
|
||||
this.setWalkAnimationState(packet.animState);
|
||||
}
|
||||
}
|
||||
@ -173,7 +174,7 @@ export class PlayerEntity extends PonyEntity {
|
||||
this._pf.copy(this._p1);
|
||||
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._qf.lerp(this._q2, t);
|
||||
|
||||
|
@ -2,8 +2,13 @@ import { IcyNetUser } from '../../common/types/user';
|
||||
import { Socket } from 'socket.io-client';
|
||||
import { PonyEntity } from './model/pony';
|
||||
import { Scene, Vector2, Vector3 } from 'three';
|
||||
import { ThirdPersonCamera } from './camera';
|
||||
import { clamp } from '../../common/helper';
|
||||
import { Renderer } from '../renderer';
|
||||
|
||||
export class Player extends PonyEntity {
|
||||
public thirdPersonCamera!: ThirdPersonCamera;
|
||||
|
||||
public keydownMap: { [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 {
|
||||
if (Object.keys(this.changes).length) {
|
||||
socket.emit('move', this.changes);
|
||||
@ -157,11 +179,13 @@ export class Player extends PonyEntity {
|
||||
|
||||
this.moveCharacter(dt);
|
||||
super.update(dt);
|
||||
this.thirdPersonCamera?.update(dt);
|
||||
|
||||
this._prevKeydownMap = { ...this.keydownMap };
|
||||
}
|
||||
|
||||
public setCameraLock(isLocked: boolean) {
|
||||
this.thirdPersonCamera?.setLock(isLocked);
|
||||
this._cameraLock = isLocked;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
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';
|
||||
|
||||
// Adapted from the Lambert Material shader
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ImageLoader } from 'three';
|
||||
import { WorldLoader } from '../../../common/world/WorldLoader';
|
||||
import { WorldLoader } from '../../../common/world/world-loader';
|
||||
import { CanvasUtils } from '../canvas-utils';
|
||||
import { LoadingManagerWrapper } from '../resource/loading-manager';
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {
|
||||
WorldManifest,
|
||||
WorldManifestRegion,
|
||||
} from '../../../common/world/WorldManifest';
|
||||
} from '../../../common/world/world-manifest';
|
||||
|
||||
export class ClientWorldManifest implements WorldManifest {
|
||||
constructor(
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Object3D, Vector2, Vector3 } from 'three';
|
||||
import { WorldChunk } from '../../../common/world/WorldChunk';
|
||||
import { WorldManager } from '../../../common/world/WorldManager';
|
||||
import { WorldChunk } from '../../../common/world/world-chunk';
|
||||
import { WorldManager } from '../../../common/world/world-manager';
|
||||
import { ClientWorldChunkShader } from './client-world-chunk-shader';
|
||||
import { ClientWorldTexture } from './client-world-texture';
|
||||
import { QuadtreeMesher } from './quadtree/quadtree-mesher';
|
||||
|
@ -9,7 +9,7 @@ export function debounce(func: Function, timeout = 300) {
|
||||
return (...args: any[]) => {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
func.apply(this, args);
|
||||
func.apply(null, args);
|
||||
}, timeout);
|
||||
};
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { IcyNetUser } from './user';
|
||||
export interface PositionUpdatePacket {
|
||||
id?: number;
|
||||
position?: number[];
|
||||
rotation?: (number | string)[];
|
||||
rotation?: number[];
|
||||
animState?: number;
|
||||
time?: number;
|
||||
}
|
||||
@ -12,7 +12,7 @@ export interface FullStatePacket {
|
||||
velocity?: number[];
|
||||
angular?: number[];
|
||||
position?: number[];
|
||||
rotation?: (number | string)[];
|
||||
rotation?: number[];
|
||||
animState?: number;
|
||||
character?: CharacterPacket;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Vector3, Vector2 } from 'three';
|
||||
import { to1D } from '../convert';
|
||||
import { barycentricPoint } from '../helper';
|
||||
import { WorldManifestRegion } from './WorldManifest';
|
||||
import { WorldManifestRegion } from './world-manifest';
|
||||
|
||||
export class WorldChunk {
|
||||
constructor(
|
@ -1,7 +1,7 @@
|
||||
import { to1D } from '../convert';
|
||||
import { WorldChunk } from './WorldChunk';
|
||||
import { WorldLoader } from './WorldLoader';
|
||||
import { WorldManifest } from './WorldManifest';
|
||||
import { WorldChunk } from './world-chunk';
|
||||
import { WorldLoader } from './world-loader';
|
||||
import { WorldManifest } from './world-manifest';
|
||||
|
||||
export class WorldManager {
|
||||
protected _chunks!: WorldChunk[];
|
||||
@ -34,7 +34,7 @@ export class WorldManager {
|
||||
chunkX,
|
||||
chunkY,
|
||||
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 {
|
||||
const chunkX = Math.floor(x / 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 { RequestHandler } from 'express';
|
||||
import { IcyNetUser } from '../common/types/user';
|
||||
import { CharacterPacket, PositionUpdatePacket } from '../common/types/packet';
|
||||
import { Persistence } from './object/persistence';
|
||||
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 randomName = `player-${socket.id.substring(0, 8)}`;
|
||||
@ -17,23 +20,19 @@ const PLACEHOLDER_USER = (socket: Socket): IcyNetUser => {
|
||||
};
|
||||
|
||||
export class Game {
|
||||
private _connections: Socket[] = [];
|
||||
private _changedPlayers: number[] = [];
|
||||
private _players: Player[] = [];
|
||||
private _db = new Persistence();
|
||||
private _log = new Logger();
|
||||
private _world!: ServerWorld;
|
||||
|
||||
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() {
|
||||
const manifest = await ServerWorldManifest.loadManifest();
|
||||
this._world = new ServerWorld(new ServerWorldLoader(), manifest);
|
||||
|
||||
await this._db.initialize();
|
||||
await this._world.initialize();
|
||||
this._log.initialize();
|
||||
|
||||
this.io.use((socket, next) =>
|
||||
@ -46,92 +45,38 @@ export class Game {
|
||||
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)
|
||||
) {
|
||||
if (user && this._players.find((entry) => entry.user?.id === user.id)) {
|
||||
socket.emit('error.duplicate');
|
||||
return;
|
||||
}
|
||||
|
||||
this._connections.push(socket);
|
||||
|
||||
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}>`);
|
||||
|
||||
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.data.player = player;
|
||||
player.initialize().then(() => {
|
||||
// Send player list to new player
|
||||
socket.emit(
|
||||
'players',
|
||||
this._players
|
||||
.filter((player) => player.user)
|
||||
.map((player) => ({
|
||||
...player.getPublicUserProfile(),
|
||||
...player.toSave(),
|
||||
})),
|
||||
);
|
||||
});
|
||||
|
||||
socket.on('chat-send', (raw) => {
|
||||
const message = raw.trim().substring(0, 280);
|
||||
this._log.writeChat(user, message);
|
||||
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,
|
||||
});
|
||||
}
|
||||
this.io.emit('chat', {
|
||||
sender: player.getPublicUserProfile(),
|
||||
message,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
@ -139,35 +84,34 @@ export class Game {
|
||||
}
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
this._connections.splice(this._connections.indexOf(socket), 1);
|
||||
|
||||
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(
|
||||
'PlayerLeave',
|
||||
`<${user.display_name}:${user.id}>`,
|
||||
);
|
||||
this._db.upsertPony(user, socket.data.playerinfo);
|
||||
|
||||
player.dispose();
|
||||
this._players.splice(this._players.indexOf(player), 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
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._players.forEach((player) => player.update(1000 / 60));
|
||||
this.io.emit(
|
||||
'playerupdate',
|
||||
this._players.map((player) => player.toUpdatePacket()),
|
||||
);
|
||||
this.io.emit('playerupdate', playerInfo);
|
||||
this._changedPlayers.length = 0;
|
||||
}, 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