Merge branch 'master' into production

This commit is contained in:
Nick Winter 2014-10-17 20:47:46 -07:00
commit 9b62f2c7bf
6 changed files with 202 additions and 126 deletions

View file

@ -86,6 +86,12 @@
-ms-flex-pack: justify
justify-content: space-between
@mixin flex-justify-center()
-webkit-box-pack: center
-webkit-justify-content: center
-ms-flex-pack: center
justify-content: center
@mixin flex-align-content-start()
-webkit-align-content: flex-start
-ms-flex-align-content: flex-start

View file

@ -1,18 +1,31 @@
@import "app/styles/mixins"
@import "app/styles/bootstrap/variables"
#hero-victory-modal
//- Top-level modal container
.modal-dialog
margin-top: 15px
padding-top: 0
//- Header
.background-wrapper
background: url("/images/pages/play/level/modal/victory_modal_background.png")
height: 650px
//background: url("/images/pages/play/level/modal/victory_modal_background.png")
width: 550px
border-width: 25px
border-image: url("/images/pages/play/level/modal/victory_modal_background.png") 25 fill round
border-radius: 10px
#victory-header
display: block
margin: 40px auto 0
margin: 15px auto 0
@include transition(0.25s ease-in)
&.out
margin-top: -100px
.modal-header
height: 110px
height: 85px
border: none
@ -28,15 +41,12 @@
margin: 5px auto
position: relative
-webkit-transition-duration: 1s
-moz-transition-duration: 1s
-o-transition-duration: 1s
transition-duration: 1s
@include transition-duration(1s)
-webkit-filter: grayscale(100%)
-moz-filter: grayscale(100%)
-o-filter: grayscale(100%)
filter: grayscale(100%)
-webkit-filter: grayscale(100%) brightness(75%)
-moz-filter: grayscale(100%) brightness(75%)
-o-filter: grayscale(100%) brightness(75%)
filter: grayscale(100%) brightness(75%)
&.earned
-webkit-filter: none
@ -44,7 +54,11 @@
-o-filter: none
filter: none
.achievement-description
@include opacity(1)
.achievement-description
@include opacity(0.75)
position: absolute
text-align: center
left: 95px
@ -54,25 +68,36 @@
white-space: nowrap
overflow: hidden
text-overflow: ellipsis
.achievement-rewards
position: absolute
left: 25px
right: 23px
top: 41px
bottom: 18px
@include flexbox()
@include flex-justify-center()
//- Reward panels
.reward-panel
background: url("/images/pages/play/level/modal/reward_plate.png")
background: url("/images/pages/play/level/modal/reward_plate.png")
width: 77px
height: 85px
float: left
margin: 0 1.8px
position: relative
z-index: 1
@include transition(0.25s ease)
&.animating
@include scale(1.5)
z-index: 2
.reward-text
font-size: 18px
overflow: visible
bottom: 9px
.reward-image-container
top: 8px
@ -81,21 +106,11 @@
width: 56px
position: relative
-webkit-transform: scale(0, 0)
-moz-transform: scale(0, 0)
-o-transform: scale(0, 0)
transform: scale(0, 0)
-webkit-transition-duration: 0.5s
-moz-transition-duration: 0.5s
-o-transition-duration: 0.5s
transition-duration: 0.5s
@include scale(0)
@include transition-duration(0.5s)
&.show
-webkit-transform: scale(1, 1)
-moz-transform: scale(1, 1)
-o-transform: scale(1, 1)
transform: scale(1, 1)
@include scale(1)
img
margin: 0
@ -103,17 +118,8 @@
top: 50%
left: 50%
margin-right: -50%
-webkit-transition-duration: 0.5s
-moz-transition-duration: 0.5s
-o-transition-duration: 0.5s
transition-duration: 0.5s
-webkit-transform: translate(-50%, -50%)
-moz-transform: translate(-50%, -50%)
-o-transform: translate(-50%, -50%)
transform: translate(-50%, -50%)
@include transition-duration(0.5s)
@include translate(-50%, -50%)
max-width: 56px
max-height: 55px
@ -130,59 +136,65 @@
white-space: nowrap
overflow: hidden
text-overflow: ellipsis
//- Pulse effect
@-webkit-keyframes pulse
+keyframes(rewardPulse)
from
-webkit-transform: translate(-50%, -50%) scale(1.0)
max-width: 56px
max-height: 55px
50%
-webkit-transform: translate(-50%, -50%) scale(1.3)
width: 66px
max-width: 66px
max-height: 66px
to
-webkit-transform: translate(-50%, -50%) scale(1.0)
max-width: 56px
max-height: 55px
@-moz-keyframes pulse
from
-moz-transform: translate(-50%, -50%) scale(1.0)
50%
-moz-transform: translate(-50%, -50%) scale(1.3)
to
-moz-transform: translate(-50%, -50%) scale(1.0)
.xp .pulse
@include animation(rewardPulse 0.15s infinite)
@-o-keyframes pulse
from
-o-transform: translate(-50%, -50%) scale(1.0)
50%
-o-transform: translate(-50%, -50%) scale(1.3)
to
-o-transform: translate(-50%, -50%) scale(1.0)
@keyframes pulse
from
transform: translate(-50%, -50%) scale(1.0)
50%
transform: translate(-50%, -50%) scale(1.3)
to
transform: translate(-50%, -50%) scale(1.0)
.pulse
-webkit-animation: pulse 0.5s infinite
-moz-animation: pulse 0.5s infinite
-o-animation: pulse 0.5s infinite
animation: pulse 0.5s infinite
.gems .pulse
@include animation(rewardPulse 0.25s infinite)
//- Footer
.modal-content
height: 650px // so the footer appears at the bottom
padding-bottom: 50px // so the footer appears at the bottom
&.with-sign-up .modal-content
padding-bottom: 100px // need more space for signup poke
.modal-footer
position: absolute
bottom: 20px
bottom: -20px
left: 20px
right: 20px
#totals
color: white
color: white
p.sign-up-poke
position: absolute
bottom: 60px
right: 20px
color: white
.sign-up-button
float: right
margin-left: 10px
html.no-borderimage
#hero-victory-modal
.background-wrapper
background: url("/images/pages/play/level/modal/victory_modal_background.png")
height: 650px
#victory-header
margin-top: 40px
.modal-header
height: 110px
.modal-content
height: 650px
padding-bottom: 0
.modal-footer
bottom: 20px

