Merge branch 'master' into production

This commit is contained in:
Nick Winter 2014-10-21 13:58:18 -07:00
commit 0a615fc24e
28 changed files with 190 additions and 61 deletions

View file

@ -614,7 +614,9 @@ module.exports = Lank = class Lank extends CocoClass
setDebug: (debug) ->
return unless @thang?.collides and @options.camera?
@addMark 'debug', @options.floatingLayer if debug
@marks.debug?.toggle debug
if d = @marks.debug
d.toggle debug
d.updatePosition()
addLabel: (name, style) ->
@labels[name] ?= new Label sprite: @, camera: @options.camera, layer: @options.textLayer, style: style

View file

@ -121,6 +121,7 @@ module.exports = LayerAdapter = class LayerAdapter extends CocoClass
@container.regY = e.surfaceViewport.y
if @transformStyle is LayerAdapter.TRANSFORM_SURFACE_TEXT
for child in @container.children
continue if child.skipScaling
child.scaleX *= change
child.scaleY *= change
@ -130,6 +131,7 @@ module.exports = LayerAdapter = class LayerAdapter extends CocoClass
@container.addChild children...
if @transformStyle is LayerAdapter.TRANSFORM_SURFACE_TEXT
for child in children
continue if child.skipScaling
child.scaleX /= @container.scaleX
child.scaleY /= @container.scaleY

View file

@ -34,12 +34,13 @@ module.exports = class Mark extends CocoClass
onLayerMadeSpriteSheet: ->
return unless @sprite
return @update() if @markLank
# need to update the mark display object manually...
# rebuild sprite for new sprite sheet
@sprite = null
@build()
@layer.addChild @sprite
@layer.updateLayerOrder()
# @updatePosition()
@update()
toggle: (to) ->
to = !!to

View file

@ -29,6 +29,6 @@ module.exports = class PointChooser extends CocoClass
updateShape: ->
sup = @options.camera.worldToSurface @point
@options.surfaceLayer.addChild @shape
@options.surfaceLayer.addChild @shape unless @shape.parent
@shape.x = sup.x
@shape.y = sup.y

View file

@ -59,10 +59,11 @@ module.exports = class RegionChooser extends CocoClass
updateShape: ->
rect = @options.camera.normalizeBounds([@firstPoint, @secondPoint])
@options.normalStage.removeChild @shape if @shape
@options.surfaceLayer.removeChild @shape if @shape
@shape = new createjs.Shape()
@shape.alpha = 0.5
@shape.mouseEnabled = false
@shape.graphics.beginFill('#fedcba').drawRect rect.x, rect.y, rect.width, rect.height
@shape.graphics.endFill()
@options.normalStage.addChild(@shape)
@shape.skipScaling = true
@options.surfaceLayer.addChild(@shape)

View file

@ -135,7 +135,7 @@ module.exports = Surface = class Surface extends CocoClass
@coordinateDisplay ?= new CoordinateDisplay camera: @camera, layer: @surfaceTextLayer if showCoordinates
hookUpChooseControls: ->
chooserOptions = stage: @normalStage, normalStage: @normalStage, camera: @camera, restrictRatio: @options.choosing is 'ratio-region'
chooserOptions = stage: @webGLStage, surfaceLayer: @surfaceTextLayer, camera: @camera, restrictRatio: @options.choosing is 'ratio-region'
klass = if @options.choosing is 'point' then PointChooser else RegionChooser
@chooser = new klass chooserOptions

View file

@ -152,6 +152,7 @@ module.exports = class GoalManager extends CocoClass
checkForInitialUserCodeProblems: ->
# There might have been some user code problems reported before the goal manager started listening.
return unless @world
for thang in @world.thangs when thang.isProgrammable
for message, problem of thang.publishedUserCodeProblems
@onUserCodeProblem {thang: thang, problem: problem}, 0

View file

