mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-29 18:45:48 -05:00
520 lines
23 KiB
CoffeeScript
520 lines
23 KiB
CoffeeScript
ModalView = require 'views/core/ModalView'
|
|
AuthModal = require 'views/core/AuthModal'
|
|
template = require 'templates/play/level/modal/hero-victory-modal'
|
|
Achievement = require 'models/Achievement'
|
|
EarnedAchievement = require 'models/EarnedAchievement'
|
|
CocoCollection = require 'collections/CocoCollection'
|
|
LocalMongo = require 'lib/LocalMongo'
|
|
utils = require 'core/utils'
|
|
ThangType = require 'models/ThangType'
|
|
LadderSubmissionView = require 'views/play/common/LadderSubmissionView'
|
|
AudioPlayer = require 'lib/AudioPlayer'
|
|
User = require 'models/User'
|
|
utils = require 'core/utils'
|
|
Course = require 'models/Course'
|
|
Level = require 'models/Level'
|
|
LevelFeedback = require 'models/LevelFeedback'
|
|
|
|
module.exports = class HeroVictoryModal extends ModalView
|
|
id: 'hero-victory-modal'
|
|
template: template
|
|
closeButton: false
|
|
closesOnClickOutside: false
|
|
|
|
subscriptions:
|
|
'ladder:game-submitted': 'onGameSubmitted'
|
|
|
|
events:
|
|
'click #continue-button': 'onClickContinue'
|
|
'click .leaderboard-button': 'onClickLeaderboard'
|
|
'click .return-to-course-button': 'onClickReturnToCourse'
|
|
'click .return-to-ladder-button': 'onClickReturnToLadder'
|
|
'click .sign-up-button': 'onClickSignupButton'
|
|
'click .continue-from-offer-button': 'onClickContinueFromOffer'
|
|
'click .skip-offer-button': 'onClickSkipOffer'
|
|
|
|
# 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()
|
|
|
|
constructor: (options) ->
|
|
super(options)
|
|
@courseID = options.courseID
|
|
@courseInstanceID = options.courseInstanceID
|
|
|
|
@session = options.session
|
|
@level = options.level
|
|
@thangTypes = {}
|
|
if @level.get('type', true) in ['hero', 'hero-ladder', 'course', 'course-ladder']
|
|
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
|
|
@playSound 'victory'
|
|
if @level.get('type', true) is 'course'
|
|
if nextLevel = @level.get('nextLevel')
|
|
@nextLevel = new Level().setURL "/db/level/#{nextLevel.original}/version/#{nextLevel.majorVersion}"
|
|
@nextLevel = @supermodel.loadModel(@nextLevel).model
|
|
if @courseID
|
|
@course = new Course().setURL "/db/course/#{@courseID}"
|
|
@course = @supermodel.loadModel(@course).model
|
|
if @level.get('type', true) in ['course', 'course-ladder']
|
|
@saveReviewEventually = _.debounce(@saveReviewEventually, 2000)
|
|
@loadExistingFeedback()
|
|
|
|
destroy: ->
|
|
clearInterval @sequentialAnimationInterval
|
|
@saveReview() if @$el.find('.review textarea').val()
|
|
@feedback?.off()
|
|
super()
|
|
|
|
onHidden: ->
|
|
Backbone.Mediator.publish 'music-player:exit-menu', {}
|
|
super()
|
|
|
|
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()
|
|
|
|
onAchievementsLoaded: ->
|
|
@achievements.models = _.filter @achievements.models, (m) -> not m.get('query')?.ladderAchievementDifficulty # Don't show higher AI difficulty achievements
|
|
@$el.toggleClass 'full-achievements', @achievements.models.length is 3
|
|
thangTypeOriginals = []
|
|
achievementIDs = []
|
|
for achievement in @achievements.models
|
|
rewards = achievement.get('rewards') or {}
|
|
thangTypeOriginals.push rewards.heroes or []
|
|
thangTypeOriginals.push rewards.items or []
|
|
achievement.completed = LocalMongo.matchesQuery(@session.attributes, achievement.get('query'))
|
|
achievementIDs.push(achievement.id) if achievement.completed
|
|
|
|
thangTypeOriginals = _.uniq _.flatten thangTypeOriginals
|
|
for thangTypeOriginal in thangTypeOriginals
|
|
thangType = new ThangType()
|
|
thangType.url = "/db/thang.type/#{thangTypeOriginal}/version"
|
|
#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']
|
|
@thangTypes[thangTypeOriginal] = @supermodel.loadModel(thangType).model
|
|
|
|
@newEarnedAchievements = []
|
|
hadOneCompleted = false
|
|
for achievement in @achievements.models
|
|
continue unless achievement.completed
|
|
hadOneCompleted = true
|
|
ea = new EarnedAchievement({
|
|
collection: achievement.get('collection')
|
|
triggeredBy: @session.id
|
|
achievement: achievement.id
|
|
})
|
|
ea.save()
|
|
@newEarnedAchievements.push ea
|
|
@listenToOnce ea, 'sync', ->
|
|
if _.all((ea.id for ea in @newEarnedAchievements))
|
|
@newEarnedAchievementsResource.markLoaded()
|
|
@listenToOnce me, 'sync', ->
|
|
@readyToContinue = true
|
|
@updateSavingProgressStatus()
|
|
me.fetch cache: false unless me.loading
|
|
|
|
@readyToContinue = true unless hadOneCompleted
|
|
|
|
# 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
|
|
|
|
getRenderData: ->
|
|
c = super()
|
|
c.levelName = utils.i18n @level.attributes, 'name'
|
|
if @level.get('type', true) isnt 'hero'
|
|
c.victoryText = utils.i18n @level.get('victory') ? {}, 'body'
|
|
earnedAchievementMap = _.indexBy(@newEarnedAchievements or [], (ea) -> ea.get('achievement'))
|
|
for achievement in (@achievements?.models or [])
|
|
earnedAchievement = earnedAchievementMap[achievement.id]
|
|
if earnedAchievement
|
|
achievement.completedAWhileAgo = new Date().getTime() - Date.parse(earnedAchievement.attributes.changed) > 30 * 1000
|
|
achievement.worth = achievement.get 'worth', true
|
|
achievement.gems = achievement.get('rewards')?.gems
|
|
c.achievements = @achievements?.models.slice() or []
|
|
for achievement in c.achievements
|
|
achievement.description = utils.i18n achievement.attributes, 'description'
|
|
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
|
|
previousAmount = Math.max(0, achievedAmount - 1)
|
|
func = achievement.getExpFunction()
|
|
achievement.previousWorth = (achievement.get('worth') ? 0) * func previousAmount
|
|
achievement.worth = (achievement.get('worth') ? 0) * func achievedAmount
|
|
rewards = achievement.get 'rewards'
|
|
achievement.gems = rewards?.gems * func achievedAmount if rewards?.gems
|
|
achievement.previousGems = rewards?.gems * func previousAmount if rewards?.gems
|
|
|
|
# for testing the three states
|
|
#if c.achievements.length
|
|
# c.achievements = [c.achievements[0].clone(), c.achievements[0].clone(), c.achievements[0].clone()]
|
|
#for achievement, index in c.achievements
|
|
## achievement.completed = index > 0
|
|
## achievement.completedAWhileAgo = index > 1
|
|
# achievement.completed = true
|
|
# achievement.completedAWhileAgo = false
|
|
# achievement.attributes.worth = (index + 1) * achievement.get('worth', true)
|
|
# rewards = achievement.get('rewards') or {}
|
|
# rewards.gems *= (index + 1)
|
|
|
|
c.thangTypes = @thangTypes
|
|
c.me = me
|
|
c.readyToRank = @level.get('type', true) in ['hero-ladder', 'course-ladder'] and @session.readyToRank()
|
|
c.level = @level
|
|
c.i18n = utils.i18n
|
|
|
|
elapsed = (new Date() - new Date(me.get('dateCreated')))
|
|
if me.get 'hourOfCode'
|
|
# Show the Hour of Code "I'm Done" tracking pixel after they played for 20 minutes
|
|
lastLevel = @level.get('slug') is 'course-kithgard-gates'
|
|
enough = elapsed >= 20 * 60 * 1000 or lastLevel
|
|
tooMuch = elapsed > 120 * 60 * 1000
|
|
showDone = (elapsed >= 30 * 60 * 1000 and not tooMuch) or lastLevel
|
|
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()
|
|
window.tracker?.trackEvent 'Hour of Code Finish'
|
|
# Show the "I'm done" button between 30 - 120 minutes if they definitely came from Hour of Code
|
|
c.showHourOfCodeDoneButton = showDone
|
|
|
|
c.showLeaderboard = @level.get('scoreTypes')?.length > 0 and @level.get('type', true) isnt 'course'
|
|
|
|
c.showReturnToCourse = not c.showLeaderboard and not me.get('anonymous') and @level.get('type', true) in ['course', 'course-ladder']
|
|
c.isCourseLevel = @level.get('type', true) in ['course']
|
|
c.currentCourseName = @course?.get('name')
|
|
c.currentLevelName = @level?.get('name')
|
|
c.nextLevelName = @nextLevel?.get('name')
|
|
|
|
return c
|
|
|
|
afterRender: ->
|
|
super()
|
|
@$el.toggleClass 'with-achievements', @level.get('type', true) in ['hero', 'hero-ladder']
|
|
return unless @supermodel.finished()
|
|
@playSelectionSound hero, true for original, hero of @thangTypes # Preload them
|
|
@updateSavingProgressStatus()
|
|
@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: ->
|
|
return @endSequentialAnimations() unless @level.get('type', true) in ['hero', 'hero-ladder']
|
|
@updateXPBars 0
|
|
#playVictorySound = => @playSound 'victory-title-appear' # TODO: actually add this
|
|
@$el.find('#victory-header').delay(250).queue(->
|
|
$(@).removeClass('out').dequeue()
|
|
#playVictorySound()
|
|
)
|
|
complete = _.once(_.bind(@beginSequentialAnimations, @))
|
|
@animatedPanels = $()
|
|
panels = @$el.find('.achievement-panel')
|
|
for panel in panels
|
|
panel = $(panel)
|
|
continue unless panel.data('animate')
|
|
@animatedPanels = @animatedPanels.add(panel)
|
|
panel.delay(500) # Waiting for victory header to show up and fall
|
|
panel.queue(->
|
|
$(@).addClass('earned') # animate out the grayscale
|
|
$(@).dequeue()
|
|
)
|
|
panel.delay(500)
|
|
panel.queue(->
|
|
$(@).find('.reward-image-container').addClass('show')
|
|
$(@).dequeue()
|
|
)
|
|
panel.delay(500)
|
|
panel.queue(-> complete())
|
|
@animationComplete = not @animatedPanels.length
|
|
complete() if @animationComplete
|
|
|
|
beginSequentialAnimations: ->
|
|
return if @destroyed
|
|
return unless @level.get('type', true) in ['hero', 'hero-ladder']
|
|
@sequentialAnimatedPanels = _.map(@animatedPanels.find('.reward-panel'), (panel) -> {
|
|
number: $(panel).data('number')
|
|
previousNumber: $(panel).data('previous-number')
|
|
textEl: $(panel).find('.reward-text')
|
|
rootEl: $(panel)
|
|
unit: $(panel).data('number-unit')
|
|
hero: $(panel).data('hero-thang-type')
|
|
item: $(panel).data('item-thang-type')
|
|
})
|
|
|
|
@totalXP = 0
|
|
@totalXP += panel.number for panel in @sequentialAnimatedPanels when panel.unit is 'xp'
|
|
@totalGems = 0
|
|
@totalGems += panel.number for panel in @sequentialAnimatedPanels when panel.unit is 'gem'
|
|
@gemEl = $('#gem-total')
|
|
@XPEl = $('#xp-total')
|
|
@totalXPAnimated = @totalGemsAnimated = @lastTotalXP = @lastTotalGems = 0
|
|
@sequentialAnimationStart = new Date()
|
|
@sequentialAnimationInterval = setInterval(@tickSequentialAnimation, 1000 / 60)
|
|
|
|
tickSequentialAnimation: =>
|
|
# TODO: make sure the animation pulses happen when the numbers go up and sounds play (up to a max speed)
|
|
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
|
|
if panel.unit is 'xp'
|
|
newXP = Math.floor(panel.previousNumber + ratio * (panel.number - panel.previousNumber))
|
|
totalXP = @totalXPAnimated + newXP
|
|
if totalXP isnt @lastTotalXP
|
|
panel.textEl.text('+' + newXP)
|
|
@XPEl.text(totalXP)
|
|
@updateXPBars(totalXP)
|
|
xpTrigger = 'xp-' + (totalXP % 6) # 6 xp sounds
|
|
@playSound xpTrigger, (0.5 + ratio / 2)
|
|
@XPEl.addClass 'four-digits' if totalXP >= 1000 and @lastTotalXP < 1000
|
|
@XPEl.addClass 'five-digits' if totalXP >= 10000 and @lastTotalXP < 10000
|
|
@lastTotalXP = totalXP
|
|
else if panel.unit is 'gem'
|
|
newGems = Math.floor(panel.previousNumber + ratio * (panel.number - panel.previousNumber))
|
|
totalGems = @totalGemsAnimated + newGems
|
|
if totalGems isnt @lastTotalGems
|
|
panel.textEl.text('+' + newGems)
|
|
@gemEl.text(totalGems)
|
|
gemTrigger = 'gem-' + (parseInt(panel.number * ratio) % 4) # 4 gem sounds
|
|
@playSound gemTrigger, (0.5 + ratio / 2)
|
|
@gemEl.addClass 'four-digits' if totalGems >= 1000 and @lastTotalGems < 1000
|
|
@gemEl.addClass 'five-digits' if totalGems >= 10000 and @lastTotalGems < 10000
|
|
@lastTotalGems = totalGems
|
|
else if panel.item
|
|
thangType = @thangTypes[panel.item]
|
|
panel.textEl.text utils.i18n(thangType.attributes, 'name')
|
|
@playSound 'item-unlocked' if 0.5 < ratio < 0.6
|
|
else if panel.hero
|
|
thangType = @thangTypes[panel.hero]
|
|
panel.textEl.text(thangType.get('name'))
|
|
@playSelectionSound thangType if 0.5 < ratio < 0.6
|
|
if ratio is 1
|
|
panel.rootEl.removeClass('animating').find('.reward-image-container img').removeClass('pulse')
|
|
@sequentialAnimationStart = new Date()
|
|
if panel.unit is 'xp'
|
|
@totalXPAnimated += panel.number
|
|
else if panel.unit is 'gem'
|
|
@totalGemsAnimated += panel.number
|
|
@sequentialAnimatedPanels.shift()
|
|
return
|
|
panel.rootEl.addClass('animating').find('.reward-image-container').removeClass('pending-reward-image').find('img').addClass('pulse')
|
|
|
|
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)
|
|
|
|
updateXPBars: (achievedXP) ->
|
|
previousXP = @previousXP
|
|
previousXP = previousXP + 1000000 if me.isInGodMode()
|
|
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
|
|
|
|
if leveledUp and (not @displayedLevel or currentLevel > @displayedLevel)
|
|
@playSound 'level-up'
|
|
@displayedLevel = currentLevel
|
|
|
|
endSequentialAnimations: ->
|
|
clearInterval @sequentialAnimationInterval
|
|
@animationComplete = true
|
|
@updateSavingProgressStatus()
|
|
Backbone.Mediator.publish 'music-player:enter-menu', terrain: @level.get('terrain', true)
|
|
|
|
updateSavingProgressStatus: ->
|
|
@$el.find('#saving-progress-label').toggleClass('hide', @readyToContinue)
|
|
@$el.find('.next-level-button').toggleClass('hide', not @readyToContinue)
|
|
@$el.find('.sign-up-poke').toggleClass('hide', not @readyToContinue)
|
|
|
|
onGameSubmitted: (e) ->
|
|
@returnToLadder()
|
|
|
|
returnToLadder: ->
|
|
# Preserve the supermodel as we navigate back to the ladder.
|
|
viewArgs = [{supermodel: if @options.hasReceivedMemoryWarning then null else @supermodel}, @level.get('slug')]
|
|
ladderURL = "/play/ladder/#{@level.get('slug') || @level.id}"
|
|
if leagueID = (@courseInstanceID or @getQueryVariable 'league')
|
|
leagueType = if @level.get('type') is 'course-ladder' then 'course' else 'clan'
|
|
viewArgs.push leagueType
|
|
viewArgs.push leagueID
|
|
ladderURL += "/#{leagueType}/#{leagueID}"
|
|
ladderURL += '#my-matches'
|
|
Backbone.Mediator.publish 'router:navigate', route: ladderURL, viewClass: 'views/ladder/LadderView', viewArgs: viewArgs
|
|
|
|
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
|
|
|
|
getNextLevelCampaign: ->
|
|
{'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.
|
|
|
|
getNextLevelLink: (returnToCourse=false) ->
|
|
if @level.get('type', true) is 'course' and nextLevel = @level.get('nextLevel') and not returnToCourse
|
|
# need to do something more complicated to load its slug
|
|
console.log 'have @nextLevel', @nextLevel, 'from nextLevel', nextLevel
|
|
link = "/play/level/#{@nextLevel.get('slug')}"
|
|
if @courseID
|
|
link += "?course=#{@courseID}"
|
|
link += "&course-instance=#{@courseInstanceID}" if @courseInstanceID
|
|
else if @level.get('type', true) is 'course'
|
|
link = "/courses"
|
|
if @courseID
|
|
link += "/#{@courseID}"
|
|
link += "/#{@courseInstanceID}" if @courseInstanceID
|
|
else
|
|
link = '/play'
|
|
nextCampaign = @getNextLevelCampaign()
|
|
link += '/' + nextCampaign
|
|
link
|
|
|
|
onClickContinue: (e, extraOptions=null) ->
|
|
@playSound 'menu-button-click'
|
|
nextLevelLink = @getNextLevelLink extraOptions?.returnToCourse
|
|
# Preserve the supermodel as we navigate back to the world map.
|
|
options =
|
|
justBeatLevel: @level
|
|
supermodel: if @options.hasReceivedMemoryWarning then null else @supermodel
|
|
_.merge options, extraOptions if extraOptions
|
|
if @level.get('type', true) is 'course' and @nextLevel and not options.returnToCourse
|
|
viewClass = require 'views/play/level/PlayLevelView'
|
|
options.courseID = @courseID
|
|
options.courseInstanceID = @courseInstanceID
|
|
viewArgs = [options, @nextLevel.get('slug')]
|
|
else if @level.get('type', true) is 'course'
|
|
# 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
|
|
viewArgs.push @courseInstanceID if @courseInstanceID
|
|
else if @level.get('type', true) is 'course-ladder'
|
|
leagueID = @courseInstanceID or @getQueryVariable 'league'
|
|
nextLevelLink = "/play/ladder/#{@level.get('slug')}"
|
|
nextLevelLink += "/course/#{leagueID}" if leagueID
|
|
viewClass = 'views/ladder/LadderView'
|
|
viewArgs = [options, @level.get('slug')]
|
|
viewArgs = viewArgs.concat ['course', leagueID] if leagueID
|
|
else
|
|
viewClass = require 'views/play/CampaignView'
|
|
viewArgs = [options, @getNextLevelCampaign()]
|
|
navigationEvent = route: nextLevelLink, viewClass: viewClass, viewArgs: viewArgs
|
|
if @level.get('slug') is 'lost-viking' and not (me.get('age') in ['0-13', '14-17'])
|
|
@showOffer navigationEvent
|
|
else
|
|
Backbone.Mediator.publish 'router:navigate', navigationEvent
|
|
|
|
onClickLeaderboard: (e) ->
|
|
@onClickContinue e, showLeaderboard: true
|
|
|
|
onClickReturnToCourse: (e) ->
|
|
@onClickContinue e, returnToCourse: true
|
|
|
|
onClickReturnToLadder: (e) ->
|
|
@playSound 'menu-button-click'
|
|
e.preventDefault()
|
|
@returnToLadder()
|
|
|
|
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'}
|
|
|
|
showOffer: (@navigationEventUponCompletion) ->
|
|
@$el.find('.modal-footer > *').hide()
|
|
@$el.find(".modal-footer > .offer.#{@level.get('slug')}").show()
|
|
|
|
onClickContinueFromOffer: (e) ->
|
|
url = {
|
|
'lost-viking': 'http://www.vikingcodeschool.com/codecombat?utm_source=codecombat&utm_medium=viking_level&utm_campaign=affiliate&ref=Code+Combat+Elite'
|
|
}[@level.get('slug')]
|
|
Backbone.Mediator.publish 'router:navigate', @navigationEventUponCompletion
|
|
window.open url, '_blank' if url
|
|
|
|
onClickSkipOffer: (e) ->
|
|
Backbone.Mediator.publish 'router:navigate', @navigationEventUponCompletion
|
|
|
|
# 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()
|