View file

@ -1,6 +1,6 @@
extends /templates/modal/modal_base
block modal-header-content
img(src="/images/pages/play/level/modal/victory_word.png")#victory-header
img(src="/images/pages/play/level/modal/victory_word.png")#victory-header.out
block modal-body-content
@ -14,21 +14,21 @@ block modal-body-content
div.achievement-rewards
- var worth = achievement.get('worth', true);
if worth
.reward-panel.numerical(data-number=worth, data-number-unit='xp')
.reward-panel.numerical.xp(data-number=worth, data-number-unit='xp')
.reward-image-container(class=animate?'':'show')
img(src="/images/pages/play/level/modal/reward_icon_xp.png")
.reward-text= animate ? 'x0' : '+'+worth
.reward-text= animate ? '+0' : '+'+worth
if rewards.gems
.reward-panel.numerical(data-number=rewards.gems, data-number-unit='gem')
.reward-panel.numerical.gems(data-number=rewards.gems, data-number-unit='gem')
.reward-image-container(class=animate?'':'show')
img(src="/images/pages/play/level/modal/reward_icon_gems.png")
.reward-text= animate ? 'x0' : '+'+rewards.gems
.reward-text= animate ? '+0' : '+'+rewards.gems
if rewards.heroes
for hero in rewards.heroes
- var hero = thangTypes[hero];
.reward-panel
.reward-panel.hero
.reward-image-container(class=animate?'':'show')
img(src=hero.getPortraitURL())
.reward-text= hero.get('name')
@ -36,7 +36,7 @@ block modal-body-content
if rewards.items
for item in rewards.items
- var item = thangTypes[item];
.reward-panel
.reward-panel.item
.reward-image-container(class=animate?'':'show')
img(src=item.getPortraitURL())
.reward-text= item.get('name')
@ -52,3 +52,8 @@ block modal-footer-content
button.btn.btn-warning.hide#saving-progress-label(disabled, data-i18n="play_level.victory_saving_progress") Saving Progress
a.btn.btn-success.world-map-button.hide#continue-button(href="/play-hero", data-dismiss="modal", data-i18n="play_level.victory_play_continue") Continue
if me.get('anonymous')
p.sign-up-poke
button.btn.btn-success.sign-up-button.btn-large(data-toggle="coco-modal", data-target="modal/SignupModal", data-i18n="play_level.victory_sign_up") Sign Up to Save Progress
span(data-i18n="play_level.victory_sign_up_poke") Want to save your code? Create a free account!

View file