@ -109,6 +109,7 @@ class CocoModel extends Backbone.Model
console.debug "Validation failed for #{@constructor.className}: '#{@get('name') or @}'."
for error in errors
console.debug "\t", error.dataPath, ':', error.message
console.trace()
return errors
save: (attrs, options) ->
@ -285,6 +286,7 @@ class CocoModel extends Backbone.Model
if schema.items and _.isArray data
sum += @populateI18N(value, schema.items, path+'/'+index) for value, index in data
@updateI18NCoverage()
sum
@getReferencedModel: (data, schema) ->
@ -333,6 +335,30 @@ class CocoModel extends Backbone.Model
error: ->
console.error 'Miserably failed to fetch unnotified achievements', arguments
CocoModel.pollAchievements = _.debounce CocoModel.pollAchievements, 500
CocoModel.pollAchievements = _.debounce CocoModel.pollAchievements, 500
#- Internationalization
updateI18NCoverage: ->
i18nObjects = @findI18NObjects()
console.log 'i18n objects', i18nObjects
langCodeArrays = (_.keys(i18n) for i18n in i18nObjects)
console.log 'lang code arrays', langCodeArrays
window.codes = langCodeArrays
@set('i18nCoverage', _.intersection(langCodeArrays...))
findI18NObjects: (data, results) ->
data ?= @attributes
results ?= []
if _.isPlainObject(data) or _.isArray(data)
for [key, value] in _.pairs data
if key is 'i18n'
results.push value
else if _.isPlainObject(value) or _.isArray(value)
@findI18NObjects(value, results)
return results
module.exports = CocoModel

View file

@ -256,6 +256,7 @@ c.extendSearchableProperties LevelSchema
c.extendVersionedProperties LevelSchema, 'level'
c.extendPermissionsProperties LevelSchema, 'level'
c.extendPatchableProperties LevelSchema
c.extendTranslationCoverageProperties LevelSchema
module.exports = LevelSchema

View file

@ -143,6 +143,10 @@ me.getLanguageCodeArray = ->
return Language.languageCodes
me.getLanguagesObject = -> return Language
me.extendTranslationCoverageProperties = (schema) ->
schema.properties = {} unless schema.properties?
schema.properties.i18nCoverage = { title: 'i18n Coverage', type: 'array', items: { type: 'string' }}
# OTHER

View file

@ -139,6 +139,10 @@ $stashWidth: $totalWidth - $equippedWidth - $stashMargin
&[data-slot="right-hand"] .placeholder
background-position: (-17 * $itemSlotInnerWidth) 0px
&[data-slot="flag"] .placeholder
//background-position: (-18 * $itemSlotInnerWidth) 0px
background-position: (-2 * $itemSlotInnerWidth) 0px
.item-container
position: absolute
left: 0

View file

@ -19,10 +19,11 @@
#victory-header
display: block
margin: 15px auto 0
@include transition(0.25s ease-in)
// http://easings.net/#easeOutBack plus tweaked a bit: http://cubic-bezier.com/#.18,.68,.75,2
@include transition(0.5s cubic-bezier(0.18, 0.68, 0.75, 2))
&.out
margin-top: -100px
@include scale(0)
.modal-header
height: 85px
@ -95,10 +96,10 @@
@include scale(1.5)
z-index: 2
.reward-text
font-size: 18px
overflow: visible
bottom: 9px
&.numerical &.animating .reward-text
font-size: 18px
overflow: visible
bottom: 9px
.reward-image-container
top: 8px
@ -113,6 +114,13 @@
&.show
@include scale(1)
&.pending-reward-image
img
-webkit-filter: brightness(2000%) contrast(25%)
-moz-filter: brightness(2000%) contrast(25%)
-o-filter: brightness(2000%) contrast(25%)
filter: brightness(2000%) contrast(25%)
img
margin: 0
position: absolute

View file

@ -9,7 +9,7 @@
left: 10px
right: 10px
background: transparent
border: 0
border: 1px solid transparent
padding: 0
text-shadow: none
color: white
@ -31,14 +31,14 @@
&:hover, &:focus
@include opacity(1)
.problem-hint
.problem-subtitle
font-size: 80%
//&.alert-error
&.alert-warning
border-image-source: url(/images/level/code_editor_warning_background.png)
&.alert-info
border-image-source: url(/images/level/code_editor_info_background.png)

View file

@ -8,7 +8,8 @@
.replace-me(data-item-id=equipment[slot].get('original'))
.item-slot-column.pull-left
for slot in ['minion', 'torso', 'gloves', 'left-hand', 'misc-0']
// TODO: add in 'misc-0' again somehow? Used to be where 'flag' is now.
for slot in ['minion', 'torso', 'gloves', 'left-hand', 'flag']
.item-slot(data-slot=slot)
.placeholder
.item-container

View file

@ -5,6 +5,11 @@ block modal-header-content
h3
span(data-i18n="general.version_history_for") Version History for:
|"#{dataList[0].name}"
p
|Select two changes below to see the difference.
div.delta-container
div.delta-view
block modal-body-content
if dataList
@ -25,7 +30,4 @@ block modal-body-content
td= data.creator
td #{data.commitMessage}
div.delta-container
div.delta-view
block modal-footer-content

View file

