mirror of
https://github.com/Terncode/pixel.horse.git
synced 2026-09-24 21:55:52 +02:00
tslint changes
This commit is contained in:
+347
-347
@@ -1,7 +1,7 @@
|
||||
import { writeFileAsync, readFileAsync, writeFileSync } from 'fs';
|
||||
import { fromByteArray } from 'base64-js';
|
||||
import {
|
||||
TileType, MapInfo, MapState, defaultMapState, Rect, MapType, ServerFlags, EntityFlags, MapFlags, EntityState
|
||||
TileType, MapInfo, MapState, defaultMapState, Rect, MapType, ServerFlags, EntityFlags, MapFlags, EntityState
|
||||
} from '../common/interfaces';
|
||||
import { getRegionGlobal, getTile, getRegion } from '../common/worldMap';
|
||||
import { distanceSquaredXY, containsPoint, hasFlag } from '../common/utils';
|
||||
@@ -21,509 +21,509 @@ import { setEntityName } from './entityUtils';
|
||||
import { WallController } from './controllers/wallController';
|
||||
|
||||
export interface EntityData {
|
||||
type: string;
|
||||
x: number;
|
||||
y: number;
|
||||
options?: any;
|
||||
name?: string;
|
||||
type: string;
|
||||
x: number;
|
||||
y: number;
|
||||
options?: any;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface MapData {
|
||||
width: number;
|
||||
height: number;
|
||||
tiles?: string;
|
||||
entities?: EntityData[];
|
||||
walls?: string;
|
||||
width: number;
|
||||
height: number;
|
||||
tiles?: string;
|
||||
entities?: EntityData[];
|
||||
walls?: string;
|
||||
}
|
||||
|
||||
export interface MapLoadOptions {
|
||||
offsetX?: number;
|
||||
offsetY?: number;
|
||||
loadOnlyTiles?: boolean;
|
||||
loadEntities?: boolean;
|
||||
loadEntitiesAsEditable?: boolean;
|
||||
loadWalls?: boolean;
|
||||
offsetX?: number;
|
||||
offsetY?: number;
|
||||
loadOnlyTiles?: boolean;
|
||||
loadEntities?: boolean;
|
||||
loadEntitiesAsEditable?: boolean;
|
||||
loadWalls?: boolean;
|
||||
}
|
||||
|
||||
export interface MapSaveOptions {
|
||||
saveTiles?: boolean;
|
||||
saveEntities?: boolean;
|
||||
saveOnlyEditableEntities?: boolean;
|
||||
saveWalls?: boolean;
|
||||
saveTiles?: boolean;
|
||||
saveEntities?: boolean;
|
||||
saveOnlyEditableEntities?: boolean;
|
||||
saveWalls?: boolean;
|
||||
}
|
||||
|
||||
export function createServerMap(
|
||||
id: string, type: MapType, regionsX: number, regionsY: number, defaultTile = TileType.None, usage = MapUsage.Public,
|
||||
initRegions = true
|
||||
id: string, type: MapType, regionsX: number, regionsY: number, defaultTile = TileType.None, usage = MapUsage.Public,
|
||||
initRegions = true
|
||||
): ServerMap {
|
||||
const width = regionsX * REGION_SIZE;
|
||||
const height = regionsY * REGION_SIZE;
|
||||
const regions: ServerRegion[] = [];
|
||||
const state: MapState = { ...defaultMapState };
|
||||
const spawnArea = rect(0, 0, 1, 1);
|
||||
const lockedTiles = new Set<number>();
|
||||
const width = regionsX * REGION_SIZE;
|
||||
const height = regionsY * REGION_SIZE;
|
||||
const regions: ServerRegion[] = [];
|
||||
const state: MapState = { ...defaultMapState };
|
||||
const spawnArea = rect(0, 0, 1, 1);
|
||||
const lockedTiles = new Set<number>();
|
||||
|
||||
if (regionsX <= 0 || regionsY <= 0 || width > POSITION_MAX || height > POSITION_MAX) {
|
||||
throw new Error('Invalid map parameters');
|
||||
}
|
||||
if (regionsX <= 0 || regionsY <= 0 || width > POSITION_MAX || height > POSITION_MAX) {
|
||||
throw new Error('Invalid map parameters');
|
||||
}
|
||||
|
||||
if (initRegions) {
|
||||
for (let ry = 0; ry < regionsY; ry++) {
|
||||
for (let rx = 0; rx < regionsX; rx++) {
|
||||
regions.push(createServerRegion(rx, ry, defaultTile));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (initRegions) {
|
||||
for (let ry = 0; ry < regionsY; ry++) {
|
||||
for (let rx = 0; rx < regionsX; rx++) {
|
||||
regions.push(createServerRegion(rx, ry, defaultTile));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id, usage, type, flags: MapFlags.None, width, height, state, regions, regionsX, regionsY, defaultTile, spawnArea,
|
||||
lockedTiles, spawns: new Map(), instance: undefined, lastUsed: Date.now(), controllers: [],
|
||||
dontUpdateTilesAndColliders: false, tilesLocked: false, editableEntityLimit: 0, editingLocked: false,
|
||||
};
|
||||
return {
|
||||
id, usage, type, flags: MapFlags.None, width, height, state, regions, regionsX, regionsY, defaultTile, spawnArea,
|
||||
lockedTiles, spawns: new Map(), instance: undefined, lastUsed: Date.now(), controllers: [],
|
||||
dontUpdateTilesAndColliders: false, tilesLocked: false, editableEntityLimit: 0, editingLocked: false,
|
||||
};
|
||||
}
|
||||
|
||||
export function serverMapInstanceFromTemplate(map: ServerMap): ServerMap {
|
||||
const {
|
||||
id, usage, type, flags, width, height, state, regionsX, regionsY, defaultTile, spawnArea, lockedTiles,
|
||||
editableEntityLimit
|
||||
} = map;
|
||||
const {
|
||||
id, usage, type, flags, width, height, state, regionsX, regionsY, defaultTile, spawnArea, lockedTiles,
|
||||
editableEntityLimit
|
||||
} = map;
|
||||
|
||||
return {
|
||||
id, usage, type, flags, width, height, state: { ...state },
|
||||
regions: map.regions.map(cloneServerRegion),
|
||||
regionsX, regionsY, defaultTile, spawnArea,
|
||||
lockedTiles, spawns: new Map(), instance: undefined, lastUsed: Date.now(), controllers: [],
|
||||
dontUpdateTilesAndColliders: true, tilesLocked: map.tilesLocked,
|
||||
editableEntityLimit, editingLocked: false,
|
||||
};
|
||||
return {
|
||||
id, usage, type, flags, width, height, state: { ...state },
|
||||
regions: map.regions.map(cloneServerRegion),
|
||||
regionsX, regionsY, defaultTile, spawnArea,
|
||||
lockedTiles, spawns: new Map(), instance: undefined, lastUsed: Date.now(), controllers: [],
|
||||
dontUpdateTilesAndColliders: true, tilesLocked: map.tilesLocked,
|
||||
editableEntityLimit, editingLocked: false,
|
||||
};
|
||||
}
|
||||
|
||||
export function copyMapTiles(target: ServerMap, source: ServerMap) {
|
||||
for (let i = 0; i < target.regions.length; i++) {
|
||||
const srcRegion = source.regions[i];
|
||||
const tgtRegion = target.regions[i];
|
||||
tgtRegion.tiles.set(srcRegion.tiles);
|
||||
tgtRegion.tileIndices.set(srcRegion.tileIndices);
|
||||
tgtRegion.encodedTiles = srcRegion.encodedTiles;
|
||||
tgtRegion.colliderDirty = true;
|
||||
}
|
||||
for (let i = 0; i < target.regions.length; i++) {
|
||||
const srcRegion = source.regions[i];
|
||||
const tgtRegion = target.regions[i];
|
||||
tgtRegion.tiles.set(srcRegion.tiles);
|
||||
tgtRegion.tileIndices.set(srcRegion.tileIndices);
|
||||
tgtRegion.encodedTiles = srcRegion.encodedTiles;
|
||||
tgtRegion.colliderDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
export function getMapInfo(map: ServerMap): MapInfo {
|
||||
return {
|
||||
type: map.type,
|
||||
flags: map.flags,
|
||||
regionsX: map.regionsX,
|
||||
regionsY: map.regionsY,
|
||||
defaultTile: map.defaultTile,
|
||||
editableArea: map.editableArea,
|
||||
};
|
||||
return {
|
||||
type: map.type,
|
||||
flags: map.flags,
|
||||
regionsX: map.regionsX,
|
||||
regionsY: map.regionsY,
|
||||
defaultTile: map.defaultTile,
|
||||
editableArea: map.editableArea,
|
||||
};
|
||||
}
|
||||
|
||||
export function getSizeOfMap(map: ServerMap) {
|
||||
const memory = map.regions.reduce((sum, r) => sum + getSizeOfRegion(r), 0);
|
||||
const entities = map.regions.reduce((sum, r) => sum + r.entities.length, 0);
|
||||
return { memory, entities };
|
||||
const memory = map.regions.reduce((sum, r) => sum + getSizeOfRegion(r), 0);
|
||||
const entities = map.regions.reduce((sum, r) => sum + r.entities.length, 0);
|
||||
return { memory, entities };
|
||||
}
|
||||
|
||||
export function setTile(map: ServerMap, x: number, y: number, type: TileType) {
|
||||
const region = getRegionGlobal(map, x, y);
|
||||
const region = getRegionGlobal(map, x, y);
|
||||
|
||||
if (region) {
|
||||
const regionX = Math.floor(x) - region.x * REGION_SIZE;
|
||||
const regionY = Math.floor(y) - region.y * REGION_SIZE;
|
||||
setRegionTile(map, region, regionX, regionY, type);
|
||||
}
|
||||
if (region) {
|
||||
const regionX = Math.floor(x) - region.x * REGION_SIZE;
|
||||
const regionY = Math.floor(y) - region.y * REGION_SIZE;
|
||||
setRegionTile(map, region, regionX, regionY, type);
|
||||
}
|
||||
}
|
||||
|
||||
export function snapshotTiles(map: ServerMap) {
|
||||
for (const region of map.regions) {
|
||||
snapshotRegionTiles(region);
|
||||
}
|
||||
for (const region of map.regions) {
|
||||
snapshotRegionTiles(region);
|
||||
}
|
||||
}
|
||||
|
||||
export function lockTile(map: ServerMap, x: number, y: number) {
|
||||
const index = ((x | 0) + (y | 0) * map.width) | 0;
|
||||
map.lockedTiles.add(index);
|
||||
const index = ((x | 0) + (y | 0) * map.width) | 0;
|
||||
map.lockedTiles.add(index);
|
||||
}
|
||||
|
||||
export function lockTiles(map: ServerMap, x: number, y: number, w: number, h: number) {
|
||||
for (let iy = 0; iy < h; iy++) {
|
||||
for (let ix = 0; ix < w; ix++) {
|
||||
lockTile(map, x + ix, y + iy);
|
||||
}
|
||||
}
|
||||
for (let iy = 0; iy < h; iy++) {
|
||||
for (let ix = 0; ix < w; ix++) {
|
||||
lockTile(map, x + ix, y + iy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function isTileLocked(map: ServerMap, x: number, y: number) {
|
||||
const index = ((x | 0) + (y | 0) * map.width) | 0;
|
||||
return map.lockedTiles.has(index);
|
||||
const index = ((x | 0) + (y | 0) * map.width) | 0;
|
||||
return map.lockedTiles.has(index);
|
||||
}
|
||||
|
||||
export function serializeTiles(map: ServerMap) {
|
||||
const tilesData: number[] = [];
|
||||
const data: number[] = [];
|
||||
const { width, height } = map;
|
||||
const tilesData: number[] = [];
|
||||
const data: number[] = [];
|
||||
const { width, height } = map;
|
||||
|
||||
for (let y = 0; y < height; y++) {
|
||||
for (let x = 0; x < width; x++) {
|
||||
tilesData.push(getTile(map, x, y));
|
||||
}
|
||||
}
|
||||
for (let y = 0; y < height; y++) {
|
||||
for (let x = 0; x < width; x++) {
|
||||
tilesData.push(getTile(map, x, y));
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < tilesData.length; i++) {
|
||||
const tile = tilesData[i];
|
||||
let count = 1;
|
||||
for (let i = 0; i < tilesData.length; i++) {
|
||||
const tile = tilesData[i];
|
||||
let count = 1;
|
||||
|
||||
while (tilesData.length > (i + 1) && tilesData[i + 1] === tile && count < 255) {
|
||||
count++;
|
||||
i++;
|
||||
}
|
||||
while (tilesData.length > (i + 1) && tilesData[i + 1] === tile && count < 255) {
|
||||
count++;
|
||||
i++;
|
||||
}
|
||||
|
||||
data.push(count, tile);
|
||||
}
|
||||
data.push(count, tile);
|
||||
}
|
||||
|
||||
return new Uint8Array(data);
|
||||
return new Uint8Array(data);
|
||||
}
|
||||
|
||||
export function serializeMap(map: ServerMap): MapData {
|
||||
const { width, height } = map;
|
||||
const tiles = fromByteArray(serializeTiles(map));
|
||||
return { width, height, tiles };
|
||||
const { width, height } = map;
|
||||
const tiles = fromByteArray(serializeTiles(map));
|
||||
return { width, height, tiles };
|
||||
}
|
||||
|
||||
export function deserializeMap(map: ServerMap, { tiles, width }: MapData, { offsetX = 0, offsetY = 0 }: MapLoadOptions = {}) {
|
||||
const decodedTiles = deserializeTiles(tiles!);
|
||||
const decodedTiles = deserializeTiles(tiles!);
|
||||
|
||||
for (let i = 0; i < decodedTiles.length; i++) {
|
||||
const x = i % width;
|
||||
const y = Math.floor(i / width);
|
||||
setTile(map, x + offsetX, y + offsetY, decodedTiles[i]);
|
||||
}
|
||||
for (let i = 0; i < decodedTiles.length; i++) {
|
||||
const x = i % width;
|
||||
const y = Math.floor(i / width);
|
||||
setTile(map, x + offsetX, y + offsetY, decodedTiles[i]);
|
||||
}
|
||||
}
|
||||
|
||||
export function saveMap(map: ServerMap, saveOptions: MapSaveOptions): MapData {
|
||||
const data: MapData = { width: map.width, height: map.height };
|
||||
const data: MapData = { width: map.width, height: map.height };
|
||||
|
||||
if (saveOptions.saveTiles) {
|
||||
data.tiles = serializeMap(map).tiles;
|
||||
}
|
||||
if (saveOptions.saveTiles) {
|
||||
data.tiles = serializeMap(map).tiles;
|
||||
}
|
||||
|
||||
if (saveOptions.saveEntities) {
|
||||
data.entities = [];
|
||||
if (saveOptions.saveEntities) {
|
||||
data.entities = [];
|
||||
|
||||
for (const region of map.regions) {
|
||||
for (const entity of region.entities) {
|
||||
if (!hasFlag(entity.serverFlags, ServerFlags.DoNotSave) && !hasFlag(entity.flags, EntityFlags.Debug)) {
|
||||
if (saveOptions.saveOnlyEditableEntities && !hasFlag(entity.state, EntityState.Editable))
|
||||
continue;
|
||||
for (const region of map.regions) {
|
||||
for (const entity of region.entities) {
|
||||
if (!hasFlag(entity.serverFlags, ServerFlags.DoNotSave) && !hasFlag(entity.flags, EntityFlags.Debug)) {
|
||||
if (saveOptions.saveOnlyEditableEntities && !hasFlag(entity.state, EntityState.Editable))
|
||||
continue;
|
||||
|
||||
const options = entity.options && Object.keys(entity.options).length > 0 ? entity.options : undefined;
|
||||
const name = entity.name;
|
||||
data.entities.push({ type: getEntityTypeName(entity.type), x: entity.x, y: entity.y, options, name });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const options = entity.options && Object.keys(entity.options).length > 0 ? entity.options : undefined;
|
||||
const name = entity.name;
|
||||
data.entities.push({ type: getEntityTypeName(entity.type), x: entity.x, y: entity.y, options, name });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (saveOptions.saveWalls) {
|
||||
const controller = map.controllers.find(c => c.toggleWall) as WallController | undefined;
|
||||
if (saveOptions.saveWalls) {
|
||||
const controller = map.controllers.find(c => c.toggleWall) as WallController | undefined;
|
||||
|
||||
if (controller) {
|
||||
data.walls = controller.serialize();
|
||||
}
|
||||
}
|
||||
if (controller) {
|
||||
data.walls = controller.serialize();
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function saveMapToFile(map: ServerMap, fileName: string, options: MapSaveOptions) {
|
||||
const data = saveMap(map, options);
|
||||
const json = JSON.stringify(data, null, 2);
|
||||
await writeFileAsync(fileName, json, 'utf8');
|
||||
const data = saveMap(map, options);
|
||||
const json = JSON.stringify(data, null, 2);
|
||||
await writeFileAsync(fileName, json, 'utf8');
|
||||
}
|
||||
|
||||
export async function saveMapToFileBinary(map: ServerMap, fileName: string) {
|
||||
const tiles = serializeTiles(map);
|
||||
const buffer = new Uint8Array(4 + 4 + tiles.byteLength);
|
||||
const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
||||
view.setInt32(0, map.width, true);
|
||||
view.setInt32(4, map.height, true);
|
||||
buffer.set(tiles, 8);
|
||||
await writeFileAsync(fileName, Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength));
|
||||
const tiles = serializeTiles(map);
|
||||
const buffer = new Uint8Array(4 + 4 + tiles.byteLength);
|
||||
const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
||||
view.setInt32(0, map.width, true);
|
||||
view.setInt32(4, map.height, true);
|
||||
buffer.set(tiles, 8);
|
||||
await writeFileAsync(fileName, Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength));
|
||||
}
|
||||
|
||||
export async function saveMapToFileBinaryAlt(map: ServerMap, fileName: string) {
|
||||
const buffer = Buffer.alloc(4 + 4 + map.width * map.height);
|
||||
buffer.writeUInt32LE(map.width, 0);
|
||||
buffer.writeUInt32LE(map.height, 4);
|
||||
const buffer = Buffer.alloc(4 + 4 + map.width * map.height);
|
||||
buffer.writeUInt32LE(map.width, 0);
|
||||
buffer.writeUInt32LE(map.height, 4);
|
||||
|
||||
for (let y = 0, i = 8; y < map.height; y++) {
|
||||
for (let x = 0; x < map.width; x++ , i++) {
|
||||
buffer.writeUInt8(getTile(map, x, y), i);
|
||||
}
|
||||
}
|
||||
for (let y = 0, i = 8; y < map.height; y++) {
|
||||
for (let x = 0; x < map.width; x++ , i++) {
|
||||
buffer.writeUInt8(getTile(map, x, y), i);
|
||||
}
|
||||
}
|
||||
|
||||
await writeFileAsync(fileName, buffer);
|
||||
await writeFileAsync(fileName, buffer);
|
||||
}
|
||||
|
||||
export async function saveEntitiesToFile(map: ServerMap, fileName: string) {
|
||||
const lines: string[] = [];
|
||||
const lines: string[] = [];
|
||||
|
||||
for (const region of map.regions) {
|
||||
for (const entity of region.entities) {
|
||||
lines.push(`${getEntityTypeName(entity.type)} ${entity.x} ${entity.y}`);
|
||||
}
|
||||
}
|
||||
for (const region of map.regions) {
|
||||
for (const entity of region.entities) {
|
||||
lines.push(`${getEntityTypeName(entity.type)} ${entity.x} ${entity.y}`);
|
||||
}
|
||||
}
|
||||
|
||||
await writeFileAsync(fileName, lines.join('\n'), 'utf8');
|
||||
await writeFileAsync(fileName, lines.join('\n'), 'utf8');
|
||||
}
|
||||
|
||||
export function loadMap(world: World, map: ServerMap, data: MapData, loadOptions: MapLoadOptions) {
|
||||
if (data.tiles) {
|
||||
deserializeMap(map, data, loadOptions);
|
||||
}
|
||||
if (data.tiles) {
|
||||
deserializeMap(map, data, loadOptions);
|
||||
}
|
||||
|
||||
if (loadOptions.loadOnlyTiles)
|
||||
return;
|
||||
if (loadOptions.loadOnlyTiles)
|
||||
return;
|
||||
|
||||
if (loadOptions.loadEntitiesAsEditable) {
|
||||
const entitiesToRemove: ServerEntity[] = [];
|
||||
if (loadOptions.loadEntitiesAsEditable) {
|
||||
const entitiesToRemove: ServerEntity[] = [];
|
||||
|
||||
for (const region of map.regions) {
|
||||
for (const entity of region.entities) {
|
||||
if (hasFlag(entity.state, EntityState.Editable)) {
|
||||
entitiesToRemove.push(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const region of map.regions) {
|
||||
for (const entity of region.entities) {
|
||||
if (hasFlag(entity.state, EntityState.Editable)) {
|
||||
entitiesToRemove.push(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const entity of entitiesToRemove) {
|
||||
world.removeEntity(entity, map);
|
||||
}
|
||||
}
|
||||
for (const entity of entitiesToRemove) {
|
||||
world.removeEntity(entity, map);
|
||||
}
|
||||
}
|
||||
|
||||
if (loadOptions.loadEntities && data.entities) {
|
||||
for (const { x, y, type, name, options } of data.entities) {
|
||||
const typeNumber = getEntityType(type);
|
||||
const entity = createAnEntity(typeNumber, 0, x, y, options, mockPaletteManager, world);
|
||||
if (loadOptions.loadEntities && data.entities) {
|
||||
for (const { x, y, type, name, options } of data.entities) {
|
||||
const typeNumber = getEntityType(type);
|
||||
const entity = createAnEntity(typeNumber, 0, x, y, options, mockPaletteManager, world);
|
||||
|
||||
if (name) {
|
||||
setEntityName(entity, name);
|
||||
}
|
||||
if (name) {
|
||||
setEntityName(entity, name);
|
||||
}
|
||||
|
||||
if (loadOptions.loadEntitiesAsEditable) {
|
||||
entity.state |= EntityState.Editable;
|
||||
}
|
||||
if (loadOptions.loadEntitiesAsEditable) {
|
||||
entity.state |= EntityState.Editable;
|
||||
}
|
||||
|
||||
world.addEntity(entity, map);
|
||||
}
|
||||
}
|
||||
world.addEntity(entity, map);
|
||||
}
|
||||
}
|
||||
|
||||
if (loadOptions.loadWalls && data.walls) {
|
||||
const controller = map.controllers.find(c => c.toggleWall) as WallController | undefined;
|
||||
if (loadOptions.loadWalls && data.walls) {
|
||||
const controller = map.controllers.find(c => c.toggleWall) as WallController | undefined;
|
||||
|
||||
if (controller) {
|
||||
controller.deserialize(data.width, data.height, data.walls);
|
||||
}
|
||||
}
|
||||
if (controller) {
|
||||
controller.deserialize(data.width, data.height, data.walls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadMapFromFile(world: World, map: ServerMap, fileName: string, options: MapLoadOptions) {
|
||||
const json = await readFileAsync(fileName, 'utf8');
|
||||
const data = JSON.parse(json);
|
||||
loadMap(world, map, data, options);
|
||||
const json = await readFileAsync(fileName, 'utf8');
|
||||
const data = JSON.parse(json);
|
||||
loadMap(world, map, data, options);
|
||||
}
|
||||
|
||||
export function saveRegionCollider(region: ServerRegion) {
|
||||
const canvas = createCanvas(REGION_WIDTH, REGION_HEIGHT);
|
||||
const context = canvas.getContext('2d')!;
|
||||
context.fillStyle = 'white';
|
||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
const canvas = createCanvas(REGION_WIDTH, REGION_HEIGHT);
|
||||
const context = canvas.getContext('2d')!;
|
||||
context.fillStyle = 'white';
|
||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
context.fillStyle = '#eee';
|
||||
context.fillStyle = '#eee';
|
||||
|
||||
for (let y = 0, i = 0; y < REGION_SIZE; y++ , i++) {
|
||||
for (let x = 0; x < REGION_SIZE; x++ , i++) {
|
||||
if ((i % 2) === 0) {
|
||||
context.fillRect(x * tileWidth, y * tileHeight, tileWidth, tileHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let y = 0, i = 0; y < REGION_SIZE; y++ , i++) {
|
||||
for (let x = 0; x < REGION_SIZE; x++ , i++) {
|
||||
if ((i % 2) === 0) {
|
||||
context.fillRect(x * tileWidth, y * tileHeight, tileWidth, tileHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.globalAlpha = 0.8;
|
||||
context.fillStyle = 'red';
|
||||
context.globalAlpha = 0.8;
|
||||
context.fillStyle = 'red';
|
||||
|
||||
for (let y = 0; y < REGION_HEIGHT; y++) {
|
||||
for (let x = 0; x < REGION_WIDTH; x++) {
|
||||
if (region.collider[x + y * REGION_WIDTH]) {
|
||||
context.fillRect(x, y, 1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let y = 0; y < REGION_HEIGHT; y++) {
|
||||
for (let x = 0; x < REGION_WIDTH; x++) {
|
||||
if (region.collider[x + y * REGION_WIDTH]) {
|
||||
context.fillRect(x, y, 1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writeFileSync(pathTo('store', 'collider.png'), canvas.toBuffer());
|
||||
writeFileSync(pathTo('store', 'collider.png'), canvas.toBuffer());
|
||||
}
|
||||
|
||||
function distanceSquaredToRegion(x: number, y: number, region: ServerRegion) {
|
||||
const left = region.x * REGION_SIZE;
|
||||
const top = region.y * REGION_SIZE;
|
||||
const right = left + REGION_SIZE;
|
||||
const bottom = top + REGION_SIZE;
|
||||
const dx = x < left ? (left - x) : (x > right ? (x - right) : 0);
|
||||
const dy = y < left ? (top - y) : (y > bottom ? (y - bottom) : 0);
|
||||
return dx * dx + dy * dy;
|
||||
const left = region.x * REGION_SIZE;
|
||||
const top = region.y * REGION_SIZE;
|
||||
const right = left + REGION_SIZE;
|
||||
const bottom = top + REGION_SIZE;
|
||||
const dx = x < left ? (left - x) : (x > right ? (x - right) : 0);
|
||||
const dy = y < left ? (top - y) : (y > bottom ? (y - bottom) : 0);
|
||||
return dx * dx + dy * dy;
|
||||
}
|
||||
|
||||
export function findClosestEntity(
|
||||
map: ServerMap, originX: number, originY: number, predicate: (entity: ServerEntity) => boolean
|
||||
map: ServerMap, originX: number, originY: number, predicate: (entity: ServerEntity) => boolean
|
||||
): ServerEntity | undefined {
|
||||
let minX = Math.floor(originX / REGION_SIZE);
|
||||
let minY = Math.floor(originY / REGION_SIZE);
|
||||
let maxX = minX;
|
||||
let maxY = minY;
|
||||
let minX = Math.floor(originX / REGION_SIZE);
|
||||
let minY = Math.floor(originY / REGION_SIZE);
|
||||
let maxX = minX;
|
||||
let maxY = minY;
|
||||
|
||||
let closest: ServerEntity | undefined = undefined;
|
||||
let closestDist = Number.MAX_VALUE;
|
||||
let closest: ServerEntity | undefined = undefined;
|
||||
let closestDist = Number.MAX_VALUE;
|
||||
|
||||
while (minX >= 0 || minY >= 0 || maxX < map.regionsX || maxY < map.regionsY) {
|
||||
let regionsChecked = 0;
|
||||
while (minX >= 0 || minY >= 0 || maxX < map.regionsX || maxY < map.regionsY) {
|
||||
let regionsChecked = 0;
|
||||
|
||||
for (let y = minY; y <= maxY; y++) {
|
||||
for (let x = minX; x <= maxX; x = (x === maxX || y === minY || y === maxY) ? x + 1 : maxX) {
|
||||
if (x >= 0 && y >= 0 && x < map.regionsX && y < map.regionsY) {
|
||||
const region = getRegion(map, x, y);
|
||||
for (let y = minY; y <= maxY; y++) {
|
||||
for (let x = minX; x <= maxX; x = (x === maxX || y === minY || y === maxY) ? x + 1 : maxX) {
|
||||
if (x >= 0 && y >= 0 && x < map.regionsX && y < map.regionsY) {
|
||||
const region = getRegion(map, x, y);
|
||||
|
||||
if (distanceSquaredToRegion(originX, originY, region) < closestDist) {
|
||||
regionsChecked++;
|
||||
if (distanceSquaredToRegion(originX, originY, region) < closestDist) {
|
||||
regionsChecked++;
|
||||
|
||||
for (const entity of region.entities) {
|
||||
if (predicate(entity)) {
|
||||
const dist = distanceSquaredXY(originX, originY, entity.x, entity.y);
|
||||
for (const entity of region.entities) {
|
||||
if (predicate(entity)) {
|
||||
const dist = distanceSquaredXY(originX, originY, entity.x, entity.y);
|
||||
|
||||
if (dist < closestDist) {
|
||||
closest = entity;
|
||||
closestDist = dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dist < closestDist) {
|
||||
closest = entity;
|
||||
closestDist = dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!regionsChecked) {
|
||||
break;
|
||||
}
|
||||
if (!regionsChecked) {
|
||||
break;
|
||||
}
|
||||
|
||||
minX -= 1;
|
||||
minY -= 1;
|
||||
maxX += 1;
|
||||
maxY += 1;
|
||||
}
|
||||
minX -= 1;
|
||||
minY -= 1;
|
||||
maxX += 1;
|
||||
maxY += 1;
|
||||
}
|
||||
|
||||
return closest;
|
||||
return closest;
|
||||
}
|
||||
|
||||
export function findEntities(map: ServerMap, predicate: (entity: ServerEntity) => boolean): ServerEntity[] {
|
||||
const entities: ServerEntity[] = [];
|
||||
const entities: ServerEntity[] = [];
|
||||
|
||||
for (const region of map.regions) {
|
||||
for (const entity of region.entities) {
|
||||
if (predicate(entity)) {
|
||||
entities.push(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const region of map.regions) {
|
||||
for (const entity of region.entities) {
|
||||
if (predicate(entity)) {
|
||||
entities.push(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entities;
|
||||
return entities;
|
||||
}
|
||||
|
||||
// TODO: maybe only regions in bounds, instead of adding 1 region border ?
|
||||
function forEachRegionInBounds(map: ServerMap, bounds: Rect, callback: (region: ServerRegion) => void) {
|
||||
const minX = Math.max(0, Math.floor(bounds.x / REGION_SIZE) - 1) | 0;
|
||||
const minY = Math.max(0, Math.floor(bounds.y / REGION_SIZE) - 1) | 0;
|
||||
const maxX = Math.min(Math.floor((bounds.x + bounds.w) / REGION_SIZE) + 1, map.regionsX - 1) | 0;
|
||||
const maxY = Math.min(Math.floor((bounds.y + bounds.h) / REGION_SIZE) + 1, map.regionsY - 1) | 0;
|
||||
const minX = Math.max(0, Math.floor(bounds.x / REGION_SIZE) - 1) | 0;
|
||||
const minY = Math.max(0, Math.floor(bounds.y / REGION_SIZE) - 1) | 0;
|
||||
const maxX = Math.min(Math.floor((bounds.x + bounds.w) / REGION_SIZE) + 1, map.regionsX - 1) | 0;
|
||||
const maxY = Math.min(Math.floor((bounds.y + bounds.h) / REGION_SIZE) + 1, map.regionsY - 1) | 0;
|
||||
|
||||
for (let ry = minY; ry <= maxY; ry++) {
|
||||
for (let rx = minX; rx <= maxX; rx++) {
|
||||
const region = getRegion(map, rx, ry);
|
||||
callback(region);
|
||||
}
|
||||
}
|
||||
for (let ry = minY; ry <= maxY; ry++) {
|
||||
for (let rx = minX; rx <= maxX; rx++) {
|
||||
const region = getRegion(map, rx, ry);
|
||||
callback(region);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function findEntitiesInBounds(map: ServerMap, bounds: Rect) {
|
||||
const result: ServerEntity[] = [];
|
||||
const result: ServerEntity[] = [];
|
||||
|
||||
forEachRegionInBounds(map, bounds, region => {
|
||||
for (const entity of region.entities) {
|
||||
if (containsPoint(0, 0, bounds, entity.x, entity.y)) {
|
||||
result.push(entity);
|
||||
}
|
||||
}
|
||||
});
|
||||
forEachRegionInBounds(map, bounds, region => {
|
||||
for (const entity of region.entities) {
|
||||
if (containsPoint(0, 0, bounds, entity.x, entity.y)) {
|
||||
result.push(entity);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
return result;
|
||||
}
|
||||
|
||||
export function updateMapState(map: ServerMap, update: Partial<MapState>) {
|
||||
Object.assign(map.state, update);
|
||||
Object.assign(map.state, update);
|
||||
|
||||
for (const region of map.regions) {
|
||||
for (const client of region.clients) {
|
||||
client.mapUpdate(map.state);
|
||||
}
|
||||
}
|
||||
for (const region of map.regions) {
|
||||
for (const client of region.clients) {
|
||||
client.mapUpdate(map.state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function hasAnyClients(map: ServerMap) {
|
||||
for (const region of map.regions) {
|
||||
if (region.clients.length > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (const region of map.regions) {
|
||||
if (region.clients.length > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
export function createMinimap(world: World, map: ServerMap) {
|
||||
const { width, height } = map;
|
||||
const buffer = new Uint32Array(width * height);
|
||||
const { width, height } = map;
|
||||
const buffer = new Uint32Array(width * height);
|
||||
|
||||
for (let y = 0; y < map.height; y++) {
|
||||
for (let x = 0; x < map.width; x++) {
|
||||
const tile = getTile(map, x, y);
|
||||
buffer[x + y * width] = getTileColor(tile, world.season);
|
||||
}
|
||||
}
|
||||
for (let y = 0; y < map.height; y++) {
|
||||
for (let x = 0; x < map.width; x++) {
|
||||
const tile = getTile(map, x, y);
|
||||
buffer[x + y * width] = getTileColor(tile, world.season);
|
||||
}
|
||||
}
|
||||
|
||||
// map.entities = info.entities
|
||||
// .map(({ type, id, x, y }) => createAnEntity(type, id, x, y, {}, mockPaletteManager));
|
||||
// map.entities = info.entities
|
||||
// .map(({ type, id, x, y }) => createAnEntity(type, id, x, y, {}, mockPaletteManager));
|
||||
|
||||
// for (let i = 1; i <= 2; i++) {
|
||||
// for (const e of map.entities) {
|
||||
// if (e.minimap && e.minimap.order === i) {
|
||||
// const { color, rect } = e.minimap;
|
||||
// mapContext.fillStyle = colorToCSS(color);
|
||||
// mapContext.fillRect(Math.round(e.x + rect.x), Math.round(e.y + rect.y), rect.w, rect.h);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// for (let i = 1; i <= 2; i++) {
|
||||
// for (const e of map.entities) {
|
||||
// if (e.minimap && e.minimap.order === i) {
|
||||
// const { color, rect } = e.minimap;
|
||||
// mapContext.fillStyle = colorToCSS(color);
|
||||
// mapContext.fillRect(Math.round(e.x + rect.x), Math.round(e.y + rect.y), rect.w, rect.h);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// canvas.width = mapCanvas.width * scale;
|
||||
// canvas.height = mapCanvas.height * scale;
|
||||
// const context = canvas.getContext('2d')!;
|
||||
// context.save();
|
||||
// canvas.width = mapCanvas.width * scale;
|
||||
// canvas.height = mapCanvas.height * scale;
|
||||
// const context = canvas.getContext('2d')!;
|
||||
// context.save();
|
||||
|
||||
// if (scale >= 1) {
|
||||
// disableImageSmoothing(context);
|
||||
// }
|
||||
// if (scale >= 1) {
|
||||
// disableImageSmoothing(context);
|
||||
// }
|
||||
|
||||
// context.scale(scale, scale);
|
||||
// context.drawImage(mapCanvas, 0, 0);
|
||||
// context.restore();
|
||||
// context.scale(scale, scale);
|
||||
// context.drawImage(mapCanvas, 0, 0);
|
||||
// context.restore();
|
||||
|
||||
return new Uint8Array(buffer.buffer);
|
||||
return new Uint8Array(buffer.buffer);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user