mirror of
https://github.com/Terncode/pixel.horse.git
synced 2026-09-24 13:55:04 +02:00
298 lines
9.2 KiB
TypeScript
298 lines
9.2 KiB
TypeScript
import {
|
|
Entity, DrawOptions, Camera, PaletteSpriteBatch, SpriteBatch, TileSets, Engine, Pony, WorldMap, EntityState
|
|
} from '../common/interfaces';
|
|
import { isBoundsVisible } from '../common/camera';
|
|
import { drawBounds, drawPixelText, drawBoundsOutline, drawOutlineRect, drawWorldBounds } from '../graphics/graphicsUtils';
|
|
import { ORANGE, BLUE, PURPLE, BLACK, RED, WHITE, CYAN, HOTPINK, GRAY } from '../common/colors';
|
|
import { toScreenX, toScreenY, toWorldX, toWorldY } from '../common/positionUtils';
|
|
import { tileWidth, tileHeight, PONY_TYPE, REGION_SIZE, REGION_WIDTH, REGION_HEIGHT } from '../common/constants';
|
|
import { forEachRegion, getAnyBounds, getRegion, isInWaterAt } from '../common/worldMap';
|
|
import { drawPonyEntity, drawPonyEntityLight, drawPonyEntityLightSprite } from '../common/pony';
|
|
import { getInteractBounds, sortEntities, isHidden, getSitOnBounds } from '../common/entityUtils';
|
|
import { drawTiles, drawTilesNew, drawTilesDebugInfo } from './tileUtils';
|
|
import { withAlphaFloat } from '../common/color';
|
|
import { timeStart, timeEnd } from './timing';
|
|
|
|
const SELECTED_ENTITY_BOUNDS = withAlphaFloat(ORANGE, 0.5);
|
|
|
|
function drawEntities(batch: PaletteSpriteBatch, entities: Entity[], camera: Camera, options: DrawOptions) {
|
|
const drawHidden = options.drawHidden;
|
|
let entitiesDrawn = 0;
|
|
|
|
for (const entity of entities) {
|
|
if ((!isHidden(entity) || drawHidden) && isBoundsVisible(camera, entity.bounds, entity.x, entity.y)) {
|
|
if (entity.type === PONY_TYPE) {
|
|
drawPonyEntity(batch, entity as Pony, options);
|
|
entitiesDrawn++;
|
|
} else if (entity.draw !== undefined) {
|
|
entity.draw(batch, options);
|
|
entitiesDrawn++;
|
|
}
|
|
} else {
|
|
if (entity.type === PONY_TYPE) {
|
|
const pony = entity as Pony;
|
|
|
|
if (pony.batch !== undefined) {
|
|
batch.releaseBatch(pony.batch);
|
|
pony.batch = undefined;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return entitiesDrawn;
|
|
}
|
|
|
|
export function drawEntityLights(batch: SpriteBatch, entities: Entity[], camera: Camera, options: DrawOptions) {
|
|
const drawHidden = options.drawHidden;
|
|
|
|
for (const entity of entities) {
|
|
if (DEVELOPMENT && (entity.type !== PONY_TYPE && !entity.drawLight)) {
|
|
console.error('Cannot draw entity light', entity);
|
|
}
|
|
|
|
if ((!isHidden(entity) || drawHidden) && isBoundsVisible(camera, entity.lightBounds, entity.x, entity.y)) {
|
|
if (entity.type === PONY_TYPE) {
|
|
drawPonyEntityLight(batch, entity as Pony, options);
|
|
} else {
|
|
entity.drawLight!(batch, options);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export function drawEntityLightSprites(batch: SpriteBatch, entities: Entity[], camera: Camera, options: DrawOptions) {
|
|
const drawHidden = options.drawHidden;
|
|
|
|
for (const entity of entities) {
|
|
if (DEVELOPMENT && (entity.type !== PONY_TYPE && !entity.drawLightSprite)) {
|
|
console.error('Cannot draw entity light sprite', entity);
|
|
}
|
|
|
|
if ((!isHidden(entity) || drawHidden) && isBoundsVisible(camera, entity.lightSpriteBounds, entity.x, entity.y)) {
|
|
if (entity.type === PONY_TYPE) {
|
|
drawPonyEntityLightSprite(batch, entity as Pony, options);
|
|
} else {
|
|
entity.drawLightSprite!(batch, options);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export function hasDrawLight(entity: Entity) {
|
|
if (entity.type === PONY_TYPE) {
|
|
const pony = entity as Pony;
|
|
return (pony.ponyState.holding !== undefined && pony.ponyState.holding.drawLight !== undefined) ||
|
|
((pony.state & EntityState.Magic) !== 0);
|
|
} else {
|
|
return entity.drawLight !== undefined;
|
|
}
|
|
}
|
|
|
|
export function hasLightSprite(entity: Entity) {
|
|
if (entity.type === PONY_TYPE) {
|
|
const pony = entity as Pony;
|
|
return (pony.ponyState.holding !== undefined && pony.ponyState.holding.drawLightSprite !== undefined);
|
|
} else {
|
|
return entity.drawLightSprite !== undefined;
|
|
}
|
|
}
|
|
|
|
export function drawMap(
|
|
batch: PaletteSpriteBatch, map: WorldMap, camera: Camera, player: Pony, options: DrawOptions,
|
|
tileSets: TileSets, selectedEntities: Entity[],
|
|
) {
|
|
TIMING && timeStart('forEachRegion');
|
|
if (BETA && options.engine === Engine.Whiteness) {
|
|
batch.drawRect(WHITE, 0, 0, toScreenX(map.width), toScreenY(map.height));
|
|
} else if (BETA && options.engine === Engine.LayeredTiles) {
|
|
forEachRegion(map, region => drawTilesNew(batch, region, camera, map, tileSets, options));
|
|
} else {
|
|
forEachRegion(map, region => drawTiles(batch, region, camera, map, tileSets, options));
|
|
}
|
|
TIMING && timeEnd();
|
|
|
|
TIMING && timeStart('sortEntities');
|
|
sortEntities(map.entitiesDrawable);
|
|
TIMING && timeEnd();
|
|
|
|
TIMING && timeStart('drawEntities');
|
|
const entitiesDrawn = drawEntities(batch, map.entitiesDrawable, camera, options);
|
|
TIMING && timeEnd();
|
|
|
|
if (BETA || TOOLS) {
|
|
forEachRegion(map, region => drawTilesDebugInfo(batch, region, camera, options));
|
|
}
|
|
|
|
if (BETA && options.debug.showHelpers) {
|
|
drawDebugHelpers(batch, map.entities, options);
|
|
}
|
|
|
|
if (BETA) {
|
|
for (const entity of selectedEntities) {
|
|
const bounds = getAnyBounds(entity);
|
|
drawBoundsOutline(batch, entity, bounds, SELECTED_ENTITY_BOUNDS, 2);
|
|
}
|
|
}
|
|
|
|
if (BETA && options.debug.showHelpers) {
|
|
drawOutlineRect(batch, PURPLE, getInteractBounds(player));
|
|
drawOutlineRect(batch, 0xff000066, getSitOnBounds(player));
|
|
}
|
|
|
|
if (BETA && options.showColliderMap) {
|
|
drawDebugCollider(batch, map, camera);
|
|
batch.drawRect(PURPLE, toScreenX(player.x) - 1, toScreenY(player.y), 3, 1);
|
|
batch.drawRect(PURPLE, toScreenX(player.x), toScreenY(player.y) - 1, 1, 3);
|
|
}
|
|
|
|
if (BETA && options.showHeightmap) {
|
|
drawDebugInWater(batch, map, camera);
|
|
}
|
|
|
|
return entitiesDrawn;
|
|
}
|
|
|
|
// debug
|
|
|
|
function drawDebugHelpers(batch: PaletteSpriteBatch, entities: Entity[], options: DrawOptions) {
|
|
const textColor = 0x000000b2;
|
|
const show = options.debug;
|
|
|
|
for (const e of entities) {
|
|
batch.globalAlpha = 0.3;
|
|
show.bounds && drawBounds(batch, e, e.bounds, ORANGE);
|
|
show.cover && drawBounds(batch, e, e.coverBounds, BLUE);
|
|
show.interact && drawBounds(batch, e, e.interactBounds, PURPLE);
|
|
show.trigger && drawWorldBounds(batch, e, e.triggerBounds, CYAN);
|
|
|
|
if (show.collider) {
|
|
batch.globalAlpha = 0.5;
|
|
|
|
const x = Math.floor(e.x * tileWidth);
|
|
const y = Math.floor(e.y * tileHeight);
|
|
|
|
if (e.colliders !== undefined) {
|
|
for (const collider of e.colliders) {
|
|
const x1 = x + collider.x;
|
|
const x2 = x + collider.x + collider.w;
|
|
const y1 = y + collider.y;
|
|
const y2 = y + collider.y + collider.h;
|
|
batch.drawRect(collider.tall ? RED : HOTPINK, x1, y1, x2 - x1, y2 - y1);
|
|
}
|
|
}
|
|
}
|
|
|
|
batch.globalAlpha = 1;
|
|
batch.drawRect(BLACK, toScreenX(e.x), toScreenY(e.y), 1, 1); // anchor
|
|
|
|
if (show.id) {
|
|
drawPixelText(batch, toScreenX(e.x) + 2, toScreenY(e.y) + 2, textColor, e.id.toFixed());
|
|
}
|
|
}
|
|
}
|
|
|
|
function drawDebugInWater(batch: PaletteSpriteBatch, map: WorldMap, camera: Camera) {
|
|
const color = withAlphaFloat(ORANGE, 0.4);
|
|
|
|
forEachRegion(map, region => {
|
|
const sx = toScreenX(region.x * REGION_SIZE);
|
|
const sy = toScreenY(region.y * REGION_SIZE);
|
|
const w = REGION_WIDTH;
|
|
const h = REGION_HEIGHT;
|
|
|
|
const cameraLeft = camera.x;
|
|
const cameraRight = camera.x + camera.w;
|
|
const cameraTop = camera.actualY;
|
|
const cameraBottom = camera.actualY + camera.h;
|
|
|
|
if (sx > cameraRight || sy > cameraBottom || (sx + w) < cameraLeft || (sy + h) < cameraTop)
|
|
return;
|
|
|
|
for (let y = 0; y < h; y++) {
|
|
if ((sy + y + 1) < cameraTop || (sy + y) > cameraBottom)
|
|
continue;
|
|
|
|
for (let x = 0; x < w; x++) {
|
|
if ((sx + x + 1) < cameraLeft || (sx + x) > cameraRight)
|
|
continue;
|
|
|
|
const tx = x;
|
|
|
|
while (isInWaterAt(map, toWorldX(sx + x + 0.5), toWorldY(sy + y + 0.5)) && x < w) {
|
|
x++;
|
|
}
|
|
|
|
if (x > tx) {
|
|
batch.drawRect(color, sx + tx, sy + y, x - tx, 1);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function drawDebugCollider(batch: PaletteSpriteBatch, map: WorldMap, camera: Camera) {
|
|
const color = withAlphaFloat(PURPLE, 0.4);
|
|
|
|
forEachRegion(map, ({ x, y, collider }) => {
|
|
const sx = toScreenX(x * REGION_SIZE);
|
|
const sy = toScreenY(y * REGION_SIZE);
|
|
const w = REGION_WIDTH;
|
|
const h = REGION_HEIGHT;
|
|
|
|
const cameraLeft = camera.x;
|
|
const cameraRight = camera.x + camera.w;
|
|
const cameraTop = camera.actualY;
|
|
const cameraBottom = camera.actualY + camera.h;
|
|
|
|
if (sx > cameraRight || sy > cameraBottom || (sx + w) < cameraLeft || (sy + h) < cameraTop)
|
|
return;
|
|
|
|
for (let y = 0; y < h; y++) {
|
|
if ((sy + y + 1) < cameraTop || (sy + y) > cameraBottom)
|
|
continue;
|
|
|
|
for (let x = 0; x < w; x++) {
|
|
if ((sx + x + 1) < cameraLeft || (sx + x) > cameraRight)
|
|
continue;
|
|
|
|
const tx = x;
|
|
|
|
while (collider[x + y * w] !== 0 && x < w) {
|
|
x++;
|
|
}
|
|
|
|
if (x > tx) {
|
|
batch.drawRect(color, sx + tx, sy + y, x - tx, 1);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
export function drawDebugRegions(batch: SpriteBatch, map: WorldMap, player: Pony, { w, h }: Camera) {
|
|
const rw = 10;
|
|
const rh = 8;
|
|
const width = rw * map.regionsX;
|
|
const height = rh * map.regionsY;
|
|
const x = w - width - 10;
|
|
const y = h - height - 30;
|
|
|
|
for (let i = 0; i < map.regionsY; i++) {
|
|
for (let j = 0; j < map.regionsX; j++) {
|
|
if (getRegion(map, j, i)) {
|
|
const inside = j === Math.floor(player.x / REGION_SIZE) && i === Math.floor(player.y / REGION_SIZE);
|
|
batch.drawRect(inside ? ORANGE : RED, x + rw * j, y + rh * i, rw, rh);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (let i = 0; i <= map.regionsY; i++) {
|
|
batch.drawRect(GRAY, x, y + rh * i, width + 1, 1);
|
|
}
|
|
|
|
for (let i = 0; i <= map.regionsX; i++) {
|
|
batch.drawRect(GRAY, x + rw * i, y, 1, height);
|
|
}
|
|
}
|