Merge branch 'master' of github.com:codecombat/codecombat

This commit is contained in:
iAladdin 2014-11-20 07:43:04 +08:00
commit 038e8cf776
24 changed files with 292 additions and 48 deletions

View file

@ -10,6 +10,7 @@ module.exports = LevelOptions =
hidesRealTimePlayback: true hidesRealTimePlayback: true
requiredGear: {feet: 'simple-boots'} requiredGear: {feet: 'simple-boots'}
restrictedGear: {feet: 'leather-boots'} restrictedGear: {feet: 'leather-boots'}
requiredCode: ['moveRight']
'gems-in-the-deep': 'gems-in-the-deep':
disableSpaces: true disableSpaces: true
hidesSubmitUntilRun: true hidesSubmitUntilRun: true
@ -71,8 +72,10 @@ module.exports = LevelOptions =
hidesRealTimePlayback: true hidesRealTimePlayback: true
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', waist: 'leather-belt'} requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', waist: 'leather-belt'}
restrictedGear: {feet: 'leather-boots'} restrictedGear: {feet: 'leather-boots'}
requiredCode: ['Brak']
'favorable-odds': 'favorable-odds':
disableSpaces: true disableSpaces: true
hidesPlayButton: true
hidesRunShortcut: true hidesRunShortcut: true
hidesHUD: true hidesHUD: true
hidesSay: true hidesSay: true
@ -82,6 +85,7 @@ module.exports = LevelOptions =
restrictedGear: {feet: 'leather-boots'} restrictedGear: {feet: 'leather-boots'}
'the-raised-sword': 'the-raised-sword':
disableSpaces: true disableSpaces: true
hidesPlayButton: true
hidesRunShortcut: true hidesRunShortcut: true
hidesHUD: true hidesHUD: true
hidesSay: true hidesSay: true
@ -97,6 +101,7 @@ module.exports = LevelOptions =
hidesRealTimePlayback: true hidesRealTimePlayback: true
requiredGear: {feet: 'simple-boots', 'programming-book': 'programmaticon-i'} requiredGear: {feet: 'simple-boots', 'programming-book': 'programmaticon-i'}
restrictedGear: {feet: 'leather-boots'} restrictedGear: {feet: 'leather-boots'}
requiredCode: ['loop']
'haunted-kithmaze': 'haunted-kithmaze':
hidesRunShortcut: true hidesRunShortcut: true
hidesHUD: true hidesHUD: true
@ -105,6 +110,7 @@ module.exports = LevelOptions =
hidesRealTimePlayback: true hidesRealTimePlayback: true
requiredGear: {feet: 'simple-boots', 'programming-book': 'programmaticon-i'} requiredGear: {feet: 'simple-boots', 'programming-book': 'programmaticon-i'}
restrictedGear: {feet: 'leather-boots'} restrictedGear: {feet: 'leather-boots'}
requiredCode: ['loop']
'descending-further': 'descending-further':
hidesHUD: true hidesHUD: true
hidesSay: true hidesSay: true
@ -140,6 +146,8 @@ module.exports = LevelOptions =
hidesRealTimePlayback: true hidesRealTimePlayback: true
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses', torso: 'leather-tunic'} requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses', torso: 'leather-tunic'}
restrictedGear: {feet: 'leather-boots'} restrictedGear: {feet: 'leather-boots'}
requiredCode: ['findNearestEnemy']
suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
'lowly-kithmen': 'lowly-kithmen':
hidesHUD: true hidesHUD: true
hidesSay: true hidesSay: true
@ -147,6 +155,8 @@ module.exports = LevelOptions =
hidesRealTimePlayback: true hidesRealTimePlayback: true
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses', torso: 'leather-tunic'} requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses', torso: 'leather-tunic'}
restrictedGear: {feet: 'leather-boots'} restrictedGear: {feet: 'leather-boots'}
requiredCode: ['findNearestEnemy']
suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
'closing-the-distance': 'closing-the-distance':
hidesHUD: true hidesHUD: true
hidesSay: true hidesSay: true
@ -154,6 +164,7 @@ module.exports = LevelOptions =
hidesRealTimePlayback: true hidesRealTimePlayback: true
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic', eyes: 'crude-glasses'} requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic', eyes: 'crude-glasses'}
restrictedGear: {feet: 'leather-boots'} restrictedGear: {feet: 'leather-boots'}
suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
'tactical-strike': 'tactical-strike':
hidesHUD: true hidesHUD: true
hidesSay: true hidesSay: true
@ -161,12 +172,14 @@ module.exports = LevelOptions =
hidesRealTimePlayback: true hidesRealTimePlayback: true
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic', eyes: 'crude-glasses'} requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic', eyes: 'crude-glasses'}
restrictedGear: {feet: 'leather-boots'} restrictedGear: {feet: 'leather-boots'}
suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
'the-final-kithmaze': 'the-final-kithmaze':
hidesHUD: true hidesHUD: true
hidesSay: true hidesSay: true
hidesCodeToolbar: true hidesCodeToolbar: true
hidesRealTimePlayback: true hidesRealTimePlayback: true
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'} requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
'the-gauntlet': 'the-gauntlet':
hidesHUD: true hidesHUD: true
hidesSay: true hidesSay: true
@ -174,6 +187,7 @@ module.exports = LevelOptions =
hidesRealTimePlayback: true hidesRealTimePlayback: true
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'} requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
restrictedGear: {feet: 'leather-boots'} restrictedGear: {feet: 'leather-boots'}
suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
'kithgard-gates': 'kithgard-gates':
hidesSay: true hidesSay: true
hidesCodeToolbar: true hidesCodeToolbar: true

