import { CCMap } from '../tilemap/cc-map' ; import { MapEntity , Point , Point3 } from '../../../models/cross-code-map' ; import { Helper } from '../helper' ; import * as Phaser from 'phaser' ; import { Vec2 } from '../vec2' ; import { Globals } from '../../globals' ; import { BaseObject } from '../base-object' ; import { Subscription } from 'rxjs' ; export interface ScaleSettings { scalableX : boolean ; scalableY : boolean ; baseSize : Point ; scalableStep : number ; } export interface EntityAttributes { [ key ] : AttributeValue ; } export interface AttributeValue { type : string ; description : string ; options ? : { [ key ] : any ; } ; withNull ? : boolean ; large ? : boolean ; optional ? : boolean ; [ key ] : any ; } export interface DetailSettings { mapId : number ; [ key ] : any ; } export interface Fix { gfx : any ; x : number ; y : number ; w : number ; h : number ; scaleX ? : number ; scaleY ? : number ; renderHeight ? : number ; renderMode ? : string ; offsetX ? : number ; offsetY ? : number ; flipX ? : boolean ; flipY ? : boolean ; tint ? : number ; alpha ? : number ; } export abstract class CCEntity extends BaseObject { private map : CCMap ; private levelOffset = 0 ; public container ! : Phaser . GameObjects . Container ; private text ? : Phaser . GameObjects . Text ; private images : Phaser . GameObjects . Image [ ] = [ ] ; private readonly filterSubscription : Subscription ; private collisionImage ! : Phaser . GameObjects . Graphics ; private inputZone ! : Phaser . GameObjects . Zone ; private _selected = false ; get selected ( ) : boolean { return this . _selected ; } public isDragged = false ; public startOffset : Point = { x : 0 , y : 0 } ; details : { level : { level : number ; offset : number ; } ; type : string ; settings : DetailSettings ; } = < any > { } ; entitySettings : { collType : string ; baseSize : Point3 ; sheets : { fix : Fix [ ] ; offset ? : Point ; renderMode ? : string ; flipX ? : boolean ; ignoreScalable ? : boolean ; } ; scalableX : boolean ; scalableY : boolean ; scalableStep : number ; pivot : Point ; } = < any > { } ; protected constructor ( scene , map , x , y , typeName ) { super ( scene , typeName , false ) ; scene . add . existing ( this ) ; this . map = map ; this . container . x = Math . round ( x ) ; this . container . y = Math . round ( y ) ; this . details = < any > { type : typeName } ; this . filterSubscription = Globals . globalEventsService . filterEntity . subscribe ( filter => this . setVisible ( this . filter ( filter ) ) ) ; } protected init ( ) { this . container = this . scene . add . container ( 0 , 0 ) ; const collImg = this . scene . add . graphics ( ) ; this . container . add ( collImg ) ; this . collisionImage = collImg ; collImg . alpha = 0 ; this . inputZone = this . scene . add . zone ( 0 , 0 , 50 , 50 ) ; this . inputZone . setOrigin ( 0 ) ; this . inputZone . setData ( 'entity' , this ) ; this . container . add ( this . inputZone ) ; this . inputZone . on ( 'pointerover' , ( ) => this . inputOver ( ) ) ; this . inputZone . on ( 'pointerout' , ( ) => this . inputOut ( ) ) ; } public inputOver ( ) { if ( ! this . _selected ) { this . collisionImage . alpha = 0.35 ; } } public inputOut ( ) { if ( ! this . _selected ) { this . collisionImage . alpha = 0 ; } } protected activate ( ) { this . inputZone . setInteractive ( ) ; } protected deactivate ( ) { this . inputZone . disableInteractive ( ) ; } preUpdate ( ) { if ( this . isDragged ) { const container = this . container ; const p = this . scene . input . activePointer ; container . x = Math . round ( p . worldX - this . startOffset . x ) ; container . y = Math . round ( p . worldY - this . startOffset . y ) ; const settings = Globals . entitySettings ; if ( settings . enableGrid ) { const diffX = container . x % settings . gridSize ; if ( diffX * 2 < settings . gridSize ) { container . x -= diffX ; } else { container . x += settings . gridSize - diffX ; } const diffY = container . y % settings . gridSize ; if ( diffY * 2 < settings . gridSize ) { container . y -= diffY ; } else { container . y += settings . gridSize - diffY ; } } this . updateZIndex ( ) ; } } updateSettings ( ) { const s = this . entitySettings ; const settings = this . details . settings ; this . images . forEach ( img => this . container . remove ( img , true ) ) ; this . images = [ ] ; const boundBoxOffset = { x : 0 , y : 0 } ; if ( s . baseSize ) { boundBoxOffset . x = s . baseSize . x / 2 ; boundBoxOffset . y = s . baseSize . y ; } if ( s . sheets && s . sheets . fix ) { for ( const fix of s . sheets . fix ) { if ( ! this . scene . textures . exists ( fix . gfx ) ) { console . error ( `texture not loaded: [${ fix . gfx }] in class: [${ this . constructor . name }]` ) ; } } if ( ! s . sheets . ignoreScalable && ( s . scalableX || s . scalableY ) ) { const fix = s . sheets . fix [ 0 ] ; const width = settings . size . x ; const height = ( fix . renderHeight || s . baseSize . z ) + settings . size . y ; for ( let x = 0 ; x < width ; x += fix . w ) { const imgWidth = Math . min ( fix . w , width - x ) ; for ( let y = 0 ; y < height ; y += fix . h ) { const imgHeight = Math . min ( fix . h , height - y ) ; const img = this . scene . add . image ( x , - y + settings . size . y , fix . gfx ) ; img . setCrop ( fix . x , fix . y , imgWidth , imgHeight ) ; img . setOrigin ( 0 , 0 ) ; img . y += this . levelOffset ; img . y -= imgHeight ; img . x -= fix . x ; img . y -= fix . y ; if ( fix . renderMode === 'lighter' ) { img . blendMode = Phaser . BlendModes . ADD ; } this . container . add ( img ) ; this . images . push ( img ) ; } } } else { s . sheets . fix . forEach ( sheet => { sheet . x = sheet . x || 0 ; sheet . y = sheet . y || 0 ; sheet . offsetX = sheet . offsetX || 0 ; sheet . offsetY = sheet . offsetY || 0 ; const img = this . scene . add . image ( sheet . offsetX , sheet . offsetY , sheet . gfx ) ; img . setOrigin ( 0 , 0 ) ; if ( sheet . tint !== undefined ) { img . setTintFill ( sheet . tint ) ; } img . alpha = sheet . alpha || 1 ; img . scaleX = sheet . scaleX || 1 ; img . scaleY = sheet . scaleY || 1 ; img . y += this . levelOffset ; img . x -= sheet . w / 2 ; img . y -= sheet . h ; img . x += boundBoxOffset . x ; img . y += boundBoxOffset . y ; let cropX = sheet . x ; if ( sheet . flipX ) { cropX = img . displayWidth - sheet . x - sheet . w ; } let cropY = sheet . y ; if ( sheet . flipY ) { cropY = img . displayWidth - sheet . y - sheet . h ; } img . x -= sheet . x ; img . y -= sheet . y ; img . setCrop ( cropX , cropY , sheet . w , sheet . h ) ; img . flipX = ! ! sheet . flipX ; img . flipY = ! ! sheet . flipY ; if ( sheet . renderMode === 'lighter' ) { img . blendMode = Phaser . BlendModes . ADD ; } this . container . add ( img ) ; this . images . push ( img ) ; } ) ; if ( s . sheets . offset ) { this . images . forEach ( img => Vec2 . add ( img , s . sheets . offset ! ) ) ; } if ( s . sheets . flipX ) { this . images . forEach ( img => img . flipX = ! img . flipX ) ; } } if ( s . sheets . renderMode === 'lighter' ) { this . images . forEach ( img => img . blendMode = Phaser . BlendModes . ADD ) ; } else if ( s . sheets . renderMode === 'source-over' ) { console . warn ( 'renderMode source-over found' ) ; } } this . container . bringToTop ( this . collisionImage ) ; if ( this . text ) { this . container . bringToTop ( this . text ) ; } this . drawBoundingBox ( ) ; Globals . globalEventsService . updateEntitySettings . next ( this ) ; } set level ( level ) { const details = this . details ; if ( typeof level === 'object' ) { details . level = level ; } else { details . level = { level : level , offset : 0 } ; } this . updateLevel ( ) ; } updateLevel ( ) { this . updateZIndex ( ) ; const level = this . map . levels [ this . details . level . level ] ; let height = 0 ; if ( level ) { height = level . height ; } const offset = this . details . level . offset ; this . levelOffset = - ( height + offset ) ; this . updateSettings ( ) ; } async setSettings ( settings ) { this . details . settings = settings ; await this . updateType ( ) ; } setSelected ( selected ) { this . _selected = selected ; if ( this . collisionImage ) { this . collisionImage . alpha = selected ? 0.6 : 0 ; } if ( ! selected ) { this . isDragged = false ; } } destroy ( ) { super . destroy ( ) ; this . container . destroy ( ) ; this . filterSubscription . unsubscribe ( ) ; if ( this . text ) { this . text . destroy ( ) ; } } updateZIndex ( ) { let zIndex = this . details . level . level * 10 + 1 ; if ( this . details . type === 'OLPlatform' || this . details . type === 'ObjectLayerView' ) { zIndex += 100 ; } zIndex += this . container . y * 0.000001 ; this . container . depth = zIndex ; } exportEntity ( ) { const out = { type : this . details . type , x : this . container . x , y : this . container . y , level : this . details . level . offset ? this . details . level : this . details . level . level , settings : this . details . settings } ; return JSON . parse ( JSON . stringify ( out ) ) ; } public abstract getScaleSettings ( ) ; public abstract getAttributes ( ) ; protected abstract setupType ( settings ) ; public async updateType ( ) { const settings = this . details . settings ; await this . setupType ( settings ) ; this . setActive ( true ) ; } public generateErrorImage ( ) { this . generateNoImageType ( 0xFF0000 , 1 ) ; } public generateNoImageType ( rgbTop = 0xc06040 , aTop = 0.5 , rgb = 0x800000 , a = 0.5 ) { const settings = this . details . settings ; const baseSize = { x : 16 , y : 16 , z : 0 } ; if ( settings . size ) { baseSize . x = settings . size . x ; baseSize . y = settings . size . y ; } baseSize . z = settings . zHeight || settings . wallZHeight || 0 ; this . entitySettings = < any > { } ; this . entitySettings . baseSize = baseSize ; const scaleSettings = this . getScaleSettings ( ) ; if ( scaleSettings && ( scaleSettings . scalableX || scaleSettings . scalableY ) ) { this . entitySettings . scalableX = scaleSettings . scalableX ; this . entitySettings . scalableY = scaleSettings . scalableY ; } this . generateSingleColorSheet ( rgb , a , rgbTop , aTop ) ; this . updateSettings ( ) ; } private generateSingleColorSheet ( rgb , a , rgbTop ? , aTop ? ) { const size = this . getActualSize ( ) ; if ( rgbTop === undefined ) { rgbTop = rgb ; } if ( aTop === undefined ) { aTop = a ; } if ( ! size . z ) { this . entitySettings . sheets = { ignoreScalable : true , fix : [ { gfx : 'pixel' , x : 0 , y : 0 , w : size . x , h : size . y , scaleX : size . x , scaleY : size . y , tint : rgbTop , alpha : aTop } ] , } ; } else { this . entitySettings . sheets = { ignoreScalable : true , fix : [ { gfx : 'pixel' , x : 0 , y : 0 , w : size . x , h : size . z , scaleX : size . x , scaleY : size . z , tint : rgb , alpha : a } , { gfx : 'pixel' , x : 0 , y : 0 , offsetY : - size . z , w : size . x , h : size . y , scaleX : size . x , scaleY : size . y , tint : rgbTop , alpha : aTop } ] , } ; } } protected replaceJsonParams ( jsonInstance , prop ) { Object . entries ( jsonInstance ) . forEach ( ( [ key , value ] ) => { if ( value [ 'jsonPARAM' ] ) { jsonInstance [ key ] = prop [ value [ 'jsonPARAM' ] ] ; return ; } if ( typeof value === 'object' ) { this . replaceJsonParams ( value , prop ) ; } } ) ; } public getBoundingBox ( ) { if ( ! this . inputZone . input ) { console . warn ( 'no bounding box for: ' + this . details . type ) ; return new Phaser . Geom . Rectangle ( 0 , 0 , 0 , 0 ) ; } const hitArea = this . inputZone . input . hitArea ; const box = new Phaser . Geom . Rectangle ( this . inputZone . x + this . container . x , this . inputZone . y + this . container . y , hitArea . width , hitArea . height ) ; return box ; } public getActualSize ( ) { const s = this . entitySettings ; const size = Object . assign ( { } , this . details . settings . size || s . baseSize ) ; try { size . x = Number ( size . x ) ; size . y = Number ( size . y ) ; if ( size . z !== 0 ) { size . z = Number ( size . z || this . details . settings . zHeight || this . details . settings . wallZHeight || ( s . baseSize ? s . baseSize . z || 0 : 0 ) ) ; } } catch ( e ) { console . log ( this ) ; console . error ( e ) ; } return size ; } private drawBoundingBox ( ) { const collImg = this . collisionImage ; collImg . clear ( ) ; const size = this . getActualSize ( ) ; const inputArea = new Phaser . Geom . Rectangle ( 0 , 0 , size . x , size . y ) ; const outline = 0 ; const outlineAlpha = 1 ; const bottomRect = new Phaser . Geom . Rectangle ( 0 , size . z , inputArea . width , inputArea . height - 1 ) ; if ( size . z > 0 ) { const middleRect = new Phaser . Geom . Rectangle ( 0 , inputArea . height , inputArea . width , size . z - 1 ) ; Helper . drawRect ( collImg , middleRect , 0xff0707 , 0.5 , outline , outlineAlpha ) ; const topRect = new Phaser . Geom . Rectangle ( 0 , 0 , inputArea . width , inputArea . height ) ; Helper . drawRect ( collImg , topRect , 0xffff07 , 1 , outline , outlineAlpha ) ; Helper . drawRect ( collImg , bottomRect , 0xffff07 , 0.1 , outline , outlineAlpha ) ; } else { Helper . drawRect ( collImg , bottomRect , 0xffff07 , 1 , outline , outlineAlpha ) ; } collImg . x = inputArea . x ; collImg . y = inputArea . y - ( size . z || 0 ) + this . levelOffset ; const shape = new Phaser . Geom . Rectangle ( 0 , 0 , size . x , size . y + ( size . z || 0 ) ) ; this . inputZone . x = collImg . x ; this . inputZone . y = collImg . y ; this . inputZone . setSize ( shape . width , shape . height , true ) ; this . generateText ( this . details . settings . name , size ) ; } private generateText ( name , size ) { if ( name ) { if ( ! this . text ) { this . text = this . scene . add . text ( 0 , 0 , '' , { font : '400 18pt Roboto' , fill : 'white' , } ) ; this . text . setOrigin ( 0.5 , 0.5 ) ; this . text . setScale ( 0.3 ) ; this . container . add ( this . text ) ; } this . text . setText ( name ) ; this . text . setPosition ( size . x / 2 , size . y / 2 + this . levelOffset ) ; } else if ( this . text ) { this . text . destroy ( ) ; this . text = undefined ; } } protected filter ( filter ) { const lower = filter . toLocaleLowerCase ( ) ; const attributes = this . getAttributes ( ) ; for ( const name of Object . keys ( attributes ) ) { const value = this . details . settings [ name ] || '' ; if ( typeof value === 'string' && value . toLowerCase ( ) . includes ( lower ) ) { return true ; } } return this . details . type . toLowerCase ( ) . includes ( lower ) || ( this . details . settings . name || '' ) . toLowerCase ( ) . includes ( lower ) ; } private setVisible ( visible ) { this . setActive ( visible ) ; if ( visible ) { this . container . alpha = 1 ; } else { this . container . alpha = 0.2 ; } } }