mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-04-14 14:04:38 -04:00
Removed achievement and rewards from course-type level victory screens. Added victory text, return-to-course-button, and level feedback and reviews to course-type levels.
This commit is contained in:
parent
4ef6d471ee
commit
3f323d27d0
5 changed files with 185 additions and 25 deletions
app
locale
styles/play/level/modal
templates/play/level/modal
views/play
|
@ -245,8 +245,9 @@
|
|||
victory_return_to_ladder: "Return to Ladder"
|
||||
victory_play_continue: "Continue"
|
||||
victory_saving_progress: "Saving Progress"
|
||||
victory_go_home: "Go Home" # Only in old-style levels.
|
||||
victory_review: "Tell us more!" # Only in old-style levels.
|
||||
victory_go_home: "Go Home"
|
||||
victory_review: "Tell us more!"
|
||||
victory_review_placeholder: "How was the level?"
|
||||
victory_hour_of_code_done: "Are You Done?"
|
||||
victory_hour_of_code_done_yes: "Yes, I'm finished with my Hour of Code™!"
|
||||
victory_experience_gained: "XP Gained"
|
||||
|
|
|
@ -329,12 +329,16 @@
|
|||
margin: 0
|
||||
float: left
|
||||
|
||||
.leaderboard-button
|
||||
.leaderboard-button, .courses-button
|
||||
height: 60px
|
||||
line-height: 30px
|
||||
margin: 0 10px
|
||||
float: right
|
||||
|
||||
.return-to-course-button
|
||||
width: 258px
|
||||
float: left
|
||||
|
||||
.next-level-buttons
|
||||
float: right
|
||||
|
||||
|
@ -384,6 +388,69 @@
|
|||
p
|
||||
color: white
|
||||
|
||||
#victory-text
|
||||
z-index: 1
|
||||
text-align: center
|
||||
padding: 30px 13px 0 13px
|
||||
margin-bottom: 35px
|
||||
font-size: 24px
|
||||
color: white
|
||||
font-weight: bold
|
||||
text-transform: uppercase
|
||||
font-family: $headings-font-family
|
||||
text-shadow: black 2px 2px 0, black -2px -2px 0, black 2px -2px 0, black -2px 2px 0, black 2px 0px 0, black 0px -2px 0, black -2px 0px 0, black 0px 2px 0
|
||||
|
||||
#level-feedback
|
||||
color: $hero-yellow-text
|
||||
font-weight: bold
|
||||
text-transform: uppercase
|
||||
font-size: 20px
|
||||
font-family: $headings-font-family
|
||||
padding: 0 13px 20px 13px
|
||||
text-shadow: black 2px 2px 0, black -2px -2px 0, black 2px -2px 0, black -2px 2px 0, black 2px 0px 0, black 0px -2px 0, black -2px 0px 0, black 0px 2px 0
|
||||
|
||||
.rating
|
||||
position: relative
|
||||
margin-top: 5px
|
||||
text-align: center
|
||||
float: left
|
||||
width: 50%
|
||||
|
||||
.rating-label
|
||||
margin-bottom: 8px
|
||||
|
||||
i
|
||||
cursor: pointer
|
||||
padding: 5px
|
||||
font-size: 48px
|
||||
text-shadow: black 3px 3px 0, black -3px -3px 0, black 3px -3px 0, black -3px 3px 0, black 3px 0px 0, black 0px -3px 0, black -3px 0px 0, black 0px 3px 0
|
||||
|
||||
.review-label
|
||||
margin-top: 5px
|
||||
text-align: center
|
||||
float: right
|
||||
width: 50%
|
||||
|
||||
.review
|
||||
width: 100%
|
||||
text-align: center
|
||||
|
||||
textarea
|
||||
float: right
|
||||
width: 50%
|
||||
height: 80px
|
||||
box-sizing: border-box
|
||||
background-color: rgba(255, 255, 255, 0.7)
|
||||
font-size: 16px
|
||||
|
||||
&:not(.with-achievements)
|
||||
#totals
|
||||
display: none
|
||||
|
||||
&.with-achievements
|
||||
#victory-text
|
||||
display: none
|
||||
|
||||
html.no-borderimage
|
||||
#hero-victory-modal
|
||||
.modal-dialog
|
||||
|
|
|
@ -8,7 +8,24 @@ block modal-header-content
|
|||
h1(data-i18n="play_level.victory") Victory
|
||||
|
||||
block modal-body-content
|
||||
|
||||
if victoryText
|
||||
#victory-text= victoryText
|
||||
|
||||
#level-feedback
|
||||
div.rating.secret
|
||||
div.rating-label(data-i18n="play_level.victory_rate_the_level") Rate the level:
|
||||
i.glyphicon.glyphicon-star-empty
|
||||
i.glyphicon.glyphicon-star-empty
|
||||
i.glyphicon.glyphicon-star-empty
|
||||
i.glyphicon.glyphicon-star-empty
|
||||
i.glyphicon.glyphicon-star-empty
|
||||
if !me.get('anonymous')
|
||||
span.review-label.secret(data-i18n="play_level.victory_review") Tell us more!
|
||||
.review.secret
|
||||
br
|
||||
textarea(data-i18n="[placeholder]play_level.victory_review_placeholder")
|
||||
.clearfix
|
||||
|
||||
for achievement in achievements
|
||||
- var animate = achievement.completed && !achievement.completedAWhileAgo
|
||||
.achievement-panel(class=achievement.completedAWhileAgo ? 'earned' : '' data-achievement-id=achievement.id data-animate=animate)
|
||||
|
@ -85,6 +102,8 @@ block modal-footer-content
|
|||
|
||||
if !me.get('anonymous') && !showHourOfCodeDoneButton && showLeaderboard
|
||||
button.btn.btn-illustrated.btn-success.leaderboard-button.btn-lg(data-dismiss="modal", data-i18n="leaderboard.view_other_solutions") View Other Solutions
|
||||
else if showReturnToCourse
|
||||
button.btn.btn-illustrated.btn-warning.return-to-course-button.btn-lg(data-dismiss="modal", data-i18n="play_level.victory_go_home") Go Home
|
||||
|
||||
if showHourOfCodeDoneButton
|
||||
.hour-of-code-done
|
||||
|
|
|
@ -12,6 +12,7 @@ AudioPlayer = require 'lib/AudioPlayer'
|
|||
User = require 'models/User'
|
||||
utils = require 'core/utils'
|
||||
Level = require 'models/Level'
|
||||
LevelFeedback = require 'models/LevelFeedback'
|
||||
|
||||
module.exports = class HeroVictoryModal extends ModalView
|
||||
id: 'hero-victory-modal'
|
||||
|
@ -25,38 +26,76 @@ module.exports = class HeroVictoryModal extends ModalView
|
|||
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'
|
||||
|
||||
# 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)
|
||||
@session = options.session
|
||||
@level = options.level
|
||||
achievements = new CocoCollection([], {
|
||||
url: "/db/achievement?related=#{@session.get('level').original}"
|
||||
model: Achievement
|
||||
})
|
||||
@thangTypes = {}
|
||||
@achievements = @supermodel.loadCollection(achievements, 'achievements').model
|
||||
@listenToOnce @achievements, 'sync', @onAchievementsLoaded
|
||||
@readyToContinue = false
|
||||
@waitingToContinueSince = new Date()
|
||||
@previousXP = me.get 'points', true
|
||||
@previousLevel = me.level()
|
||||
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
|
||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'victory'
|
||||
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
|
||||
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: ->
|
||||
@$el.toggleClass 'full-achievements', @achievements.models.length is 3
|
||||
thangTypeOriginals = []
|
||||
|
@ -102,14 +141,15 @@ module.exports = class HeroVictoryModal extends ModalView
|
|||
getRenderData: ->
|
||||
c = super()
|
||||
c.levelName = utils.i18n @level.attributes, 'name'
|
||||
c.victoryText = utils.i18n @level.get('victory') ? {}, 'body'
|
||||
earnedAchievementMap = _.indexBy(@newEarnedAchievements or [], (ea) -> ea.get('achievement'))
|
||||
for achievement in @achievements.models
|
||||
for achievement in (@achievements?.models or [])
|
||||
earnedAchievement = earnedAchievementMap[achievement.id]
|
||||
if earnedAchievement
|
||||
achievement.completedAWhileAgo = new Date().getTime() - Date.parse(earnedAchievement.get('created')) > 30 * 1000
|
||||
achievement.worth = achievement.get 'worth', true
|
||||
achievement.gems = achievement.get('rewards')?.gems
|
||||
c.achievements = @achievements.models.slice()
|
||||
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'
|
||||
|
@ -159,14 +199,24 @@ module.exports = class HeroVictoryModal extends ModalView
|
|||
|
||||
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']
|
||||
|
||||
return c
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
@$el.toggleClass 'show-achievements', @level.get('type', true) is 'hero'
|
||||
return unless @supermodel.finished()
|
||||
@playSelectionSound hero, true for original, hero of @thangTypes # Preload them
|
||||
@updateSavingProgressStatus()
|
||||
@updateXPBars 0
|
||||
@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
|
||||
@$el.find('#victory-header').delay(250).queue(->
|
||||
$(@).removeClass('out').dequeue()
|
||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'victory-title-appear' # TODO: actually add this
|
||||
|
@ -192,12 +242,10 @@ module.exports = class HeroVictoryModal extends ModalView
|
|||
panel.queue(-> complete())
|
||||
@animationComplete = not @animatedPanels.length
|
||||
complete() if @animationComplete
|
||||
if @level.get('type', true) in ['hero-ladder', 'course-ladder']
|
||||
@ladderSubmissionView = new LadderSubmissionView session: @session, level: @level
|
||||
@insertSubView @ladderSubmissionView, @$el.find('.ladder-submission-view')
|
||||
|
||||
beginSequentialAnimations: ->
|
||||
return if @destroyed
|
||||
return unless @level.get('type', true) is 'hero'
|
||||
@sequentialAnimatedPanels = _.map(@animatedPanels.find('.reward-panel'), (panel) -> {
|
||||
number: $(panel).data('number')
|
||||
previousNumber: $(panel).data('previous-number')
|
||||
|
@ -336,8 +384,8 @@ module.exports = class HeroVictoryModal extends ModalView
|
|||
getNextLevelCampaign: ->
|
||||
{'kithgard-gates': 'forest', 'kithgard-mastery': 'forest', 'siege-of-stonehold': 'desert', 'clash-of-clones': 'mountain'}[@level.get('slug')] or @level.get 'campaign' # Much easier to just keep this updated than to dynamically figure it out.
|
||||
|
||||
getNextLevelLink: ->
|
||||
if @level.get('type', true) is 'course' and nextLevel = @level.get('nextLevel')
|
||||
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
|
||||
return "/play/level/#{@nextLevel.get('slug')}"
|
||||
|
@ -351,13 +399,13 @@ module.exports = class HeroVictoryModal extends ModalView
|
|||
|
||||
onClickContinue: (e, extraOptions=null) ->
|
||||
@playSound 'menu-button-click'
|
||||
nextLevelLink = @getNextLevelLink()
|
||||
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
|
||||
if @level.get('type', true) is 'course' and @nextLevel and not options.returnToCourse
|
||||
viewClass = require 'views/play/level/PlayLevelView'
|
||||
viewArgs = [options, @nextLevel.get('slug')]
|
||||
else if @level.get('type', true) is 'course'
|
||||
|
@ -376,6 +424,9 @@ module.exports = class HeroVictoryModal extends ModalView
|
|||
onClickLeaderboard: (e) ->
|
||||
@onClickContinue e, showLeaderboard: true
|
||||
|
||||
onClickReturnToCourse: (e) ->
|
||||
@onClickContinue e, returnToCourse: true
|
||||
|
||||
onClickReturnToLadder: (e) ->
|
||||
@playSound 'menu-button-click'
|
||||
e.preventDefault()
|
||||
|
@ -398,3 +449,25 @@ module.exports = class HeroVictoryModal extends ModalView
|
|||
}[@level.get('slug')]
|
||||
Backbone.Mediator.publish 'router:navigate', @navigationEventUponCompletion
|
||||
window.open url, '_blank' if url
|
||||
|
||||
# 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()
|
||||
|
|
|
@ -172,7 +172,7 @@ module.exports = class PlayHeroesModal extends ModalView
|
|||
layer.on 'new-spritesheet', ->
|
||||
#- maybe put some more normalization here?
|
||||
m = multiplier
|
||||
m *= 0.75 if fullHero.get('slug') in ['knight', 'samurai', 'librarian', 'sorcerer', 'necromancer'] # these heroes are larger for some reason, shrink 'em
|
||||
m *= 0.75 if fullHero.get('slug') in ['knight', 'samurai', 'librarian', 'sorcerer', 'necromancer'] # These heroes are larger for some reason. Shrink 'em.
|
||||
layer.container.scaleX = layer.container.scaleY = m
|
||||
layer.container.children[0].x = 160/m
|
||||
layer.container.children[0].y = 250/m
|
||||
|
|
Loading…
Add table
Reference in a new issue