mirror of
https://github.com/Terncode/pixel.horse.git
synced 2026-09-25 06:05:52 +02:00
295 lines
8.8 KiB
TypeScript
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];
|
|
}
|
|
}
|