import Platform from "./platforms/WebPlatform" ; import Card from "./Card" ; import { CardType } from "./Enums" ; import { cleanEnum } from "./helpers" ; import HeroCard from "./HeroCard" ; import HeroCardPremium from "./HeroCardPremium" ; import HeroPowerCard from "./HeroPowerCard" ; import HeroPowerCardPremium from "./HeroPowerCardPremium" ; import { ICoords } from "./interfaces" ; import MinionCard from "./MinionCard" ; import MinionCardPremium from "./MinionCardPremium" ; import SpellCard from "./SpellCard" ; import SpellCardPremium from "./SpellCardPremium" ; import WeaponCard from "./WeaponCard" ; import WeaponCardPremium from "./WeaponCardPremium" ; interface ISunwellOptions { titleFont : string ; bodyFontRegular : string ; bodyFontBold : string ; bodyFontItalic : string ; bodyFontBoldItalic : string ; gemFont : string ; aspectRatio : number ; bodyFontSize : number ; bodyFontOffset : { x : number ; y : number ; } ; bodyLineHeight : number ; bodyLineStyle : string ; assetFolder : string ; drawTimeout : number ; cacheSkeleton : boolean ; debug : boolean ; } export default class Sunwell { public options : ISunwellOptions ; public assets : { [ key ] : HTMLImageElement ; } ; public canvas : HTMLCanvasElement ; public target : any ; public platform : Platform ; public renderCache : { [ cacheKey ] : any ; } ; private assetListeners : { [ path ] : Array < ( HTMLCanvasElement ) => void > ; } ; private renderQuery : { [ key ] : Card ; } ; private isRendering : boolean ; constructor ( options ) { options . titleFont = options . titleFont || "Belwe" ; options . bodyFontRegular = options . bodyFontRegular || "Franklin Gothic" ; options . bodyFontBold = options . bodyFontBold || options . bodyFontRegular ; options . bodyFontItalic = options . bodyFontItalic || options . bodyFontRegular ; options . bodyFontBoldItalic = options . bodyFontBoldItalic || options . bodyFontRegular ; options . gemFont = options . gemFont || "Belwe" ; options . aspectRatio = options . aspectRatio || 1.492537 ; options . bodyFontSize = options . bodyFontSize || 60 ; options . bodyFontOffset = options . bodyFontOffset || { x : 0 , y : 0 } ; options . bodyLineHeight = options . bodyLineHeight || 50 ; options . bodyLineStyle = options . bodyLineStyle || "1em" ; options . assetFolder = options . assetFolder || "/assets/" ; options . drawTimeout = options . drawTimeout || 5000 ; options . cacheSkeleton = options . cacheSkeleton || false ; options . debug = options . debug || false ; this . platform = new Platform ( ) ; this . options = options ; this . assets = { } ; this . assetListeners = { } ; this . renderQuery = { } ; this . renderCache = { } ; this . isRendering = false ; } public log ( ... args ) { if ( this . options . debug ) { console . log . apply ( "[INFO]" , arguments ) ; } } public error ( ... args ) { console . log . apply ( "[ERROR]" , arguments ) ; } public drawImage ( context , assetKey , coords ) { const asset = this . getAsset ( assetKey ) ; if ( ! asset ) { this . error ( "Not drawing asset" , assetKey ) ; return ; } const ratio = coords . ratio || 1 ; const width = coords . sWidth || asset . width ; const height = coords . sHeight || asset . height ; context . drawImage ( asset , coords . sx || 0 , coords . sy || 0 , width , height , coords . dx * ratio , coords . dy * ratio , ( coords . dWidth || width ) * ratio , ( coords . dHeight || height ) * ratio ) ; } public fetchAsset ( path ) { const assets = this . assets ; const assetListeners = this . assetListeners ; const sw = this ; return new this . platform . Promise ( resolve => { if ( assets [ path ] === undefined ) { assets [ path ] = new sw . platform . Image ( ) ; sw . log ( "Requesting" , path ) ; sw . platform . loadAsset ( assets [ path ] , path , ( ) => { if ( assetListeners [ path ] ) { for ( const listener of assetListeners [ path ] ) { listener ( assets [ path ] ) ; } delete assetListeners [ path ] ; } resolve ( ) ; } , ( ) => { sw . error ( "Error loading asset:" , path ) ; resolve ( ) ; } ) ; } else if ( ! assets [ path ] . complete ) { assetListeners [ path ] = assetListeners [ path ] || [ ] ; assetListeners [ path ] . push ( resolve ) ; } else { resolve ( ) ; } } ) ; } public getBuffer ( width ? , height ? , clear ? ) { return this . platform . getBuffer ( width , height , clear ) ; } public freeBuffer ( buffer ) { return this . platform . freeBuffer ( buffer ) ; } public render ( ) { const keys = Object . keys ( this . renderQuery ) ; if ( ! keys . length ) { return ; } const first = keys [ 0 ] ; const card = this . renderQuery [ first ] ; delete this . renderQuery [ first ] ; const context = card . canvas . getContext ( "2d" ) ; this . log ( "Preparing assets for" , card . cardDef . name ) ; const texturesToLoad = [ ] ; if ( card . texture && typeof card . texture === "string" ) { texturesToLoad . push ( card . texture ) ; } for ( const asset of card . getAssetsToLoad ( ) ) { if ( ! asset ) { continue ; } const path = this . getAssetPath ( asset ) ; if ( ! this . assets [ path ] || ! this . assets [ path ] . complete ) { texturesToLoad . push ( path ) ; } } this . log ( "Preparing to load assets" ) ; const fetches = [ ] ; for ( const texture of texturesToLoad ) { fetches . push ( this . fetchAsset ( texture ) ) ; } this . platform . Promise . all ( fetches ) . then ( ( ) => { const start = Date . now ( ) ; card . draw ( card . canvas , context ) ; this . log ( card , "finished drawing in " + ( Date . now ( ) - start ) + "ms" ) ; this . isRendering = false ; if ( Object . keys ( this . renderQuery ) . length ) { this . renderTick ( ) ; } } ) . catch ( e => { this . error ( "Error while drawing card:" , e ) ; this . isRendering = false ; } ) ; } public getAssetPath ( key ) { return this . options . assetFolder + key + ".png" ; } public getAsset ( key ) { const path = this . getAssetPath ( key ) ; const asset = this . assets [ path ] ; if ( ! asset ) { this . error ( "Missing asset" , key , "at" , path ) ; return ; } if ( ! asset . complete ) { this . error ( "Attempting to getAsset not loaded" , asset , path ) ; return ; } return asset ; } public renderTick ( ) { this . isRendering = true ; this . platform . requestAnimationFrame ( ( ) => this . render ( ) ) ; } public createCard ( props , width , premium , target , callback ? ) { let canvas ; const height = Math . round ( width * this . options . aspectRatio ) ; if ( target && target instanceof HTMLCanvasElement ) { canvas = target ; canvas . width = width ; canvas . height = height ; } else { canvas = this . getBuffer ( width , height , true ) ; } const ctors = { } ; ctors [ CardType . HERO ] = premium ? HeroCardPremium : HeroCard ; ctors [ CardType . MINION ] = premium ? MinionCardPremium : MinionCard ; ctors [ CardType . SPELL ] = premium ? SpellCardPremium : SpellCard ; ctors [ CardType . WEAPON ] = premium ? WeaponCardPremium : WeaponCard ; ctors [ CardType . HERO_POWER ] = premium ? HeroPowerCardPremium : HeroPowerCard ; const type = cleanEnum ( props . type , CardType ) ; const ctor = ctors [ type ] ; if ( ! ctor ) { throw new Error ( `Got an unrenderable card type: ${ type }` ) ; } const card = new ctor ( this , props ) ; card . canvas = canvas ; card . initRender ( width , target , callback ) ; this . log ( "Queried render:" , card . cardDef . name ) ; if ( this . renderQuery [ card . key ] ) { this . log ( "Skipping" , card . key , "(already queued)" ) ; } else { this . renderQuery [ card . key ] = card ; if ( ! this . isRendering ) { this . renderTick ( ) ; } } return card ; } }