mirror of
https://github.com/Terncode/pixel.horse.git
synced 2026-09-25 06:05:52 +02:00
Archive commit
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import { tileWidth } from '../../common/constants';
|
||||
import { Entity } from '../../common/interfaces';
|
||||
import { entitiesIntersect } from '../../common/utils';
|
||||
import { cloud } from '../../common/entities';
|
||||
import * as sprites from '../../generated/sprites';
|
||||
import { Controller, ServerMap, ServerEntity } from '../serverInterfaces';
|
||||
import { World } from '../world';
|
||||
import { updateEntityVelocity } from '../entityUtils';
|
||||
import { timingEnd, timingStart } from '../timing';
|
||||
|
||||
const spriteWidth = sprites.cloud.shadow!.w / tileWidth;
|
||||
const cloudVX = -0.5;
|
||||
|
||||
export class CloudController implements Controller {
|
||||
private clouds: Entity[] = [];
|
||||
private initialized = false;
|
||||
constructor(private world: World, private map: ServerMap, private cloudCount: number) {
|
||||
}
|
||||
initialize() {
|
||||
if (this.initialized)
|
||||
return;
|
||||
|
||||
for (let i = 0; i < this.cloudCount; i++) {
|
||||
this.addCloud(false, this.world.now / 1000);
|
||||
}
|
||||
|
||||
this.initialized = true;
|
||||
}
|
||||
update(_: number, now: number) {
|
||||
timingStart('CloudController.update()');
|
||||
for (let i = this.clouds.length - 1; i >= 0; i--) {
|
||||
const cloud = this.clouds[i];
|
||||
|
||||
if (cloud.x < -spriteWidth) {
|
||||
this.clouds.splice(i, 1);
|
||||
this.world.removeEntity(cloud, this.map);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.clouds.length < this.cloudCount) {
|
||||
this.addCloud(true, now);
|
||||
}
|
||||
timingEnd();
|
||||
}
|
||||
private addCloud(end: boolean, timestamp: number) {
|
||||
const x = end ? this.map.width + spriteWidth : this.map.width * Math.random();
|
||||
const y = this.map.height * Math.random();
|
||||
const entity = cloud(x, y) as ServerEntity;
|
||||
|
||||
if (!this.clouds.some(c => entitiesIntersect(c, entity))) {
|
||||
this.clouds.push(this.world.addEntity(entity, this.map));
|
||||
updateEntityVelocity(entity, cloudVX, 0, timestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { remove, sample } from 'lodash';
|
||||
import { Entity, CreateEntityMethod } from '../../common/interfaces';
|
||||
import { IClient, Controller, ServerEntity, ServerMap } from '../serverInterfaces';
|
||||
import { World } from '../world';
|
||||
import { timingEnd, timingStart } from '../timing';
|
||||
import { canPlaceItem, canBePickedByPlayer, pushRemoveEntityToClient } from '../entityUtils';
|
||||
|
||||
export function randomPosition(map: ServerMap) {
|
||||
const x = Math.random() * map.width;
|
||||
const y = Math.random() * map.height;
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
export class CollectableController implements Controller {
|
||||
private items: Entity[] = [];
|
||||
constructor(
|
||||
private world: World,
|
||||
private map: ServerMap,
|
||||
private ctors: CreateEntityMethod[],
|
||||
public limit: number,
|
||||
private pick: (client: IClient, entity: ServerEntity) => void,
|
||||
private check: (client: IClient) => boolean = () => true,
|
||||
private tries = 1,
|
||||
private position = randomPosition,
|
||||
private active = () => true
|
||||
) {
|
||||
}
|
||||
initialize() {
|
||||
}
|
||||
update() {
|
||||
timingStart('CollectableController.update()');
|
||||
|
||||
if (this.active()) {
|
||||
for (let i = 0; i < this.tries; i++) {
|
||||
if (this.items.length < this.limit) {
|
||||
this.generateItem();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
timingEnd();
|
||||
}
|
||||
private generateItem() {
|
||||
const { world, map } = this;
|
||||
const { x, y } = this.position(map);
|
||||
const ctor = sample(this.ctors)!;
|
||||
const entity = ctor(x, y) as ServerEntity;
|
||||
|
||||
if (!entity.interactRange) {
|
||||
entity.interactRange = 1.5;
|
||||
}
|
||||
|
||||
if (
|
||||
x > 0 && y > 0 && x < map.width && y < map.height && canPlaceItem(map, entity) && !canBePickedByPlayer(map, entity)
|
||||
) {
|
||||
entity.interact = this.interact;
|
||||
this.items.push(world.addEntity(entity, map));
|
||||
}
|
||||
}
|
||||
private interact = (entity: Entity, client: IClient) => {
|
||||
if (this.check(client)) {
|
||||
if (client.shadowed) {
|
||||
pushRemoveEntityToClient(client, entity);
|
||||
} else {
|
||||
remove(this.items, e => e === entity);
|
||||
this.world.removeEntity(entity, this.map);
|
||||
this.generateItem();
|
||||
this.pick(client, entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
import { sample } from 'lodash';
|
||||
import {
|
||||
createBinaryWriter, getWriterBuffer, resetWriter, resizeWriter, writeArrayHeader, writeUint8Array,
|
||||
writeUint8
|
||||
} from 'ag-sockets';
|
||||
import { Controller, IClient } from '../serverInterfaces';
|
||||
import { World } from '../world';
|
||||
import { Character, Account } from '../db';
|
||||
import { createClientAndPony } from '../playerUtils';
|
||||
import { CounterService } from '../services/counter';
|
||||
import { CharacterState, ServerConfig } from '../../common/adminInterfaces';
|
||||
import { removeItem, times } from '../../common/utils';
|
||||
import { timingStart, timingEnd } from '../timing';
|
||||
|
||||
interface Options {
|
||||
count: number;
|
||||
}
|
||||
|
||||
const mockCharacterStates = new CounterService<CharacterState>(0);
|
||||
|
||||
export class FakeClientsController implements Controller {
|
||||
private clients: IClient[] = [];
|
||||
private tokens: any[] = [];
|
||||
private initialized = false;
|
||||
constructor(private world: World, private server: ServerConfig, private options: Options) {
|
||||
}
|
||||
initialize() {
|
||||
if (this.initialized)
|
||||
return;
|
||||
|
||||
times(1000, async i => {
|
||||
try {
|
||||
const name = `perf-${i}`;
|
||||
const account = await Account.findOne({ name }).exec();
|
||||
|
||||
if (!account)
|
||||
throw new Error(`Missing debug account (${name})`);
|
||||
|
||||
const character = await Character.findOne({ account: account._id }).exec();
|
||||
|
||||
if (!character)
|
||||
throw new Error(`Missing debug character (${name})`);
|
||||
|
||||
this.tokens.push({ id: name, account, character });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
|
||||
this.initialized = true;
|
||||
}
|
||||
update() {
|
||||
}
|
||||
sparseUpdate() {
|
||||
timingStart('FakeClientController.sparseUpdate()');
|
||||
|
||||
if (this.tokens.length) {
|
||||
for (let i = this.clients.length - 1; i >= 0; i--) {
|
||||
if (Math.random() < (10 / this.options.count)) {
|
||||
this.leave(this.clients[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.clients.length < this.options.count) {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
this.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
timingEnd();
|
||||
}
|
||||
async join() {
|
||||
try {
|
||||
const token = sample(this.tokens)!;
|
||||
|
||||
if (!this.clients.some(c => c.tokenId === token.id)) {
|
||||
const client = await joinFakeClient(token, this.server, this.world);
|
||||
this.clients.push(client);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
async leave(client: IClient) {
|
||||
this.world.leaveClient(client);
|
||||
removeItem(this.clients, client);
|
||||
}
|
||||
}
|
||||
|
||||
const packetWriter = createBinaryWriter();
|
||||
export let lastPacket: Uint8Array | undefined;
|
||||
|
||||
async function joinFakeClient(token: any, server: ServerConfig, world: World): Promise<IClient> {
|
||||
const client: Partial<IClient> = {
|
||||
tokenId: token.id,
|
||||
tokenData: token,
|
||||
disconnect() {
|
||||
world.leaveClient(client as IClient);
|
||||
},
|
||||
queue() { },
|
||||
left() { },
|
||||
worldState() { },
|
||||
mapState() { },
|
||||
myEntity() { },
|
||||
mapTest() { },
|
||||
updateFriends() { },
|
||||
actionParam() { },
|
||||
update(_, subscribes, adds, datas) {
|
||||
do {
|
||||
try {
|
||||
resetWriter(packetWriter);
|
||||
writeUint8(packetWriter, 123);
|
||||
|
||||
if (writeArrayHeader(packetWriter, subscribes)) {
|
||||
for (let i = 0; i < subscribes.length; i++) {
|
||||
writeUint8Array(packetWriter, subscribes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
writeUint8Array(packetWriter, adds);
|
||||
|
||||
if (writeArrayHeader(packetWriter, datas)) {
|
||||
for (let i = 0; i < datas.length; i++) {
|
||||
writeUint8Array(packetWriter, datas[i]);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
} catch (e) {
|
||||
if (e instanceof RangeError || /DataView/.test(e.message)) {
|
||||
resizeWriter(packetWriter);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
} while (true);
|
||||
|
||||
lastPacket = getWriterBuffer(packetWriter);
|
||||
},
|
||||
addNotification() { },
|
||||
removeNotification() { },
|
||||
};
|
||||
|
||||
createClientAndPony(client as IClient, [], [], server, world, mockCharacterStates);
|
||||
|
||||
world.joinClientToQueue(client as IClient);
|
||||
|
||||
return client as IClient;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import { sample } from 'lodash';
|
||||
import { Entity, CreateEntityMethod, ServerFlags } from '../../common/interfaces';
|
||||
import { Controller, ServerEntity, ServerMap } from '../serverInterfaces';
|
||||
import { World } from '../world';
|
||||
import { findClosestEntity, findEntities } from '../serverMap';
|
||||
import { timingEnd, timingStart } from '../timing';
|
||||
import { hasFlag, distanceXY } from '../../common/utils';
|
||||
import { moveRandomly, findClosest, moveTowards } from '../entityUtils';
|
||||
import { randomPosition } from './collectableController';
|
||||
|
||||
export class FlyingCritterController implements Controller {
|
||||
private entities: Entity[] = [];
|
||||
constructor(
|
||||
private world: World, private map: ServerMap, private critter: CreateEntityMethod, private speed: number,
|
||||
private limit: number, private isActive: () => boolean, private spawnOnStart = false
|
||||
) {
|
||||
}
|
||||
initialize() {
|
||||
if (this.spawnOnStart) {
|
||||
for (let i = 0; i < this.limit; i++) {
|
||||
const { x, y } = randomPosition(this.map);
|
||||
this.entities.push(this.world.addEntity(this.critter(x, y), this.map));
|
||||
}
|
||||
}
|
||||
}
|
||||
update(_: number, now: number) {
|
||||
timingStart('FlyingCritterController.update()');
|
||||
updateTreehidingEntities(
|
||||
this.entities, this.world, this.map, this.limit, this.speed, now, this.critter, this.isActive);
|
||||
timingEnd();
|
||||
}
|
||||
}
|
||||
|
||||
function isTreeCrown(entity: ServerEntity) {
|
||||
return hasFlag(entity.serverFlags || 0, ServerFlags.TreeCrown);
|
||||
}
|
||||
|
||||
export function findClosestTree(map: ServerMap, x: number, y: number) {
|
||||
return findClosestEntity(map, x, y, isTreeCrown);
|
||||
}
|
||||
|
||||
export function findTrees(map: ServerMap) {
|
||||
return findEntities(map, isTreeCrown);
|
||||
}
|
||||
|
||||
interface TargetTree extends Entity {
|
||||
targetTree?: Entity;
|
||||
}
|
||||
|
||||
export function updateTreehidingEntities(
|
||||
entities: TargetTree[], world: World, map: ServerMap, limit: number, speed: number, timestamp: number,
|
||||
create: (x: number, y: number) => Entity, isActive: () => boolean
|
||||
) {
|
||||
const offsetY = -2;
|
||||
|
||||
if (isActive()) {
|
||||
// release new critter
|
||||
if (entities.length < limit && Math.random() < 0.1) {
|
||||
const trees = findTrees(map);
|
||||
const tree = sample(trees);
|
||||
|
||||
if (tree) {
|
||||
const entity = create(tree.x, tree.y + offsetY);
|
||||
entities.push(world.addEntity(entity, map));
|
||||
moveRandomly(map, entity, speed, 1, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
for (const entity of entities) {
|
||||
moveRandomly(map, entity, speed, 0.02, timestamp);
|
||||
}
|
||||
} else if (entities.length) {
|
||||
// head to tree and disappear
|
||||
const trees = findTrees(map);
|
||||
|
||||
for (let i = entities.length - 1; i >= 0; i--) {
|
||||
const e = entities[i];
|
||||
|
||||
e.targetTree = e.targetTree || findClosest(e.x, e.y, trees);
|
||||
|
||||
if (distanceXY(e.x, e.y, e.targetTree.x, e.targetTree.y + offsetY) < 0.1) {
|
||||
entities.splice(i, 1);
|
||||
world.removeEntity(e, map);
|
||||
} else {
|
||||
moveTowards(e, e.targetTree.x, e.targetTree.y + offsetY, speed, timestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import { range, compact } from 'lodash';
|
||||
import { pony } from '../../common/entities';
|
||||
import { Entity, EntityState, MessageType, EntityFlags } from '../../common/interfaces';
|
||||
import { Controller, ServerEntity, IClient } from '../serverInterfaces';
|
||||
import { World } from '../world';
|
||||
import { Character } from '../db';
|
||||
import { PONY_SPEED_TROT } from '../../common/constants';
|
||||
import { setEntityName, updateEntityVelocity } from '../entityUtils';
|
||||
import { encryptInfo } from '../characterUtils';
|
||||
import { createCamera } from '../../common/camera';
|
||||
import { shouldBeFacingRight } from '../../common/movementUtils';
|
||||
import { timingEnd, timingStart } from '../timing';
|
||||
import { sayToAll } from '../chat';
|
||||
|
||||
interface Options {
|
||||
count: number;
|
||||
moving: number;
|
||||
saying?: boolean;
|
||||
unique?: boolean;
|
||||
spread?: boolean;
|
||||
x?: number;
|
||||
y?: number;
|
||||
}
|
||||
|
||||
export class PerfController implements Controller {
|
||||
private entities: Entity[] = [];
|
||||
private limitLeft = 11;
|
||||
private limitWidth = 30;
|
||||
private limitTop = 9;
|
||||
private limitHeight = 25;
|
||||
private initialized = false;
|
||||
constructor(private world: World, private options: Options) {
|
||||
if (options.spread) {
|
||||
this.limitWidth = 60;
|
||||
this.limitHeight = 60;
|
||||
}
|
||||
|
||||
if (options.x !== undefined) {
|
||||
this.limitLeft = options.x;
|
||||
}
|
||||
|
||||
if (options.y !== undefined) {
|
||||
this.limitTop = options.y;
|
||||
}
|
||||
}
|
||||
initialize() {
|
||||
if (this.initialized)
|
||||
return;
|
||||
|
||||
const world = this.world;
|
||||
const map = world.getMainMap();
|
||||
|
||||
const names = [
|
||||
'performance',
|
||||
'performance 2',
|
||||
];
|
||||
|
||||
const query = this.options.unique ?
|
||||
Promise.resolve(Character.find({ account: '57ae2336a67f4dc52e123ed1' }).limit(this.options.count).exec()) :
|
||||
Promise.all(names.map(name => Character.findOne({ name }).exec())).then(compact);
|
||||
|
||||
query
|
||||
.then(characters => {
|
||||
if (characters.length) {
|
||||
this.entities = range(this.options.count).map(i => {
|
||||
const character = characters[i % characters.length]!;
|
||||
const name = character._id.toString();
|
||||
const x = this.limitLeft + this.limitWidth * Math.random();
|
||||
const y = this.limitTop + this.limitHeight * Math.random();
|
||||
const p = pony(x, y) as ServerEntity;
|
||||
setEntityName(p, name);
|
||||
p.flags |= EntityFlags.CanCollide;
|
||||
p.encryptedInfoSafe = encryptInfo(character.info || '');
|
||||
p.client = {
|
||||
pony: p,
|
||||
accountId: 'foobar',
|
||||
characterId: character._id.toString(),
|
||||
ignores: new Set(),
|
||||
hides: new Set(),
|
||||
permaHides: new Set(),
|
||||
account: {} as any,
|
||||
regions: [],
|
||||
camera: createCamera(),
|
||||
updateRegion() { },
|
||||
addEntity() { },
|
||||
mapTest() { },
|
||||
} as Partial<IClient> as any;
|
||||
p.client!.camera.x = -10000;
|
||||
p.vx = this.options.moving ? randomVelocity() : 0;
|
||||
p.vy = this.options.moving ? randomVelocity() : 0;
|
||||
p.state = shouldBeFacingRight(p) ? EntityState.FacingRight : EntityState.None;
|
||||
return world.addEntity(p, map);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.initialized = true;
|
||||
}
|
||||
update(_: number, now: number) {
|
||||
timingStart('PerfController.update()');
|
||||
|
||||
const limitBottom = this.limitTop + this.limitHeight;
|
||||
const limitRight = this.limitTop + this.limitHeight;
|
||||
|
||||
if (this.options.moving) {
|
||||
for (const entity of this.entities) {
|
||||
if ((entity.vy > 0 && entity.y > limitBottom) || (entity.vy < 0 && entity.y < this.limitTop)) {
|
||||
updateEntityVelocity(entity, entity.vx, -entity.vy, now);
|
||||
} else if ((entity.vx > 0 && entity.x > limitRight) || (entity.vx < 0 && entity.x < this.limitLeft)) {
|
||||
updateEntityVelocity(entity, -entity.vx, entity.vy, now);
|
||||
} else if (Math.random() < 0.1) {
|
||||
updateEntityVelocity(entity, randomVelocity(), randomVelocity(), now);
|
||||
}
|
||||
|
||||
if (this.options.saying && Math.random() < 0.01) {
|
||||
sayToAll(entity, 'Hello World', 'Hello World', MessageType.Chat, {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
timingEnd();
|
||||
}
|
||||
}
|
||||
|
||||
function randomVelocity() {
|
||||
const rand = Math.random();
|
||||
return rand < 0.333 ? 0 : (rand < 0.666 ? -PONY_SPEED_TROT : +PONY_SPEED_TROT);
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import { sample, random } from 'lodash';
|
||||
import { Controller, ServerEntity, ServerMap, Interact } from '../serverInterfaces';
|
||||
import { World } from '../world';
|
||||
import { timingStart, timingEnd } from '../timing';
|
||||
import { Rect, CreateEntityMethod, ServerFlags, TileType } from '../../common/interfaces';
|
||||
import { removeItem, randomPoint } from '../../common/utils';
|
||||
import { getTile } from '../../common/worldMap';
|
||||
|
||||
interface Plant extends ServerEntity {
|
||||
plantStage: number;
|
||||
plantStageNext: number;
|
||||
}
|
||||
|
||||
export interface PlantConfig {
|
||||
area: Rect;
|
||||
count: number;
|
||||
stages: CreateEntityMethod[][];
|
||||
onPick?: Interact;
|
||||
growOnlyOn?: TileType;
|
||||
isActive?: () => boolean;
|
||||
}
|
||||
|
||||
export class PlantController implements Controller {
|
||||
private plants: Plant[] = [];
|
||||
private interact: Interact = (entity, client) => {
|
||||
this.world.removeEntity(entity, this.map);
|
||||
removeItem(this.plants, entity);
|
||||
this.config.onPick && this.config.onPick(entity, client);
|
||||
}
|
||||
private nextSpawn = 0;
|
||||
constructor(private world: World, private map: ServerMap, private config: PlantConfig) {
|
||||
}
|
||||
initialize() {
|
||||
}
|
||||
update() {
|
||||
}
|
||||
sparseUpdate() {
|
||||
timingStart('PlantController.sparseUpdate()');
|
||||
|
||||
const now = Date.now();
|
||||
const maxStage = this.config.stages.length - 1;
|
||||
|
||||
if (
|
||||
this.nextSpawn < now &&
|
||||
(this.config.isActive === undefined || this.config.isActive()) &&
|
||||
this.plants.length < this.config.count
|
||||
) {
|
||||
const { x, y } = randomPoint(this.config.area);
|
||||
|
||||
if (this.config.growOnlyOn === undefined || getTile(this.map, x, y) === this.config.growOnlyOn) {
|
||||
this.addPlant(x, y, 0);
|
||||
this.nextSpawn = now + random(10000, 20000);
|
||||
}
|
||||
}
|
||||
|
||||
const plantsToRemove: Plant[] = [];
|
||||
|
||||
for (const plant of this.plants) {
|
||||
if (plant.plantStage < maxStage && plant.plantStageNext < now) {
|
||||
plantsToRemove.push(plant);
|
||||
this.addPlant(plant.x, plant.y, plant.plantStage + 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (const plant of plantsToRemove) {
|
||||
this.removePlant(plant);
|
||||
}
|
||||
|
||||
timingEnd();
|
||||
}
|
||||
private removePlant(plant: Plant) {
|
||||
removeItem(this.plants, plant);
|
||||
this.world.removeEntity(plant, this.map);
|
||||
}
|
||||
private addPlant(x: number, y: number, stage: number) {
|
||||
const create = sample(this.config.stages[stage])!;
|
||||
const plant = create(x, y) as Plant;
|
||||
plant.plantStage = stage;
|
||||
plant.plantStageNext = Date.now() + random(15000, 40000);
|
||||
plant.serverFlags = ServerFlags.DoNotSave;
|
||||
|
||||
if (stage === (this.config.stages.length - 1)) {
|
||||
plant.interact = this.interact;
|
||||
}
|
||||
|
||||
this.plants.push(plant);
|
||||
this.world.addEntity(plant, this.map);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { compact } from 'lodash';
|
||||
import * as entities from '../../common/entities';
|
||||
import { IClient, ServerEntity, Controller, ServerMap } from '../serverInterfaces';
|
||||
import { World } from '../world';
|
||||
import { Character } from '../db';
|
||||
import { setEntityName } from '../entityUtils';
|
||||
import { times } from '../../common/utils';
|
||||
import { encryptInfo } from '../characterUtils';
|
||||
import { createCamera } from '../../common/camera';
|
||||
import { timingStart, timingEnd } from '../timing';
|
||||
import { createBinaryWriter } from 'ag-sockets';
|
||||
|
||||
export class TestController implements Controller {
|
||||
private clients: IClient[] = [];
|
||||
private initialized = false;
|
||||
constructor(private world: World, private map: ServerMap) {
|
||||
}
|
||||
initialize() {
|
||||
if (this.initialized)
|
||||
return;
|
||||
|
||||
const world = this.world;
|
||||
const map = this.map;
|
||||
|
||||
if (DEVELOPMENT) {
|
||||
Promise.all(times(10, i => `debug ${i + 1}`).map(name => Character.findOne({ name }).exec()))
|
||||
.then(compact)
|
||||
.then(items => items.forEach((item, i) => {
|
||||
const name = item.name;
|
||||
const tag = i === 0 ? 'mod' : (i === 2 ? 'sup2' : '');
|
||||
const extraOptions = i === 0 ? {
|
||||
site: {
|
||||
provider: 'github',
|
||||
name: 'Test name',
|
||||
url: 'https://github.com/Microsoft/TypeScript',
|
||||
}
|
||||
} : undefined;
|
||||
const p = entities.pony(57 + 1 * i, 47 + 1 * i) as ServerEntity;
|
||||
p.options = { tag };
|
||||
setEntityName(p, name);
|
||||
p.encryptedInfoSafe = encryptInfo(item.info || '');
|
||||
p.client = {
|
||||
map,
|
||||
accountSettings: {},
|
||||
account: { id: 'foobar', name: 'Debug account' } as any,
|
||||
country: 'XY',
|
||||
regions: [],
|
||||
saysQueue: { push() { }, length: 0 } as any,
|
||||
notifications: [],
|
||||
camera: createCamera(),
|
||||
accountId: 'foobar',
|
||||
characterId: '',
|
||||
ignores: new Set(),
|
||||
hides: new Set(),
|
||||
permaHides: new Set(),
|
||||
updateQueue: createBinaryWriter(1),
|
||||
addEntity() { },
|
||||
addNotification() { },
|
||||
removeNotification() { },
|
||||
updateParty() { },
|
||||
mapUpdate() { },
|
||||
} as Partial<IClient> as any;
|
||||
p.client!.pony = p;
|
||||
this.clients.push(p.client!);
|
||||
p.extraOptions = extraOptions;
|
||||
world.addEntity(p, map);
|
||||
}));
|
||||
}
|
||||
|
||||
this.initialized = true;
|
||||
}
|
||||
update() {
|
||||
timingStart('TestController.update()');
|
||||
timingEnd();
|
||||
}
|
||||
sparseUpdate() {
|
||||
timingStart('TestController.sparseUpdate()');
|
||||
|
||||
for (const client of this.clients) {
|
||||
for (const notification of client.notifications) {
|
||||
notification.accept && notification.accept();
|
||||
}
|
||||
}
|
||||
|
||||
timingEnd();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { isNight } from '../../common/timeUtils';
|
||||
import { Controller, ServerEntity, ServerMap } from '../serverInterfaces';
|
||||
import { World } from '../world';
|
||||
import { timingStart, timingEnd } from '../timing';
|
||||
import { updateLights } from '../controllerUtils';
|
||||
import { hasFlag } from '../../common/utils';
|
||||
import { EntityFlags } from '../../common/interfaces';
|
||||
|
||||
export class TorchController implements Controller {
|
||||
private lights: ServerEntity[] = [];
|
||||
constructor(private world: World, private map: ServerMap) {
|
||||
}
|
||||
initialize() {
|
||||
this.lights = [];
|
||||
|
||||
for (const region of this.map.regions) {
|
||||
for (const entity of region.entities) {
|
||||
if (hasFlag(entity.flags, EntityFlags.OnOff)) {
|
||||
this.lights.push(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
update() {
|
||||
timingStart('TorchController.update()');
|
||||
timingEnd();
|
||||
}
|
||||
sparseUpdate() {
|
||||
timingStart('TorchController.sparseUpdate()');
|
||||
updateLights(this.lights, isNight(this.world.time));
|
||||
timingEnd();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Controller, ServerEntity, ServerMap } from '../serverInterfaces';
|
||||
import { timingStart, timingEnd } from '../timing';
|
||||
|
||||
export class UpdateController implements Controller {
|
||||
private updatables: ServerEntity[] = [];
|
||||
constructor(private map: ServerMap) {
|
||||
}
|
||||
initialize() {
|
||||
this.updatables = [];
|
||||
|
||||
for (const region of this.map.regions) {
|
||||
for (const entity of region.entities) {
|
||||
if (entity.serverUpdate) {
|
||||
this.updatables.push(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
update(delta: number, now: number) {
|
||||
timingStart('TorchController.update()');
|
||||
|
||||
for (const entity of this.updatables) {
|
||||
entity.serverUpdate!(delta, now);
|
||||
}
|
||||
|
||||
timingEnd();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
import { fromByteArray, toByteArray } from 'base64-js';
|
||||
import { Controller, ServerMap } from '../serverInterfaces';
|
||||
import { World } from '../world';
|
||||
import { Entity, TileType } from '../../common/interfaces';
|
||||
import { tileHeight } from '../../common/constants';
|
||||
import { array } from '../../common/utils';
|
||||
import { Walls } from '../../common/entities';
|
||||
|
||||
const createGetAt = (width: number, height: number) => <T>(items: T[], x: number, y: number) => {
|
||||
return (x < 0 || y < 0 || x >= width || y >= height) ? undefined : items[x + y * width];
|
||||
};
|
||||
|
||||
const createSetAt = (width: number, height: number) => <T>(items: T[], x: number, y: number, value: T) => {
|
||||
if (x >= 0 && y >= 0 && x < width && y < height) {
|
||||
items[x + y * width] = value;
|
||||
}
|
||||
};
|
||||
|
||||
export class WallController implements Controller {
|
||||
top = 0;
|
||||
isTall = (_x: number, _y: number) => false;
|
||||
lockOuterWalls = false;
|
||||
private lockedTiles = new Set<string>();
|
||||
private hWalls: (Entity | undefined)[];
|
||||
private vWalls: (Entity | undefined)[];
|
||||
constructor(world: World, map: ServerMap, walls: Walls) {
|
||||
const width = map.width + 1;
|
||||
const height = map.height + 1;
|
||||
|
||||
const getAt = createGetAt(width, height);
|
||||
const setAt = createSetAt(width, height);
|
||||
|
||||
const hWalls = this.hWalls = array<Entity | undefined>(width * height, undefined);
|
||||
const vWalls = this.vWalls = array<Entity | undefined>(width * height, undefined);
|
||||
const cWalls = array<Entity | undefined>(width * height, undefined);
|
||||
|
||||
const yOffset = 3 / tileHeight;
|
||||
|
||||
const { wallHShort, wallVShort, wallH, wallV, wallCorners, wallCornersShort, wallCutR, wallCutL } = walls;
|
||||
|
||||
const calcCorner = (x: number, y: number) => {
|
||||
// top right bottom left
|
||||
return (getAt(vWalls, x, y - 1) ? 8 : 0)
|
||||
+ (getAt(hWalls, x, y) ? 4 : 0)
|
||||
+ (getAt(vWalls, x, y) ? 2 : 0)
|
||||
+ (getAt(hWalls, x - 1, y) ? 1 : 0);
|
||||
};
|
||||
|
||||
const updateCorner = (x: number, y: number) => {
|
||||
if (x < 0 || y < 0 || x >= width || y >= height)
|
||||
return;
|
||||
|
||||
const top = this.top;
|
||||
const isOutside = x === 0 || y <= top || x === map.width || this.isTall(x, y);
|
||||
const corners = isOutside ? wallCorners : wallCornersShort;
|
||||
const current = getAt(cWalls, x, y);
|
||||
const calc = calcCorner(x, y);
|
||||
|
||||
if (!current || current.type !== corners[calc].type) {
|
||||
if (current) {
|
||||
world.removeEntity(current, map);
|
||||
}
|
||||
|
||||
setAt(cWalls, x, y, calc ? world.addEntity(corners[calc](x, y + yOffset), map) : undefined);
|
||||
}
|
||||
};
|
||||
|
||||
this.toggleWall = (x, y, type) => {
|
||||
if (x < 0 || y < 0 || x >= width || y >= height)
|
||||
return;
|
||||
|
||||
if (this.lockedTiles.has(`${x},${y}:${type}`))
|
||||
return;
|
||||
|
||||
const walls = type === TileType.WallH ? hWalls : vWalls;
|
||||
const entity = getAt(walls, x, y);
|
||||
const top = this.top;
|
||||
|
||||
if (type === TileType.WallH && x === (width - 1))
|
||||
return;
|
||||
|
||||
if (type === TileType.WallV && y === (height - 1))
|
||||
return;
|
||||
|
||||
if (this.lockOuterWalls) {
|
||||
if (type === TileType.WallH && (y <= top || y === (width - 1)))
|
||||
return;
|
||||
if (type === TileType.WallV && (x === 0 || x === (height - 1) || y < top))
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity) {
|
||||
world.removeEntity(entity, map);
|
||||
setAt(walls, x, y, undefined);
|
||||
} else {
|
||||
if (type === TileType.WallH) {
|
||||
const ctor = (y <= top || this.isTall(x, y)) ?
|
||||
wallH : (x === 0 ? wallCutL : (x === (width - 2) ? wallCutR : wallHShort));
|
||||
setAt(walls, x, y, world.addEntity(ctor(x + 0.5, y + yOffset), map));
|
||||
} else {
|
||||
const ctor = (x === 0 || x === (width - 1) || this.isTall(x, y)) ? wallV : wallVShort;
|
||||
setAt(walls, x, y, world.addEntity(ctor(x, y + 0.5), map));
|
||||
}
|
||||
}
|
||||
|
||||
updateCorner(x, y);
|
||||
updateCorner(x + 1, y);
|
||||
updateCorner(x, y + 1);
|
||||
};
|
||||
}
|
||||
initialize() {
|
||||
}
|
||||
update() {
|
||||
}
|
||||
toggleWall?: (x: number, y: number, type: TileType) => void;
|
||||
lockWall(x: number, y: number, type: TileType.WallH | TileType.WallV) {
|
||||
this.lockedTiles.add(`${x},${y}:${type}`);
|
||||
}
|
||||
serialize() {
|
||||
const data = new Uint8Array(Math.ceil(this.vWalls.length / 8) + Math.ceil(this.hWalls.length / 8));
|
||||
let offset = 0;
|
||||
|
||||
for (let i = 0; i < this.vWalls.length; i += 8, offset++) {
|
||||
let value = 0;
|
||||
|
||||
for (let j = 0; j < 8; j++) {
|
||||
if (this.vWalls[i + j]) {
|
||||
value |= (1 << j);
|
||||
}
|
||||
}
|
||||
|
||||
data[offset] = value;
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.hWalls.length; i += 8, offset++) {
|
||||
let value = 0;
|
||||
|
||||
for (let j = 0; j < 8; j++) {
|
||||
if (this.hWalls[i + j]) {
|
||||
value |= (1 << j);
|
||||
}
|
||||
}
|
||||
|
||||
data[offset] = value;
|
||||
}
|
||||
|
||||
return fromByteArray(data);
|
||||
}
|
||||
deserialize(width: number, height: number, serialized: string) {
|
||||
const data = toByteArray(serialized);
|
||||
const size = (width + 1) * (height + 1);
|
||||
let offset = 0;
|
||||
|
||||
for (let i = 0; i < size; i += 8, offset++) {
|
||||
let value = data[offset];
|
||||
|
||||
for (let j = 0; j < 8; j++) {
|
||||
if ((!!this.vWalls[i + j]) !== ((value & (1 << j)) !== 0)) {
|
||||
const x = (i + j) % (width + 1);
|
||||
const y = Math.floor((i + j) / (width + 1));
|
||||
this.toggleWall!(x, y, TileType.WallV);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < size; i += 8, offset++) {
|
||||
let value = data[offset];
|
||||
|
||||
for (let j = 0; j < 8; j++) {
|
||||
if ((!!this.hWalls[i + j]) !== ((value & (1 << j)) !== 0)) {
|
||||
const x = (i + j) % (width + 1);
|
||||
const y = Math.floor((i + j) / (width + 1));
|
||||
this.toggleWall!(x, y, TileType.WallH);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user