View file

@ -6,6 +6,7 @@ module.exports = class CoordinateDisplay extends createjs.Container
'surface:mouse-over': 'onMouseOver' 'surface:mouse-over': 'onMouseOver'
'surface:stage-mouse-down': 'onMouseDown' 'surface:stage-mouse-down': 'onMouseDown'
'camera:zoom-updated': 'onZoomUpdated' 'camera:zoom-updated': 'onZoomUpdated'
'level:flag-color-selected': 'onFlagColorSelected'
constructor: (options) -> constructor: (options) ->
super() super()
@ -60,6 +61,9 @@ module.exports = class CoordinateDisplay extends createjs.Container
@hide() @hide()
@show() @show()
onFlagColorSelected: (e) ->
@placingFlag = Boolean e.color
hide: -> hide: ->
return unless @label.parent return unless @label.parent
@removeChild @label @removeChild @label
@ -154,6 +158,6 @@ module.exports = class CoordinateDisplay extends createjs.Container
@y = sup.y @y = sup.y
@addChild @background @addChild @background
@addChild @label @addChild @label
@addChild @pointMarker @addChild @pointMarker unless @placingFlag
@updateCache() @updateCache()
Backbone.Mediator.publish 'surface:coordinates-shown', {} Backbone.Mediator.publish 'surface:coordinates-shown', {}

View file

@ -664,7 +664,9 @@ module.exports = Lank = class Lank extends CocoClass
updateLabels: -> updateLabels: ->
return unless @thang return unless @thang
blurb = if @thang.health <= 0 then null else @thang.sayMessage # Dead men tell no tales blurb = if @thang.health <= 0 then null else @thang.sayMessage # Dead men tell no tales
@addLabel 'say', Label.STYLE_SAY if blurb blurb = null if blurb in ['For Thoktar!', 'Bones!', 'Behead!', 'Destroy!', 'Die, humans!'] # Let's just hear, not see, these ones.
labelStyle = if /Hero Placeholder/.test(@thang.id) then Label.STYLE_DIALOGUE else Label.STYLE_SAY
@addLabel 'say', labelStyle if blurb
if @labels.say?.setText blurb if @labels.say?.setText blurb
@notifySpeechUpdated blurb: blurb @notifySpeechUpdated blurb: blurb
label.update() for name, label of @labels label.update() for name, label of @labels

View file

@ -50,6 +50,8 @@ class CocoModel extends Backbone.Model
@loading = false @loading = false
@jqxhr = null @jqxhr = null
@loadFromBackup() @loadFromBackup()
getCreationDate: -> new Date(parseInt(@id.slice(0,8), 16)*1000)
getNormalizedURL: -> "#{@urlRoot}/#{@id}" getNormalizedURL: -> "#{@urlRoot}/#{@id}"

View file