@ -15,31 +15,31 @@ block modal-body-content
- var worth = achievement.get('worth', true);
if worth
.reward-panel.numerical.xp(data-number=worth, data-number-unit='xp')
.reward-image-container(class=animate?'':'show')
.reward-image-container(class=animate ? 'pending-reward-image' : 'show')
img(src="/images/pages/play/level/modal/reward_icon_xp.png")
.reward-text= animate ? '+0' : '+'+worth
if rewards.gems
.reward-panel.numerical.gems(data-number=rewards.gems, data-number-unit='gem')
.reward-image-container(class=animate?'':'show')
.reward-image-container(class=animate ? 'pending-reward-image' : 'show')
img(src="/images/pages/play/level/modal/reward_icon_gems.png")
.reward-text= animate ? '+0' : '+'+rewards.gems
if rewards.heroes
for hero in rewards.heroes
- var hero = thangTypes[hero];
.reward-panel.hero
.reward-image-container(class=animate?'':'show')
.reward-panel.hero(data-hero-thang-type=hero.get('original'))
.reward-image-container(class=animate ? 'pending-reward-image' : 'show')
img(src=hero.getPortraitURL())
.reward-text= hero.get('name')
.reward-text= animate ? 'New Hero' : hero.get('name')
if rewards.items
for item in rewards.items
- var item = thangTypes[item];
.reward-panel.item
.reward-image-container(class=animate?'':'show')
.reward-panel.item(data-item-thang-type=item.get('original'))
.reward-image-container(class=animate ? 'pending-reward-image' : 'show')
img(src=item.getPortraitURL())
.reward-text= item.get('name')
.reward-text= animate ? 'New Item' : item.get('name')
block modal-footer-content
@ -60,7 +60,7 @@ block modal-footer-content
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
p.sign-up-poke.hide
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

@ -1,5 +1,7 @@
button.close(type="button", data-dismiss="alert") ×
span.problem-message!= message
if hint
span.problem-title!= hint
br
span.problem-hint!= hint
span.problem-subtitle!= message
else
span.problem-title!= message

View file

@ -370,7 +370,7 @@ module.exports = class ThangsTabView extends CocoView
@adjustThangPos @addThangLank, thang, pos
else
@addThangLank = null
@surface.lankBoss.reallyStopMoving = false
@surface?.lankBoss.reallyStopMoving = false
createEssentialComponents: (defaultComponents) ->
physicalConfig = {pos: {x: 10, y: 10, z: 1}}

View file

@ -10,7 +10,7 @@ module.exports = class InventoryView extends CocoView
id: 'inventory-view'
className: 'tab-pane'
template: template
slots: ['head', 'eyes', 'neck', 'torso', 'wrists', 'gloves', 'left-ring', 'right-ring', 'right-hand', 'left-hand', 'waist', 'feet', 'programming-book', 'pet', 'minion', 'misc-0', 'misc-1']
slots: ['head', 'eyes', 'neck', 'torso', 'wrists', 'gloves', 'left-ring', 'right-ring', 'right-hand', 'left-hand', 'waist', 'feet', 'programming-book', 'pet', 'minion', 'flag'] #, 'misc-0', 'misc-1'] # TODO: bring in misc slot(s) again when we have space
events:
'click .item-slot': 'onItemSlotClick'

View file

@ -7,6 +7,7 @@ LocalMongo = require 'lib/LocalMongo'
utils = require 'lib/utils'
ThangType = require 'models/ThangType'
LadderSubmissionView = require 'views/play/common/LadderSubmissionView'
AudioPlayer = require 'lib/AudioPlayer'
module.exports = class HeroVictoryModal extends ModalView
id: 'hero-victory-modal'
@ -45,7 +46,7 @@ module.exports = class HeroVictoryModal extends ModalView
for thangTypeOriginal in thangTypeOriginals
thangType = new ThangType()
thangType.url = "/db/thang.type/#{thangTypeOriginal}/version"
thangType.project = ['original', 'rasterIcon', 'name']
thangType.project = ['original', 'rasterIcon', 'name', 'soundTriggers']
@thangTypes[thangTypeOriginal] = @supermodel.loadModel(thangType, 'thang').model
if achievementIDs.length
@ -86,7 +87,7 @@ module.exports = class HeroVictoryModal extends ModalView
## achievement.completedAWhileAgo = index > 1
# achievement.completed = true
# achievement.completedAWhileAgo = false
# achievement.attributes.worth = (index + 1) * achievement.get('worth')
# achievement.attributes.worth = (index + 1) * achievement.get('worth', true)
# rewards = achievement.get('rewards')
# rewards.gems *= (index + 1)
@ -99,10 +100,14 @@ module.exports = class HeroVictoryModal extends ModalView
afterRender: ->
super()
return unless @supermodel.finished()
@playSelectionSound hero, true for original, hero of @thangTypes # Preload them
@$el.addClass 'with-sign-up' if me.get('anonymous')
@updateSavingProgressStatus()
@$el.find('#victory-header').delay(250).queue(-> $(@).removeClass('out').dequeue())
complete = _.once(_.bind(@beginAnimateNumbers, @))
@$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
)
complete = _.once(_.bind(@beginSequentialAnimations, @))
@animatedPanels = $()
panels = @$el.find('.achievement-panel')
for panel in panels
@ -126,29 +131,34 @@ module.exports = class HeroVictoryModal extends ModalView
@ladderSubmissionView = new LadderSubmissionView session: @session, level: @level
@insertSubView @ladderSubmissionView, @$el.find('.ladder-submission-view')
beginAnimateNumbers: ->
@numericalItemPanels = _.map(@animatedPanels.find('.numerical'), (panel) -> {
beginSequentialAnimations: ->
@sequentialAnimatedPanels = _.map(@animatedPanels.find('.reward-panel'), (panel) -> {
number: $(panel).data('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 @numericalItemPanels when panel.unit is 'xp'
@totalXP += panel.number for panel in @sequentialAnimatedPanels when panel.unit is 'xp'
@totalGems = 0
@totalGems += panel.number for panel in @numericalItemPanels when panel.unit is 'gem'
@totalGems += panel.number for panel in @sequentialAnimatedPanels when panel.unit is 'gem'
@gemEl = $('#gem-total')
@XPEl = $('#xp-total')
@totalXPAnimated = @totalGemsAnimated = @lastTotalXP = @lastTotalGems = 0
@numberAnimationStart = new Date()
@numberAnimationInterval = setInterval(@tickNumberAnimation, 1000 / 60)
@sequentialAnimationStart = new Date()
@sequentialAnimationInterval = setInterval(@tickSequentialAnimation, 1000 / 60)
tickNumberAnimation: =>
tickSequentialAnimation: =>
# 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.log(panel.number + 1) / Math.LN10 * 1000 # Math.log10 is ES6
ratio = @getEaseRatio (new Date() - @numberAnimationStart), duration
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(ratio * panel.number)
totalXP = @totalXPAnimated + newXP
@ -158,7 +168,7 @@ module.exports = class HeroVictoryModal extends ModalView
xpTrigger = 'xp-' + (totalXP % 6) # 6 xp sounds
Backbone.Mediator.publish 'audio-player:play-sound', trigger: xpTrigger, volume: 0.5 + ratio / 2
@lastTotalXP = totalXP
else
else if panel.unit is 'gem'
newGems = Math.floor(ratio * panel.number)
totalGems = @totalGemsAnimated + newGems
if totalGems isnt @lastTotalGems
@ -167,16 +177,24 @@ module.exports = class HeroVictoryModal extends ModalView
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
else if panel.item
thangType = @thangTypes[panel.item]
panel.textEl.text(thangType.get('name'))
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'item-unlocked', volume: 1 if 0.5 < ratio < 0.6
else if panel.hero
thangType = @thangTypes[panel.hero]
panel.textEl.text(thangType.get('name'))
@playSelectionSound hero if 0.5 < ratio < 0.6
if ratio is 1
panel.rootEl.removeClass('animating').find('.reward-image-container img').removeClass('pulse')
@numberAnimationStart = new Date()
@sequentialAnimationStart = new Date()
if panel.unit is 'xp'
@totalXPAnimated += panel.number
else
else if panel.unit is 'gem'
@totalGemsAnimated += panel.number
@numericalItemPanels.shift()
@sequentialAnimatedPanels.shift()
return
panel.rootEl.addClass('animating').find('.reward-image-container img').addClass('pulse')
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/
@ -187,8 +205,8 @@ module.exports = class HeroVictoryModal extends ModalView
--t
-0.5 * (t * (t - 2) - 1)
endAnimateNumbers: ->
clearInterval @numberAnimationInterval
endSequentialAnimations: ->
clearInterval @sequentialAnimationInterval
@animationComplete = true
@updateSavingProgressStatus()
@ -196,14 +214,23 @@ 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)
@$el.find('.sign-up-poke').toggleClass('hide', not @readyToContinue)
onGameSubmitted: (e) ->
ladderURL = "/play/ladder/#{@level.get('slug')}#my-matches"
Backbone.Mediator.publish 'router:navigate', route: ladderURL
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
# TODO: award heroes/items and play an awesome sound when you get one
destroy: ->
clearInterval @numberAnimationInterval
clearInterval @sequentialAnimationInterval
super()

View file

@ -15,7 +15,7 @@ module.exports = class PlayItemsModal extends ModalView
hands: ['right-hand', 'left-hand']
accessories: ['eyes', 'neck', 'left-ring', 'right-ring', 'waist']
minions: ['minion', 'pet']
misc: ['programming-book', 'misc-0', 'misc-1']
misc: ['programming-book', 'flag', 'misc-0', 'misc-1']
#events:
# 'change input.select': 'onSelectionChanged'

View file

@ -83,9 +83,11 @@ module.exports = class PlayLevelModal extends ModalView
aceConfig.language = codeLanguage
me.set 'aceConfig', aceConfig
if patchMe
console.log 'setting me.heroConfig to', lastHeroConfig
me.set 'heroConfig', lastHeroConfig
me.patch()
if patchSession
console.log 'setting session.heroConfig to', sessionHeroConfig
@options.session.set 'heroConfig', sessionHeroConfig
@options.session.patch success: callback unless skipSessionSave
else

View file

@ -11,6 +11,7 @@ LevelSchema.plugin(plugins.PermissionsPlugin)
LevelSchema.plugin(plugins.VersionedPlugin)
LevelSchema.plugin(plugins.SearchablePlugin, {searchable: ['name', 'description']})
LevelSchema.plugin(plugins.PatchablePlugin)
LevelSchema.plugin(plugins.TranslationCoveragePlugin)
LevelSchema.post 'init', (doc) ->
if _.isString(doc.get('nextLevel'))

View file

@ -9,5 +9,6 @@ ThangTypeSchema.plugin plugins.NamedPlugin
ThangTypeSchema.plugin plugins.VersionedPlugin
ThangTypeSchema.plugin plugins.SearchablePlugin, {searchable: ['name']}
ThangTypeSchema.plugin plugins.PatchablePlugin
ThangTypeSchema.plugin plugins.TranslationCoveragePlugin
module.exports = mongoose.model('thang.type', ThangTypeSchema)

View file

@ -35,6 +35,7 @@ ThangTypeHandler = class ThangTypeHandler extends Handler
'rasterIcon'
'featureImage'
'spriteType'
'i18nCoverage'
]
hasAccess: (req) ->

View file

@ -281,7 +281,7 @@ module.exports.SearchablePlugin = (schema, options) ->
searchable = options.searchable
unless searchable
throw Error('SearchablePlugin options must include list of searchable properties.')
throw new Error('SearchablePlugin options must include list of searchable properties.')
index = {}
@ -307,3 +307,19 @@ module.exports.SearchablePlugin = (schema, options) ->
@index = @getOwner() unless access
next()
module.exports.TranslationCoveragePlugin = (schema, options) ->
schema.uses_coco_translation_coverage = true
schema.set('autoIndex', true)
index = {}
if schema.uses_coco_versions
if not schema.uses_coco_names
throw Error('If using translation coverage and versioning, should also use names for indexing.')
index.slug = 1
index.i18nCoverage = 1
schema.index(index, {sparse: true, name: 'translation coverage index', background: true})

View file

@ -136,12 +136,14 @@ describe 'SegmentedSprite', ->
segmentedSprite.tick(500)
expect(segmentedSprite.baseMovieClip.currentFrame).toBe(6)
it 'emits animationend for animations where loops is false and there is no goesTo', ->
it 'emits animationend for animations where loops is false and there is no goesTo', (done) ->
fired = false
segmentedSprite.gotoAndPlay('onestep')
segmentedSprite.on('animationend', -> fired = true)
segmentedSprite.tick(1000)
expect(fired).toBe(true)
_.defer -> # because the event is deferred
expect(fired).toBe(true)
done()
it 'scales rendered animations like a MovieClip', ->
# build a movie clip, put it on top of the segmented sprite and make sure

View file

@ -150,3 +150,27 @@ describe 'CocoModel', ->
else return ready false
request.response {status:200, responseText: JSON.stringify me}
describe 'updateI18NCoverage', ->
class FlexibleClass extends CocoModel
@className: 'Flexible'
@schema: {}
it 'only includes languages for which all objects include a translation', ->
m = new FlexibleClass({
i18n: { es: {}, fr: {} }
prop1: 1
prop2: 'string'
prop3: true
innerObject: {
i18n: { es: {}, de: {}, fr: {} }
prop4: [
{
i18n: { es: {} }
}
]
}
})
m.updateI18NCoverage()
expect(JSON.stringify(m.get('i18nCoverage'))).toBe('["es"]')