Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
"main": "src/game-server.ts",
"scripts": {
"server": "npm start",
"start": "rm -rf dist/ && npm run build && concurrently \"npm run watch-plugins\" \"node dist/main.js\"",
"start": "rm -rf dist/ && npm run build && concurrently \"npm run watch-plugins\" \"node --max-old-space-size=4096 dist/main.js\"",
"lint": "tslint --project tsconfig.json",
"fake-players": "npm start -- --fakePlayers",
"fake-players": "rm -rf dist/ && npm run build && concurrently \"npm run watch-plugins\" \"node --max-old-space-size=4096 dist/main.js -fakePlayers\"",
"fake-players-tick": "rm -rf dist/ && npm run build && concurrently \"npm run watch-plugins\" \"node --max-old-space-size=4096 dist/main.js -fakePlayers -tickTime\"",
"build": "tsc --project tsconfig.json && tscpaths -p tsconfig.json -s ./src -o ./dist",
"watch-plugins": "nodemon --watch src/plugins/ --exec \"npm run build\" -e ts"
},
Expand All @@ -29,6 +30,7 @@
"body-parser": "^1.19.0",
"express": "^4.17.1",
"js-yaml": "^3.13.1",
"quadtree-lib": "^1.0.9",
"rxjs": "^6.5.4",
"ts-node": "^8.4.1",
"tslib": "^1.10.0",
Expand Down
2 changes: 1 addition & 1 deletion src/game-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export function runGameServer(): void {
world.init();
injectPlugins();

if(yargs.argv.fakePlayers) {
if(process.argv.indexOf('-fakePlayers') !== -1) {
world.generateFakePlayers();
}

Expand Down
17 changes: 17 additions & 0 deletions src/world/mob/npc/npc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import uuidv4 from 'uuid/v4';
import { Position } from '@server/world/position';
import { world } from '@server/game-server';
import { Direction } from '@server/world/direction';
import { QuadtreeKey } from '@server/world/world';

interface NpcAnimations {
walk: number;
Expand All @@ -28,6 +29,7 @@ export class Npc extends Mob {
private _movementRadius: number = 0;
private _initialFaceDirection: Direction = 'NORTH';
public readonly initialPosition: Position;
private quadtreeKey: QuadtreeKey = null;

public constructor(npcSpawn: NpcSpawn, cacheData: NpcDefinition) {
super();
Expand Down Expand Up @@ -80,6 +82,21 @@ export class Npc extends Mob {
return other.id === this.id && other.uuid === this.uuid;
}

public set position(position: Position) {
super.position = position;

if(this.quadtreeKey !== null) {
world.npcTree.remove(this.quadtreeKey);
}

this.quadtreeKey = { x: position.x, y: position.y, mob: this };
world.npcTree.push(this.quadtreeKey);
}

public get position(): Position {
return super.position;
}

public get name(): string {
return this._name;
}
Expand Down
19 changes: 19 additions & 0 deletions src/world/mob/player/action/input-command-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,25 @@ const commands: { [key: string]: commandHandler } = {
player.playAnimation(animationId);
},

quadtree: player => {
// console.log(world.playerTree);
const values = world.playerTree.colliding({
x: player.position.x - 2,
y: player.position.y - 2,
width: 5,
height: 5
});
console.log(values);
},

trackedplayers: player => {
player.packetSender.chatboxMessage(`Tracked players: ${player.trackedPlayers.length}`);
},

trackednpcs: player => {
player.packetSender.chatboxMessage(`Tracked Npcs: ${player.trackedNpcs.length}`);
},

};

export const inputCommandAction = (player: Player, command: string, args: string[]): void => {
Expand Down
2 changes: 0 additions & 2 deletions src/world/mob/player/packet/impl/button-click-packet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,5 @@ export const buttonClickPacket: incomingPacket = (player: Player, packetId: numb

if(ignoreButtons.indexOf(buttonId) === -1) {
buttonAction(player, buttonId);
} else {
console.log('ignore');
}
};
2 changes: 1 addition & 1 deletion src/world/mob/player/packet/packet-sender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ export class PacketSender {
public sendMembershipStatusAndWorldIndex(): void {
const packet = new Packet(126);
packet.writeUnsignedByte(1); // @TODO member status
packet.writeShortLE(this.player.worldIndex);
packet.writeShortLE(this.player.worldIndex + 1);

this.send(packet);
}
Expand Down
22 changes: 20 additions & 2 deletions src/world/mob/player/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { Npc } from '../npc/npc';
import { NpcUpdateTask } from './updating/npc-update-task';
import { Subject } from 'rxjs';
import { Chunk, ChunkUpdateItem } from '@server/world/map/chunk';
import { QuadtreeKey } from '@server/world/world';

const DEFAULT_TAB_WIDGET_IDS = [
2423, 3917, 638, 3213, 1644, 5608, 1151, -1, 5065, 5715, 2449, 904, 147, 962
Expand Down Expand Up @@ -64,6 +65,7 @@ export class Player extends Mob {
private _walkingTo: Position;
private _nearbyChunks: Chunk[];
public readonly actionsCancelled: Subject<boolean>;
private quadtreeKey: QuadtreeKey = null;

public constructor(socket: Socket, inCipher: Isaac, outCipher: Isaac, clientUuid: number, username: string, password: string, isLowDetail: boolean) {
super();
Expand Down Expand Up @@ -118,7 +120,7 @@ export class Player extends Mob {
this._loginDate = new Date(lastLogin);
}

this._lastAddress = playerSave.lastLogin?.address || (this._socket.address() as AddressInfo).address;
this._lastAddress = playerSave.lastLogin?.address || (this._socket?.address() as AddressInfo)?.address || '127.0.0.1';
} else {
// Brand new player logging in
this.position = new Position(3222, 3222);
Expand Down Expand Up @@ -193,7 +195,7 @@ export class Player extends Mob {
});

this._loginDate = new Date();
this._lastAddress = (this._socket.address() as AddressInfo).address;
this._lastAddress = (this._socket?.address() as AddressInfo)?.address || '127.0.0.1';

logger.info(`${this.username}:${this.worldIndex} has logged in.`);
}
Expand All @@ -203,6 +205,7 @@ export class Player extends Mob {
return;
}

world.playerTree.remove(this.quadtreeKey);
savePlayerData(this);

this.packetSender.sendLogout();
Expand Down Expand Up @@ -489,6 +492,21 @@ export class Player extends Mob {
this.activeWidget = null;
}

public set position(position: Position) {
super.position = position;

if(this.quadtreeKey !== null) {
world.playerTree.remove(this.quadtreeKey);
}

this.quadtreeKey = { x: position.x, y: position.y, mob: this };
world.playerTree.push(this.quadtreeKey);
}

public get position(): Position {
return super.position;
}

public equals(player: Player): boolean {
return this.worldIndex === player.worldIndex && this.username === player.username && this.clientUuid === player.clientUuid;
}
Expand Down
87 changes: 49 additions & 38 deletions src/world/mob/player/updating/mob-updating.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,91 +5,102 @@ import { Packet } from '@server/net/packet';
import { Npc } from '@server/world/mob/npc/npc';
import { Player } from '../player';
import { Position } from '@server/world/position';
import { QuadtreeKey } from '@server/world/world';

/**
* Handles the registration of nearby NPCs or Players for the specified player.
*/
export function registerNewMobs<T extends Mob>(packet: Packet, player: Player, trackedMobs: T[], nearbyMobs: T[], registerMob: (mob: Mob) => void): void {
// The client can only handle 80 new players or npcs at a time, so we limit each update to a max of 80
export function registerNewMobs(packet: Packet, player: Player, trackedMobs: Mob[], nearbyMobs: QuadtreeKey[], registerMob: (mob: Mob) => void): void {
if(trackedMobs.length >= 255) {
return;
}

// We only want to send about 20 new mobs at a time, to help save some memory and computing time
// Any remaining players or npcs will be automatically picked up by subsequent updates
let newMobs: T[] = nearbyMobs.filter(m1 => !trackedMobs.find(m2 => m2.equals(m1)));
if(newMobs.length > 80) {
let newMobs: QuadtreeKey[] = nearbyMobs.filter(m1 => !trackedMobs.includes(m1.mob));
if(newMobs.length > 50) {
// We also sort the list of players or npcs here by how close they are to the current player if there are more than 80, so we can render the nearest first
newMobs = newMobs
.sort((a, b) => player.position.distanceBetween(a.position) - player.position.distanceBetween(b.position))
.slice(0, 80);
.sort((a, b) => player.position.distanceBetween(a.mob.position) - player.position.distanceBetween(b.mob.position))
.slice(0, 50);
}

newMobs.forEach(nearbyMob => {
for(const newMob of newMobs) {
const nearbyMob = newMob.mob;

if(nearbyMob instanceof Player) {
if(player.equals(nearbyMob)) {
// Other player is actually this player!
return;
continue;
}

if(!world.playerExists(nearbyMob)) {
// Other player is no longer in the game world
return;
continue;
}
} else if(nearbyMob instanceof Npc) {
if(!world.npcExists(nearbyMob)) {
// Npc is no longer in the game world
return;
continue;
}
}

if(trackedMobs.findIndex(m => m.equals(nearbyMob)) !== -1) {
// Npc or other player is already tracked by this player
return;
continue;
}

if(!nearbyMob.position.withinViewDistance(player.position)) {
// Player or npc is still too far away to be worth rendering
// Also - values greater than 15 and less than -15 are too large, or too small, to be sent via 5 bits (max length of 32)
return;
continue;
}

// Only 255 players or npcs are able to be rendered at a time, so we cut it off it there are more than that
// Only 255 players or npcs are able to be rendered at a time
// To help performance, we limit it to 200 here
if(trackedMobs.length >= 255) {
return;
}

registerMob(nearbyMob);
});
}
}

/**
* Handles updating of nearby NPCs or Players for the specified player.
*/
export function updateTrackedMobs<T extends Mob>(packet: Packet, playerPosition: Position, appendUpdateMaskData: (mob: Mob) => void, trackedMobs: T[], nearbyMobs: T[]): T[] {
const existingTrackedMobs: T[] = [];
export function updateTrackedMobs(packet: Packet, playerPosition: Position, appendUpdateMaskData: (mob: Mob) => void, trackedMobs: Mob[], nearbyMobs: QuadtreeKey[]): Mob[] {
packet.writeBits(8, trackedMobs.length); // Tracked mob count

if(trackedMobs.length != 0) {
for(let i = 0; i < trackedMobs.length; i++) {
const trackedMob: Mob = trackedMobs[i];
let exists = true;

if(trackedMob instanceof Player) {
if(!world.playerExists(trackedMob as Player)) {
exists = false;
}
} else {
if(!world.npcExists(trackedMob as Npc)) {
exists = false;
}
}
if(trackedMobs.length === 0) {
return [];
}

if(exists && nearbyMobs.findIndex(m => m.equals(trackedMob)) !== -1
&& trackedMob.position.withinViewDistance(playerPosition)) {
appendMovement(trackedMob, packet);
appendUpdateMaskData(trackedMob);
existingTrackedMobs.push(trackedMob as T);
} else {
packet.writeBits(1, 1);
packet.writeBits(2, 3);
const existingTrackedMobs: Mob[] = [];

for(let i = 0; i < trackedMobs.length; i++) {
const trackedMob: Mob = trackedMobs[i];
let exists = true;

if(trackedMob instanceof Player) {
if(!world.playerExists(trackedMob as Player)) {
exists = false;
}
} else {
if(!world.npcExists(trackedMob as Npc)) {
exists = false;
}
}

if(exists && nearbyMobs.findIndex(m => m.mob.equals(trackedMob)) !== -1
&& trackedMob.position.withinViewDistance(playerPosition)) {
appendMovement(trackedMob, packet);
appendUpdateMaskData(trackedMob);
existingTrackedMobs.push(trackedMob);
} else {
packet.writeBits(1, 1);
packet.writeBits(2, 3);
}
}

return existingTrackedMobs;
Expand Down
21 changes: 15 additions & 6 deletions src/world/mob/player/updating/npc-update-task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,17 @@ export class NpcUpdateTask extends Task<void> {
const currentMapChunk = world.chunkManager.getChunkForWorldPosition(this.player.position);
const updateMaskData = RsBuffer.create();

const nearbyNpcs = world.chunkManager.getSurroundingChunks(currentMapChunk).map(chunk => chunk.npcs).flat();
const nearbyNpcs = world.npcTree.colliding({
x: this.player.position.x - 15,
y: this.player.position.y - 15,
width: 32,
height: 32
});

this.player.trackedNpcs = updateTrackedMobs<Npc>(npcUpdatePacket, this.player.position,
mob => this.appendUpdateMaskData(mob as Npc, updateMaskData), this.player.trackedNpcs, nearbyNpcs);
this.player.trackedNpcs = updateTrackedMobs(npcUpdatePacket, this.player.position,
mob => this.appendUpdateMaskData(mob as Npc, updateMaskData), this.player.trackedNpcs, nearbyNpcs) as Npc[];

registerNewMobs<Npc>(npcUpdatePacket, this.player, this.player.trackedNpcs, nearbyNpcs, mob => {
registerNewMobs(npcUpdatePacket, this.player, this.player.trackedNpcs, nearbyNpcs, mob => {
const newNpc = mob as Npc;
const positionOffsetX = newNpc.position.x - this.player.position.x;
const positionOffsetY = newNpc.position.y - this.player.position.y;
Expand Down Expand Up @@ -60,7 +65,11 @@ export class NpcUpdateTask extends Task<void> {
npcUpdatePacket.closeBitChannel();
}

this.player.packetSender.send(npcUpdatePacket);
new Promise(resolve => {
this.player.packetSender.send(npcUpdatePacket);
resolve();
});

resolve();
});
}
Expand Down Expand Up @@ -101,7 +110,7 @@ export class NpcUpdateTask extends Task<void> {
// Client checks if index is less than 32768.
// If it is, it looks for an NPC.
// If it isn't, it looks for a player (subtracting 32768 to find the index).
mobIndex += 32768;
mobIndex += 32768 + 1;
}

updateMaskData.writeUnsignedShortLE(mobIndex);
Expand Down
Loading