@ -35,7 +35,7 @@ module.exports =
'tome:toggle-spell-list': c.object {title: 'Toggle Spell List', description: 'Published when you toggle the dropdown for a thang\'s spells'} 'tome:toggle-spell-list': c.object {title: 'Toggle Spell List', description: 'Published when you toggle the dropdown for a thang\'s spells'}
'tome:reload-code': c.object {title: 'Reload Code', description: 'Published when you reset a spell to its original source', required: ['spell']}, 'tome:reload-code': c.object {title: 'Reload Code', description: 'Published when you reset a spell to its original source', required: []},
spell: {type: 'object'} spell: {type: 'object'}
'tome:palette-cleared': c.object {title: 'Palette Cleared', description: 'Published when the spell palette is about to be cleared and recreated.'}, 'tome:palette-cleared': c.object {title: 'Palette Cleared', description: 'Published when the spell palette is about to be cleared and recreated.'},
@ -122,6 +122,10 @@ module.exports =
'tome:required-code-fragment-deleted': c.object {title: 'Required Code Fragment Deleted', description: 'Published when a required code fragment is deleted from the sample code.', required: ['codeFragment']}, 'tome:required-code-fragment-deleted': c.object {title: 'Required Code Fragment Deleted', description: 'Published when a required code fragment is deleted from the sample code.', required: ['codeFragment']},
codeFragment: {type: 'string'} codeFragment: {type: 'string'}
'tome:suspect-code-fragment-added': c.object {title: 'Suspect Code Fragment Added', description: 'Published when a suspect code fragment is added to the sample code.', required: ['codeFragment']},
codeFragment: {type: 'string'}
codeLanguage: {type: 'string'}
'tome:winnability-updated': c.object {title: 'Winnability Updated', description: 'When we think we can now win (or can no longer win), we may want to emphasize the submit button versus the run button (or vice versa), so this fires when we get new goal states (even preloaded goal states) suggesting success or failure change.', required: ['winnable']}, 'tome:winnability-updated': c.object {title: 'Winnability Updated', description: 'When we think we can now win (or can no longer win), we may want to emphasize the submit button versus the run button (or vice versa), so this fires when we get new goal states (even preloaded goal states) suggesting success or failure change.', required: ['winnable']},
winnable: {type: 'boolean'} winnable: {type: 'boolean'}

View file

@ -9,6 +9,7 @@ module.exports =
thang: {type: 'object'} thang: {type: 'object'}
killer: {type: 'object'} killer: {type: 'object'}
killerHealth: {type: ['number', 'undefined']} killerHealth: {type: ['number', 'undefined']}
maxHealth: {type: 'number'}
'world:thang-touched-goal': c.object {required: ['actor', 'touched']}, 'world:thang-touched-goal': c.object {required: ['actor', 'touched']},
replacedNoteChain: {type: 'array'} replacedNoteChain: {type: 'array'}

View file

@ -43,6 +43,13 @@ $level-resize-transition-time: 0.5s
visibility: hidden visibility: hidden
#gold-view #gold-view
right: 1% right: 1%
@include box-shadow(-1px 1px 10px cyan)
.team-gold
font-size: 2vw
line-height: 2vw
img
width: 1.8vw
heighT: 1.8vw
#control-bar-view .title #control-bar-view .title
left: 20% left: 20%
width: 60% width: 60%

View file

@ -12,11 +12,13 @@
padding: 19px 0px 2px 25px padding: 19px 0px 2px 25px
z-index: 3 z-index: 3
font-size: 14px font-size: 14px
min-width: 230px
&.brighter &.brighter
font-size: 18px font-size: 18px
font-size: 1.4vw font-size: 1.4vw
border-width: 0.91vw 1.22vw 3.10vw 0.91vw border-width: 0.91vw 1.22vw 3.10vw 0.91vw
min-width: 23vw
.goals-status .goals-status
margin: 5px 0 0 0 margin: 5px 0 0 0

View file

