2014-11-28 20:49:41 -05:00
ModalView = require ' views/core/ModalView '
2014-12-08 16:45:01 -05:00
AuthModal = require ' views/core/AuthModal '
2014-09-30 19:14:47 -04:00
template = require ' templates/play/level/modal/hero-victory-modal '
Achievement = require ' models/Achievement '
2014-10-10 16:11:35 -04:00
EarnedAchievement = require ' models/EarnedAchievement '
2014-09-30 19:14:47 -04:00
CocoCollection = require ' collections/CocoCollection '
LocalMongo = require ' lib/LocalMongo '
2014-11-28 20:49:41 -05:00
utils = require ' core/utils '
2014-09-30 19:14:47 -04:00
ThangType = require ' models/ThangType '
2014-10-19 20:38:10 -04:00
LadderSubmissionView = require ' views/play/common/LadderSubmissionView '
2014-10-20 22:01:00 -04:00
AudioPlayer = require ' lib/AudioPlayer '
2014-12-07 17:03:11 -05:00
User = require ' models/User '
utils = require ' core/utils '
2015-07-24 20:37:42 -04:00
Level = require ' models/Level '
2015-08-04 14:35:10 -04:00
LevelFeedback = require ' models/LevelFeedback '
2014-09-30 19:14:47 -04:00
2014-10-10 16:11:35 -04:00
module.exports = class HeroVictoryModal extends ModalView
2014-09-30 19:14:47 -04:00
id: ' hero-victory-modal '
template: template
2014-10-10 16:11:35 -04:00
closeButton: false
2014-10-14 14:11:56 -04:00
closesOnClickOutside: false
2014-09-30 19:14:47 -04:00
2014-10-19 20:38:10 -04:00
subscriptions:
' ladder:game-submitted ' : ' onGameSubmitted '
2014-10-23 19:36:59 -04:00
events:
' click # continue-button ' : ' onClickContinue '
2015-01-30 15:27:19 -05:00
' click .leaderboard-button ' : ' onClickLeaderboard '
2015-08-04 14:35:10 -04:00
' click .return-to-course-button ' : ' onClickReturnToCourse '
2014-11-03 12:58:57 -05:00
' click .return-to-ladder-button ' : ' onClickReturnToLadder '
2014-12-08 16:45:01 -05:00
' click .sign-up-button ' : ' onClickSignupButton '
2015-04-07 14:19:22 -04:00
' click .continue-from-offer-button ' : ' onClickContinueFromOffer '
2015-09-18 11:27:37 -04:00
' click .skip-offer-button ' : ' onClickSkipOffer '
2014-10-23 19:36:59 -04:00
2015-08-04 14:35:10 -04:00
# Feedback events
' mouseover .rating i ' : (e) -> @ showStars ( @ starNum ( $ ( e . target ) ) )
' mouseout .rating i ' : -> @ showStars ( )
' click .rating i ' : (e) ->
@ setStars ( @ starNum ( $ ( e . target ) ) )
@ $el . find ( ' .review, .review-label ' ) . show ( )
' keypress .review textarea ' : -> @ saveReviewEventually ( )
2014-09-30 19:14:47 -04:00
constructor: (options) ->
super ( options )
2015-09-24 20:12:18 -04:00
@courseID = options . courseID
@courseInstanceID = options . courseInstanceID
2014-09-30 19:14:47 -04:00
@session = options . session
@level = options . level
@thangTypes = { }
2015-08-04 14:35:10 -04:00
if @ level . get ( ' type ' , true ) is ' hero '
achievements = new CocoCollection ( [ ] , {
url: " /db/achievement?related= #{ @ session . get ( ' level ' ) . original } "
model: Achievement
} )
@achievements = @ supermodel . loadCollection ( achievements , ' achievements ' ) . model
@ listenToOnce @ achievements , ' sync ' , @ onAchievementsLoaded
@readyToContinue = false
@waitingToContinueSince = new Date ( )
@previousXP = me . get ' points ' , true
@previousLevel = me . level ( )
else
@readyToContinue = true
2015-09-09 17:36:05 -04:00
@ playSound ' victory '
2015-07-24 20:37:42 -04:00
if @ level . get ( ' type ' , true ) is ' course ' and nextLevel = @ level . get ( ' nextLevel ' )
@nextLevel = new Level ( ) . setURL " /db/level/ #{ nextLevel . original } /version/ #{ nextLevel . majorVersion } "
@nextLevel = @ supermodel . loadModel ( @ nextLevel , ' level ' ) . model
2015-08-04 14:35:10 -04:00
if @ level . get ( ' type ' , true ) in [ ' course ' , ' course-ladder ' ]
@saveReviewEventually = _ . debounce ( @ saveReviewEventually , 2000 )
@ loadExistingFeedback ( )
2014-10-14 14:11:56 -04:00
2014-10-23 19:36:59 -04:00
destroy: ->
clearInterval @ sequentialAnimationInterval
2015-08-04 14:35:10 -04:00
@ saveReview ( ) if @ $el . find ( ' .review textarea ' ) . val ( )
2015-08-04 20:38:26 -04:00
@ feedback ? . off ( )
2014-10-23 19:36:59 -04:00
super ( )
2014-10-23 23:03:19 -04:00
onHidden: ->
Backbone . Mediator . publish ' music-player:exit-menu ' , { }
super ( )
2015-08-04 14:35:10 -04:00
loadExistingFeedback: ->
url = " /db/level/ #{ @ level . id } /feedback "
@feedback = new LevelFeedback ( )
@ feedback . setURL url
@ feedback . fetch cache: false
@ listenToOnce ( @ feedback , ' sync ' , -> @ onFeedbackLoaded ( ) )
@ listenToOnce ( @ feedback , ' error ' , -> @ onFeedbackNotFound ( ) )
onFeedbackLoaded: ->
@feedback.url = -> ' /db/level.feedback/ ' + @ id
@ $el . find ( ' .review textarea ' ) . val ( @ feedback . get ( ' review ' ) )
@ $el . find ( ' .review, .review-label ' ) . show ( )
@ showStars ( )
onFeedbackNotFound: ->
@feedback = new LevelFeedback ( )
@ feedback . set ( ' levelID ' , @ level . get ( ' slug ' ) or @ level . id )
@ feedback . set ( ' levelName ' , @ level . get ( ' name ' ) or ' ' )
@ feedback . set ( ' level ' , { majorVersion: @ level . get ( ' version ' ) . major , original: @ level . get ( ' original ' ) } )
@ showStars ( )
2014-09-30 19:14:47 -04:00
onAchievementsLoaded: ->
2014-12-07 17:03:11 -05:00
@ $el . toggleClass ' full-achievements ' , @ achievements . models . length is 3
2014-09-30 19:14:47 -04:00
thangTypeOriginals = [ ]
2014-10-10 16:11:35 -04:00
achievementIDs = [ ]
2014-09-30 19:14:47 -04:00
for achievement in @ achievements . models
2014-10-22 16:52:37 -04:00
rewards = achievement . get ( ' rewards ' ) or { }
2014-09-30 19:14:47 -04:00
thangTypeOriginals . push rewards . heroes or [ ]
thangTypeOriginals . push rewards . items or [ ]
2014-10-13 17:18:33 -04:00
achievement.completed = LocalMongo . matchesQuery ( @ session . attributes , achievement . get ( ' query ' ) )
achievementIDs . push ( achievement . id ) if achievement . completed
2014-10-14 14:11:56 -04:00
2014-09-30 19:14:47 -04:00
thangTypeOriginals = _ . uniq _ . flatten thangTypeOriginals
for thangTypeOriginal in thangTypeOriginals
thangType = new ThangType ( )
thangType.url = " /db/thang.type/ #{ thangTypeOriginal } /version "
2015-07-10 14:13:31 -04:00
#thangType.project = ['original', 'rasterIcon', 'name', 'soundTriggers', 'i18n'] # This is what we need, but the PlayHeroesModal needs more, and so we load more to fill up the supermodel.
thangType.project = [ ' original ' , ' rasterIcon ' , ' name ' , ' slug ' , ' soundTriggers ' , ' featureImages ' , ' gems ' , ' heroClass ' , ' description ' , ' components ' , ' extendedName ' , ' unlockLevelName ' , ' i18n ' ]
2014-09-30 19:14:47 -04:00
@ thangTypes [ thangTypeOriginal ] = @ supermodel . loadModel ( thangType , ' thang ' ) . model
2014-10-13 17:18:33 -04:00
2014-11-26 15:02:42 -05:00
@newEarnedAchievements = [ ]
for achievement in @ achievements . models
continue unless achievement . completed
ea = new EarnedAchievement ( {
collection: achievement . get ( ' collection ' )
triggeredBy: @ session . id
achievement: achievement . id
2014-10-13 17:18:33 -04:00
} )
2014-11-26 15:02:42 -05:00
ea . save ( )
@ newEarnedAchievements . push ea
@ listenToOnce ea , ' sync ' , ->
if _ . all ( ( ea . id for ea in @ newEarnedAchievements ) )
2015-01-12 17:43:29 -05:00
@ newEarnedAchievementsResource . markLoaded ( )
2014-11-26 15:02:42 -05:00
@ listenToOnce me , ' sync ' , ->
@readyToContinue = true
@ updateSavingProgressStatus ( )
2015-01-07 21:36:02 -05:00
me . fetch cache: false unless me . loading
2014-11-27 12:44:08 -05:00
2014-11-26 15:02:42 -05:00
@readyToContinue = true if not @ achievements . models . length
2015-01-21 14:49:56 -05:00
2015-01-12 17:43:29 -05:00
# have to use a something resource because addModelResource doesn't handle models being upserted/fetched via POST like we're doing here
@newEarnedAchievementsResource = @ supermodel . addSomethingResource ( ' earned achievements ' ) if @ newEarnedAchievements . length
2014-10-14 14:11:56 -04:00
2014-09-30 19:14:47 -04:00
getRenderData: ->
c = super ( )
c.levelName = utils . i18n @ level . attributes , ' name '
2015-08-05 19:17:27 -04:00
if @ level . get ( ' type ' , true ) isnt ' hero '
c.victoryText = utils . i18n @ level . get ( ' victory ' ) ? { } , ' body '
2015-01-12 17:43:29 -05:00
earnedAchievementMap = _ . indexBy ( @ newEarnedAchievements or [ ] , (ea) -> ea . get ( ' achievement ' ) )
2015-08-04 14:35:10 -04:00
for achievement in ( @ achievements ? . models or [ ] )
2014-10-10 16:11:35 -04:00
earnedAchievement = earnedAchievementMap [ achievement . id ]
if earnedAchievement
2015-01-12 17:43:29 -05:00
achievement.completedAWhileAgo = new Date ( ) . getTime ( ) - Date . parse ( earnedAchievement . get ( ' created ' ) ) > 30 * 1000
2015-01-07 21:36:02 -05:00
achievement.worth = achievement . get ' worth ' , true
achievement.gems = achievement . get ( ' rewards ' ) ? . gems
2015-08-04 14:35:10 -04:00
c.achievements = @ achievements ? . models . slice ( ) or [ ]
2015-01-07 21:36:02 -05:00
for achievement in c . achievements
2015-02-09 16:05:53 -05:00
achievement.description = utils . i18n achievement . attributes , ' description '
2015-01-07 21:36:02 -05:00
continue unless @ supermodel . finished ( ) and proportionalTo = achievement . get ' proportionalTo '
# For repeatable achievements, we modify their base worth/gems by their repeatable growth functions.
achievedAmount = utils . getByPath @ session . attributes , proportionalTo
2015-02-18 21:01:22 -05:00
previousAmount = Math . max ( 0 , achievedAmount - 1 )
2015-01-07 21:36:02 -05:00
func = achievement . getExpFunction ( )
2015-02-18 21:01:22 -05:00
achievement.previousWorth = ( achievement . get ( ' worth ' ) ? 0 ) * func previousAmount
2015-01-07 21:36:02 -05:00
achievement.worth = ( achievement . get ( ' worth ' ) ? 0 ) * func achievedAmount
rewards = achievement . get ' rewards '
achievement.gems = rewards ? . gems * func achievedAmount if rewards ? . gems
2015-02-18 21:01:22 -05:00
achievement.previousGems = rewards ? . gems * func previousAmount if rewards ? . gems
2014-10-10 16:11:35 -04:00
2014-10-17 23:15:41 -04:00
# for testing the three states
2014-10-17 20:57:18 -04:00
#if c.achievements.length
# c.achievements = [c.achievements[0].clone(), c.achievements[0].clone(), c.achievements[0].clone()]
#for achievement, index in c.achievements
2014-10-17 23:15:41 -04:00
## achievement.completed = index > 0
## achievement.completedAWhileAgo = index > 1
# achievement.completed = true
# achievement.completedAWhileAgo = false
2014-10-20 22:01:00 -04:00
# achievement.attributes.worth = (index + 1) * achievement.get('worth', true)
2014-10-22 16:52:37 -04:00
# rewards = achievement.get('rewards') or {}
2014-10-17 23:15:41 -04:00
# rewards.gems *= (index + 1)
2014-10-10 16:11:35 -04:00
2014-09-30 19:14:47 -04:00
c.thangTypes = @ thangTypes
2014-10-17 23:47:32 -04:00
c.me = me
2015-07-24 20:37:42 -04:00
c.readyToRank = @ level . get ( ' type ' , true ) in [ ' hero-ladder ' , ' course-ladder ' ] and @ session . readyToRank ( )
2014-10-20 16:57:32 -04:00
c.level = @ level
2015-04-19 00:10:35 -04:00
c.i18n = utils . i18n
2014-11-27 12:44:08 -05:00
elapsed = ( new Date ( ) - new Date ( me . get ( ' dateCreated ' ) ) )
isHourOfCode = me . get ( ' hourOfCode ' ) or elapsed < 120 * 60 * 1000
# Later we should only check me.get('hourOfCode'), but for now so much traffic comes in that we just assume it.
if isHourOfCode
# Show the Hour of Code "I'm Done" tracking pixel after they played for 20 minutes
enough = elapsed >= 20 * 60 * 1000
tooMuch = elapsed > 120 * 60 * 1000
showDone = elapsed >= 30 * 60 * 1000 and not tooMuch
if enough and not tooMuch and not me . get ( ' hourOfCodeComplete ' )
$ ( ' body ' ) . append ( $ ( ' <img src= " http://code.org/api/hour/finish_codecombat.png " style= " visibility: hidden; " > ' ) )
me . set ' hourOfCodeComplete ' , true # Note that this will track even for players who don't have hourOfCode set.
me . patch ( )
2015-02-27 19:07:41 -05:00
window . tracker ? . trackEvent ' Hour of Code Finish '
2014-11-27 12:44:08 -05:00
# Show the "I'm done" button between 30 - 120 minutes if they definitely came from Hour of Code
c.showHourOfCodeDoneButton = me . get ( ' hourOfCode ' ) and showDone
2015-07-24 20:37:42 -04:00
c.showLeaderboard = @ level . get ( ' scoreTypes ' ) ? . length > 0 and @ level . get ( ' type ' , true ) isnt ' course '
2015-01-30 15:27:19 -05:00
2015-08-04 14:35:10 -04:00
c.showReturnToCourse = not c . showLeaderboard and not me . get ( ' anonymous ' ) and @ level . get ( ' type ' , true ) in [ ' course ' , ' course-ladder ' ]
2014-10-10 16:11:35 -04:00
return c
2014-10-14 14:11:56 -04:00
2014-10-10 16:11:35 -04:00
afterRender: ->
super ( )
2015-08-04 14:35:10 -04:00
@ $el . toggleClass ' show-achievements ' , @ level . get ( ' type ' , true ) is ' hero '
2014-10-10 16:11:35 -04:00
return unless @ supermodel . finished ( )
2014-10-20 22:01:00 -04:00
@ playSelectionSound hero , true for original , hero of @ thangTypes # Preload them
2014-10-13 17:18:33 -04:00
@ updateSavingProgressStatus ( )
2015-08-04 14:35:10 -04:00
@ initializeAnimations ( )
if @ level . get ( ' type ' , true ) in [ ' hero-ladder ' , ' course-ladder ' ]
@ladderSubmissionView = new LadderSubmissionView session: @ session , level: @ level
@ insertSubView @ ladderSubmissionView , @ $el . find ( ' .ladder-submission-view ' )
initializeAnimations: ->
if @ level . get ( ' type ' , true ) is ' hero '
@ updateXPBars 0
2015-09-25 18:19:44 -04:00
#playVictorySound = => @playSound 'victory-title-appear' # TODO: actually add this
2014-10-20 22:01:00 -04:00
@ $el . find ( ' # victory-header ' ) . delay ( 250 ) . queue ( ->
$ ( @ ) . removeClass ( ' out ' ) . dequeue ( )
2015-09-25 18:19:44 -04:00
#playVictorySound()
2014-10-20 22:01:00 -04:00
)
complete = _ . once ( _ . bind ( @ beginSequentialAnimations , @ ) )
2014-10-10 16:11:35 -04:00
@animatedPanels = $ ( )
panels = @ $el . find ( ' .achievement-panel ' )
for panel in panels
panel = $ ( panel )
continue unless panel . data ( ' animate ' )
@animatedPanels = @ animatedPanels . add ( panel )
2014-10-17 23:15:41 -04:00
panel . delay ( 500 ) # Waiting for victory header to show up and fall
2014-10-10 16:11:35 -04:00
panel . queue ( ->
2014-10-17 23:15:41 -04:00
$ ( @ ) . addClass ( ' earned ' ) # animate out the grayscale
$ ( @ ) . dequeue ( )
2014-10-10 16:11:35 -04:00
)
panel . delay ( 500 )
panel . queue ( ->
2014-10-17 23:15:41 -04:00
$ ( @ ) . find ( ' .reward-image-container ' ) . addClass ( ' show ' )
$ ( @ ) . dequeue ( )
2014-10-10 16:11:35 -04:00
)
panel . delay ( 500 )
panel . queue ( -> complete ( ) )
2014-10-14 14:11:56 -04:00
@animationComplete = not @ animatedPanels . length
2014-10-23 23:03:19 -04:00
complete ( ) if @ animationComplete
2014-10-14 14:11:56 -04:00
2014-10-20 22:01:00 -04:00
beginSequentialAnimations: ->
2014-11-10 18:16:28 -05:00
return if @ destroyed
2015-08-04 14:35:10 -04:00
return unless @ level . get ( ' type ' , true ) is ' hero '
2014-10-20 22:01:00 -04:00
@sequentialAnimatedPanels = _ . map ( @ animatedPanels . find ( ' .reward-panel ' ) , (panel) -> {
2014-10-10 16:11:35 -04:00
number: $ ( panel ) . data ( ' number ' )
2015-02-18 21:01:22 -05:00
previousNumber: $ ( panel ) . data ( ' previous-number ' )
2014-10-10 16:11:35 -04:00
textEl: $ ( panel ) . find ( ' .reward-text ' )
rootEl: $ ( panel )
unit: $ ( panel ) . data ( ' number-unit ' )
2014-10-20 22:01:00 -04:00
hero: $ ( panel ) . data ( ' hero-thang-type ' )
item: $ ( panel ) . data ( ' item-thang-type ' )
2014-10-10 16:11:35 -04:00
} )
2014-10-14 14:11:56 -04:00
2014-10-10 16:11:35 -04:00
@totalXP = 0
2014-10-20 22:01:00 -04:00
@ totalXP += panel . number for panel in @ sequentialAnimatedPanels when panel . unit is ' xp '
2014-10-10 16:11:35 -04:00
@totalGems = 0
2014-10-20 22:01:00 -04:00
@ totalGems += panel . number for panel in @ sequentialAnimatedPanels when panel . unit is ' gem '
2014-10-10 16:11:35 -04:00
@gemEl = $ ( ' # gem-total ' )
@XPEl = $ ( ' # xp-total ' )
2014-10-17 23:15:41 -04:00
@totalXPAnimated = @totalGemsAnimated = @lastTotalXP = @lastTotalGems = 0
2014-10-20 22:01:00 -04:00
@sequentialAnimationStart = new Date ( )
@sequentialAnimationInterval = setInterval ( @ tickSequentialAnimation , 1000 / 60 )
2014-10-14 14:11:56 -04:00
2014-10-20 22:01:00 -04:00
tickSequentialAnimation: =>
2014-10-17 20:57:18 -04:00
# TODO: make sure the animation pulses happen when the numbers go up and sounds play (up to a max speed)
2014-10-20 22:01:00 -04:00
return @ endSequentialAnimations ( ) unless panel = @ sequentialAnimatedPanels [ 0 ]
if panel . number
duration = Math . log ( panel . number + 1 ) / Math . LN10 * 1000 # Math.log10 is ES6
else
duration = 1000
ratio = @ getEaseRatio ( new Date ( ) - @ sequentialAnimationStart ) , duration
2014-10-17 23:15:41 -04:00
if panel . unit is ' xp '
2015-02-18 21:01:22 -05:00
newXP = Math . floor ( panel . previousNumber + ratio * ( panel . number - panel . previousNumber ) )
2014-10-20 19:13:56 -04:00
totalXP = @ totalXPAnimated + newXP
2014-10-17 23:15:41 -04:00
if totalXP isnt @ lastTotalXP
2014-10-20 19:13:56 -04:00
panel . textEl . text ( ' + ' + newXP )
2014-12-07 17:03:11 -05:00
@ XPEl . text ( totalXP )
@ updateXPBars ( totalXP )
2014-10-17 23:15:41 -04:00
xpTrigger = ' xp- ' + ( totalXP % 6 ) # 6 xp sounds
2015-09-09 17:36:05 -04:00
@ playSound xpTrigger , ( 0.5 + ratio / 2 )
2015-04-18 22:40:53 -04:00
@ XPEl . addClass ' four-digits ' if totalXP >= 1000 and @ lastTotalXP < 1000
@ XPEl . addClass ' five-digits ' if totalXP >= 10000 and @ lastTotalXP < 10000
2014-10-17 23:15:41 -04:00
@lastTotalXP = totalXP
2014-10-20 22:01:00 -04:00
else if panel . unit is ' gem '
2015-02-18 21:01:22 -05:00
newGems = Math . floor ( panel . previousNumber + ratio * ( panel . number - panel . previousNumber ) )
2014-10-20 19:13:56 -04:00
totalGems = @ totalGemsAnimated + newGems
2014-10-17 23:15:41 -04:00
if totalGems isnt @ lastTotalGems
2014-10-20 19:13:56 -04:00
panel . textEl . text ( ' + ' + newGems )
2014-12-07 17:03:11 -05:00
@ gemEl . text ( totalGems )
2014-10-17 23:15:41 -04:00
gemTrigger = ' gem- ' + ( parseInt ( panel . number * ratio ) % 4 ) # 4 gem sounds
2015-09-09 17:36:05 -04:00
@ playSound gemTrigger , ( 0.5 + ratio / 2 )
2015-04-18 22:40:53 -04:00
@ gemEl . addClass ' four-digits ' if totalGems >= 1000 and @ lastTotalGems < 1000
@ gemEl . addClass ' five-digits ' if totalGems >= 10000 and @ lastTotalGems < 10000
2014-10-17 23:15:41 -04:00
@lastTotalGems = totalGems
2014-10-20 22:01:00 -04:00
else if panel . item
thangType = @ thangTypes [ panel . item ]
2015-04-19 00:10:35 -04:00
panel . textEl . text utils . i18n ( thangType . attributes , ' name ' )
2015-09-09 17:36:05 -04:00
@ playSound ' item-unlocked ' if 0.5 < ratio < 0.6
2014-10-20 22:01:00 -04:00
else if panel . hero
thangType = @ thangTypes [ panel . hero ]
panel . textEl . text ( thangType . get ( ' name ' ) )
2014-10-26 13:16:43 -04:00
@ playSelectionSound thangType if 0.5 < ratio < 0.6
2014-10-17 23:15:41 -04:00
if ratio is 1
panel . rootEl . removeClass ( ' animating ' ) . find ( ' .reward-image-container img ' ) . removeClass ( ' pulse ' )
2014-10-20 22:01:00 -04:00
@sequentialAnimationStart = new Date ( )
2014-10-17 23:15:41 -04:00
if panel . unit is ' xp '
@ totalXPAnimated += panel . number
2014-10-20 22:01:00 -04:00
else if panel . unit is ' gem '
2014-10-17 23:15:41 -04:00
@ totalGemsAnimated += panel . number
2014-10-20 22:01:00 -04:00
@ sequentialAnimatedPanels . shift ( )
2014-10-17 23:15:41 -04:00
return
2014-10-20 22:01:00 -04:00
panel . rootEl . addClass ( ' animating ' ) . find ( ' .reward-image-container ' ) . removeClass ( ' pending-reward-image ' ) . find ( ' img ' ) . addClass ( ' pulse ' )
2014-10-17 23:15:41 -04:00
getEaseRatio: (timeSinceStart, duration) ->
# Ease in/out quadratic - http://gizma.com/easing/
timeSinceStart = Math . min timeSinceStart , duration
t = 2 * timeSinceStart / duration
if t < 1
return 0.5 * t * t
- - t
- 0.5 * ( t * ( t - 2 ) - 1 )
2014-10-10 16:11:35 -04:00
2014-12-07 17:03:11 -05:00
updateXPBars: (achievedXP) ->
previousXP = @ previousXP
2015-02-18 21:01:22 -05:00
previousXP = previousXP + 1000000 if me . isInGodMode ( )
2014-12-07 17:03:11 -05:00
previousLevel = @ previousLevel
currentXP = previousXP + achievedXP
currentLevel = User . levelFromExp currentXP
currentLevelXP = User . expForLevel currentLevel
nextLevel = currentLevel + 1
nextLevelXP = User . expForLevel nextLevel
leveledUp = currentLevel > previousLevel
totalXPNeeded = nextLevelXP - currentLevelXP
alreadyAchievedPercentage = 100 * ( previousXP - currentLevelXP ) / totalXPNeeded
alreadyAchievedPercentage = 0 if alreadyAchievedPercentage < 0 # In case of level up
if leveledUp
newlyAchievedPercentage = 100 * ( currentXP - currentLevelXP ) / totalXPNeeded
else
newlyAchievedPercentage = 100 * achievedXP / totalXPNeeded
xpEl = $ ( ' # xp-wrapper ' )
xpBarJustEarned = xpEl . find ( ' .xp-bar-already-achieved ' ) . css ( ' width ' , alreadyAchievedPercentage + ' % ' )
xpBarTotal = xpEl . find ( ' .xp-bar-total ' ) . css ( ' width ' , ( alreadyAchievedPercentage + newlyAchievedPercentage ) + ' % ' )
levelLabel = xpEl . find ( ' .level ' )
utils . replaceText levelLabel , currentLevel
2014-12-07 22:38:24 -05:00
if leveledUp and ( not @ displayedLevel or currentLevel > @ displayedLevel )
@ playSound ' level-up '
@displayedLevel = currentLevel
2014-10-20 22:01:00 -04:00
endSequentialAnimations: ->
clearInterval @ sequentialAnimationInterval
2014-10-13 17:18:33 -04:00
@animationComplete = true
@ updateSavingProgressStatus ( )
2014-10-23 23:03:19 -04:00
Backbone . Mediator . publish ' music-player:enter-menu ' , terrain: @ level . get ( ' terrain ' , true )
2014-10-13 17:18:33 -04:00
updateSavingProgressStatus: ->
@ $el . find ( ' # saving-progress-label ' ) . toggleClass ( ' hide ' , @ readyToContinue )
2014-10-22 18:42:51 -04:00
@ $el . find ( ' .next-level-button ' ) . toggleClass ( ' hide ' , not @ readyToContinue )
2014-10-20 22:01:00 -04:00
@ $el . find ( ' .sign-up-poke ' ) . toggleClass ( ' hide ' , not @ readyToContinue )
2014-10-17 20:57:18 -04:00
2014-10-19 20:38:10 -04:00
onGameSubmitted: (e) ->
2015-09-04 19:21:35 -04:00
@ returnToLadder ( )
returnToLadder: ->
2014-10-23 19:36:59 -04:00
# Preserve the supermodel as we navigate back to the ladder.
2015-09-04 19:21:35 -04:00
viewArgs = [ { supermodel: if @ options . hasReceivedMemoryWarning then null else @ supermodel } , @ level . get ( ' slug ' ) ]
ladderURL = " /play/ladder/ #{ @ level . get ( ' slug ' ) || @ level . id } # my-matches "
if leagueID = @ getQueryVariable ' league '
leagueType = if @ level . get ( ' type ' ) is ' course-ladder ' then ' course ' else ' clan '
viewArgs . push leagueType
viewArgs . push leagueID
ladderURL += " / #{ leagueType } / #{ leagueID } "
Backbone . Mediator . publish ' router:navigate ' , route: ladderURL , viewClass: ' views/ladder/LadderView ' , viewArgs: viewArgs
2014-10-19 20:38:10 -04:00
2014-10-20 22:01:00 -04:00
playSelectionSound: (hero, preload=false) ->
return unless sounds = hero . get ( ' soundTriggers ' ) ? . selected
return unless sound = sounds [ Math . floor Math . random ( ) * sounds . length ]
name = AudioPlayer . nameForSoundReference sound
if preload
AudioPlayer . preloadSoundReference sound
else
AudioPlayer . playSound name , 1
2014-10-19 20:38:10 -04:00
2014-11-10 15:47:24 -05:00
getNextLevelCampaign: ->
2015-10-05 19:19:43 -04:00
{ ' kithgard-gates ' : ' forest ' , ' kithgard-mastery ' : ' forest ' , ' siege-of-stonehold ' : ' desert ' , ' clash-of-clones ' : ' mountain ' , ' summits-gate ' : ' glacier ' } [ @ level . get ( ' slug ' ) ] or @ level . get ' campaign ' # Much easier to just keep this updated than to dynamically figure it out.
2014-10-29 13:47:17 -04:00
2015-08-04 14:35:10 -04:00
getNextLevelLink: (returnToCourse=false) ->
if @ level . get ( ' type ' , true ) is ' course ' and nextLevel = @ level . get ( ' nextLevel ' ) and not returnToCourse
2015-07-24 20:37:42 -04:00
# need to do something more complicated to load its slug
console . log ' have @nextLevel ' , @ nextLevel , ' from nextLevel ' , nextLevel
2015-09-24 20:12:18 -04:00
link = " /play/level/ #{ @ nextLevel . get ( ' slug ' ) } "
2015-07-24 20:37:42 -04:00
else if @ level . get ( ' type ' , true ) is ' course '
2015-09-24 20:12:18 -04:00
link = " /courses "
if @ courseID
link += " / #{ @ courseID } "
if @ courseInstanceID
2015-09-24 20:52:00 -04:00
link += " / #{ @ courseInstanceID } "
2015-09-24 20:12:18 -04:00
else
link = ' /play '
nextCampaign = @ getNextLevelCampaign ( )
link += ' / ' + nextCampaign
2014-12-28 16:25:20 -05:00
link
2014-11-10 15:47:24 -05:00
2015-01-30 15:27:19 -05:00
onClickContinue: (e, extraOptions=null) ->
2014-11-26 09:58:23 -05:00
@ playSound ' menu-button-click '
2015-08-04 14:35:10 -04:00
nextLevelLink = @ getNextLevelLink extraOptions ? . returnToCourse
2014-12-05 18:44:49 -05:00
# Preserve the supermodel as we navigate back to the world map.
2015-01-30 15:27:19 -05:00
options =
justBeatLevel: @ level
supermodel: if @ options . hasReceivedMemoryWarning then null else @ supermodel
_ . merge options , extraOptions if extraOptions
2015-08-04 14:35:10 -04:00
if @ level . get ( ' type ' , true ) is ' course ' and @ nextLevel and not options . returnToCourse
2015-07-24 20:37:42 -04:00
viewClass = require ' views/play/level/PlayLevelView '
2015-09-24 20:12:18 -04:00
if @ courseID
options.courseID = @ courseID
if @ courseInstanceID
options.courseInstanceID = @ courseInstanceID
2015-07-24 20:37:42 -04:00
viewArgs = [ options , @ nextLevel . get ( ' slug ' ) ]
else if @ level . get ( ' type ' , true ) is ' course '
2015-09-24 20:12:18 -04:00
# TODO: shouldn't set viewClass and route in different places
viewClass = require ' views/courses/CoursesView '
viewArgs = [ options ]
if @ courseID
viewClass = require ' views/courses/CourseDetailsView '
viewArgs . push @ courseID
if @ courseInstanceID
viewArgs . push @ courseInstanceID
2015-07-24 20:37:42 -04:00
else
viewClass = require ' views/play/CampaignView '
viewArgs = [ options , @ getNextLevelCampaign ( ) ]
navigationEvent = route: nextLevelLink , viewClass: viewClass , viewArgs: viewArgs
2015-04-07 14:19:22 -04:00
if @ level . get ( ' slug ' ) is ' lost-viking ' and not ( me . get ( ' age ' ) in [ ' 0-13 ' , ' 14-17 ' ] )
@ showOffer navigationEvent
2015-08-12 13:25:56 -04:00
else if @ level . get ( ' slug ' ) is ' a-mayhem-of-munchkins ' and not ( me . get ( ' age ' ) in [ ' 0-13 ' ] ) and not options . showLeaderboard
@ showOffer navigationEvent
2015-04-07 14:19:22 -04:00
else
Backbone . Mediator . publish ' router:navigate ' , navigationEvent
2015-01-30 15:27:19 -05:00
onClickLeaderboard: (e) ->
@ onClickContinue e , showLeaderboard: true
2014-11-03 12:58:57 -05:00
2015-08-04 14:35:10 -04:00
onClickReturnToCourse: (e) ->
@ onClickContinue e , returnToCourse: true
2014-11-03 12:58:57 -05:00
onClickReturnToLadder: (e) ->
2014-11-26 09:58:23 -05:00
@ playSound ' menu-button-click '
2014-11-03 12:58:57 -05:00
e . preventDefault ( )
2015-09-04 19:21:35 -04:00
@ returnToLadder ( )
2014-12-08 16:45:01 -05:00
onClickSignupButton: (e) ->
e . preventDefault ( )
window . tracker ? . trackEvent ' Started Signup ' , category: ' Play Level ' , label: ' Hero Victory Modal ' , level: @ level . get ( ' slug ' )
@ openModalView new AuthModal { mode: ' signup ' }
2015-04-07 14:19:22 -04:00
showOffer: (@navigationEventUponCompletion) ->
@ $el . find ( ' .modal-footer > * ' ) . hide ( )
@ $el . find ( " .modal-footer > .offer. #{ @ level . get ( ' slug ' ) } " ) . show ( )
onClickContinueFromOffer: (e) ->
url = {
2015-04-08 12:44:15 -04:00
' lost-viking ' : ' http://www.vikingcodeschool.com/codecombat?utm_source=codecombat&utm_medium=viking_level&utm_campaign=affiliate&ref=Code+Combat+Elite '
2015-08-12 13:25:56 -04:00
' a-mayhem-of-munchkins ' : ' https://www.bloc.io/web-developer-career-track?utm_campaign=affiliate&utm_source=codecombat&utm_medium=bloc_level '
2015-04-07 14:19:22 -04:00
} [ @ level . get ( ' slug ' ) ]
Backbone . Mediator . publish ' router:navigate ' , @ navigationEventUponCompletion
window . open url , ' _blank ' if url
2015-08-04 14:35:10 -04:00
2015-09-18 11:27:37 -04:00
onClickSkipOffer: (e) ->
Backbone . Mediator . publish ' router:navigate ' , @ navigationEventUponCompletion
2015-08-04 14:35:10 -04:00
# Ratings and reviews
starNum: (starEl) -> starEl . prevAll ( ' i ' ) . length + 1
showStars: (num) ->
@ $el . find ( ' .rating ' ) . show ( )
num ? = @ feedback ? . get ( ' rating ' ) or 0
stars = @ $el . find ( ' .rating i ' )
stars . removeClass ( ' glyphicon-star ' ) . addClass ( ' glyphicon-star-empty ' )
stars . slice ( 0 , num ) . removeClass ( ' glyphicon-star-empty ' ) . addClass ( ' glyphicon-star ' )
setStars: (num) ->
@ feedback . set ( ' rating ' , num )
@ feedback . save ( )
saveReviewEventually: ->
@ saveReview ( )
saveReview: ->
@ feedback . set ( ' review ' , @ $el . find ( ' .review textarea ' ) . val ( ) )
@ feedback . save ( )