mirror of
https://github.com/Terncode/pixel.horse.git
synced 2026-09-24 13:55:04 +02:00
208 lines
6.4 KiB
TypeScript
208 lines
6.4 KiB
TypeScript
import { compact } from 'lodash';
|
|
import {
|
|
Entity, PaletteManager, ServerFlags, PaletteRenderable, EntityDescriptor, EntityOptions,
|
|
CreateEntityMethod, CreateEntity, EntityFlags, MixinEntity, EntityWorldState, defaultWorldState, InteractAction
|
|
} from '../interfaces';
|
|
import { ENTITY_TYPE_LIMIT } from '../constants';
|
|
import {
|
|
setPaletteManager, ponyColliders, ponyCollidersBounds, mixTrigger, mixDraw, mixInteract
|
|
} from '../mixins';
|
|
import { times } from '../utils';
|
|
import { rect } from '../rect';
|
|
|
|
export const entities: EntityDescriptor[] = [];
|
|
|
|
export function createBaseEntity(type: number, id: number, x: number, y: number): Entity {
|
|
return { id, type, x, y, z: 0, vx: 0, vy: 0, depth: 0, order: 0, state: 0, playerState: 0, flags: 0, timestamp: 0 };
|
|
}
|
|
|
|
function createEntity(
|
|
type: number, id: number, x: number, y: number, options: EntityOptions, worldState: EntityWorldState
|
|
): Entity {
|
|
const descriptor = entities[type];
|
|
|
|
if (!descriptor) {
|
|
throw new Error(`Invalid entity type ${type}`);
|
|
}
|
|
|
|
return descriptor.create(createBaseEntity(type, id, x, y), options, worldState);
|
|
}
|
|
|
|
export function register(typeName: string, create: CreateEntity): CreateEntityMethod {
|
|
if (DEVELOPMENT && entities.length >= ENTITY_TYPE_LIMIT) {
|
|
throw new Error(`Exceeded entity limit of ${ENTITY_TYPE_LIMIT} with (${typeName})`);
|
|
}
|
|
|
|
if (DEVELOPMENT && entities.some(e => e.typeName === typeName)) {
|
|
throw new Error(`Entity name already registered (${typeName})`);
|
|
}
|
|
|
|
const type = entities.length;
|
|
entities.push({ type, typeName, create });
|
|
const method: any = (x: number, y: number, options: EntityOptions = {}, worldState: EntityWorldState = defaultWorldState) =>
|
|
createEntity(type, 0, x, y, options, worldState);
|
|
method.type = type;
|
|
method.typeName = typeName;
|
|
|
|
return method;
|
|
}
|
|
|
|
export function registerMix(typeName: string, ...mixins: (MixinEntity | undefined)[]) {
|
|
const mixinsCompacted = compact(mixins);
|
|
|
|
return register(typeName, (base, options, worldState) => {
|
|
for (const mixin of mixinsCompacted) {
|
|
mixin(base, options, worldState);
|
|
}
|
|
|
|
return base;
|
|
});
|
|
}
|
|
|
|
export function getEntityTypeName(type: number): string {
|
|
return entities[type].typeName;
|
|
}
|
|
|
|
export function getEntityType(typeName: string): number {
|
|
for (let i = 1; i < entities.length; i++) {
|
|
if (entities[i].typeName === typeName) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
export function getEntityTypesAndNames() {
|
|
return entities.map(({ type, typeName }) => ({ type, name: typeName }));
|
|
}
|
|
|
|
function checkEntity(entity: Entity) {
|
|
if (entity.draw && !entity.bounds) {
|
|
console.error('missing bounds for', getEntityTypeName(entity.type), entity);
|
|
}
|
|
|
|
if (entity.drawLight && !entity.lightBounds) {
|
|
console.error('missing lightBounds for', getEntityTypeName(entity.type), entity);
|
|
}
|
|
|
|
if (entity.drawLightSprite && !entity.lightSpriteBounds) {
|
|
console.error('missing lightSpriteBounds for', getEntityTypeName(entity.type), entity);
|
|
}
|
|
}
|
|
|
|
export function createAnEntity(
|
|
type: number, id: number, x: number, y: number, options: any, paletteManager: PaletteManager,
|
|
worldState: EntityWorldState
|
|
): Entity {
|
|
setPaletteManager(paletteManager);
|
|
|
|
const entity = createEntity(type, id, x, y, options, worldState);
|
|
|
|
if (DEVELOPMENT) {
|
|
checkEntity(entity);
|
|
}
|
|
|
|
return entity;
|
|
}
|
|
|
|
// helpers
|
|
|
|
// strips names in release build
|
|
export function n(value: string) {
|
|
return (DEVELOPMENT || SERVER) ? value : '';
|
|
}
|
|
|
|
export function mixCover(x: number, y: number, w: number, h: number): MixinEntity {
|
|
const bounds = rect(x, y, w, h);
|
|
return base => base.coverBounds = bounds;
|
|
}
|
|
|
|
export function mixFlags(flags: EntityFlags): MixinEntity {
|
|
return base => base.flags |= flags;
|
|
}
|
|
|
|
export function mixInteractAction(action: InteractAction): MixinEntity {
|
|
return base => base.interactAction = action;
|
|
}
|
|
|
|
export function mixBounds(x: number, y: number, w: number, h: number): MixinEntity {
|
|
const bounds = rect(x, y, w, h);
|
|
return base => base.bounds = bounds;
|
|
}
|
|
|
|
export function mixServerFlags(flags: ServerFlags): MixinEntity {
|
|
if (SERVER) {
|
|
return base => base.serverFlags! |= flags;
|
|
} else {
|
|
return () => { };
|
|
}
|
|
}
|
|
|
|
export function mixOrder(order: number): MixinEntity {
|
|
return base => base.order = order;
|
|
}
|
|
|
|
export const collectableInteractive = mixInteract(-8, -12, 16, 16, 1.5);
|
|
|
|
// entity with centered sprite and larger clickable area
|
|
export function collectable(name: string, sprite: PaletteRenderable, paletteIndex = 0, ...other: MixinEntity[]) {
|
|
return doodad(name, sprite, Math.floor(sprite.color!.w / 2), sprite.color!.h - 1, paletteIndex,
|
|
collectableInteractive,
|
|
...other);
|
|
}
|
|
|
|
// for ground details. puts origin point on the top of sprite, so you always go on top of it. collectables are able to spawn overlapping decals
|
|
export function decal(name: string, sprite: PaletteRenderable, palette = 0, ...other: MixinEntity[]) {
|
|
return registerMix(name,
|
|
mixDraw(sprite, Math.floor((sprite.color!.w + sprite.color!.ox) / 2), sprite.color!.oy, palette),
|
|
mixFlags(EntityFlags.Decal),
|
|
...other);
|
|
}
|
|
|
|
export function decalOffset(name: string, sprite: PaletteRenderable, dx: number, dy: number, palette = 0, ...other: MixinEntity[]) {
|
|
return registerMix(name,
|
|
mixDraw(sprite, dx, dy, palette),
|
|
mixFlags(EntityFlags.Decal),
|
|
...other);
|
|
}
|
|
|
|
// for decorative objects
|
|
export function doodad(
|
|
name: string, sprite: PaletteRenderable, ox: number, oy: number, palatte = 0, ...other: (MixinEntity | undefined)[]
|
|
) {
|
|
return registerMix(name, mixDraw(sprite, ox, oy, palatte), ...compact(other));
|
|
}
|
|
|
|
export function doodadSet(name: string, sprite: PaletteRenderable, ox: number, oy: number, ...other: MixinEntity[]) {
|
|
return times(sprite.palettes!.length, i => registerMix(`${name}-${i}`, mixDraw(sprite, ox, oy, i), ...other));
|
|
}
|
|
|
|
// placeholder entity
|
|
|
|
registerMix(n('null'), () => { throw new Error('Invalid type (0)'); });
|
|
|
|
// entities
|
|
|
|
export const pony = registerMix(n('pony'),
|
|
base => {
|
|
base.flags = EntityFlags.Movable | EntityFlags.CanCollide;
|
|
base.colliders = ponyColliders;
|
|
base.collidersBounds = ponyCollidersBounds;
|
|
},
|
|
mixServerFlags(ServerFlags.DoNotSave));
|
|
|
|
// triggers
|
|
|
|
export const triggerDoor = registerMix(n('trigger-door'),
|
|
mixTrigger(-32, -6, 64, 12, true));
|
|
|
|
export const triggerHouseDoor = registerMix(n('trigger-house-door'),
|
|
mixTrigger(-32, -6, 64, 12, false));
|
|
|
|
export const triggerBoat = registerMix(n('trigger-boat'),
|
|
mixTrigger(-50, -12, 100, 24, false));
|
|
|
|
export const trigger3x1 = registerMix(n('trigger-3x1'),
|
|
mixTrigger(-48, 0, 96, 24, true));
|