@ -9,7 +9,7 @@
z-index: 6 z-index: 6
@include transition(box-shadow .2s linear) @include transition(box-shadow .2s linear)
@include user-select(none) @include user-select(none)
padding: 4px padding: 0.4vw
background: transparent url(/images/level/gold_background.png) no-repeat background: transparent url(/images/level/gold_background.png) no-repeat
background-size: 100% 100% background-size: 100% 100%
border-radius: 4px border-radius: 4px
@ -18,9 +18,9 @@
box-shadow: 2px 2px 2px black box-shadow: 2px 2px 2px black
.team-gold .team-gold
font-size: 18px font-size: 1.4vw
line-height: 1.4vw
margin: 0 margin: 0
line-height: 20px
color: hsla(205,0%,51%,1) color: hsla(205,0%,51%,1)
display: inline-block display: inline-block
padding: 0px 4px padding: 0px 4px
@ -35,11 +35,12 @@
color: hsla(116,80%,51%,1) color: hsla(116,80%,51%,1)
img img
width: 16px width: 1.2vw
height: 16px height: 1.2vw
border-radius: 2px border-radius: 2px
padding: 1px padding: 0.1vw
margin-top: -1px margin-top: -0.2vw
margin-right: 0.1vw
.gold-amount .gold-amount
display: inline-block display: inline-block

View file

@ -80,6 +80,7 @@
h1:not(.not-code), h2:not(.not-code), h3:not(.not-code), h4:not(.not-code), h5:not(.not-code), h6:not(.not-code) h1:not(.not-code), h2:not(.not-code), h3:not(.not-code), h4:not(.not-code), h5:not(.not-code), h6:not(.not-code)
font-family: Menlo, Monaco, Consolas, "Courier New", monospace font-family: Menlo, Monaco, Consolas, "Courier New", monospace
font-variant: normal
.popover-title .popover-title
background-color: transparent background-color: transparent

View file

@ -1,4 +1,82 @@
#play-achievements-modal #play-achievements-modal
.achievement-view
color: black .modal-header
padding-bottom: 20px
img.icon
float: left
width: 40px
margin-right: 10px
//- Unachieved Panels
.panel
margin: 5px 0
position: relative
border: 2px solid rgb(75,75,75)
padding: 2px
h3
margin: 0 0 0 50px
color: rgb(75,75,75)
p
margin: 0 0 0 50px
color: rgb(75,75,75)
.panel-body
padding: 5px 150px 5px 5px
border: 2px solid rgb(150,150,150)
.created
position: absolute
right: 10px
top: 5px
color: rgb(75,75,75)
font-size: 12px
//- Achieved Panels
.panel.earned
background: rgb(50,40,33)
border: 5px solid rgb(26,21,17)
padding: 0
h3
color: white
p
color: rgb(203,170,148)
.panel-body
border: 2px solid rgb(75,62,51)
.created
color: rgb(255,189,68)
//- Rewards
.rewards
position: absolute
right: .2em
//bottom: 10px
top: 29px
.label
font-size: 18px
margin-left: 5px
color: rgb(50,40,33)
background: rgb(203,170,148)
.gems
background: #94ccc7
.worth
background: #d8c488
img
width: 12px
height: 12px

View file

@ -3,7 +3,6 @@ h3.problem-alert-title(data-i18n="play_level.problem_alert_title") Fix Your Code
if hint if hint
span.problem-title!= hint span.problem-title!= hint
br br
br
span.problem-subtitle!= message span.problem-subtitle!= message
else else
span.problem-title!= message span.problem-title!= message

View file

@ -1,5 +1,5 @@
h4 h4
span= doc.shortName span.prop-name= doc.shortName
| - | -
code.prop-type= doc.type == 'function' && doc.owner == 'this' ? 'method' : doc.type code.prop-type= doc.type == 'function' && doc.owner == 'this' ? 'method' : doc.type
if doc.type != 'function' if doc.type != 'function'

View file

@ -4,4 +4,29 @@ block modal-header-content
h3(data-i18n="play.achievements") Achievements h3(data-i18n="play.achievements") Achievements
block modal-body-content block modal-body-content
p TODO: show all dem achievements for achievement in achievements
.panel(class=achievement.earned ? 'earned' : '')
.panel-body
img.icon(src=achievement.getImageURL())
h3= achievement.name
p= achievement.description
if achievement.earnedDate
.created=moment(achievement.earnedDate).fromNow()
else
.created(data-i18n="user.status_unfinished")
.rewards
- rewards = achievement.get('rewards')
- rewards = { gems: 100 }
if rewards && rewards.gems
span.gems.label.label-default
span= rewards.gems
img.gem(src="/images/common/gem.png")
- worth = achievement.get('worth')
if worth
span.worth.label.label-default
span #{worth}xp
// maybe add more icons/numbers for items, heroes, levels, xp?
block modal-footer

