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-09-20 01:15:58 -04:00
@heroes:
captain: ' 529ec584c423d4e83b000014 '
knight: ' 529ffbf1cf1818f2be000001 '
librarian: ' 52fbf74b7e01835453bd8d8e '
equestrian: ' 52e95b4222efc8e70900175d '
' potion-master ' : ' 52e9adf7427172ae56002172 '
thoktar: ' 52a00542cf1818f2be000006 '
' robot-walker ' : ' 5301696ad82649ec2c0c9b0d '
' michael-heasell ' : ' 53e126a4e06b897606d38bef '
' ian-elliott ' : ' 53e12be0d042f23505c3023b '
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
@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
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-10-18 21:18:12 -04:00
2014-09-12 19:33:01 -04:00
loadRasterImage: ->
2014-09-16 18:36:59 -04:00
return if @ loadingRaster or @ loadedRaster
2014-09-12 19:33:01 -04:00
return unless raster = @ get ( ' raster ' )
@rasterImage = $ ( " <img src= ' /file/ #{ raster } ' /> " )
2014-09-16 18:36:59 -04:00
@loadingRaster = true
@ rasterImage . one ( ' load ' , =>
@loadingRaster = false
@loadedRaster = true
@ trigger ( ' raster-image-loaded ' , @ ) )
2014-09-18 12:46:02 -04:00
@ rasterImage . one ( ' error ' , =>
@loadingRaster = false
@ trigger ( ' raster-image-load-errored ' , @ )
)
2014-10-18 21:18:12 -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
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-08-30 16:43:56 -04:00
return false unless @ isFullyLoaded ( ) and @ get ' raw '
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-10-22 10:53:28 -04:00
return @ getPortraitURL ( ) if @ get ( ' rasterIcon ' ) or @ get ( ' raster ' )
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> " )
2014-10-19 20:38:10 -04:00
console . log ' made canvas ' , canvas , ' with size ' , size unless canvas [ 0 ]
2014-01-03 13:32:13 -05:00
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-10-22 10:53:28 -04:00
return callback ? ( ) unless src and src . startsWith ' data: '
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 } "
2014-10-22 10:53:28 -04:00
if rasterURL = @ get ( ' raster ' )
return " /file/ #{ rasterURL } "
2014-08-18 18:25:22 -04:00
" /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 )
2014-10-30 16:07:04 -04:00
return itemComponentRef ? . config ? . slots or [ ' right-hand ' ] # ['right-hand'] is default
getAllowedHeroClasses: ->
return [ heroClass ] if heroClass = @ get ' heroClass '
[ ' Warrior ' , ' Ranger ' , ' Wizard ' ]
2014-08-21 19:27:52 -04:00
2014-08-13 20:21:37 -04:00
getFrontFacingStats: ->
2014-08-31 15:21:25 -04:00
components = @ get ( ' components ' ) or [ ]
unless itemConfig = _ . find ( components , original: LevelComponent . ItemID ) ? . config
console . warn @ get ( ' name ' ) , ' is not an item, but you are asking for its stats. '
return props: [ ] , stats: { }
props = itemConfig . programmableProperties ? [ ]
props = props . concat itemConfig . moreProgrammableProperties ? [ ]
stats = { }
for stat , modifiers of itemConfig . stats ? { }
stats [ stat ] = @ formatStatDisplay stat , modifiers
for stat in itemConfig . extraHUDProperties ? [ ]
2014-09-22 19:09:27 -04:00
stats [ stat ] ? = null # Find it in the other Components.
2014-08-31 15:21:25 -04:00
for component in components
2014-08-13 20:21:37 -04:00
continue unless config = component . config
2014-08-31 15:21:25 -04:00
for stat , value of stats when not value ?
value = config [ stat ]
continue unless value ?
stats [ stat ] = @ formatStatDisplay stat , setTo: value
2014-08-31 15:27:52 -04:00
if stat is ' attackDamage '
dps = ( value / ( config . cooldown or 0.5 ) ) . toFixed ( 1 )
stats [ stat ] . display += " ( #{ dps } DPS) "
2014-08-13 20:21:37 -04:00
if config . programmableSnippets
2014-08-31 15:21:25 -04:00
props = props . concat config . programmableSnippets
2014-09-22 19:09:27 -04:00
for stat , value of stats when not value ?
stats [ stat ] = name: stat , display: ' ??? '
2014-08-31 15:21:25 -04:00
props: props , stats: stats
formatStatDisplay: (name, modifiers) ->
name = { maxHealth: ' Health ' , maxSpeed: ' Speed ' , healthReplenishRate: ' Regeneration ' } [ name ] ? name
name = _ . string . humanize name
format = ' '
format = ' m ' if /(range|radius|distance)$/i . test name
format || = ' s ' if /cooldown$/i . test name
format || = ' m/s ' if /speed$/i . test name
format || = ' /s ' if /(regeneration| rate)$/i . test name
value = modifiers . setTo
value = value . join ' , ' if _ . isArray value
display = [ ]
display . push " #{ value } #{ format } " if value ?
display . push " + #{ modifiers . addend } #{ format } " if modifiers . addend > 0
display . push " #{ modifiers . addend } #{ format } " if modifiers . addend < 0
display . push " x #{ modifiers . factor } " if modifiers . factor ? and modifiers . factor isnt 1
display = display . join ' , '
display = display . replace / 9001 m ? / , ' Infinity '
name: name , display: display
2014-10-30 16:07:04 -04:00
isSilhouettedItem: ->
# TODO: have items have actual levels instead of just going by their gem count
return console . error " Trying to determine whether #{ @ get ( ' name ' ) } should be a silhouetted item, but it has no gem cost. " unless @ get ' gems '
points = me . get ( ' points ' )
expectedTotalGems = ( points ? 0 ) * 1.5 # Not actually true, but roughly kinda close for tier 0, kinda tier 1
@ get ( ' gems ' ) > ( 100 + expectedTotalGems ) * 2