Files
pixel.horse/src/ts/server/mapUtils.ts
T
2019-08-28 21:15:02 -07:00

295 lines
8.8 KiB
TypeScript

import { ServerEntity, ServerMap, IClient } from './serverInterfaces';
import { World } from './world';
import { Rect, SignEntityOptions, MessageType, CreateEntityMethod, PonyOptions, Point } from '../common/interfaces';
import { roundPosition } from '../common/positionUtils';
import { getRegionGlobal } from '../common/worldMap';
import { addEntityToRegion, getRegionTiles, removeEntityFromRegion } from './serverRegion';
import * as entities from '../common/entities';
import { updateTileIndices } from '../client/tileUtils';
import { generateRegionCollider } from '../common/region';
import { PONY_TYPE, tileWidth, tileHeight } from '../common/constants';
import { sayTo, saySystem } from './chat';
import { setEntityName, updateEntityVelocity, setEntityAnimation } from './entityUtils';
import { updateAccountState } from './accountUtils';
import { toInt, includes } from '../common/utils';
import { holdItem } from './playerUtils';
import { random, sample, clamp } from 'lodash';
import { findEntities } from './serverMap';
import { randomPosition } from './controllers/collectableController';
import { BunnyAnimation } from '../common/entities';
export const worldForTemplates: any = {
featureFlags: {},
addEntity(entity: ServerEntity, map: ServerMap) {
roundPosition(entity);
const region = getRegionGlobal(map, entity.x, entity.y);
entity.region = region;
addEntityToRegion(region, entity, map);
return entity;
},
removeEntity(entity: ServerEntity, map: ServerMap) {
let removed = false;
if (entity.region) {
removed = removeEntityFromRegion(entity.region, entity, map);
}
return removed;
},
};
export function addSpawnPointIndicators(world: World, map: ServerMap) {
const addSpawn = ({ x, y, w, h }: Rect) => {
world.addEntity(entities.spawnPole(x, y), map);
if (w && h) {
world.addEntity(entities.spawnPole(x + w, y), map);
world.addEntity(entities.spawnPole(x, y + h), map);
world.addEntity(entities.spawnPole(x + w, y + h), map);
}
};
addSpawn(map.spawnArea);
for (const spawn of Array.from(map.spawns.values())) {
addSpawn(spawn);
}
}
export function generateTileIndicesAndColliders(map: ServerMap) {
for (const region of map.regions) {
getRegionTiles(region); // initialize encodedTiles
if (region.tilesDirty) {
updateTileIndices(region, map);
}
}
for (const region of map.regions) {
if (region.colliderDirty) {
generateRegionCollider(region, map);
}
}
}
export function removePonies(entities: ServerEntity[]) {
for (let i = entities.length - 1; i >= 0; i--) {
if (entities[i].type === PONY_TYPE) {
entities.splice(i, 1);
}
}
}
export interface SignDirection {
icon: number;
name: string;
}
export interface SignConfig {
r?: number;
w?: (SignDirection | undefined)[];
e?: (SignDirection | undefined)[];
n?: (SignDirection | undefined)[];
s?: (SignDirection | undefined)[];
}
export function createDirectionSign(x: number, y: number, config: SignConfig) {
const result: ServerEntity[] = [];
const options: SignEntityOptions = { sign: {} };
const lines: string[] = [];
const { w = [], e = [], s = [], n = [] } = config;
const max = clamp(Math.max(w.length, e.length, s.length, n.length), 3, 5);
const skip = 5 - max;
function parse(entries: (SignDirection | undefined)[], arrow: string, plates: CreateEntityMethod[], ox: number) {
for (let i = 0; i < entries.length; i++) {
const e = entries[i];
if (e) {
lines.push(`${arrow} ${e.name}`);
const nameplate = plates[i](x + ox / tileWidth, y);
setEntityName(nameplate, e.name);
result.push(nameplate);
}
}
}
if (config.r) {
options.sign.r = config.r;
}
const ups = config.r ? entities.directionSignUpsRight : entities.directionSignUpsLeft;
const downs = config.r ? entities.directionSignDownsLeft : entities.directionSignDownsRight;
if (config.n) {
options.sign.n = config.n.map(x => x ? x.icon : -1);
parse(config.n, '↑', ups.slice(skip), 0);
}
if (config.w) {
options.sign.w = config.w.map(x => x ? x.icon : -1);
parse(config.w, '←', entities.directionSignLefts.slice(skip), -10);
}
if (config.e) {
options.sign.e = config.e.map(x => x ? x.icon : -1);
parse(config.e, '→', entities.directionSignRights.slice(skip), 10);
}
if (config.s) {
options.sign.s = config.s.map(x => x ? x.icon : -1);
parse(config.s, '↓', downs.slice(skip), 0);
}
const text = lines.join('\n');
const entity = entities.directionSign(x, y, options) as ServerEntity;
entity.interact = (entity, client) => sayTo(client, entity, text, MessageType.System);
result.push(entity);
return result;
}
const patchTypes = [
entities.cloverPatch3, entities.cloverPatch4, entities.cloverPatch5, entities.cloverPatch6, entities.cloverPatch7
].map(x => x.type);
const eggBasketTypes = entities.eggBaskets.map(b => b.type);
export function pickCandy(client: IClient) {
let count = 0;
updateAccountState(client.account, state => state.candies = count = toInt(state.candies) + 1);
saySystem(client, `${count} 🍬`);
}
export function pickGift(client: IClient) {
let count = 0;
updateAccountState(client.account, state => state.gifts = count = toInt(state.gifts) + 1);
saySystem(client, `${count} 🎁`);
holdItem(client.pony, entities.gift2.type);
}
export function pickClover(client: IClient) {
let count = 0;
updateAccountState(client.account, state => state.clovers = count = toInt(state.clovers) + 1);
saySystem(client, `${count} 🍀`);
holdItem(client.pony, entities.cloverPick.type);
}
export function pickEgg(client: IClient) {
let count = 0;
updateAccountState(client.account, state => state.eggs = count = toInt(state.eggs) + 1);
saySystem(client, `${count} 🥚`);
if (Math.random() < 0.05) {
const options = client.pony.options as PonyOptions;
const basketIndex = eggBasketTypes.indexOf(options.hold || 0);
if (basketIndex >= 0 && basketIndex < (eggBasketTypes.length - 1)) {
holdItem(client.pony, eggBasketTypes[basketIndex + 1]);
}
}
}
export function pickEntity(client: IClient, entity: ServerEntity) {
holdItem(client.pony, entity.type);
}
export function checkLantern(client: IClient) {
const options = client.pony.options as PonyOptions;
const canPick = options.hold === entities.jackoLanternOn.type || options.hold === entities.jackoLanternOff.type;
if (!canPick) {
saySystem(client, 'Get a lantern to collect candies');
}
return canPick;
}
export function checkBasket(client: IClient) {
const options = client.pony.options as PonyOptions;
const canPick = includes(eggBasketTypes, options.hold);
if (!canPick) {
saySystem(client, 'Get a basket to collect eggs');
}
return canPick;
}
export function checkNotCollecting(client: IClient) {
const options = client.pony.options as PonyOptions;
const canPick = includes(eggBasketTypes, options.hold) ||
options.hold === entities.jackoLanternOn.type ||
options.hold === entities.jackoLanternOff.type;
return !canPick;
}
export function positionClover(map: ServerMap) {
const patch = sample(findEntities(map, e => includes(patchTypes, e.type)));
if (patch && patch.bounds) {
const bounds = patch.bounds;
const position = {
x: patch.x + bounds.x / tileWidth + random(0, bounds.w / tileWidth, true),
y: patch.y + bounds.y / tileHeight + random(0, bounds.h / tileHeight, true),
};
return position;
} else {
return randomPosition(map);
}
}
export function createBunny(waypoints: Point[]) {
const { x, y } = waypoints[0];
const entity = entities.bunny(x, y) as ServerEntity;
const bunnySpeed = 2;
let waypoint = 0;
let sleepUntil = 0;
entity.serverUpdate = (_delta, now) => {
if (sleepUntil > now)
return;
const { x, y } = waypoints[waypoint];
const reachedX = Math.abs(entity.x - x) < 0.2;
const reachedY = Math.abs(entity.y - y) < 0.2;
if (reachedX && reachedY) {
const rand = Math.random();
updateEntityVelocity(entity, 0, 0, now);
if (rand < 0.1) {
setEntityAnimation(entity, BunnyAnimation.Clean);
sleepUntil = now + 2;
} else if (rand < 0.2) {
setEntityAnimation(entity, BunnyAnimation.Look);
sleepUntil = now + 2;
} else if (rand < 0.3) {
setEntityAnimation(entity, BunnyAnimation.Blink);
sleepUntil = now + 2;
} else if (rand < 0.6) {
setEntityAnimation(entity, BunnyAnimation.Sit);
sleepUntil = now + 2;
} else {
waypoint = (waypoint + 1) % waypoints.length;
setEntityAnimation(entity, BunnyAnimation.Sit);
sleepUntil = now + random(0.2, 2, true);
}
} else {
const vx = reachedX ? 0 : (x < entity.x ? -bunnySpeed : bunnySpeed);
const vy = reachedY ? 0 : (y < entity.y ? -bunnySpeed : bunnySpeed);
if (entity.vx !== vx || entity.vy !== vy) {
updateEntityVelocity(entity, vx, vy, now);
setEntityAnimation(entity, BunnyAnimation.Walk, vx === 0 ? undefined : vx > 0);
}
}
};
if (DEVELOPMENT && false) {
return [entity, ...waypoints.map(({ x, y }) => entities.routePole(x, y))];
} else {
return [entity];
}
}