View file

@ -43,10 +43,10 @@
.game-controls.header-font .game-controls.header-font
button.btn.items(data-toggle='coco-modal', data-target='play/modal/PlayItemsModal', data-i18n="[title]play.items") button.btn.items(data-toggle='coco-modal', data-target='play/modal/PlayItemsModal', data-i18n="[title]play.items")
button.btn.heroes(data-toggle='coco-modal', data-target='play/modal/PlayHeroesModal', data-i18n="[title]play.heroes") button.btn.heroes(data-toggle='coco-modal', data-target='play/modal/PlayHeroesModal', data-i18n="[title]play.heroes")
button.btn.achievements(data-toggle='coco-modal', data-target='play/modal/PlayAchievementsModal', data-i18n="[title]play.achievements")
if me.get('anonymous') === false if me.get('anonymous') === false
button.btn.gems(data-toggle='coco-modal', data-target='play/modal/BuyGemsModal', data-i18n="[title]play.buy_gems") button.btn.gems(data-toggle='coco-modal', data-target='play/modal/BuyGemsModal', data-i18n="[title]play.buy_gems")
if me.isAdmin() if me.isAdmin()
button.btn.achievements(data-toggle='coco-modal', data-target='play/modal/PlayAchievementsModal', data-i18n="[title]play.achievements")
button.btn.account(data-toggle='coco-modal', data-target='play/modal/PlayAccountModal', data-i18n="[title]play.account") button.btn.account(data-toggle='coco-modal', data-target='play/modal/PlayAccountModal', data-i18n="[title]play.account")
button.btn.settings(data-toggle='coco-modal', data-target='play/modal/PlaySettingsModal', data-i18n="[title]play.settings") button.btn.settings(data-toggle='coco-modal', data-target='play/modal/PlaySettingsModal', data-i18n="[title]play.settings")
else if me.get('anonymous', true) else if me.get('anonymous', true)

View file

@ -86,7 +86,7 @@ module.exports = class InventoryModal extends ModalView
locked = not (item.get('original') in me.items()) locked = not (item.get('original') in me.items())
locked = false if me.get('slug') is 'nick' locked = false if me.get('slug') is 'nick'
if not item.getFrontFacingStats().props.length and not _.size item.getFrontFacingStats().stats # Temp: while there are placeholder items if not item.getFrontFacingStats().props.length and not _.size(item.getFrontFacingStats().stats) and not locked # Temp: while there are placeholder items
null # Don't put into a collection null # Don't put into a collection
else if locked and item.get('slug') isnt 'simple-boots' else if locked and item.get('slug') isnt 'simple-boots'
@itemGroups.lockedItems.add(item) @itemGroups.lockedItems.add(item)

View file

@ -102,7 +102,7 @@ module.exports = class WorldMapView extends RootView
context.isIPadApp = application.isIPadApp context.isIPadApp = application.isIPadApp
context.mapType = _.string.slugify @terrain context.mapType = _.string.slugify @terrain
context.nextLevel = @nextLevel context.nextLevel = @nextLevel
context.forestIsAvailable = '541b67f71ccc8eaae19f3c62' in (me.get('earned')?.levels or []) context.forestIsAvailable = @startedForestLevel or '541b67f71ccc8eaae19f3c62' in (me.get('earned')?.levels or [])
context context
afterRender: -> afterRender: ->
@ -131,8 +131,10 @@ module.exports = class WorldMapView extends RootView
@openModalView authModal @openModalView authModal
onSessionsLoaded: (e) -> onSessionsLoaded: (e) ->
forestLevels = (f.id for f in forest)
for session in @sessions.models for session in @sessions.models
@levelStatusMap[session.get('levelID')] = if session.get('state')?.complete then 'complete' else 'started' @levelStatusMap[session.get('levelID')] = if session.get('state')?.complete then 'complete' else 'started'
@startedForestLevel = true if session.get('levelID') in forestLevels
if @nextLevel and @levelStatusMap[@nextLevel] is 'complete' if @nextLevel and @levelStatusMap[@nextLevel] is 'complete'
@nextLevel = null @nextLevel = null
@render() @render()
@ -771,3 +773,4 @@ if me.getKithmazeGroup() is 'the-first-kithmaze'
_.remove dungeon, id: 'haunted-kithmaze' _.remove dungeon, id: 'haunted-kithmaze'
else else
_.remove dungeon, id: 'the-first-kithmaze' _.remove dungeon, id: 'the-first-kithmaze'
_.find(dungeon, id: 'the-raised-sword').nextLevels.continue = 'haunted-kithmaze'

