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 '
Purchase = require ' models/Purchase '
2014-11-26 11:53:06 -05:00
LevelOptions = require ' lib/LevelOptions '
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 '
2014-11-06 19:23:23 -05:00
' enter ' : ' saveAndHide '
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 = { }
@session = options . session
2014-11-10 14:08:17 -05:00
@ initCodeLanguageList options . hadEverChosenHero
2014-11-25 12:28:42 -05:00
@heroAnimationInterval = setInterval @ animateHeroes , 2500
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) ->
hero.name = utils . i18n hero . attributes , ' extendedName ' # or whatever the property name ends up being
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-11-26 11:53:06 -05:00
if @ options . levelID and allowedHeroSlugs = LevelOptions [ @ options . levelID ] ? . allowedHeroes
hero.restricted = not ( hero . get ( ' slug ' ) in allowedHeroSlugs )
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 ( )
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
$ ( @ ) . find ( ' .hero-avatar ' ) . css ( ' background-image ' , " url( #{ hero . getPortraitURL ( ) } ) " ) . tooltip ( )
@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 ]
@ $el . find ( ' .hero-stat ' ) . tooltip ( )
@ 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 ' }
{ id: ' coffeescript ' , name: ' CoffeeScript ' }
{ id: ' clojure ' , name: " Clojure ( #{ $ . i18n . t ( ' choose_hero.experimental ' ) } ) " }
{ id: ' lua ' , name: " Lua ( #{ $ . i18n . t ( ' choose_hero.experimental ' ) } ) " }
{ id: ' io ' , name: " Io ( #{ $ . i18n . t ( ' choose_hero.experimental ' ) } ) " }
]
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
builder = new SpriteBuilder ( fullHero )
movieClip = builder . buildMovieClip ( fullHero . get ( ' actions ' ) . attack ? . animation ? fullHero . get ( ' actions ' ) . idle . animation )
movieClip.scaleX = movieClip.scaleY = canvas . prop ( ' height ' ) / 120 # Average hero height is ~110px tall at normal resolution
movieClip.regX = - fullHero . get ( ' positions ' ) . registration . x
movieClip.regY = - fullHero . get ( ' positions ' ) . registration . y
movieClip.x = canvas . prop ( ' width ' ) * 0.5
movieClip.y = canvas . prop ( ' height ' ) * 0.925 # This is where the feet go.
2014-11-25 12:28:42 -05:00
if fullHero . get ( ' name ' ) is ' Knight '
movieClip . scaleX *= 0.7
movieClip . scaleY *= 0.7
if fullHero . get ( ' name ' ) is ' Potion Master '
movieClip . scaleX *= 0.9
movieClip . scaleY *= 0.9
movieClip . regX *= 1.1
movieClip . regY *= 1.4
if fullHero . get ( ' name ' ) is ' Samurai '
movieClip . scaleX *= 0.7
movieClip . scaleY *= 0.7
movieClip . regX *= 1.2
movieClip . regY *= 1.35
if fullHero . get ( ' name ' ) is ' Librarian '
movieClip . regX *= 0.7
movieClip . regY *= 1.2
if fullHero . get ( ' name ' ) is ' Sorcerer '
movieClip . scaleX *= 0.9
movieClip . scaleY *= 0.9
movieClip . regX *= 1.15
movieClip . regY *= 1.3
2014-11-05 22:03:05 -05:00
stage = new createjs . Stage ( canvas [ 0 ] )
@ stages [ heroIndex ] = stage
stage . addChild movieClip
stage . update ( )
2014-11-25 12:28:42 -05:00
movieClip.loop = false
2014-11-05 22:03:05 -05:00
movieClip . gotoAndPlay 0
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 ' ) ) )
@ stages [ heroIndex ] ? . children ? [ 0 ] ? . gotoAndPlay ? 0
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 ]
askToBuyGems: (unlockButton) ->
if me . getGemPromptGroup ( ) is ' no-prompt '
return @ openModalView new BuyGemsModal ( )
@ $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 '
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 ' )
unless hero
console . error ' Somehow we tried to hide without having a hero selected yet... '
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 ( )
Backbone . Mediator . publish ' audio-player:play-sound ' , trigger: ' game-menu-close ' , volume: 1
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 ( )
super ( )