'use strict'; import * as util from '../util'; import EntityPosition from '../model/EntityPosition'; import MapData from '../model/MapData'; import Position from '../model/Position'; import TileData from '../model/TileData'; const tileSize = 32; const body = document.getElementsByTagName('body')[0]; function loadImage(url: string): Promise { return new Promise(function(resolve, reject) { var img = new Image(); img.addEventListener('load', () => { resolve(img); }); img.addEventListener('error', () => { reject(Error('Failed to load ' + url)); }); img.src = url; }); } function loadImages(imgs: {[key: string]: string}): Promise<{[key: string]: HTMLImageElement}> { return util.mapPromises(_.mapValues(imgs, loadImage)); } function loadTiles(tiles: {[key: string]: TileData}): Promise<{[key: string]: HTMLImageElement}> { var imgs: {[key: string]: string} = {} _.forOwn(tiles, t => { imgs[t.file] = `resources/sprite/tile/${t.file}.png` }); return loadImages(imgs); } function loadEntities(entities: EntityPosition[]): Promise<{[key: string]: HTMLImageElement}> { var p: {[key: string]: Promise} = {}; entities.forEach(e => { p[e.entity.name] = loadImage(`resources/sprite/entity/${e.entity.name}.png`); }); return util.mapPromises(p); } function entityPosition(e: EntityPosition, time: number): Position { if (e.transition) { var t = e.transition; var d = (time - t.start) / (t.end-t.start); return new Position( (1-d) * t.orig.x + d * t.dest.x, (1-d) * t.orig.y + d * t.dest.y ); } else { return e.position; } } function tiles(n: number) { return Math.round(tileSize*n); } class Rect { constructor(public x1: number, public y1: number, public x2: number, public y2: number) {} } export default class MapView { private redrawPending: boolean = false; private canvas: HTMLCanvasElement; private ctx: CanvasRenderingContext2D; private scale: number; private tiles: {[key: string]: HTMLImageElement}; private entitySprites: {[key: string]: HTMLImageElement}; constructor(private map: MapData, private entities: EntityPosition[], private origin: EntityPosition, private updateState: (time: number) => void) { this.canvas = document.createElement('canvas'); this.canvas.style.position = 'absolute'; body.appendChild(this.canvas); this.ctx = this.canvas.getContext('2d'); window.addEventListener('resize', () => this.setSize()); this.setSize(); var tilesReady = loadTiles(map.tiles).then((tiles) => { this.tiles = tiles; }); var entitiesReady = loadEntities(this.entities).then((entities) => { this.entitySprites = entities; }); Promise.all([tilesReady, entitiesReady]).then(() => { this.redraw(); }); } private setSize(): void { var e = document.documentElement; var w = window.innerWidth || e.clientWidth || body.clientWidth; var h = window.innerHeight || e.clientHeight || body.clientHeight; this.canvas.width = w; this.canvas.height = h; var pixels = Math.max(w/20, h/15.0); this.scale = Math.ceil(pixels / tileSize); this.redraw() } private drawSprite(img: HTMLImageElement, srcX: number, srcY: number, destX: number, destY: number): void { this.ctx.drawImage( img, tiles(srcX), tiles(srcY), tileSize, tileSize, tiles(destX)*this.scale, tiles(destY)*this.scale, tileSize*this.scale, tileSize*this.scale ); } private drawTile(x: number, y: number, tile: TileData): void { if (!tile) return; var img = this.tiles[tile.file]; if (!img) return; this.drawSprite(img, tile.subtile || 0, 0, x, y); } private drawEntity(e: EntityPosition, time: number): boolean { var sprite = this.entitySprites[e.entity.name]; if (!sprite) return false; var p = entityPosition(e, time); this.drawSprite(sprite, e.direction, 0, p.x, p.y); return !!e.transition; } private setOrigin(time: number): Rect { var origin = entityPosition(this.origin, time); var w = this.canvas.width; var h = this.canvas.height; this.ctx.translate(Math.round(w/2), Math.round(h/2)); this.ctx.translate(-tiles(origin.x + 0.5)*this.scale, -tiles(origin.y + 0.5)*this.scale); var sw = w / (this.scale*tileSize), sh = h / (this.scale*tileSize); return new Rect( Math.floor(origin.x-sw/2+0.5), Math.floor(origin.y-sh/2+0.5), Math.ceil(origin.x+sw/2-0.5), Math.ceil(origin.y+sh/2-0.5) ); } private draw(time: number): void { this.updateState(time); this.redrawPending = false; this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); if (!this.tiles || !this.entitySprites) return; this.ctx.save(); var rect = this.setOrigin(time); (this.ctx).mozImageSmoothingEnabled = false; (this.ctx).webkitImageSmoothingEnabled = false; (this.ctx).msImageSmoothingEnabled = false; (this.ctx).imageSmoothingEnabled = false; this.map.layers.forEach((layer) => { for (let y = rect.y1; y <= rect.y2; y++) { let row = layer[y]; if (!row) continue; for (let x = rect.x1; x <= rect.x2; x++) { let tile = row[x]; if (!tile) continue; this.drawTile(x, y, this.map.tiles[tile]); } } }); var animate = false; this.entities.forEach(e => { if (this.drawEntity(e, time)) animate = true; }); this.ctx.restore(); if (animate) this.redraw(); } redraw(): void { if (this.redrawPending) return; this.redrawPending = true; window.requestAnimationFrame((time: number) => this.draw(time)); } }