View file

@ -6,4 +6,10 @@ module.exports = class ReloadLevelModal extends ModalView
template: template template: template
events: events:
'click #restart-level-confirm-button': -> Backbone.Mediator.publish 'level:restart', {} 'click #restart-level-confirm-button': 'onClickRestart'
onClickRestart: (e) ->
if key.shift
Backbone.Mediator.publish 'level:restart', {}
else
Backbone.Mediator.publish 'tome:reload-code', {}

View file

@ -34,7 +34,7 @@ module.exports = class ProblemAlertView extends CocoView
getRenderData: (context={}) -> getRenderData: (context={}) ->
context = super context context = super context
if @problem? if @problem?
format = (s) -> s?.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br>') format = (s) -> marked(s.replace(/</g, '&lt;').replace(/>/g, '&gt;')) if s?
context.message = format @problem.aetherProblem.message context.message = format @problem.aetherProblem.message
context.hint = format @problem.aetherProblem.hint context.hint = format @problem.aetherProblem.hint
context context

View file

@ -394,7 +394,7 @@ module.exports = class SpellView extends CocoView
@focus() if cast @focus() if cast
onCodeReload: (e) -> onCodeReload: (e) ->
return unless e.spell is @spell return unless e.spell is @spell or not e.spell
@reloadCode true @reloadCode true
@ace.clearSelection() @ace.clearSelection()
_.delay (=> @ace?.clearSelection()), 500 # Make double sure this gets done (saw some timing issues?) _.delay (=> @ace?.clearSelection()), 500 # Make double sure this gets done (saw some timing issues?)
@ -450,7 +450,8 @@ module.exports = class SpellView extends CocoView
_.throttle @updateLines, 500 _.throttle @updateLines, 500
_.throttle @hideProblemAlert, 500 _.throttle @hideProblemAlert, 500
] ]
onSignificantChange.push _.debounce @checkRequiredCode, 1500 if requiredCodePerLevel[@options.level.get('slug')] onSignificantChange.push _.debounce @checkRequiredCode, 750 if LevelOptions[@options.level.get('slug')]?.requiredCode
onSignificantChange.push _.debounce @checkSuspectCode, 750 if LevelOptions[@options.level.get('slug')]?.suspectCode
@onCodeChangeMetaHandler = => @onCodeChangeMetaHandler = =>
return if @eventsSuppressed return if @eventsSuppressed
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'code-change', volume: 0.5 Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'code-change', volume: 0.5
@ -881,13 +882,26 @@ module.exports = class SpellView extends CocoView
checkRequiredCode: => checkRequiredCode: =>
return if @destroyed return if @destroyed
source = @getSource().replace @singleLineCommentRegex(), '' source = @getSource().replace @singleLineCommentRegex(), ''
for requiredCodeFragment in requiredCodePerLevel[@options.level.get('slug')] requiredCodeFragments = LevelOptions[@options.level.get('slug')].requiredCode
for requiredCodeFragment in requiredCodeFragments
# Could make this obey regular expressions like suspectCode if needed
if source.indexOf(requiredCodeFragment) is -1 if source.indexOf(requiredCodeFragment) is -1
@warnedCodeFragments ?= {} @warnedCodeFragments ?= {}
unless @warnedCodeFragments[requiredCodeFragment] unless @warnedCodeFragments[requiredCodeFragment]
Backbone.Mediator.publish 'tome:required-code-fragment-deleted', codeFragment: requiredCodeFragment Backbone.Mediator.publish 'tome:required-code-fragment-deleted', codeFragment: requiredCodeFragment
@warnedCodeFragments[requiredCodeFragment] = true @warnedCodeFragments[requiredCodeFragment] = true
checkSuspectCode: =>
return if @destroyed
source = @getSource().replace @singleLineCommentRegex(), ''
suspectCodeFragments = LevelOptions[@options.level.get('slug')].suspectCode
for suspectCodeFragment in suspectCodeFragments
if suspectCodeFragment.pattern.test source
@warnedCodeFragments ?= {}
unless @warnedCodeFragments[suspectCodeFragment.name]
Backbone.Mediator.publish 'tome:suspect-code-fragment-added', codeFragment: suspectCodeFragment.name, codeLanguage: @spell.language
@warnedCodeFragments[suspectCodeFragment] = true
destroy: -> destroy: ->
$(@ace?.container).find('.ace_gutter').off 'click', '.ace_error, .ace_warning, .ace_info', @onAnnotationClick $(@ace?.container).find('.ace_gutter').off 'click', '.ace_error, .ace_warning, .ace_info', @onAnnotationClick
$(@ace?.container).find('.ace_gutter').off 'click', @onGutterClick $(@ace?.container).find('.ace_gutter').off 'click', @onGutterClick
@ -900,11 +914,3 @@ module.exports = class SpellView extends CocoView
@debugView?.destroy() @debugView?.destroy()
$(window).off 'resize', @onWindowResize $(window).off 'resize', @onWindowResize
super() super()
requiredCodePerLevel =
'dungeons-of-kithgard': ['moveRight']
'true-names': ['Brak']
'the-first-kithmaze': ['loop']
'haunted-kithmaze': ['loop']
'lowly-kithmen': ['findNearestEnemy']

