2014-11-28 20:49:41 -05:00
ModalView = require ' views/core/ModalView '
2014-09-17 21:56:08 -04:00
template = require ' templates/play/modal/play-heroes-modal '
2014-11-25 13:15:10 -05:00
buyGemsPromptTemplate = require ' templates/play/modal/buy-gems-prompt '
2014-09-17 21:56:08 -04:00
CocoCollection = require ' collections/CocoCollection '
ThangType = require ' models/ThangType '
2014-11-05 22:03:05 -05:00
SpriteBuilder = require ' lib/sprites/SpriteBuilder '
AudioPlayer = require ' lib/AudioPlayer '
2014-11-28 20:49:41 -05:00
utils = require ' core/utils '
2014-11-25 13:15:10 -05:00
BuyGemsModal = require ' views/play/modal/BuyGemsModal '
2015-02-11 17:09:00 -05:00
AuthModal = require ' views/core/AuthModal '
2014-11-25 13:15:10 -05:00
Purchase = require ' models/Purchase '
2015-01-14 17:10:27 -05:00
LayerAdapter = require ' lib/surface/LayerAdapter '
Lank = require ' lib/surface/Lank '
2014-09-17 21:56:08 -04:00
module.exports = class PlayHeroesModal extends ModalView
className: ' modal fade play-modal '
template: template
id: ' play-heroes-modal '
2014-11-05 22:03:05 -05:00
events:
' slide.bs.carousel # hero-carousel ' : ' onHeroChanged '
' change # option-code-language ' : ' onCodeLanguageChanged '
' click # close-modal ' : ' hide '
' click # confirm-button ' : ' saveAndHide '
2014-11-25 13:15:10 -05:00
' click .unlock-button ' : ' onUnlockButtonClicked '
' click .buy-gems-prompt-button ' : ' onBuyGemsPromptButtonClicked '
' click ' : ' onClickedSomewhere '
2014-11-05 22:03:05 -05:00
shortcuts:
' left ' : -> @ $el . find ( ' # hero-carousel ' ) . carousel ( ' prev ' ) if @ heroes . models . length and not @ $el . hasClass ' secret '
' right ' : -> @ $el . find ( ' # hero-carousel ' ) . carousel ( ' next ' ) if @ heroes . models . length and not @ $el . hasClass ' secret '
2015-02-20 18:45:00 -05:00
' enter ' : -> @ saveAndHide ( ) if @ visibleHero and not @ visibleHero . locked
2014-09-17 21:56:08 -04:00
constructor: (options) ->
super options
2014-11-05 22:03:05 -05:00
options ? = { }
@confirmButtonI18N = options . confirmButtonI18N ? " common.save "
2014-09-17 21:56:08 -04:00
@heroes = new CocoCollection ( [ ] , { model: ThangType } )
2014-11-05 22:03:05 -05:00
@heroes.url = ' /db/thang.type?view=heroes '
2014-11-28 15:11:51 -05:00
@ heroes . setProjection [ ' original ' , ' name ' , ' slug ' , ' soundTriggers ' , ' featureImages ' , ' gems ' , ' heroClass ' , ' description ' , ' components ' , ' extendedName ' , ' unlockLevelName ' , ' i18n ' ]
2014-11-11 19:36:44 -05:00
@heroes.comparator = ' gems '
2014-11-05 22:03:05 -05:00
@ listenToOnce @ heroes , ' sync ' , @ onHeroesLoaded
2014-09-17 21:56:08 -04:00
@ supermodel . loadCollection ( @ heroes , ' heroes ' )
2014-11-05 22:03:05 -05:00
@stages = { }
2015-01-14 17:10:27 -05:00
@layers = [ ]
2014-11-05 22:03:05 -05:00
@session = options . session
2014-11-10 14:08:17 -05:00
@ initCodeLanguageList options . hadEverChosenHero
2015-01-14 17:10:27 -05:00
@heroAnimationInterval = setInterval @ animateHeroes , 1000
2014-11-05 22:37:13 -05:00
2014-11-05 22:03:05 -05:00
onHeroesLoaded: ->
2014-11-25 12:28:42 -05:00
@ formatHero hero for hero in @ heroes . models
formatHero: (hero) ->
2015-02-03 20:07:15 -05:00
hero.name = utils . i18n hero . attributes , ' extendedName '
2014-11-25 12:28:42 -05:00
hero . name ? = utils . i18n hero . attributes , ' name '
hero.description = utils . i18n hero . attributes , ' description '
hero.unlockLevelName = utils . i18n hero . attributes , ' unlockLevelName '
original = hero . get ( ' original ' )
hero.locked = not me . ownsHero ( original )
hero.purchasable = hero . locked and ( original in ( me . get ( ' earned ' ) ? . heroes ? [ ] ) )
2014-12-28 16:25:20 -05:00
if @ options . level and allowedHeroes = @ options . level . get ' allowedHeroes '
hero.restricted = not ( hero . get ( ' original ' ) in allowedHeroes )
2014-11-25 12:28:42 -05:00
hero.class = ( hero . get ( ' heroClass ' ) or ' warrior ' ) . toLowerCase ( )
hero.stats = hero . getHeroStats ( )
2014-09-17 21:56:08 -04:00
getRenderData: (context={}) ->
context = super ( context )
context.heroes = @ heroes . models
2014-11-05 22:03:05 -05:00
context.level = @ options . level
2014-11-10 14:08:17 -05:00
context.codeLanguages = @ codeLanguageList
2014-11-05 22:03:05 -05:00
context.codeLanguage = @codeLanguage = @ options ? . session ? . get ( ' codeLanguage ' ) ? me . get ( ' aceConfig ' ) ? . language ? ' python '
context.confirmButtonI18N = @ confirmButtonI18N
2014-11-25 12:28:42 -05:00
context.visibleHero = @ visibleHero
2014-11-30 17:00:29 -05:00
context.gems = me . gems ( )
2014-12-10 13:37:57 -05:00
context.isIE = @ isIE ( )
2014-09-17 21:56:08 -04:00
context
afterRender: ->
super ( )
return unless @ supermodel . finished ( )
2015-08-20 15:43:04 -04:00
@ playSound ' game-menu-open '
2014-12-10 13:37:57 -05:00
@ $el . find ( ' .hero-avatar ' ) . addClass ' ie ' if @ isIE ( )
2014-11-05 22:03:05 -05:00
heroes = @ heroes . models
@ $el . find ( ' .hero-indicator ' ) . each ->
heroID = $ ( @ ) . data ( ' hero-id ' )
hero = _ . find heroes , (hero) -> hero . get ( ' original ' ) is heroID
2015-02-02 21:02:57 -05:00
$ ( @ ) . find ( ' .hero-avatar ' ) . css ( ' background-image ' , " url( #{ hero . getPortraitURL ( ) } ) " ) . addClass ( ' has-tooltip ' ) . tooltip ( )
2014-11-05 22:03:05 -05:00
@canvasWidth = 313 # @$el.find('canvas').width() # unreliable, whatever
@canvasHeight = @ $el . find ( ' canvas ' ) . height ( )
heroConfig = @ options ? . session ? . get ( ' heroConfig ' ) ? me . get ( ' heroConfig ' ) ? { }
heroIndex = Math . max 0 , _ . findIndex ( heroes , ( (hero) -> hero . get ( ' original ' ) is heroConfig . thangType ) )
@ $el . find ( " .hero-item:nth-child( #{ heroIndex + 1 } ), .hero-indicator:nth-child( #{ heroIndex + 1 } ) " ) . addClass ( ' active ' )
@ onHeroChanged direction: null , relatedTarget: @ $el . find ( ' .hero-item ' ) [ heroIndex ]
2015-02-02 21:02:57 -05:00
@ $el . find ( ' .hero-stat ' ) . addClass ( ' has-tooltip ' ) . tooltip ( )
2014-11-05 22:03:05 -05:00
@ buildCodeLanguages ( )
2014-11-25 12:28:42 -05:00
rerenderFooter: ->
@ formatHero @ visibleHero
@ renderSelectors ' # hero-footer '
@ buildCodeLanguages ( )
2014-11-30 17:00:29 -05:00
@ $el . find ( ' # gems-count-container ' ) . toggle Boolean @ visibleHero . purchasable
2014-11-25 12:28:42 -05:00
2014-11-10 14:08:17 -05:00
initCodeLanguageList: (hadEverChosenHero) ->
2014-11-30 16:19:00 -05:00
if application . isIPadApp
@codeLanguageList = [
{ id: ' python ' , name: " Python ( #{ $ . i18n . t ( ' choose_hero.default ' ) } ) " }
{ id: ' javascript ' , name: ' JavaScript ' }
]
else
@codeLanguageList = [
{ id: ' python ' , name: " Python ( #{ $ . i18n . t ( ' choose_hero.default ' ) } ) " }
{ id: ' javascript ' , name: ' JavaScript ' }
2015-03-18 01:04:30 -04:00
{ id: ' coffeescript ' , name: " CoffeeScript ( #{ $ . i18n . t ( ' choose_hero.experimental ' ) } ) " }
2014-11-30 16:19:00 -05:00
{ id: ' clojure ' , name: " Clojure ( #{ $ . i18n . t ( ' choose_hero.experimental ' ) } ) " }
2015-03-18 01:04:30 -04:00
{ id: ' lua ' , name: ' Lua ' }
2015-09-08 17:49:55 -04:00
#{id: 'io', name: "Io (#{$.i18n.t('choose_hero.experimental')})"}
2014-11-30 16:19:00 -05:00
]
2014-11-10 14:08:17 -05:00
2014-11-05 22:03:05 -05:00
onHeroChanged: (e) ->
direction = e . direction # 'left' or 'right'
heroItem = $ ( e . relatedTarget )
hero = _ . find @ heroes . models , (hero) -> hero . get ( ' original ' ) is heroItem . data ( ' hero-id ' )
return console . error " Couldn ' t find hero from heroItem: " , heroItem unless hero
heroIndex = heroItem . index ( )
hero = @ loadHero hero , heroIndex
@ preloadHero heroIndex + 1
@ preloadHero heroIndex - 1
@selectedHero = hero unless hero . locked
2014-11-25 12:28:42 -05:00
@visibleHero = hero
@ rerenderFooter ( )
2014-11-06 19:23:23 -05:00
@ trigger ' hero-loaded ' , { hero: hero }
2014-11-05 22:03:05 -05:00
getFullHero: (original) ->
url = " /db/thang.type/ #{ original } /version "
if fullHero = @ supermodel . getModel url
return fullHero
fullHero = new ThangType ( )
fullHero . setURL url
fullHero = ( @ supermodel . loadModel fullHero , ' thang ' ) . model
fullHero
preloadHero: (heroIndex) ->
return unless hero = @ heroes . models [ heroIndex ]
@ loadHero hero , heroIndex , true
loadHero: (hero, heroIndex, preloading=false) ->
createjs . Ticker . removeEventListener ' tick ' , stage for stage in _ . values @ stages
createjs . Ticker . setFPS 30 # In case we paused it from being inactive somewhere else
if stage = @ stages [ heroIndex ]
unless preloading
_ . defer -> createjs . Ticker . addEventListener ' tick ' , stage # Deferred, otherwise it won't start updating for some reason.
@ playSelectionSound hero
return hero
fullHero = @ getFullHero hero . get ' original '
onLoaded = =>
2014-12-04 18:04:34 -05:00
canvas = $ ( " .hero-item[data-hero-id= ' #{ fullHero . get ( ' original ' ) } ' ] canvas " )
return unless canvas . length # Don't render it if it's not on the screen.
2014-12-06 14:33:57 -05:00
unless fullHero . get ' raw '
console . error " Couldn ' t make animation for #{ fullHero . get ( ' name ' ) } with attributes #{ _ . cloneDeep ( fullHero . attributes ) } . Was it loaded with an improper projection or something? " , fullHero
@ rerenderFooter ( )
return
2014-11-05 22:03:05 -05:00
canvas . show ( ) . prop width: @ canvasWidth , height: @ canvasHeight
2015-01-31 15:23:34 -05:00
2015-01-14 17:10:27 -05:00
layer = new LayerAdapter ( { webGL : true } )
@ layers . push layer
layer.resolutionFactor = 8 # hi res!
layer.buildAsync = false
multiplier = 7
layer.scaleX = layer.scaleY = multiplier
lank = new Lank ( fullHero , { preloadSounds: false } )
2015-01-31 15:23:34 -05:00
2015-01-14 17:10:27 -05:00
layer . addLank ( lank )
layer . on ' new-spritesheet ' , ->
#- maybe put some more normalization here?
m = multiplier
2015-08-04 14:35:10 -04:00
m *= 0.75 if fullHero . get ( ' slug ' ) in [ ' knight ' , ' samurai ' , ' librarian ' , ' sorcerer ' , ' necromancer ' ] # These heroes are larger for some reason. Shrink 'em.
2015-08-29 11:02:20 -04:00
m *= 0.4 if fullHero . get ( ' slug ' ) is ' goliath ' # Just too big!
2015-01-14 17:10:27 -05:00
layer.container.scaleX = layer.container.scaleY = m
layer . container . children [ 0 ] . x = 160 / m
layer . container . children [ 0 ] . y = 250 / m
2015-06-05 18:13:22 -04:00
if fullHero . get ( ' slug ' ) in [ ' forest-archer ' , ' librarian ' , ' sorcerer ' , ' potion-master ' , ' necromancer ' ]
2015-02-13 07:09:40 -05:00
layer . container . children [ 0 ] . y -= 3
2015-08-29 11:02:20 -04:00
if fullHero . get ( ' slug ' ) in [ ' librarian ' , ' sorcerer ' , ' potion-master ' , ' necromancer ' , ' goliath ' ]
2015-02-13 07:09:40 -05:00
layer . container . children [ 0 ] . x -= 3
2015-01-14 17:10:27 -05:00
stage = new createjs . SpriteStage ( canvas [ 0 ] )
2014-11-05 22:03:05 -05:00
@ stages [ heroIndex ] = stage
2015-01-14 17:10:27 -05:00
stage . addChild layer . container
2014-11-05 22:03:05 -05:00
stage . update ( )
unless preloading
createjs . Ticker . addEventListener ' tick ' , stage
@ playSelectionSound hero
2014-11-25 12:28:42 -05:00
@ rerenderFooter ( )
2014-11-05 22:03:05 -05:00
if fullHero . loaded
_ . defer onLoaded
else
@ listenToOnce fullHero , ' sync ' , onLoaded
fullHero
2014-11-25 12:28:42 -05:00
animateHeroes: =>
return unless @ visibleHero
heroIndex = Math . max 0 , _ . findIndex ( @ heroes . models , ( (hero) => hero . get ( ' original ' ) is @ visibleHero . get ( ' original ' ) ) )
2015-02-13 07:09:40 -05:00
animation = _ . sample ( [ ' attack ' , ' move_side ' , ' move_fore ' ] ) # Must be in LayerAdapter default actions.
2015-01-14 17:10:27 -05:00
@ stages [ heroIndex ] ? . children ? [ 0 ] ? . children ? [ 0 ] ? . gotoAndPlay ? animation
2014-11-25 12:28:42 -05:00
2014-11-05 22:03:05 -05:00
playSelectionSound: (hero) ->
return if @ $el . hasClass ' secret '
@ currentSoundInstance ? . stop ( )
return unless sounds = hero . get ( ' soundTriggers ' ) ? . selected
return unless sound = sounds [ Math . floor Math . random ( ) * sounds . length ]
name = AudioPlayer . nameForSoundReference sound
AudioPlayer . preloadSoundReference sound
@currentSoundInstance = AudioPlayer . playSound name , 1
@ currentSoundInstance
buildCodeLanguages: ->
$select = @ $el . find ( ' # option-code-language ' )
$select . fancySelect ( ) . parent ( ) . find ( ' .options li ' ) . each ->
languageName = $ ( @ ) . text ( )
languageID = $ ( @ ) . data ( ' value ' )
blurb = $ . i18n . t ( " choose_hero. #{ languageID } _blurb " )
$ ( @ ) . text ( " #{ languageName } - #{ blurb } " )
onCodeLanguageChanged: (e) ->
@codeLanguage = @ $el . find ( ' # option-code-language ' ) . val ( )
@codeLanguageChanged = true
2014-11-25 13:15:10 -05:00
#- Purchasing the hero
onUnlockButtonClicked: (e) ->
e . stopPropagation ( )
button = $ ( e . target ) . closest ( ' button ' )
affordable = @ visibleHero . get ( ' gems ' ) <= me . gems ( )
if not affordable
2014-11-26 09:58:23 -05:00
@ playSound ' menu-button-click '
2014-11-25 13:15:10 -05:00
@ askToBuyGems button
else if button . hasClass ( ' confirm ' )
2014-11-26 09:58:23 -05:00
@ playSound ' menu-button-unlock-end '
2014-11-25 13:15:10 -05:00
purchase = Purchase . makeFor ( @ visibleHero )
purchase . save ( )
#- set local changes to mimic what should happen on the server...
purchased = me . get ( ' purchased ' ) ? { }
purchased . heroes ? = [ ]
purchased . heroes . push ( @ visibleHero . get ( ' original ' ) )
me . set ( ' purchased ' , purchased )
me . set ( ' spent ' , ( me . get ( ' spent ' ) ? 0 ) + @ visibleHero . get ( ' gems ' ) )
#- ...then rerender visible hero
heroEntry = @ $el . find ( " .hero-item[data-hero-id= ' #{ @ visibleHero . get ( ' original ' ) } ' ] " )
heroEntry . find ( ' .hero-status-value ' ) . attr ( ' data-i18n ' , ' play.available ' ) . i18n ( )
heroEntry . removeClass ' locked purchasable '
2014-11-26 15:56:48 -05:00
@selectedHero = @ visibleHero
2014-11-25 13:15:10 -05:00
@ rerenderFooter ( )
Backbone . Mediator . publish ' store:hero-purchased ' , hero: @ visibleHero , heroSlug: @ visibleHero . get ( ' slug ' )
else
2014-11-26 09:58:23 -05:00
@ playSound ' menu-button-unlock-start '
2014-11-25 13:15:10 -05:00
button . addClass ( ' confirm ' ) . text ( $ . i18n . t ( ' play.confirm ' ) )
@ $el . one ' click ' , (e) ->
button . removeClass ( ' confirm ' ) . text ( $ . i18n . t ( ' play.unlock ' ) ) if e . target isnt button [ 0 ]
2015-02-11 17:09:00 -05:00
askToSignUp: ->
authModal = new AuthModal supermodel: @ supermodel
authModal.mode = ' signup '
return @ openModalView authModal
2014-11-25 13:15:10 -05:00
askToBuyGems: (unlockButton) ->
@ $el . find ( ' .unlock-button ' ) . popover ' destroy '
popoverTemplate = buyGemsPromptTemplate { }
unlockButton . popover (
animation: true
trigger: ' manual '
placement: ' left '
content: ' ' # template has it
container: @ $el
template: popoverTemplate
) . popover ' show '
popover = unlockButton . data ( ' bs.popover ' )
popover ? . $tip ? . i18n ( )
onBuyGemsPromptButtonClicked: (e) ->
2014-11-26 09:58:23 -05:00
@ playSound ' menu-button-click '
2015-02-11 17:09:00 -05:00
return @ askToSignUp ( ) if me . get ( ' anonymous ' )
2014-11-25 13:15:10 -05:00
@ openModalView new BuyGemsModal ( )
onClickedSomewhere: (e) ->
return if @ destroyed
@ $el . find ( ' .unlock-button ' ) . popover ' destroy '
#- Exiting
2014-11-05 22:03:05 -05:00
saveAndHide: ->
2014-12-06 12:46:37 -05:00
hero = @ selectedHero ? . get ( ' original ' )
2015-02-25 21:09:55 -05:00
hero ? = @ visibleHero ? . get ( ' original ' ) if @ visibleHero ? . loaded and not @ visibleHero . locked
2014-12-06 12:46:37 -05:00
unless hero
console . error ' Somehow we tried to hide without having a hero selected yet... '
2015-02-25 19:19:49 -05:00
noty {
text: " Error: hero not loaded. If this keeps happening, please report the bug. "
layout: ' topCenter '
timeout: 10000
type: ' error '
}
return
2014-11-05 22:37:13 -05:00
2014-11-05 22:03:05 -05:00
if @ session
changed = @ updateHeroConfig ( @ session , hero )
if @ session . get ( ' codeLanguage ' ) isnt @ codeLanguage
@ session . set ( ' codeLanguage ' , @ codeLanguage )
changed = true
2014-12-01 18:26:53 -05:00
#Backbone.Mediator.publish 'tome:change-language', language: @codeLanguage, reload: true # We'll reload the PlayLevelView instead.
2014-11-05 22:37:13 -05:00
2014-11-05 22:03:05 -05:00
@ session . patch ( ) if changed
2014-11-05 22:37:13 -05:00
2014-11-05 22:03:05 -05:00
changed = @ updateHeroConfig ( me , hero )
2014-11-06 19:23:23 -05:00
aceConfig = _ . clone ( me . get ( ' aceConfig ' ) ) or { }
2014-11-05 22:03:05 -05:00
if @ codeLanguage isnt aceConfig . language
aceConfig.language = @ codeLanguage
me . set ' aceConfig ' , aceConfig
changed = true
2014-11-05 22:37:13 -05:00
2014-11-05 22:03:05 -05:00
me . patch ( ) if changed
2014-11-05 22:37:13 -05:00
2014-11-05 22:03:05 -05:00
@ hide ( )
2014-11-07 13:47:57 -05:00
@ trigger ? ( ' confirm-click ' , hero: @ selectedHero )
2014-11-05 22:37:13 -05:00
2014-11-05 22:03:05 -05:00
updateHeroConfig: (model, hero) ->
2014-12-06 12:46:37 -05:00
return false unless hero
2014-11-05 22:03:05 -05:00
heroConfig = _ . clone ( model . get ( ' heroConfig ' ) ) or { }
if heroConfig . thangType isnt hero
heroConfig.thangType = hero
model . set ( ' heroConfig ' , heroConfig )
return true
2014-09-17 21:56:08 -04:00
onHidden: ->
super ( )
2015-08-20 15:43:04 -04:00
@ playSound ' game-menu-close '
2014-09-17 21:56:08 -04:00
2014-11-05 22:03:05 -05:00
destroy: ->
2014-11-25 12:28:42 -05:00
clearInterval @ heroAnimationInterval
2014-11-05 22:03:05 -05:00
for heroIndex , stage of @ stages
createjs . Ticker . removeEventListener " tick " , stage
stage . removeAllChildren ( )
2015-01-14 17:10:27 -05:00
layer . destroy ( ) for layer in @ layers
2014-11-05 22:03:05 -05:00
super ( )