import AvatarInfo , { Direction , FigurePart } from "./AvatarInfo" ; import AvatarChunk from "./AvatarChunk" ; import Constants from "../../../Constants" ; export const LOCAL_RESOURCES_URL = Constants . AVATAR_RESOURCES_URL ; export default class AvatarImager { ready : boolean ; offsets : any ; chunks : any ; figuremap : any ; figuredata : any ; partsets : any ; draworder : any ; animation : any ; constructor ( ) { this . ready = false ; this . offsets = { } ; this . chunks = { } ; this . figuremap = { } ; this . figuredata = { } ; this . partsets = { } ; this . draworder = { } ; this . animation = { } ; } initialize ( ) { const p = this . loadFiles ( ) ; return Promise . all ( p ) . then ( ( ) => { this . ready = true ; } ) ; } loadFiles ( ) { return [ this . fetchJsonAsync ( LOCAL_RESOURCES_URL + "map.json" ) . then ( data => { this . figuremap = data ; } ) , this . fetchJsonAsync ( LOCAL_RESOURCES_URL + "figuredata.json" ) . then ( data => { this . figuredata = data ; } ) , this . fetchJsonAsync ( LOCAL_RESOURCES_URL + "partsets.json" ) . then ( data => { this . partsets = data ; } ) , this . fetchJsonAsync ( LOCAL_RESOURCES_URL + "draworder.json" ) . then ( data => { this . draworder = data ; } ) , this . fetchJsonAsync ( LOCAL_RESOURCES_URL + "animation.json" ) . then ( data => { this . animation = data ; } ) ] ; } fetchJsonAsync ( URL ) { return new Promise ( ( resolve , reject ) => { const options = { method : 'GET' , mode : 'cors' , cache : 'default' , } ; fetch ( URL , options ) . then ( response => response . json ( ) ) . then ( data => resolve ( data ) ) . catch ( err => reject ( err ) ) ; } ) ; } fetchOffsetAsync ( uniqueName ) { return this . fetchJsonAsync ( LOCAL_RESOURCES_URL + uniqueName + "/offset.json" ) . then ( data => { this . offsets [ uniqueName ] . data = data ; } ) ; } getTypeColorId ( figure , part ) { const avatarInfo = new AvatarInfo ( figure , 0 , 0 , [ "std" ] , "std" , 0 , false , false , "d" ) ; let color = 0x000000 ; for ( let figurePart of avatarInfo . figure ) { if ( figurePart . type === part ) { const parts = this . getPartColor ( figurePart ) ; for ( let type in parts ) { const part = parts [ type ] ; for ( let particle of part ) { if ( particle . color != null ) { color = parseInt ( particle . color , 16 ) ; return color ; } } } } } return color ; } getChatColor ( figure ) { return this . getTypeColorId ( figure , 'ch' ) ; } generateGeneric ( avatarInfo , isGhost ) { const activeParts = { } ; activeParts . rect = this . getActivePartSet ( avatarInfo . isHeadOnly ? "head" : "figure" ) ; activeParts . head = this . getActivePartSet ( "head" ) ; activeParts . eye = this . getActivePartSet ( "eye" ) ; activeParts . gesture = this . getActivePartSet ( "gesture" ) ; activeParts . speak = this . getActivePartSet ( "speak" ) ; activeParts . walk = this . getActivePartSet ( "walk" ) ; activeParts . sit = this . getActivePartSet ( "sit" ) ; activeParts . itemRight = this . getActivePartSet ( "itemRight" ) ; activeParts . handRight = this . getActivePartSet ( "handRight" ) ; activeParts . handLeft = this . getActivePartSet ( "handLeft" ) ; activeParts . swim = this . getActivePartSet ( "swim" ) ; let drawParts = this . getDrawOrder ( avatarInfo . drawOrder , avatarInfo . direction ) ; if ( drawParts == null ) { drawParts = this . getDrawOrder ( "std" , avatarInfo . direction ) ; } const setParts = { } ; for ( let partSet of avatarInfo . figure ) { const parts = this . getPartColor ( partSet ) ; for ( let type in parts ) { if ( setParts [ type ] == null ) { setParts [ type ] = [ ] ; } setParts [ type ] = parts [ type ] . concat ( setParts [ type ] ) ; } } if ( avatarInfo . handItem > 0 ) { setParts [ "ri" ] = [ { "index" : 0 , "id" : avatarInfo . handItem } ] ; } const chunks = [ ] ; const offsetsPromises = [ ] ; for ( let type of drawParts ) { const drawableParts = setParts [ type ] ; if ( drawableParts != null ) { for ( let drawablePart of drawableParts ) { const uniqueName = this . getPartUniqueName ( type , drawablePart [ "id" ] ) ; if ( uniqueName != null ) { if ( setParts [ "hidden" ] . includes ( type ) ) { continue ; } if ( activeParts . head . includes ( type ) && avatarInfo . isBodyOnly ) { continue ; } if ( ! activeParts . rect . includes ( type ) ) { continue ; } if ( isGhost && ( activeParts . gesture . includes ( type ) || activeParts . eye . includes ( type ) ) ) { continue ; } let drawDirection = avatarInfo . direction ; let drawAction = null ; if ( activeParts . rect . includes ( type ) ) { drawAction = avatarInfo . drawAction [ 'body' ] ; } if ( activeParts . head . includes ( type ) ) { drawDirection = avatarInfo . headDirection ; } if ( activeParts . speak . includes ( type ) && avatarInfo . drawAction [ 'speak' ] ) { drawAction = avatarInfo . drawAction [ 'speak' ] ; } if ( activeParts . gesture . includes ( type ) && avatarInfo . drawAction [ 'gesture' ] ) { drawAction = avatarInfo . drawAction [ 'gesture' ] ; } if ( activeParts . eye . includes ( type ) ) { drawablePart . colorable = false ; if ( avatarInfo . drawAction [ 'eye' ] ) { drawAction = avatarInfo . drawAction [ 'eye' ] ; } } if ( activeParts . walk . includes ( type ) && avatarInfo . drawAction [ 'wlk' ] ) { drawAction = avatarInfo . drawAction [ 'wlk' ] ; } if ( activeParts . sit . includes ( type ) && avatarInfo . drawAction [ 'sit' ] ) { drawAction = avatarInfo . drawAction [ 'sit' ] ; } if ( activeParts . handRight . includes ( type ) && avatarInfo . drawAction [ 'handRight' ] ) { drawAction = avatarInfo . drawAction [ 'handRight' ] ; } if ( activeParts . itemRight . includes ( type ) && avatarInfo . drawAction [ 'itemRight' ] ) { drawAction = avatarInfo . drawAction [ 'itemRight' ] ; } if ( activeParts . handLeft . includes ( type ) && avatarInfo . drawAction [ 'handLeft' ] ) { drawAction = avatarInfo . drawAction [ 'handLeft' ] ; } if ( activeParts . swim . includes ( type ) && avatarInfo . drawAction [ 'swm' ] ) { drawAction = avatarInfo . drawAction [ 'swm' ] ; } if ( drawAction == null ) { continue ; } if ( this . offsets [ uniqueName ] == null ) { this . offsets [ uniqueName ] = { 'promise' : this . fetchOffsetAsync ( uniqueName ) , 'data' : { } } ; } offsetsPromises . push ( this . offsets [ uniqueName ] . promise ) ; const color = drawablePart . colorable ? drawablePart . color : null ; const drawPartChunk = this . getPartResource ( uniqueName , drawAction , type , avatarInfo . isSmall , drawablePart [ "id" ] , drawDirection , avatarInfo . frame , color ) ; chunks . push ( drawPartChunk ) ; } } } } return new Promise ( ( resolve , reject ) => { Promise . all ( offsetsPromises ) . then ( ( ) => { let tempCanvas = document . createElement ( 'canvas' ) ; let tempCtx = tempCanvas . getContext ( '2d' ) ; tempCanvas . width = avatarInfo . rectWidth ; tempCanvas . height = avatarInfo . rectHeight ; const chunksPromises = [ ] ; for ( let chunk of chunks ) { if ( this . offsets [ chunk . lib ] . data != null && this . offsets [ chunk . lib ] . data [ chunk . getResourceName ( ) ] != null && ! this . offsets [ chunk . lib ] . data [ chunk . getResourceName ( ) ] . flipped ) { chunksPromises . push ( chunk . downloadAsync ( ) ) ; } else { let flippedType = this . partsets . partSet [ chunk . type ] [ 'flipped-set-type' ] ; if ( flippedType !== "" ) { chunk . resType = flippedType ; } if ( chunk . action === "std" && ( this . offsets [ chunk . lib ] . data == null || this . offsets [ chunk . lib ] . data [ chunk . getResourceName ( ) ] == null || this . offsets [ chunk . lib ] . data [ chunk . getResourceName ( ) ] . flipped ) ) { chunk . resAction = "spk" ; } if ( this . offsets [ chunk . lib ] . data == null || this . offsets [ chunk . lib ] . data [ chunk . getResourceName ( ) ] == null || this . offsets [ chunk . lib ] . data [ chunk . getResourceName ( ) ] . flipped ) { chunk . isFlip = true ; chunk . resAction = chunk . action ; chunk . resDirection = 6 - chunk . direction ; } if ( this . offsets [ chunk . lib ] . data == null || this . offsets [ chunk . lib ] . data [ chunk . getResourceName ( ) ] == null || this . offsets [ chunk . lib ] . data [ chunk . getResourceName ( ) ] . flipped ) { chunk . resFrame = chunk . frame + 1 ; chunk . isFlip = false ; } if ( this . offsets [ chunk . lib ] . data == null || this . offsets [ chunk . lib ] . data [ chunk . getResourceName ( ) ] == null || this . offsets [ chunk . lib ] . data [ chunk . getResourceName ( ) ] . flipped ) { chunk . isFlip = false ; chunk . resFrame = chunk . frame ; chunk . resAction = chunk . action ; if ( chunk . direction === 7 ) { chunk . resDirection = 3 ; } if ( chunk . direction === 3 ) { chunk . resDirection = 7 ; } } if ( this . offsets [ chunk . lib ] . data == null || this . offsets [ chunk . lib ] . data [ chunk . getResourceName ( ) ] == null || this . offsets [ chunk . lib ] . data [ chunk . getResourceName ( ) ] . flipped ) { chunk . resFrame = chunk . frame + 1 ; chunk . isFlip = false ; } if ( this . offsets [ chunk . lib ] . data == null || this . offsets [ chunk . lib ] . data [ chunk . getResourceName ( ) ] == null || this . offsets [ chunk . lib ] . data [ chunk . getResourceName ( ) ] . flipped ) { chunk . resAction = chunk . action ; chunk . resType = flippedType ; chunk . resDirection = chunk . direction ; } if ( chunk . action === "std" && ( this . offsets [ chunk . lib ] . data == null || this . offsets [ chunk . lib ] . data [ chunk . getResourceName ( ) ] == null || this . offsets [ chunk . lib ] . data [ chunk . getResourceName ( ) ] . flipped ) ) { chunk . resAction = "spk" ; chunk . resType = chunk . type ; } if ( this . offsets [ chunk . lib ] . data != null && this . offsets [ chunk . lib ] . data [ chunk . getResourceName ( ) ] != null && ! this . offsets [ chunk . lib ] . data [ chunk . getResourceName ( ) ] . flipped ) { chunksPromises . push ( chunk . downloadAsync ( ) ) ; } else { } } } Promise . all ( chunksPromises ) . catch ( err => { reject ( "Error downloading chunks" ) ; } ) . then ( ( ) => { for ( let chunk of chunks ) { if ( this . offsets [ chunk . lib ] . data != null && this . offsets [ chunk . lib ] . data [ chunk . getResourceName ( ) ] != null ) { if ( chunk . resource != null ) { let posX = - this . offsets [ chunk . lib ] . data [ chunk . getResourceName ( ) ] . x ; let posY = ( avatarInfo . rectHeight / 2 ) - this . offsets [ chunk . lib ] . data [ chunk . getResourceName ( ) ] . y + avatarInfo . rectHeight / 2.5 ; let img = chunk . resource ; if ( chunk . color != null ) { img = this . tintSprite ( img , chunk . color , ( isGhost ? 170 : 255 ) ) ; } if ( chunk . isFlip ) { posX = - ( posX + img . width - avatarInfo . rectWidth + 1 ) ; img = this . flipImage ( img ) ; } if ( tempCtx != null ) { tempCtx . drawImage ( img , posX , posY ) ; } } else { } } } if ( avatarInfo . isDownsampled ) { tempCanvas = this . downsampleImage ( tempCanvas ) ; } resolve ( tempCanvas ) ; } ) ; } ) ; } ) ; } getActivePartSet ( partSet ) { const activeParts = this . partsets [ 'activePartSet' ] [ partSet ] [ 'activePart' ] ; if ( activeParts == null || activeParts . length === 0 ) { return null ; } return activeParts ; } getDrawOrder ( action , direction ) { const drawOrder = this . draworder [ action ] [ direction ] ; if ( drawOrder == null || drawOrder . length === 0 ) { return null ; } return drawOrder ; } getPartPalette ( partType ) { let partSet = this . figuredata [ 'settype' ] [ partType ] ; if ( partSet != null ) { const paletteId = partSet [ 'paletteid' ] ; return this . figuredata [ 'palette' ] [ paletteId ] ; } return null ; } getPartPaletteCount ( partType , partId ) { const partSet = this . getPartSet ( partType ) ; if ( partSet != null ) { const selectedPart = partSet [ partId ] ; if ( selectedPart != null ) { const chunks = selectedPart . part as any [ ] ; const maxColors = Math . max . apply ( Math , chunks . map ( o => o . colorindex ) ) ; return Math . max ( 1 , maxColors ) ; } } return 1 ; } getPartSet ( partType ) { let partSet = this . figuredata [ 'settype' ] [ partType ] ; if ( partSet != null ) { return partSet . set ; } return null ; } getPartColor ( figure ) { const parts = { } ; let partSet = this . figuredata [ 'settype' ] [ figure . type ] ; if ( partSet != null ) { if ( partSet [ 'set' ] [ figure . id ] != null && partSet [ 'set' ] [ figure . id ] [ 'part' ] != null ) { for ( let rawPart of partSet [ 'set' ] [ figure . id ] [ 'part' ] ) { const part = rawPart ; let element = { "index" : part . index , "id" : part . id , "colorable" : part . colorable } ; if ( part . colorable ) { element . color = this . getColorByPaletteId ( partSet . paletteid , figure . colors [ part . colorindex - 1 ] ) ; } if ( parts [ part . type ] == null ) { parts [ part . type ] = [ element ] ; } else { parts [ part . type ] . push ( element ) ; } } } parts . hidden = [ ] ; if ( partSet [ 'set' ] [ figure . id ] != null && Array . isArray ( partSet [ 'set' ] [ figure . id ] [ 'hidden' ] ) ) { for ( let partType of partSet [ 'set' ] [ figure . id ] [ 'hidden' ] ) { parts . hidden . push ( partType ) ; } } } return parts ; } getColorByPaletteId ( paletteId , colorId ) { if ( this . figuredata [ 'palette' ] [ paletteId ] != null && this . figuredata [ 'palette' ] [ paletteId ] [ colorId ] != null && this . figuredata [ 'palette' ] [ paletteId ] [ colorId ] [ 'color' ] != null ) { return this . figuredata [ 'palette' ] [ paletteId ] [ colorId ] [ 'color' ] ; } return null ; } getPartUniqueName ( type , partId ) { let uniqueName = this . figuremap [ type ] [ partId ] ; if ( uniqueName == null && type === "hrb" ) { uniqueName = this . figuremap [ "hr" ] [ partId ] ; } if ( uniqueName == null ) { uniqueName = this . figuremap [ type ] [ 1 ] ; } if ( uniqueName == null ) { uniqueName = this . figuremap [ type ] [ 0 ] ; } return uniqueName ; } getPartResource ( uniqueName , action , type , isSmall , partId , direction , frame , color ) { let partFrame = this . getFrameNumber ( type , action , frame ) ; let chunk = new AvatarChunk ( uniqueName , action , type , isSmall , partId , direction , partFrame , color ) ; let resourceName = chunk . getResourceName ( ) ; if ( this . chunks [ resourceName ] != null && this . chunks [ resourceName ] . resource != null ) { chunk . resource = this . chunks [ resourceName ] . resource ; chunk . promise = this . chunks [ resourceName ] . promise ; } else { this . chunks [ resourceName ] = chunk ; } return chunk ; } getFrameNumber ( type , action , frame ) { const translations = { "wav" : "Wave" , "wlk" : "Move" , "spk" : "Talk" } ; if ( translations [ action ] != null ) { if ( this . animation [ translations [ action ] ] . part [ type ] != null ) { const count = this . animation [ translations [ action ] ] . part [ type ] . length ; if ( this . animation [ translations [ action ] ] . part [ type ] [ frame % count ] != null ) { return this . animation [ translations [ action ] ] . part [ type ] [ frame % count ] . number ; } } } return 0 ; } tintSprite ( img , color , alpha ) { let element = document . createElement ( 'canvas' ) ; let c = element . getContext ( "2d" ) ; if ( c == null ) return null ; let rgb = this . hex2rgb ( color ) ; let width = img . width ; let height = img . height ; element . width = width ; element . height = height ; c . drawImage ( img , 0 , 0 ) ; let imageData = c . getImageData ( 0 , 0 , width , height ) ; for ( let y = 0 ; y < height ; y ++ ) { let inpos = y * width * 4 ; for ( let x = 0 ; x < width ; x ++ ) { inpos ++ ; inpos ++ ; inpos ++ ; let pa = imageData . data [ inpos ++ ] ; if ( pa !== 0 ) { imageData . data [ inpos - 1 ] = alpha ; imageData . data [ inpos - 2 ] = Math . round ( rgb . b * imageData . data [ inpos - 2 ] / 255 ) ; imageData . data [ inpos - 3 ] = Math . round ( rgb . g * imageData . data [ inpos - 3 ] / 255 ) ; imageData . data [ inpos - 4 ] = Math . round ( rgb . r * imageData . data [ inpos - 4 ] / 255 ) ; } } } c . putImageData ( imageData , 0 , 0 ) ; return element ; } hex2rgb ( hex ) { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i . exec ( hex ) ; return result ? { r : parseInt ( result [ 1 ] , 16 ) , g : parseInt ( result [ 2 ] , 16 ) , b : parseInt ( result [ 3 ] , 16 ) } : null ; } flipImage ( img ) { let element = document . createElement ( 'canvas' ) ; let c = element . getContext ( "2d" ) ; if ( c == null ) return null ; let width = img . width ; let height = img . height ; element . width = width ; element . height = height ; c . save ( ) ; c . scale ( - 1 , 1 ) ; c . drawImage ( img , 0 , 0 , width * - 1 , height ) ; c . restore ( ) ; return element ; } downsampleImage ( img ) { let element = document . createElement ( 'canvas' ) ; let c = element . getContext ( "2d" ) ; if ( c == null ) return null ; let width = img . width ; let height = img . height ; element . width = width ; element . height = height ; c . save ( ) ; c . scale ( 0.5 , 0.5 ) ; c . drawImage ( img , 0 , 0 ) ; c . restore ( ) ; return element ; } }