View file

@ -2,27 +2,92 @@ ModalView = require 'views/kinds/ModalView'
template = require 'templates/play/modal/play-achievements-modal' template = require 'templates/play/modal/play-achievements-modal'
CocoCollection = require 'collections/CocoCollection' CocoCollection = require 'collections/CocoCollection'
Achievement = require 'models/Achievement' Achievement = require 'models/Achievement'
#AchievementView = require 'views/game-menu/AchievementView' EarnedAchievement = require 'models/EarnedAchievement'
utils = require 'lib/utils'
PAGE_SIZE = 200
module.exports = class PlayAchievementsModal extends ModalView module.exports = class PlayAchievementsModal extends ModalView
className: 'modal fade play-modal' className: 'modal fade play-modal'
template: template template: template
modalWidthPercent: 90 modalWidthPercent: 90
id: 'play-achievements-modal' id: 'play-achievements-modal'
#instant: true plain: true
#events: earnedMap: {}
# 'change input.select': 'onSelectionChanged'
constructor: (options) -> constructor: (options) ->
super options super options
#@achievements = new CocoCollection([], {model: Achievement}) @achievements = new Backbone.Collection()
#@achievements.url = '/db/thang.type?view=achievements&project=name,description,components,original,rasterIcon' earnedMap = {}
#@supermodel.loadCollection(@achievements, 'achievements')
achievementsFetcher = new CocoCollection([], {url: '/db/achievement', model: Achievement})
achievementsFetcher.setProjection([
'name'
'description'
'icon'
'worth'
'i18n'
'rewards'
'collection'
])
earnedAchievementsFetcher = new CocoCollection([], {url: '/db/earned_achievement', model: EarnedAchievement})
earnedAchievementsFetcher.setProjection([ 'achievement' ])
achievementsFetcher.skip = 0
achievementsFetcher.fetch({data: {skip: 0, limit: PAGE_SIZE}})
earnedAchievementsFetcher.skip = 0
earnedAchievementsFetcher.fetch({data: {skip: 0, limit: PAGE_SIZE}})
@listenTo achievementsFetcher, 'sync', @onAchievementsLoaded
@listenTo earnedAchievementsFetcher, 'sync', @onEarnedAchievementsLoaded
@supermodel.loadCollection(achievementsFetcher, 'achievement')
@supermodel.loadCollection(earnedAchievementsFetcher, 'achievement')
@onEverythingLoaded = _.after(2, @onEverythingLoaded)
onAchievementsLoaded: (fetcher) ->
needMore = fetcher.models.length is PAGE_SIZE
@achievements.add(fetcher.models)
if needMore
fetcher.skip += PAGE_SIZE
fetcher.fetch({data: {skip: fetcher.skip, limit: PAGE_SIZE}})
else
@stopListening(fetcher)
@onEverythingLoaded()
onEarnedAchievementsLoaded: (fetcher) ->
needMore = fetcher.models.length is PAGE_SIZE
for earned in fetcher.models
@earnedMap[earned.get('achievement')] = earned
if needMore
fetcher.skip += PAGE_SIZE
fetcher.fetch({data: {skip: fetcher.skip, limit: PAGE_SIZE}})
else
@stopListening(fetcher)
@onEverythingLoaded()
onEverythingLoaded: =>
@achievements.set(@achievements.filter((m) -> m.get('collection') isnt 'level.sessions'))
for achievement in @achievements.models
if earned = @earnedMap[achievement.id]
achievement.earned = earned
achievement.earnedDate = earned.getCreationDate()
achievement.earnedDate ?= ''
@achievements.comparator = (m) -> m.earnedDate
@achievements.sort()
@achievements.set(@achievements.models.reverse())
for achievement in @achievements.models
achievement.name = utils.i18n achievement.attributes, 'name'
achievement.description = utils.i18n achievement.attributes, 'description'
@render()
getRenderData: (context={}) -> getRenderData: (context={}) ->
context = super(context) context = super(context)
#context.achievements = @achievements.models context.achievements = @achievements.models
context context
afterRender: -> afterRender: ->