@ -25,6 +25,7 @@ module.exports = class HeroVictoryModal extends ModalView
@achievements = @supermodel.loadCollection(achievements, 'achievements').model
@listenToOnce @achievements, 'sync', @onAchievementsLoaded
@readyToContinue = false
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'victory'
onAchievementsLoaded: ->
thangTypeOriginals = []
@ -74,19 +75,27 @@ module.exports = class HeroVictoryModal extends ModalView
c.achievements = @achievements.models
# 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
#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')
# rewards = achievement.get('rewards')
# rewards.gems *= (index + 1)
c.thangTypes = @thangTypes
c.me = me
return c
afterRender: ->
super()
return unless @supermodel.finished()
@$el.addClass 'with-sign-up' if me.get('anonymous')
@updateSavingProgressStatus()
@$el.find('#victory-header').delay(250).queue(-> $(@).removeClass('out').dequeue())
complete = _.once(_.bind(@beginAnimateNumbers, @))
@animatedPanels = $()
panels = @$el.find('.achievement-panel')
@ -94,15 +103,15 @@ module.exports = class HeroVictoryModal extends ModalView
panel = $(panel)
continue unless panel.data('animate')
@animatedPanels = @animatedPanels.add(panel)
panel.delay(500)
panel.delay(500) # Waiting for victory header to show up and fall
panel.queue(->
$(this).addClass('earned') # animate out the grayscale
$(this).dequeue()
$(@).addClass('earned') # animate out the grayscale
$(@).dequeue()
)
panel.delay(500)
panel.queue(->
$(this).find('.reward-image-container').addClass('show')
$(this).dequeue()
$(@).find('.reward-image-container').addClass('show')
$(@).dequeue()
)
panel.delay(500)
panel.queue(-> complete())
@ -116,27 +125,59 @@ module.exports = class HeroVictoryModal extends ModalView
unit: $(panel).data('number-unit')
})
# TODO: mess with this more later. Doesn't seem to work, often times will pulse background red rather than animate
# itemPanel.rootEl.find('.reward-image-container img').addClass('pulse') for itemPanel in @numericalItemPanels
@numberAnimationStart = new Date()
@totalXP = 0
@totalXP += panel.number for panel in @numericalItemPanels when panel.unit is 'xp'
@totalGems = 0
@totalGems += panel.number for panel in @numericalItemPanels when panel.unit is 'gem'
@gemEl = $('#gem-total')
@XPEl = $('#xp-total')
@numberAnimationInterval = setInterval(@tickNumberAnimation, 15 / 1000)
@totalXPAnimated = @totalGemsAnimated = @lastTotalXP = @lastTotalGems = 0
@numberAnimationStart = new Date()
@numberAnimationInterval = setInterval(@tickNumberAnimation, 1000 / 60)
tickNumberAnimation: =>
pct = Math.min(1, (new Date() - @numberAnimationStart) / 1500)
panel.textEl.text('+'+parseInt(panel.number*pct)) for panel in @numericalItemPanels
@XPEl.text('+'+parseInt(@totalXP * pct))
@gemEl.text('+'+parseInt(@totalGems * pct))
@endAnimateNumbers() if pct is 1
# TODO: make sure the animation pulses happen when the numbers go up and sounds play (up to a max speed)
return @endAnimateNumbers() unless panel = @numericalItemPanels[0]
duration = Math.log10(panel.number + 1) * 1000
ratio = @getEaseRatio (new Date() - @numberAnimationStart), duration
if panel.unit is 'xp'
totalXP = @totalXPAnimated + Math.floor(ratio * panel.number)
if totalXP isnt @lastTotalXP
panel.textEl.text('+' + totalXP)
@XPEl.text('+' + totalXP)
xpTrigger = 'xp-' + (totalXP % 6) # 6 xp sounds
Backbone.Mediator.publish 'audio-player:play-sound', trigger: xpTrigger, volume: 0.5 + ratio / 2
@lastTotalXP = totalXP
else
totalGems = @totalGemsAnimated + Math.floor(ratio * panel.number)
if totalGems isnt @lastTotalGems
panel.textEl.text('+' + totalGems)
@gemEl.text('+' + totalGems)
gemTrigger = 'gem-' + (parseInt(panel.number * ratio) % 4) # 4 gem sounds
Backbone.Mediator.publish 'audio-player:play-sound', trigger: gemTrigger, volume: 0.5 + ratio / 2
@lastTotalGems = totalGems
if ratio is 1
panel.rootEl.removeClass('animating').find('.reward-image-container img').removeClass('pulse')
@numberAnimationStart = new Date()
if panel.unit is 'xp'
@totalXPAnimated += panel.number
else
@totalGemsAnimated += panel.number
@numericalItemPanels.shift()
return
panel.rootEl.addClass('animating').find('.reward-image-container 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)
endAnimateNumbers: ->
@$el.find('.pulse').removeClass('pulse')
clearInterval(@numberAnimationInterval)
clearInterval @numberAnimationInterval
@animationComplete = true
@updateSavingProgressStatus()
@ -144,3 +185,9 @@ module.exports = class HeroVictoryModal extends ModalView
return unless @animationComplete
@$el.find('#saving-progress-label').toggleClass('hide', @readyToContinue)
@$el.find('#continue-button').toggleClass('hide', not @readyToContinue)
# TODO: award heroes/items and play an awesome sound when you get one
destroy: ->
clearInterval @numberAnimationInterval
super()

View file

@ -1,6 +1,5 @@
ProblemAlertView = require './ProblemAlertView'
Range = ace.require('ace/range').Range
UserCodeProblem = require 'models/UserCodeProblem'
module.exports = class Problem
annotation: null
@ -11,7 +10,6 @@ module.exports = class Problem
@buildAlertView() if withAlert
@buildMarkerRange() if isCast
Backbone.Mediator.publish("problem:problem-created", line:@annotation.row, text: @annotation.text) if application.isIPadApp
@saveUserCodeProblem() if isCast
destroy: ->
unless @alertView?.destroyed
@ -50,21 +48,3 @@ module.exports = class Problem
@ace.getSession().removeMarker @markerRange.id
@markerRange.start.detach()
@markerRange.end.detach()
saveUserCodeProblem: () ->
@userCodeProblem = new UserCodeProblem()
@userCodeProblem.set 'code', @aether.raw
if @aetherProblem.range
rawLines = @aether.raw.split '\n'
errorLines = rawLines.slice @aetherProblem.range[0].row, @aetherProblem.range[1].row + 1
@userCodeProblem.set 'codeSnippet', errorLines.join '\n'
@userCodeProblem.set 'errHint', @aetherProblem.hint if @aetherProblem.hint
@userCodeProblem.set 'errId', @aetherProblem.id if @aetherProblem.id
@userCodeProblem.set 'errLevel', @aetherProblem.level if @aetherProblem.level
@userCodeProblem.set 'errMessage', @aetherProblem.message if @aetherProblem.message
@userCodeProblem.set 'errRange', @aetherProblem.range if @aetherProblem.range
@userCodeProblem.set 'errType', @aetherProblem.type if @aetherProblem.type
@userCodeProblem.set 'language', @aether.language.id if @aether.language?.id
@userCodeProblem.set 'levelID', @levelID if @levelID
@userCodeProblem.save()
null

View file

@ -8,6 +8,7 @@ Problem = require './Problem'
SpellDebugView = require './SpellDebugView'
SpellToolbarView = require './SpellToolbarView'
LevelComponent = require 'models/LevelComponent'
UserCodeProblem = require 'models/UserCodeProblem'
module.exports = class SpellView extends CocoView
id: 'spell-view'
@ -63,6 +64,7 @@ module.exports = class SpellView extends CocoView
@listenTo(@session, 'change:multiplayer', @onMultiplayerChanged)
@spell = options.spell
@problems = []
@savedProblems = {} # Cache saved user code problems to prevent duplicates
@writable = false unless me.team in @spell.permissions.readwrite # TODO: make this do anything
@highlightCurrentLine = _.throttle @highlightCurrentLine, 100
$(window).on 'resize', @onWindowResize
@ -486,6 +488,7 @@ module.exports = class SpellView extends CocoView
continue if key = aetherProblem.userInfo?.key and key of seenProblemKeys
seenProblemKeys[key] = true if key
@problems.push problem = new Problem aether, aetherProblem, @ace, isCast and problemIndex is 0, isCast, @spell.levelID
@saveUserCodeProblem(aether, aetherProblem) if isCast
annotations.push problem.annotation if problem.annotation
@aceSession.setAnnotations annotations
@highlightCurrentLine aether.flow unless _.isEmpty aether.flow
@ -498,6 +501,29 @@ module.exports = class SpellView extends CocoView
Backbone.Mediator.publish 'tome:problems-updated', spell: @spell, problems: @problems, isCast: isCast
@ace.resize()
saveUserCodeProblem: (aether, aetherProblem) ->
# Skip duplicate problems
hashValue = aether.raw + aetherProblem.message
return if hashValue of @savedProblems
@savedProblems[hashValue] = true
# Save new problem
@userCodeProblem = new UserCodeProblem()
@userCodeProblem.set 'code', aether.raw
if aetherProblem.range
rawLines = aether.raw.split '\n'
errorLines = rawLines.slice aetherProblem.range[0].row, aetherProblem.range[1].row + 1
@userCodeProblem.set 'codeSnippet', errorLines.join '\n'
@userCodeProblem.set 'errHint', aetherProblem.hint if aetherProblem.hint
@userCodeProblem.set 'errId', aetherProblem.id if aetherProblem.id
@userCodeProblem.set 'errLevel', aetherProblem.level if aetherProblem.level
@userCodeProblem.set 'errMessage', aetherProblem.message if aetherProblem.message
@userCodeProblem.set 'errRange', aetherProblem.range if aetherProblem.range
@userCodeProblem.set 'errType', aetherProblem.type if aetherProblem.type
@userCodeProblem.set 'language', aether.language.id if aether.language?.id
@userCodeProblem.set 'levelID', @spell.levelID if @spell.levelID
@userCodeProblem.save()
null
# Autocast:
# Goes immediately if the code is a) changed and b) complete/valid and c) the cursor is at beginning or end of a line
# We originally thought it would: