diff --git a/app/lib/LevelOptions.coffee b/app/lib/LevelOptions.coffee index 4b82d8764..6e036418a 100644 --- a/app/lib/LevelOptions.coffee +++ b/app/lib/LevelOptions.coffee @@ -10,6 +10,7 @@ module.exports = LevelOptions = hidesRealTimePlayback: true requiredGear: {feet: 'simple-boots'} restrictedGear: {feet: 'leather-boots'} + requiredCode: ['moveRight'] 'gems-in-the-deep': disableSpaces: true hidesSubmitUntilRun: true @@ -71,8 +72,10 @@ module.exports = LevelOptions = hidesRealTimePlayback: true requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', waist: 'leather-belt'} restrictedGear: {feet: 'leather-boots'} + requiredCode: ['Brak'] 'favorable-odds': disableSpaces: true + hidesPlayButton: true hidesRunShortcut: true hidesHUD: true hidesSay: true @@ -82,6 +85,7 @@ module.exports = LevelOptions = restrictedGear: {feet: 'leather-boots'} 'the-raised-sword': disableSpaces: true + hidesPlayButton: true hidesRunShortcut: true hidesHUD: true hidesSay: true @@ -97,6 +101,7 @@ module.exports = LevelOptions = hidesRealTimePlayback: true requiredGear: {feet: 'simple-boots', 'programming-book': 'programmaticon-i'} restrictedGear: {feet: 'leather-boots'} + requiredCode: ['loop'] 'haunted-kithmaze': hidesRunShortcut: true hidesHUD: true @@ -105,6 +110,7 @@ module.exports = LevelOptions = hidesRealTimePlayback: true requiredGear: {feet: 'simple-boots', 'programming-book': 'programmaticon-i'} restrictedGear: {feet: 'leather-boots'} + requiredCode: ['loop'] 'descending-further': hidesHUD: true hidesSay: true @@ -140,6 +146,8 @@ module.exports = LevelOptions = hidesRealTimePlayback: true requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses', torso: 'leather-tunic'} restrictedGear: {feet: 'leather-boots'} + requiredCode: ['findNearestEnemy'] + suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}] 'lowly-kithmen': hidesHUD: true hidesSay: true @@ -147,6 +155,8 @@ module.exports = LevelOptions = hidesRealTimePlayback: true requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses', torso: 'leather-tunic'} restrictedGear: {feet: 'leather-boots'} + requiredCode: ['findNearestEnemy'] + suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}] 'closing-the-distance': hidesHUD: true hidesSay: true @@ -154,6 +164,7 @@ module.exports = LevelOptions = hidesRealTimePlayback: true requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic', eyes: 'crude-glasses'} restrictedGear: {feet: 'leather-boots'} + suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}] 'tactical-strike': hidesHUD: true hidesSay: true @@ -161,12 +172,14 @@ module.exports = LevelOptions = hidesRealTimePlayback: true requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic', eyes: 'crude-glasses'} restrictedGear: {feet: 'leather-boots'} + suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}] 'the-final-kithmaze': hidesHUD: true hidesSay: true hidesCodeToolbar: true hidesRealTimePlayback: true 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': hidesHUD: true hidesSay: true @@ -174,6 +187,7 @@ module.exports = LevelOptions = hidesRealTimePlayback: true requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'} restrictedGear: {feet: 'leather-boots'} + suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}] 'kithgard-gates': hidesSay: true hidesCodeToolbar: true diff --git a/app/lib/surface/CoordinateDisplay.coffee b/app/lib/surface/CoordinateDisplay.coffee index e9928ed60..5f4684884 100644 --- a/app/lib/surface/CoordinateDisplay.coffee +++ b/app/lib/surface/CoordinateDisplay.coffee @@ -6,6 +6,7 @@ module.exports = class CoordinateDisplay extends createjs.Container 'surface:mouse-over': 'onMouseOver' 'surface:stage-mouse-down': 'onMouseDown' 'camera:zoom-updated': 'onZoomUpdated' + 'level:flag-color-selected': 'onFlagColorSelected' constructor: (options) -> super() @@ -60,6 +61,9 @@ module.exports = class CoordinateDisplay extends createjs.Container @hide() @show() + onFlagColorSelected: (e) -> + @placingFlag = Boolean e.color + hide: -> return unless @label.parent @removeChild @label @@ -154,6 +158,6 @@ module.exports = class CoordinateDisplay extends createjs.Container @y = sup.y @addChild @background @addChild @label - @addChild @pointMarker + @addChild @pointMarker unless @placingFlag @updateCache() Backbone.Mediator.publish 'surface:coordinates-shown', {} diff --git a/app/lib/surface/Lank.coffee b/app/lib/surface/Lank.coffee index 731119d1c..4418ffbea 100644 --- a/app/lib/surface/Lank.coffee +++ b/app/lib/surface/Lank.coffee @@ -664,7 +664,9 @@ module.exports = Lank = class Lank extends CocoClass updateLabels: -> return unless @thang 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 @notifySpeechUpdated blurb: blurb label.update() for name, label of @labels diff --git a/app/models/CocoModel.coffee b/app/models/CocoModel.coffee index 4aeff828f..c6238d86b 100644 --- a/app/models/CocoModel.coffee +++ b/app/models/CocoModel.coffee @@ -50,6 +50,8 @@ class CocoModel extends Backbone.Model @loading = false @jqxhr = null @loadFromBackup() + + getCreationDate: -> new Date(parseInt(@id.slice(0,8), 16)*1000) getNormalizedURL: -> "#{@urlRoot}/#{@id}" diff --git a/app/schemas/subscriptions/tome.coffee b/app/schemas/subscriptions/tome.coffee index b4530a9a8..2d5d4a966 100644 --- a/app/schemas/subscriptions/tome.coffee +++ b/app/schemas/subscriptions/tome.coffee @@ -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: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'} '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']}, 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']}, winnable: {type: 'boolean'} diff --git a/app/schemas/subscriptions/world.coffee b/app/schemas/subscriptions/world.coffee index bd0b8c2ce..a3da038c3 100644 --- a/app/schemas/subscriptions/world.coffee +++ b/app/schemas/subscriptions/world.coffee @@ -9,6 +9,7 @@ module.exports = thang: {type: 'object'} killer: {type: 'object'} killerHealth: {type: ['number', 'undefined']} + maxHealth: {type: 'number'} 'world:thang-touched-goal': c.object {required: ['actor', 'touched']}, replacedNoteChain: {type: 'array'} diff --git a/app/styles/play/level.sass b/app/styles/play/level.sass index cad311dad..c5145b627 100644 --- a/app/styles/play/level.sass +++ b/app/styles/play/level.sass @@ -43,6 +43,13 @@ $level-resize-transition-time: 0.5s visibility: hidden #gold-view 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 left: 20% width: 60% diff --git a/app/styles/play/level/goals.sass b/app/styles/play/level/goals.sass index 310a1ee12..1d8e4a06f 100644 --- a/app/styles/play/level/goals.sass +++ b/app/styles/play/level/goals.sass @@ -12,11 +12,13 @@ padding: 19px 0px 2px 25px z-index: 3 font-size: 14px + min-width: 230px &.brighter font-size: 18px font-size: 1.4vw border-width: 0.91vw 1.22vw 3.10vw 0.91vw + min-width: 23vw .goals-status margin: 5px 0 0 0 diff --git a/app/styles/play/level/gold.sass b/app/styles/play/level/gold.sass index 370d9ec21..bc2331294 100644 --- a/app/styles/play/level/gold.sass +++ b/app/styles/play/level/gold.sass @@ -9,7 +9,7 @@ z-index: 6 @include transition(box-shadow .2s linear) @include user-select(none) - padding: 4px + padding: 0.4vw background: transparent url(/images/level/gold_background.png) no-repeat background-size: 100% 100% border-radius: 4px @@ -18,9 +18,9 @@ box-shadow: 2px 2px 2px black .team-gold - font-size: 18px + font-size: 1.4vw + line-height: 1.4vw margin: 0 - line-height: 20px color: hsla(205,0%,51%,1) display: inline-block padding: 0px 4px @@ -35,11 +35,12 @@ color: hsla(116,80%,51%,1) img - width: 16px - height: 16px + width: 1.2vw + height: 1.2vw border-radius: 2px - padding: 1px - margin-top: -1px + padding: 0.1vw + margin-top: -0.2vw + margin-right: 0.1vw .gold-amount display: inline-block diff --git a/app/styles/play/level/tome/spell_palette_entry.sass b/app/styles/play/level/tome/spell_palette_entry.sass index cb37378b5..b88b6bfb2 100644 --- a/app/styles/play/level/tome/spell_palette_entry.sass +++ b/app/styles/play/level/tome/spell_palette_entry.sass @@ -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) font-family: Menlo, Monaco, Consolas, "Courier New", monospace + font-variant: normal .popover-title background-color: transparent diff --git a/app/styles/play/modal/play-achievements-modal.sass b/app/styles/play/modal/play-achievements-modal.sass index 5f3652962..db553646e 100644 --- a/app/styles/play/modal/play-achievements-modal.sass +++ b/app/styles/play/modal/play-achievements-modal.sass @@ -1,4 +1,82 @@ #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 \ No newline at end of file diff --git a/app/templates/play/level/tome/problem_alert.jade b/app/templates/play/level/tome/problem_alert.jade index cc80b0a53..6951145b8 100644 --- a/app/templates/play/level/tome/problem_alert.jade +++ b/app/templates/play/level/tome/problem_alert.jade @@ -3,7 +3,6 @@ h3.problem-alert-title(data-i18n="play_level.problem_alert_title") Fix Your Code if hint span.problem-title!= hint br - br span.problem-subtitle!= message else span.problem-title!= message diff --git a/app/templates/play/level/tome/spell_palette_entry_popover.jade b/app/templates/play/level/tome/spell_palette_entry_popover.jade index c13df8394..bd10fc3dd 100644 --- a/app/templates/play/level/tome/spell_palette_entry_popover.jade +++ b/app/templates/play/level/tome/spell_palette_entry_popover.jade @@ -1,5 +1,5 @@ h4 - span= doc.shortName + span.prop-name= doc.shortName | - code.prop-type= doc.type == 'function' && doc.owner == 'this' ? 'method' : doc.type if doc.type != 'function' diff --git a/app/templates/play/modal/play-achievements-modal.jade b/app/templates/play/modal/play-achievements-modal.jade index 13d244310..b71a88d9d 100644 --- a/app/templates/play/modal/play-achievements-modal.jade +++ b/app/templates/play/modal/play-achievements-modal.jade @@ -4,4 +4,29 @@ block modal-header-content h3(data-i18n="play.achievements") Achievements 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 \ No newline at end of file diff --git a/app/templates/play/world-map-view.jade b/app/templates/play/world-map-view.jade index b56457b49..fc43175f7 100644 --- a/app/templates/play/world-map-view.jade +++ b/app/templates/play/world-map-view.jade @@ -43,10 +43,10 @@ .game-controls.header-font 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.achievements(data-toggle='coco-modal', data-target='play/modal/PlayAchievementsModal', data-i18n="[title]play.achievements") if me.get('anonymous') === false button.btn.gems(data-toggle='coco-modal', data-target='play/modal/BuyGemsModal', data-i18n="[title]play.buy_gems") 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.settings(data-toggle='coco-modal', data-target='play/modal/PlaySettingsModal', data-i18n="[title]play.settings") else if me.get('anonymous', true) diff --git a/app/views/game-menu/InventoryModal.coffee b/app/views/game-menu/InventoryModal.coffee index 4de79d40d..d83089468 100644 --- a/app/views/game-menu/InventoryModal.coffee +++ b/app/views/game-menu/InventoryModal.coffee @@ -86,7 +86,7 @@ module.exports = class InventoryModal extends ModalView locked = not (item.get('original') in me.items()) 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 else if locked and item.get('slug') isnt 'simple-boots' @itemGroups.lockedItems.add(item) diff --git a/app/views/play/WorldMapView.coffee b/app/views/play/WorldMapView.coffee index fc556515d..a6e45686c 100644 --- a/app/views/play/WorldMapView.coffee +++ b/app/views/play/WorldMapView.coffee @@ -102,7 +102,7 @@ module.exports = class WorldMapView extends RootView context.isIPadApp = application.isIPadApp context.mapType = _.string.slugify @terrain context.nextLevel = @nextLevel - context.forestIsAvailable = '541b67f71ccc8eaae19f3c62' in (me.get('earned')?.levels or []) + context.forestIsAvailable = @startedForestLevel or '541b67f71ccc8eaae19f3c62' in (me.get('earned')?.levels or []) context afterRender: -> @@ -131,8 +131,10 @@ module.exports = class WorldMapView extends RootView @openModalView authModal onSessionsLoaded: (e) -> + forestLevels = (f.id for f in forest) for session in @sessions.models @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' @nextLevel = null @render() @@ -771,3 +773,4 @@ if me.getKithmazeGroup() is 'the-first-kithmaze' _.remove dungeon, id: 'haunted-kithmaze' else _.remove dungeon, id: 'the-first-kithmaze' + _.find(dungeon, id: 'the-raised-sword').nextLevels.continue = 'haunted-kithmaze' diff --git a/app/views/play/level/modal/ReloadLevelModal.coffee b/app/views/play/level/modal/ReloadLevelModal.coffee index 320ecde21..d11bb4239 100644 --- a/app/views/play/level/modal/ReloadLevelModal.coffee +++ b/app/views/play/level/modal/ReloadLevelModal.coffee @@ -6,4 +6,10 @@ module.exports = class ReloadLevelModal extends ModalView template: template 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', {} diff --git a/app/views/play/level/tome/ProblemAlertView.coffee b/app/views/play/level/tome/ProblemAlertView.coffee index 907621365..24eb6b1c9 100644 --- a/app/views/play/level/tome/ProblemAlertView.coffee +++ b/app/views/play/level/tome/ProblemAlertView.coffee @@ -34,7 +34,7 @@ module.exports = class ProblemAlertView extends CocoView getRenderData: (context={}) -> context = super context if @problem? - format = (s) -> s?.replace(/</g, '<').replace(/>/g, '>').replace(/\n/g, '<br>') + format = (s) -> marked(s.replace(/</g, '<').replace(/>/g, '>')) if s? context.message = format @problem.aetherProblem.message context.hint = format @problem.aetherProblem.hint context diff --git a/app/views/play/level/tome/SpellView.coffee b/app/views/play/level/tome/SpellView.coffee index 986eb749b..c1429a400 100644 --- a/app/views/play/level/tome/SpellView.coffee +++ b/app/views/play/level/tome/SpellView.coffee @@ -394,7 +394,7 @@ module.exports = class SpellView extends CocoView @focus() if cast onCodeReload: (e) -> - return unless e.spell is @spell + return unless e.spell is @spell or not e.spell @reloadCode true @ace.clearSelection() _.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 @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 = => return if @eventsSuppressed Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'code-change', volume: 0.5 @@ -881,13 +882,26 @@ module.exports = class SpellView extends CocoView checkRequiredCode: => return if @destroyed 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 @warnedCodeFragments ?= {} unless @warnedCodeFragments[requiredCodeFragment] Backbone.Mediator.publish 'tome:required-code-fragment-deleted', codeFragment: requiredCodeFragment @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: -> $(@ace?.container).find('.ace_gutter').off 'click', '.ace_error, .ace_warning, .ace_info', @onAnnotationClick $(@ace?.container).find('.ace_gutter').off 'click', @onGutterClick @@ -900,11 +914,3 @@ module.exports = class SpellView extends CocoView @debugView?.destroy() $(window).off 'resize', @onWindowResize super() - - -requiredCodePerLevel = - 'dungeons-of-kithgard': ['moveRight'] - 'true-names': ['Brak'] - 'the-first-kithmaze': ['loop'] - 'haunted-kithmaze': ['loop'] - 'lowly-kithmen': ['findNearestEnemy'] diff --git a/app/views/play/modal/PlayAchievementsModal.coffee b/app/views/play/modal/PlayAchievementsModal.coffee index ca0940b88..360330685 100644 --- a/app/views/play/modal/PlayAchievementsModal.coffee +++ b/app/views/play/modal/PlayAchievementsModal.coffee @@ -2,27 +2,92 @@ ModalView = require 'views/kinds/ModalView' template = require 'templates/play/modal/play-achievements-modal' CocoCollection = require 'collections/CocoCollection' 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 className: 'modal fade play-modal' template: template modalWidthPercent: 90 id: 'play-achievements-modal' - #instant: true - - #events: - # 'change input.select': 'onSelectionChanged' + plain: true + + earnedMap: {} constructor: (options) -> super options - #@achievements = new CocoCollection([], {model: Achievement}) - #@achievements.url = '/db/thang.type?view=achievements&project=name,description,components,original,rasterIcon' - #@supermodel.loadCollection(@achievements, 'achievements') + @achievements = new Backbone.Collection() + earnedMap = {} + + 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={}) -> context = super(context) - #context.achievements = @achievements.models + context.achievements = @achievements.models context afterRender: -> diff --git a/server/achievements/EarnedAchievement.coffee b/server/achievements/EarnedAchievement.coffee index 685a502c6..80d6f6249 100644 --- a/server/achievements/EarnedAchievement.coffee +++ b/server/achievements/EarnedAchievement.coffee @@ -2,12 +2,6 @@ mongoose = require 'mongoose' jsonschema = require '../../app/schemas/models/earned_achievement' EarnedAchievementSchema = new mongoose.Schema({ - created: - type: Date - default: Date.now - changed: - type: Date - default: Date.now notified: type: Boolean default: false diff --git a/server/achievements/earned_achievement_handler.coffee b/server/achievements/earned_achievement_handler.coffee index e39e1dd4c..7eb2c7437 100644 --- a/server/achievements/earned_achievement_handler.coffee +++ b/server/achievements/earned_achievement_handler.coffee @@ -16,7 +16,26 @@ class EarnedAchievementHandler extends Handler get: (req, res) -> 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) -> query = { user: req.user._id+''} diff --git a/server/commons/Handler.coffee b/server/commons/Handler.coffee index 15ecc3b11..b9a5d6f2e 100644 --- a/server/commons/Handler.coffee +++ b/server/commons/Handler.coffee @@ -129,6 +129,10 @@ module.exports = class Handler term = req.query.term matchedObjects = [] 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 filters.push {filter: {index: req.user.get('id')}} projection = null @@ -158,7 +162,14 @@ module.exports = class Handler else args = [filter.filter] 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 else if req.user?.isAdmin() # admins can send any sort of query down the wire