View file

@ -2,12 +2,6 @@ mongoose = require 'mongoose'
jsonschema = require '../../app/schemas/models/earned_achievement' jsonschema = require '../../app/schemas/models/earned_achievement'
EarnedAchievementSchema = new mongoose.Schema({ EarnedAchievementSchema = new mongoose.Schema({
created:
type: Date
default: Date.now
changed:
type: Date
default: Date.now
notified: notified:
type: Boolean type: Boolean
default: false default: false

View file

@ -16,7 +16,26 @@ class EarnedAchievementHandler extends Handler
get: (req, res) -> get: (req, res) ->
return @getByAchievementIDs(req, res) if req.query.view is 'get-by-achievement-ids' return @getByAchievementIDs(req, res) if req.query.view is 'get-by-achievement-ids'
super(arguments...) query = { user: req.user._id+''}
projection = {}
if req.query.project
projection[field] = 1 for field in req.query.project.split(',')
q = EarnedAchievement.find(query, projection)
skip = parseInt(req.query.skip)
if skip? and skip < 1000000
q.skip(skip)
limit = parseInt(req.query.limit)
if limit? and limit < 1000
q.limit(limit)
q.exec (err, documents) =>
return @sendDatabaseError(res, err) if err
documents = (@formatEntity(req, doc) for doc in documents)
@sendSuccess(res, documents)
getByAchievementIDs: (req, res) -> getByAchievementIDs: (req, res) ->
query = { user: req.user._id+''} query = { user: req.user._id+''}

View file

@ -129,6 +129,10 @@ module.exports = class Handler
term = req.query.term term = req.query.term
matchedObjects = [] matchedObjects = []
filters = if @modelClass.schema.uses_coco_versions or @modelClass.schema.uses_coco_permissions then [filter: {index: true}] else [filter: {}] filters = if @modelClass.schema.uses_coco_versions or @modelClass.schema.uses_coco_permissions then [filter: {index: true}] else [filter: {}]
skip = parseInt(req.query.skip)
limit = parseInt(req.query.limit)
if @modelClass.schema.uses_coco_permissions and req.user if @modelClass.schema.uses_coco_permissions and req.user
filters.push {filter: {index: req.user.get('id')}} filters.push {filter: {index: req.user.get('id')}}
projection = null projection = null
@ -158,7 +162,14 @@ module.exports = class Handler
else else
args = [filter.filter] args = [filter.filter]
args.push projection if projection args.push projection if projection
@modelClass.find(args...).limit(FETCH_LIMIT).exec callback q = @modelClass.find(args...)
if skip? and skip < 1000000
q.skip(skip)
if limit? and limit < FETCH_LIMIT
q.limit(limit)
else
q.limit(FETCH_LIMIT)
q.exec callback
# if it's not a text search but the user is an admin, let him try stuff anyway # if it's not a text search but the user is an admin, let him try stuff anyway
else if req.user?.isAdmin() else if req.user?.isAdmin()
# admins can send any sort of query down the wire # admins can send any sort of query down the wire