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 '
2015-10-13 19:43:56 -04:00
CocoCollection = require ' collections/CocoCollection '
2014-01-03 13:32:13 -05:00
2014-11-28 20:49:41 -05:00
utils = require ' core/utils '
2014-11-01 17:15:57 -04: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 '
2015-05-12 19:59:19 -04:00
samurai: ' 53e12be0d042f23505c3023b '
raider: ' 55527eb0b8abf4ba1fe9a107 '
2015-08-29 11:02:20 -04:00
goliath: ' 55e1a6e876cb0948c96af9f8 '
2015-05-12 19:59:19 -04:00
guardian: ' '
ninja: ' 52fc0ed77e01835453bd8f6c '
2014-11-15 15:46:57 -05:00
' forest-archer ' : ' 5466d4f2417c8b48a9811e87 '
trapper: ' 5466d449417c8b48a9811e83 '
2015-05-12 19:59:19 -04:00
pixie: ' '
assassin: ' '
2014-09-20 01:15:58 -04:00
librarian: ' 52fbf74b7e01835453bd8d8e '
' potion-master ' : ' 52e9adf7427172ae56002172 '
2014-11-15 15:46:57 -05:00
sorcerer: ' 52fd1524c7e6cf99160e7bc9 '
2015-06-05 20:02:37 -04:00
necromancer: ' 55652fb3b9effa46a1f775fd '
2015-05-12 19:59:19 -04:00
' dark-wizard ' : ' '
2014-11-24 13:51:20 -05:00
@heroClasses:
2015-05-12 19:59:19 -04:00
Warrior: [ ' captain ' , ' knight ' , ' samurai ' , ' raider ' , ' goliath ' , ' guardian ' ]
Ranger: [ ' ninja ' , ' forest-archer ' , ' trapper ' , ' pixie ' , ' assassin ' ]
Wizard: [ ' librarian ' , ' potion-master ' , ' sorcerer ' , ' necromancer ' , ' dark-wizard ' ]
2014-11-04 10:52:23 -05:00
@items:
' simple-boots ' : ' 53e237bf53457600003e3f05 '
2014-06-30 22:16:26 -04:00
urlRoot: ' /db/thang.type '
2014-01-14 16:16:30 -05:00
building: { }
2015-02-25 21:41:39 -05:00
editableByArtisans: true
2015-10-12 19:47:48 -04:00
@defaultActions: [ ' idle ' , ' die ' , ' move ' , ' attack ' ]
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
2015-10-12 19:47:48 -04:00
getDefaultActions: ->
actions = [ ]
for action in _ . values ( @ getActions ( ) )
continue unless _ . any ThangType . defaultActions , (prefix) ->
_ . string . startsWith ( action . name , prefix )
actions . push ( action )
return actions
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-11-25 16:28:15 -05:00
canvas = $ ( " <canvas width= ' #{ size } ' height= ' #{ size } ' ></canvas> " )
2015-03-16 20:36:38 -04:00
try
stage = new createjs . Stage ( canvas [ 0 ] )
catch err
console . error " Error trying to create #{ @ get ( ' name ' ) } avatar stage: " , err , " with window as " , window
return null
2014-11-25 16:28:15 -05:00
return stage 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
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-11-18 21:43:53 -05:00
return # TODO: causes infinite recursion in new EaselJS
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 '
2014-11-18 21:43:53 -05:00
return # TODO: just breaks in new EaselJS
2014-01-09 14:04:22 -05:00
@ 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-11-15 10:40:10 -05:00
2014-11-12 17:06:30 -05:00
getVectorPortraitStage: (size=100) ->
return unless @ actions
canvas = $ ( " <canvas width= ' #{ size } ' height= ' #{ size } ' ></canvas> " )
stage = new createjs . Stage ( canvas [ 0 ] )
portrait = @ actions . portrait
return unless portrait and ( portrait . animation or portrait . container )
scale = portrait . scale or 1
vectorParser = new SpriteBuilder ( @ , { } )
if portrait . animation
sprite = vectorParser . buildMovieClip portrait . animation
sprite . gotoAndStop ( 0 )
else if portrait . container
sprite = vectorParser . buildContainerFromStore ( portrait . container )
pt = portrait . positions ? . registration
2015-01-07 18:11:34 -05:00
sprite.regX = pt ? . x / scale or 0
sprite.regY = pt ? . y / scale or 0
2014-11-12 17:06:30 -05:00
sprite.scaleX = sprite.scaleY = scale * size / 100
stage . addChild ( sprite )
stage . update ( )
stage
2014-01-15 16:04:48 -05:00
2014-05-02 20:03:30 -04:00
uploadGenericPortrait: (callback, src) ->
src ? = @ getPortraitSource ( )
2015-01-04 11:05:38 -05:00
return callback ? ( ) unless src and _ . string . startsWith src , ' 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-11-05 15:23:34 -05:00
getHeroStats: ->
2014-11-11 19:36:44 -05:00
# Translate from raw hero properties into appropriate display values for the PlayHeroesModal.
2014-11-05 16:28:54 -05:00
# Adapted from https://docs.google.com/a/codecombat.com/spreadsheets/d/1BGI1bzT4xHvWA81aeyIaCKWWw9zxn7-MwDdydmB5vw4/edit#gid=809922675
2014-11-05 15:23:34 -05:00
return unless heroClass = @ get ( ' heroClass ' )
2014-11-05 16:28:54 -05:00
components = @ get ( ' components ' ) or [ ]
unless equipsConfig = _ . find ( components , original: LevelComponent . EquipsID ) ? . config
return console . warn @ get ( ' name ' ) , ' is not an equipping hero, but you are asking for its hero stats. (Did you project away components?) '
unless movesConfig = _ . find ( components , original: LevelComponent . MovesID ) ? . config
return console . warn @ get ( ' name ' ) , ' is not a moving hero, but you are asking for its hero stats. '
2014-11-15 10:40:10 -05:00
unless programmableConfig = _ . find ( components , original: LevelComponent . ProgrammableID ) ? . config
return console . warn @ get ( ' name ' ) , ' is not a Programmable hero, but you are asking for its hero stats. '
2014-11-05 16:28:54 -05:00
@ classStatAverages ? =
attack: { Warrior: 7.5 , Ranger: 5 , Wizard: 2.5 }
health: { Warrior: 7.5 , Ranger: 5 , Wizard: 3.5 }
2014-11-15 10:40:10 -05:00
stats = { }
2014-11-05 16:28:54 -05:00
rawNumbers = attack: equipsConfig . attackDamageFactor ? 1 , health: equipsConfig . maxHealthFactor ? 1 , speed: movesConfig . maxSpeed
for prop in [ ' attack ' , ' health ' ]
stat = rawNumbers [ prop ]
if stat < 1
classSpecificScore = 10 - 5 / stat
else
classSpecificScore = stat * 5
classAverage = @ classStatAverages [ prop ] [ @ get ( ' heroClass ' ) ]
2015-02-26 11:02:22 -05:00
stats [ prop ] =
relative: Math . round ( 2 * ( ( classAverage - 2.5 ) + classSpecificScore / 2 ) ) / 2 / 10
absolute: stat
pieces = ( $ . i18n . t " choose_hero. #{ prop } _ #{ num } " for num in [ 1 . . 3 ] )
percent = Math . round ( stat * 100 ) + ' % '
className = $ . i18n . t " general. #{ _ . string . slugify @ get ( ' heroClass ' ) } "
stats [ prop ] . description = [ pieces [ 0 ] , percent , pieces [ 1 ] , className , pieces [ 2 ] ] . join ' '
2014-11-15 10:40:10 -05:00
2014-11-05 16:28:54 -05:00
minSpeed = 4
maxSpeed = 16
speedRange = maxSpeed - minSpeed
speedPoints = rawNumbers . speed - minSpeed
2015-02-26 11:02:22 -05:00
stats.speed =
relative: Math . round ( 20 * speedPoints / speedRange ) / 2 / 10
absolute: rawNumbers . speed
description: " #{ $ . i18n . t ' choose_hero.speed_1 ' } #{ rawNumbers . speed } #{ $ . i18n . t ' choose_hero.speed_2 ' } "
2014-11-15 10:40:10 -05:00
2015-08-29 13:01:53 -04:00
stats.skills = ( _ . string . titleize ( _ . string . humanize ( skill ) ) for skill in programmableConfig . programmableProperties when skill isnt ' say ' and not /(Range|Pos|Radius|Damage)$/ . test ( skill ) )
2014-11-15 10:40:10 -05:00
2014-11-05 16:28:54 -05:00
stats
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: { }
2014-11-12 14:01:52 -05:00
stats = { }
2014-08-31 15:21:25 -04:00
props = itemConfig . programmableProperties ? [ ]
props = props . concat itemConfig . moreProgrammableProperties ? [ ]
2014-11-12 14:01:52 -05:00
props = _ . without props , ' canCast ' , ' spellNames ' , ' spells '
2014-08-31 15:21:25 -04:00
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-11-22 23:56:46 -05:00
statKeys = _ . keys ( stats )
statKeys . sort ( )
props . sort ( )
sortedStats = { }
sortedStats [ key ] = stats [ key ] for key in statKeys
props: props , stats: sortedStats
2014-08-31 15:21:25 -04:00
formatStatDisplay: (name, modifiers) ->
2014-11-01 17:15:57 -04:00
i18nKey = {
maxHealth: ' health '
maxSpeed: ' speed '
healthReplenishRate: ' regeneration '
attackDamage: ' attack '
attackRange: ' range '
shieldDefenseFactor: ' blocks '
visualRange: ' range '
2014-11-11 21:16:33 -05:00
throwDamage: ' attack '
throwRange: ' range '
2014-11-22 23:56:46 -05:00
bashDamage: ' attack '
2014-12-08 16:59:13 -05:00
backstabDamage: ' backstab '
2014-11-01 17:15:57 -04:00
} [ name ]
2014-11-04 10:52:23 -05:00
2014-11-01 17:15:57 -04:00
if i18nKey
name = $ . i18n . t ' choose_hero. ' + i18nKey
2015-01-11 13:04:24 -05:00
matchedShortName = true
2014-11-01 17:15:57 -04:00
else
name = _ . string . humanize name
2015-01-11 13:04:24 -05:00
matchedShortName = false
2014-11-01 17:15:57 -04:00
2014-08-31 15:21:25 -04:00
format = ' '
2014-11-01 17:15:57 -04:00
format = ' m ' if /(range|radius|distance|vision)$/i . test name
2014-08-31 15:21:25 -04:00
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
2014-11-01 17:15:57 -04:00
if /(blocks)$/i . test name
format || = ' % '
value = ( value * 100 ) . toFixed ( 1 )
2014-08-31 15:21:25 -04:00
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 '
2015-01-11 13:04:24 -05:00
name: name , display: display , matchedShortName: matchedShortName
2014-10-30 16:07:04 -04:00
isSilhouettedItem: ->
2016-01-02 19:10:03 -05:00
return console . error " Trying to determine whether #{ @ get ( ' name ' ) } should be a silhouetted item, but it has no gem cost. " unless @ get ( ' gems ' ) ? or @ get ( ' tier ' ) ?
2014-11-11 01:07:55 -05:00
console . info " Add (or make sure you have fetched) a tier for #{ @ get ( ' name ' ) } to more accurately determine whether it is silhouetted. " unless @ get ( ' tier ' ) ?
tier = @ get ' tier '
if tier ?
return @ levelRequiredForItem ( ) > me . level ( )
2014-10-30 16:07:04 -04:00
points = me . get ( ' points ' )
expectedTotalGems = ( points ? 0 ) * 1.5 # Not actually true, but roughly kinda close for tier 0, kinda tier 1
2014-11-11 01:07:55 -05:00
@ get ( ' gems ' ) > ( 100 + expectedTotalGems ) * 1.2
levelRequiredForItem: ->
return console . error " Trying to determine what level is required for #{ @ get ( ' name ' ) } , but it has no tier. " unless @ get ( ' tier ' ) ?
2014-11-24 12:05:59 -05:00
itemTier = @ get ' tier '
2014-11-24 13:51:20 -05:00
playerTier = itemTier / 2.5
2014-11-24 12:05:59 -05:00
playerLevel = me . constructor . levelForTier playerTier
#console.log 'Level required for', @get('name'), 'is', playerLevel, 'player tier', playerTier, 'because it is itemTier', itemTier, 'which is normally level', me.constructor.levelForTier(itemTier)
playerLevel
2015-10-12 19:47:48 -04:00
getContainersForAnimation: (animation, action) ->
rawAnimation = @ get ( ' raw ' ) . animations [ animation ]
if not rawAnimation
console . error ' thang type ' , @ get ( ' name ' ) , ' is missing animation ' , animation , ' from action ' , action
containers = rawAnimation . containers
for animation in @ get ( ' raw ' ) . animations [ animation ] . animations
containers = containers . concat ( @ getContainersForAnimation ( animation . gn , action ) )
return containers
getContainersForActions: (actionNames) ->
containersToRender = { }
actions = @ getActions ( )
for actionName in actionNames
action = _ . find ( actions , { name: actionName } )
if action . container
containersToRender [ action . container ] = true
else if action . animation
animationContainers = @ getContainersForAnimation ( action . animation , action )
containersToRender [ container . gn ] = true for container in animationContainers
return _ . keys ( containersToRender )
2015-10-13 19:43:56 -04:00
2015-10-14 17:33:26 -04:00
nextForAction: (action) ->
next = true
next = action . goesTo if action . goesTo
next = false if action . loops is false
return next
2015-10-13 19:43:56 -04:00
initPrerenderedSpriteSheets: ->
return if @ prerenderedSpriteSheets or not data = @ get ( ' prerenderedSpriteSheetData ' )
# creates a collection of prerendered sprite sheets
@prerenderedSpriteSheets = new PrerenderedSpriteSheets ( data )
getPrerenderedSpriteSheet: (colorConfig, defaultSpriteType) ->
return unless @ prerenderedSpriteSheets
spriteType = @ get ( ' spriteType ' ) or defaultSpriteType
@ prerenderedSpriteSheets . find (pss) ->
return false if pss . get ( ' spriteType ' ) isnt spriteType
otherColorConfig = pss . get ( ' colorConfig ' )
return true if _ . isEmpty ( colorConfig ) and _ . isEmpty ( otherColorConfig )
2015-10-14 13:41:10 -04:00
getHue = (config) -> _ . result ( _ . result ( config , ' team ' ) , ' hue ' )
return getHue ( colorConfig ) is getHue ( otherColorConfig )
2015-10-13 19:43:56 -04:00
getPrerenderedSpriteSheetToLoad: ->
return unless @ prerenderedSpriteSheets
@ prerenderedSpriteSheets . find (pss) -> pss . needToLoad and not pss . loadedImage
class PrerenderedSpriteSheet extends CocoModel
@className: ' PrerenderedSpriteSheet '
loadImage: ->
2015-10-14 17:48:43 -04:00
return false if @ loadingImage or @ loadedImage
return false unless imageURL = @ get ( ' image ' )
2015-10-13 19:43:56 -04:00
@image = $ ( " <img src= ' /file/ #{ imageURL } ' /> " )
@loadingImage = true
@ image . one ( ' load ' , =>
@loadingImage = false
@loadedImage = true
@ buildSpriteSheet ( )
@ trigger ( ' image-loaded ' , @ ) )
@ image . one ( ' error ' , =>
@loadingImage = false
@ trigger ( ' image-load-error ' , @ )
)
2015-10-14 17:48:43 -04:00
return true
2015-10-13 19:43:56 -04:00
buildSpriteSheet: ->
@spriteSheet = new createjs . SpriteSheet ( {
images: [ @ image [ 0 ] ] ,
frames: @ get ( ' frames ' )
animations: @ get ( ' animations ' )
} )
markToLoad: -> @needToLoad = true
needToLoad: false
loadedImage: false
loadingImage: false
class PrerenderedSpriteSheets extends CocoCollection
model: PrerenderedSpriteSheet