import { sort } from 'timsort'; import { Entity, EntityState, BodyAnimation, Point, EntityFlags, IMap, Pony, Says, EntityPlayerState, WorldMap } from './interfaces'; import { hasFlag, distance, pushUniq, setFlag } from './utils'; import { stand, sit, lie, fly, flyBug, swim } from '../client/ponyAnimations'; import { releasePony, isPony } from './pony'; import { toScreenX, toScreenY } from './positionUtils'; import { releasePalette } from '../graphics/paletteManager'; import { rect } from './rect'; import { addOrRemoveFromEntityList } from './worldMap'; import { PONY_TYPE } from './constants'; import { isStaticCollision } from './collision'; export function releaseEntity(entity: Entity) { if (isPony(entity)) { releasePony(entity); } if (entity.palettes !== undefined) { for (const palette of entity.palettes) { releasePalette(palette); } } } export function addChatBubble(map: WorldMap, entity: Entity, says: Says) { entity.says = says; pushUniq(map.entitiesWithChat, entity); } export function updateEntityVelocity(map: WorldMap, entity: Entity, vx: number, vy: number) { const wasMoving = isMoving(entity); entity.vx = vx; entity.vy = vy; const isMovingNow = isMoving(entity); addOrRemoveFromEntityList(map.entitiesMoving, entity, wasMoving, isMovingNow); } export function compareEntities(a: Entity, b: Entity) { return (toScreenY(a.y) - toScreenY(b.y)) || (a.order - b.order) || (b.id - a.id) || (toScreenX(a.x) - toScreenX(b.x)) || (toScreenY(a.z) - toScreenY(b.z) ); } export function sortEntities(entities: Entity[]) { sort(entities, compareEntities); } export function closestEntity(point: Point, entities: Entity[]): Entity | undefined { return entities.reduce((best, entity) => distance(point, entity) < distance(point, best) ? entity : best, entities[0]); } export function getBoopRect(entity: Entity) { const right = hasFlag(entity.state, EntityState.FacingRight); const sitting = isPonySitting(entity); return rect(entity.x + (right ? 0.6 : -0.9) * (sitting ? 0.6 : 1), entity.y - 0.2, 0.3, 0.4); } export function getkissRect(entity: Entity) { const right = hasFlag(entity.state, EntityState.FacingRight); const sitting = isPonySitting(entity); return rect(entity.x + (right ? 0.4 : -0.8) * (sitting ? 0.6 : 1), entity.y - 0.225, 0.4, 0.45); } export function getSneezeRect(entity: Entity) { const right = hasFlag(entity.state, EntityState.FacingRight); const sitting = isPonySitting(entity); return rect(entity.x + (right ? 0.45 : -0.95) * (sitting ? 0.6 : 1), entity.y - 0.25, 0.6, 0.5); } export function isMoving(entity: Entity) { return entity.vx !== 0 || entity.vy !== 0; } export function isDrawable(entity: Entity) { return entity.type === PONY_TYPE || entity.draw !== undefined; } export function canLand(entity: Entity, map: IMap) { return !isStaticCollision(entity, map, true); } export function canStand(entity: Entity, map: IMap) { return !isPonyStanding(entity) && isPonyLandedOrCanLand(entity, map); } export function canSit(entity: Entity, map: IMap) { return !isPonySitting(entity) && isPonyLandedOrCanLand(entity, map) && !isMoving(entity); } export function canLie(entity: Entity, map: IMap) { return !isPonyLying(entity) && isPonyLandedOrCanLand(entity, map) && !isMoving(entity); } export function entityInRange(entity: Entity, player: Entity) { return (!entity.interactRange || distance(player, entity) < entity.interactRange); } export function getInteractBounds(pony: Pony) { const boundsWidth = 1; const boundsHeight = 1; const boundsOffset = 0.5 + (isPonySitting(pony) ? -0.3 : (isPonyLying(pony) ? -0.2 : 0)); return rect( toScreenX(isFacingRight(pony) ? (pony.x + boundsOffset) : (pony.x - boundsOffset - boundsWidth)), toScreenY(pony.y - boundsHeight / 2), toScreenX(boundsWidth), toScreenY(boundsHeight)); } export const SIT_ON_BOUNDS_WIDTH = 1.2; export const SIT_ON_BOUNDS_HEIGHT = 0.5; export const SIT_ON_BOUNDS_OFFSET = 0.4; export function getSitOnBounds(pony: Pony) { const width = SIT_ON_BOUNDS_WIDTH; const height = SIT_ON_BOUNDS_HEIGHT; const offset = isFacingRight(pony) ? -SIT_ON_BOUNDS_OFFSET : (SIT_ON_BOUNDS_OFFSET - SIT_ON_BOUNDS_WIDTH); return rect(toScreenX(pony.x + offset), toScreenY(pony.y - SIT_ON_BOUNDS_HEIGHT / 2), toScreenX(width), toScreenY(height)); } // pony state export function isIdleAnimation(animation: BodyAnimation) { return animation === stand || animation === sit || animation === lie || animation === fly || animation === flyBug || animation === swim; } export function isIdle(pony: Pony) { return !isMoving(pony) && isIdleAnimation(pony.ponyState.animation); } export function canBoop(pony: Pony) { return isIdle(pony); } export function canBoopOrKiss(entity: Entity) { return !isMoving(entity) && (isPonyStanding(entity) || isPonySitting(entity) || isPonyLying(entity) || isPonyFlying(entity)); } // entity player state export function isHidden(entity: Entity) { return (entity.playerState & EntityPlayerState.Hidden) !== 0; } export function isIgnored(entity: Entity) { return (entity.playerState & EntityPlayerState.Ignored) !== 0; } export function isFriend(entity: Entity) { return (entity.playerState & EntityPlayerState.Friend) !== 0; } export function isInTheAir(entity: Entity) { return isFlying(entity) && (entity.inTheAirDelay === undefined || entity.inTheAirDelay <= 0); } // entity state export function isFlying(entity: Entity) { return (entity.state & EntityState.Flying) !== 0; } export function isFacingRight(entity: Entity) { return (entity.state & EntityState.FacingRight) !== 0; } export function hasHeadTurned(entity: Entity) { return (entity.state & EntityState.HeadTurned) !== 0; } export function isHeadFacingRight(entity: Entity) { const headTurned = hasHeadTurned(entity); const facingRight = isFacingRight(entity); return facingRight ? !headTurned : headTurned; } export function getPonyState(state: EntityState): EntityState { return state & EntityState.PonyStateMask; } export function setPonyState(state: EntityState, set: EntityState) { state = (state & ~EntityState.PonyStateMask) | set; state = setFlag(state, EntityState.Flying, set === EntityState.PonyFlying); return state; } export function isSittingState(state: EntityState) { return getPonyState(state) === EntityState.PonySitting; } export function isLyingState(state: EntityState) { return getPonyState(state) === EntityState.PonyLying; } export function isPonyWalking(entity: Entity) { return getPonyState(entity.state) === EntityState.PonyWalking; } export function isPonyTrotting(entity: Entity) { return getPonyState(entity.state) === EntityState.PonyTrotting; } export function isPonySitting(entity: Entity) { return getPonyState(entity.state) === EntityState.PonySitting; } export function isPonyStanding(entity: Entity) { return getPonyState(entity.state) === EntityState.PonyStanding; } export function isPonyLying(entity: Entity) { return getPonyState(entity.state) === EntityState.PonyLying; } export function isPonyFlying(entity: Entity) { return getPonyState(entity.state) === EntityState.PonyFlying; } export function isPonyLandedOrCanLand(entity: Entity, map: IMap) { return !isPonyFlying(entity) || canLand(entity, map); } // entity flags export function isDecal(entity: Entity) { return (entity.flags & EntityFlags.Decal) !== 0; } export function isCritter(entity: Entity) { return (entity.flags & EntityFlags.Critter) !== 0; }