2014-06-30 22:16:26 -04:00
CocoModel = require ' ./CocoModel '
2014-01-03 13:32:13 -05:00
SpriteBuilder = require ' lib/sprites/SpriteBuilder '
2014-08-13 20:21:37 -04:00
LevelComponent = require ' ./LevelComponent '
2014-01-03 13:32:13 -05:00
2014-03-13 16:25:03 -04:00
buildQueue = [ ]
2014-01-03 13:32:13 -05:00
module.exports = class ThangType extends CocoModel
2014-06-30 22:16:26 -04:00
@className: ' ThangType '
2014-04-22 14:11:08 -04:00
@schema: require ' schemas/models/thang_type '
2014-06-30 22:16:26 -04:00
urlRoot: ' /db/thang.type '
2014-01-14 16:16:30 -05:00
building: { }
2014-01-03 13:32:13 -05:00
initialize: ->
super ( )
2014-01-14 16:16:30 -05:00
@building = { }
2014-01-03 13:32:13 -05:00
@ setDefaults ( )
@ on ' sync ' , @ setDefaults
@spriteSheets = { }
2014-05-31 01:12:44 -04:00
## Testing memory clearing
#f = =>
# console.info 'resetting raw data'
# @unset 'raw'
# @_previousAttributes.raw = null
#setTimeout f, 40000
2014-01-03 13:32:13 -05:00
setDefaults: ->
@ resetRawData ( ) unless @ get ( ' raw ' )
resetRawData: ->
2014-06-30 22:16:26 -04:00
@ set ( ' raw ' , { shapes: { } , containers: { } , animations: { } } )
2014-01-03 13:32:13 -05:00
resetSpriteSheetCache: ->
@ buildActions ( )
@spriteSheets = { }
2014-01-21 02:45:27 -05:00
@building = { }
2014-05-15 14:27:51 -04:00
2014-05-13 13:26:33 -04:00
isFullyLoaded: ->
# TODO: Come up with a better way to identify when the model doesn't have everything needed to build the sprite. ie when it's a projection without all the required data.
return @ get ( ' actions ' ) or @ get ( ' raster ' ) # needs one of these two things
2014-05-22 15:05:30 -04:00
2014-01-03 13:32:13 -05:00
getActions: ->
2014-05-13 13:26:33 -04:00
return { } unless @ isFullyLoaded ( )
2014-01-03 13:32:13 -05:00
return @ actions or @ buildActions ( )
2014-01-15 16:04:48 -05:00
2014-01-03 13:32:13 -05:00
buildActions: ->
2014-05-13 13:26:33 -04:00
return null unless @ isFullyLoaded ( )
@actions = $ . extend ( true , { } , @ get ( ' actions ' ) )
2014-01-03 13:32:13 -05:00
for name , action of @ actions
action.name = name
for relatedName , relatedAction of action . relatedActions ? { }
2014-06-30 22:16:26 -04:00
relatedAction.name = action . name + ' _ ' + relatedName
2014-01-03 13:32:13 -05:00
@ actions [ relatedAction . name ] = relatedAction
@ actions
2014-01-15 16:04:48 -05:00
2014-01-03 13:32:13 -05:00
getSpriteSheet: (options) ->
options = @ fillOptions options
key = @ spriteSheetKey ( options )
return @ spriteSheets [ key ] or @ buildSpriteSheet ( options )
2014-01-15 16:04:48 -05:00
2014-01-03 13:32:13 -05:00
fillOptions: (options) ->
options ? = { }
options = _ . clone options
2014-05-15 20:07:53 -04:00
options . resolutionFactor ? = SPRITE_RESOLUTION_FACTOR
2014-01-03 13:32:13 -05:00
options . async ? = false
2014-08-25 00:39:34 -04:00
options.thang = null # Don't hold onto any bad Thang references.
2014-01-03 13:32:13 -05:00
options
buildSpriteSheet: (options) ->
2014-05-13 13:26:33 -04:00
return false unless @ isFullyLoaded ( )
2014-01-14 16:16:30 -05:00
@options = @ fillOptions options
key = @ spriteSheetKey ( @ options )
2014-05-13 17:39:45 -04:00
if ss = @ spriteSheets [ key ] then return ss
2014-05-22 15:05:30 -04:00
if @ building [ key ]
@options = null
return key
2014-05-13 17:39:45 -04:00
@t0 = new Date ( ) . getTime ( )
2014-01-04 11:55:12 -05:00
@ initBuild ( options )
@ addGeneralFrames ( ) unless @ options . portraitOnly
@ addPortrait ( )
2014-01-14 16:16:30 -05:00
@ building [ key ] = true
result = @ finishBuild ( )
return result
2014-01-03 13:32:13 -05:00
2014-01-04 11:55:12 -05:00
initBuild: (options) ->
@ buildActions ( ) if not @ actions
2014-01-12 15:27:10 -05:00
@vectorParser = new SpriteBuilder ( @ , options )
2014-01-04 11:55:12 -05:00
@builder = new createjs . SpriteSheetBuilder ( )
@builder.padding = 2
@frames = { }
2014-01-15 16:04:48 -05:00
2014-01-04 11:55:12 -05:00
addPortrait: ->
# The portrait is built very differently than the other animations, so it gets a separate function.
2014-01-06 15:37:35 -05:00
return unless @ actions
2014-01-04 11:55:12 -05:00
portrait = @ actions . portrait
return unless portrait
scale = portrait . scale or 1
pt = portrait . positions ? . registration
rect = new createjs . Rectangle ( pt ? . x / scale or 0 , pt ? . y / scale or 0 , 100 / scale , 100 / scale )
if portrait . animation
mc = @ vectorParser . buildMovieClip portrait . animation
mc.nominalBounds = mc.frameBounds = null # override what the movie clip says on bounding
@ builder . addMovieClip ( mc , rect , scale )
frames = @ builder . _animations [ portrait . animation ] . frames
2014-01-09 01:30:00 -05:00
frames = @ mapFrames ( portrait . frames , frames [ 0 ] ) if portrait . frames ?
2014-01-04 11:55:12 -05:00
@ builder . addAnimation ' portrait ' , frames , true
else if portrait . container
s = @ vectorParser . buildContainerFromStore ( portrait . container )
frame = @ builder . addFrame ( s , rect , scale )
@ builder . addAnimation ' portrait ' , [ frame ] , false
2014-01-03 13:32:13 -05:00
2014-01-04 11:55:12 -05:00
addGeneralFrames: ->
2014-01-03 13:32:13 -05:00
framesMap = { }
for animation in @ requiredRawAnimations ( )
name = animation . animation
2014-01-04 11:55:12 -05:00
mc = @ vectorParser . buildMovieClip name
2014-01-30 19:17:41 -05:00
continue unless mc
2014-01-04 11:55:12 -05:00
@ builder . addMovieClip mc , null , animation . scale * @ options . resolutionFactor
2014-06-30 22:16:26 -04:00
framesMap [ animation . scale + ' _ ' + name ] = @ builder . _animations [ name ] . frames
2014-01-03 13:32:13 -05:00
for name , action of @ actions when action . animation
2014-01-04 11:55:12 -05:00
continue if name is ' portrait '
scale = action . scale ? @ get ( ' scale ' ) ? 1
2014-06-30 22:16:26 -04:00
frames = framesMap [ scale + ' _ ' + action . animation ]
2014-01-30 19:17:41 -05:00
continue unless frames
2014-01-04 11:55:12 -05:00
frames = @ mapFrames ( action . frames , frames [ 0 ] ) if action . frames ?
2014-01-03 13:32:13 -05:00
next = true
2014-01-04 11:55:12 -05:00
next = action . goesTo if action . goesTo
next = false if action . loops is false
@ builder . addAnimation name , frames , next
2014-01-15 16:04:48 -05:00
2014-01-03 13:32:13 -05:00
for name , action of @ actions when action . container and not action . animation
2014-01-04 11:55:12 -05:00
continue if name is ' portrait '
scale = @ options . resolutionFactor * ( action . scale or @ get ( ' scale ' ) or 1 )
s = @ vectorParser . buildContainerFromStore ( action . container )
2014-01-30 19:17:41 -05:00
continue unless s
2014-01-04 11:55:12 -05:00
frame = @ builder . addFrame ( s , s . bounds , scale )
@ builder . addAnimation name , [ frame ] , false
2014-01-09 01:30:00 -05:00
requiredRawAnimations: ->
required = [ ]
for name , action of @ get ( ' actions ' )
continue if name is ' portrait '
allActions = [ action ] . concat ( _ . values ( action . relatedActions ? { } ) )
for a in allActions when a . animation
scale = if name is ' portrait ' then a . scale or 1 else a . scale or @ get ( ' scale ' ) or 1
animation = { animation: a . animation , scale: scale }
animation.portrait = name is ' portrait '
unless _ . find ( required , (r) -> _ . isEqual r , animation )
required . push animation
required
2014-01-04 11:55:12 -05:00
mapFrames: (frames, frameOffset) ->
return frames unless _ . isString ( frames ) # don't accidentally do this again
( parseInt ( f , 10 ) + frameOffset for f in frames . split ( ' , ' ) )
2014-01-03 13:32:13 -05:00
2014-01-04 11:55:12 -05:00
finishBuild: ->
return if _ . isEmpty ( @ builder . _animations )
key = @ spriteSheetKey ( @ options )
2014-01-03 13:32:13 -05:00
spriteSheet = null
2014-01-04 11:55:12 -05:00
if @ options . async
2014-03-13 18:45:24 -04:00
buildQueue . push @ builder
2014-03-19 20:11:45 -04:00
@builder.t0 = new Date ( ) . getTime ( )
2014-03-13 16:25:03 -04:00
@ builder . buildAsync ( ) unless buildQueue . length > 1
2014-05-20 13:46:52 -04:00
@ builder . on ' complete ' , @ onBuildSpriteSheetComplete , @ , true , [ @ builder , key , @ options ]
@builder = null
return key
2014-01-04 11:55:12 -05:00
spriteSheet = @ builder . build ( )
2014-05-20 13:46:52 -04:00
@ logBuild @ t0 , false , @ options . portraitOnly
2014-01-04 11:55:12 -05:00
@ spriteSheets [ key ] = spriteSheet
2014-05-29 15:26:01 -04:00
@ building [ key ] = false
2014-05-20 13:46:52 -04:00
@builder = null
2014-05-22 15:05:30 -04:00
@options = null
2014-01-03 13:32:13 -05:00
spriteSheet
2014-01-15 16:04:48 -05:00
2014-05-20 13:46:52 -04:00
onBuildSpriteSheetComplete: (e, data) ->
[ builder , key , options ] = data
@ logBuild builder . t0 , true , options . portraitOnly
2014-03-13 16:25:03 -04:00
buildQueue = buildQueue . slice ( 1 )
2014-03-19 20:11:45 -04:00
buildQueue [ 0 ] . t0 = new Date ( ) . getTime ( ) if buildQueue [ 0 ]
2014-03-13 18:45:24 -04:00
buildQueue [ 0 ] ? . buildAsync ( )
2014-01-03 13:32:13 -05:00
@ spriteSheets [ key ] = e . target . spriteSheet
2014-05-29 15:26:01 -04:00
@ building [ key ] = false
2014-06-30 22:16:26 -04:00
@ trigger ' build-complete ' , { key: key , thangType: @ }
2014-01-15 16:04:48 -05:00
@vectorParser = null
2014-01-03 13:32:13 -05:00
2014-05-20 13:46:52 -04:00
logBuild: (startTime, async, portrait) ->
kind = if async then ' Async ' else ' Sync '
portrait = if portrait then ' (Portrait) ' else ' '
name = _ . string . rpad @ get ( ' name ' ) , 20
time = _ . string . lpad ' ' + new Date ( ) . getTime ( ) - startTime , 6
2014-08-25 00:39:34 -04:00
console . debug " Built sheet: #{ name } #{ time } ms #{ kind } #{ portrait } "
2014-05-20 13:46:52 -04:00
2014-01-03 13:32:13 -05:00
spriteSheetKey: (options) ->
2014-01-12 15:27:10 -05:00
colorConfigs = [ ]
for groupName , config of options . colorConfig or { }
colorConfigs . push " #{ groupName } : #{ config . hue } | #{ config . saturation } | #{ config . lightness } "
colorConfigs = colorConfigs . join ' , '
2014-03-05 15:53:48 -05:00
portraitOnly = ! ! options . portraitOnly
" #{ @ get ( ' name ' ) } - #{ options . resolutionFactor } - #{ colorConfigs } - #{ portraitOnly } "
2014-01-03 13:32:13 -05:00
2014-01-06 15:37:35 -05:00
getPortraitImage: (spriteOptionsOrKey, size=100) ->
src = @ getPortraitSource ( spriteOptionsOrKey , size )
2014-01-09 01:30:00 -05:00
return null unless src
2014-01-06 18:36:35 -05:00
$ ( ' <img /> ' ) . attr ( ' src ' , src )
2014-01-06 15:37:35 -05:00
getPortraitSource: (spriteOptionsOrKey, size=100) ->
2014-01-09 01:30:00 -05:00
stage = @ getPortraitStage ( spriteOptionsOrKey , size )
stage ? . toDataURL ( )
getPortraitStage: (spriteOptionsOrKey, size=100) ->
2014-05-13 13:26:33 -04:00
return unless @ isFullyLoaded ( )
2014-01-03 13:32:13 -05:00
key = spriteOptionsOrKey
2014-01-09 14:04:22 -05:00
key = if _ . isString ( key ) then key else @ spriteSheetKey ( @ fillOptions ( key ) )
2014-01-03 13:32:13 -05:00
spriteSheet = @ spriteSheets [ key ]
2014-03-03 13:21:51 -05:00
if not spriteSheet
options = if _ . isPlainObject spriteOptionsOrKey then spriteOptionsOrKey else { }
options.portraitOnly = true
spriteSheet = @ buildSpriteSheet ( options )
2014-05-20 13:46:52 -04:00
return if _ . isString spriteSheet
2014-01-03 13:32:13 -05:00
return unless spriteSheet
canvas = $ ( " <canvas width= ' #{ size } ' height= ' #{ size } ' ></canvas> " )
stage = new createjs . Stage ( canvas [ 0 ] )
sprite = new createjs . Sprite ( spriteSheet )
pt = @ actions . portrait ? . positions ? . registration
sprite.regX = pt ? . x or 0
sprite.regY = pt ? . y or 0
2014-02-17 20:38:49 -05:00
sprite.framerate = @ actions . portrait ? . framerate ? 20
2014-01-03 13:32:13 -05:00
sprite . gotoAndStop ' portrait '
stage . addChild ( sprite )
stage . update ( )
2014-01-09 01:30:00 -05:00
stage.startTalking = ->
sprite . gotoAndPlay ' portrait '
2014-01-09 14:04:22 -05:00
return if @ tick
2014-02-17 20:38:49 -05:00
@tick = (e) => @ update ( e )
2014-01-09 01:30:00 -05:00
createjs . Ticker . addEventListener ' tick ' , @ tick
stage.stopTalking = ->
2014-01-09 14:04:22 -05:00
sprite . gotoAndStop ' portrait '
@ update ( )
2014-01-09 01:30:00 -05:00
createjs . Ticker . removeEventListener ' tick ' , @ tick
2014-01-09 14:04:22 -05:00
@tick = null
2014-01-09 01:30:00 -05:00
stage
2014-01-15 16:04:48 -05:00
2014-05-02 20:03:30 -04:00
uploadGenericPortrait: (callback, src) ->
src ? = @ getPortraitSource ( )
2014-01-09 14:04:22 -05:00
return callback ? ( ) unless src
2014-01-06 15:37:35 -05:00
src = src . replace ( ' data:image/png;base64, ' , ' ' ) . replace ( /\ /g , ' + ' )
body =
filename: ' portrait.png '
mimetype: ' image/png '
path: " db/thang.type/ #{ @ get ( ' original ' ) } "
b64png: src
force: ' true '
2014-06-30 22:16:26 -04:00
$ . ajax ( ' /file ' , { type: ' POST ' , data: body , success: callback or @ onFileUploaded } )
2014-01-06 15:37:35 -05:00
onFileUploaded: =>
console . log ' Image uploaded '
2014-03-03 12:03:44 -05:00
2014-03-03 13:42:11 -05:00
@loadUniversalWizard: ->
return @ wizardType if @ wizardType
2014-06-30 22:16:26 -04:00
wizOriginal = ' 52a00d55cf1818f2be00000b '
2014-03-11 21:30:25 -04:00
url = " /db/thang.type/ #{ wizOriginal } /version "
2014-03-03 13:42:11 -05:00
@wizardType = new module . exports ( )
@wizardType.url = -> url
@ wizardType . fetch ( )
2014-03-11 21:30:25 -04:00
@ wizardType
2014-08-13 20:21:37 -04:00
2014-08-18 18:25:22 -04:00
getPortraitURL: ->
if iconURL = @ get ( ' rasterIcon ' )
return " /file/ #{ iconURL } "
" /file/db/thang.type/ #{ @ get ( ' original ' ) } /portrait.png "
2014-08-13 20:21:37 -04:00
# Item functions
getAllowedSlots: ->
itemComponentRef = _ . find (
@ get ( ' components ' ) or [ ] ,
(compRef) -> compRef . original is LevelComponent . ItemID )
return itemComponentRef ? . config ? . slots or [ ]
2014-08-21 19:27:52 -04:00
2014-08-13 20:21:37 -04:00
getFrontFacingStats: ->
stats = [ ]
for component in @ get ( ' components ' ) or [ ]
continue unless config = component . config
if config . attackDamage
stats . push { name: ' Attack Damage ' , value: config . attackDamage }
if config . attackRange
stats . push { name: ' Attack Range ' , value: " #{ config . attackRange } m " }
if config . cooldown
stats . push { name: ' Cooldown ' , value: " #{ config . cooldown } s " }
if config . maxSpeed
stats . push { name: ' Speed ' , value: " #{ config . maxSpeed } m/s " }
if config . maxAcceleration
stats . push { name: ' Acceleration ' , value: " #{ config . maxAcceleration } m/s^2 " }
if config . stats
for stat , value of config . stats
if value . factor
value = " x #{ value . factor } "
if value . addend and value . addend > 0
value = " + #{ value . addend } "
if value . addend and value . addend < 0
value = " #{ value . addend } "
if value . setTo
value = " = #{ value . setTo } "
if stat is ' maxHealth '
stats . push { name: ' Health ' , value: value }
if stat is ' healthReplenishRate '
stats . push { name: ' Regen ' , value: value }
if config . programmableProperties
props = config . programmableProperties
if props . length
stats . push { name: ' Allows ' , value: props . join ( ' , ' ) }
if config . visualRange
value = config . visualRange
if value is 9001 then value is " Infinite "
stats . push { name: ' Visual Range ' , value: " #{ value } m " }
if config . programmableSnippets
snippets = config . programmableSnippets
if snippets . length
stats . push { name: ' Snippets ' , value: snippets . join ( ' , ' ) }
2014-08-21 19:27:52 -04:00
stats