diff --git a/app/lib/Angel.coffee b/app/lib/Angel.coffee index 1c3137e85..4348b3999 100644 --- a/app/lib/Angel.coffee +++ b/app/lib/Angel.coffee @@ -39,7 +39,12 @@ module.exports = class Angel extends CocoClass # say: debugging stuff, usually off; log: important performance indicators, keep on say: (args...) -> #@log args... - log: (args...) -> console.info "|#{@shared.godNick}'s #{@nick}|", args... + log: -> + # console.info.apply is undefined in IE9, CofeeScript splats invocation won't work. + # http://stackoverflow.com/questions/5472938/does-ie9-support-console-log-and-is-it-a-real-function + message = "|#{@shared.godNick}'s #{@nick}|" + message += " #{arg}" for arg in arguments + console.info message testWorker: => return if @destroyed @@ -238,7 +243,13 @@ module.exports = class Angel extends CocoClass console?.profile? "World Generation #{(Math.random() * 1000).toFixed(0)}" if imitateIE9? work.t0 = now() work.testWorld = testWorld = new World work.userCodeMap + work.testWorld.levelSessionIDs = work.levelSessionIDs + work.testWorld.submissionCount = work.submissionCount + work.testWorld.flagHistory = work.flagHistory ? [] testWorld.loadFromLevel work.level + work.testWorld.preloading = work.preload + work.testWorld.headless = work.headless + work.testWorld.realTime = work.realTime if @shared.goalManager testGM = new GoalManager(testWorld) testGM.setGoals work.goals @@ -254,7 +265,7 @@ module.exports = class Angel extends CocoClass work.testWorld.goalManager.worldGenerationEnded() if work.testWorld.ended serialized = testWorld.serialize() window.BOX2D_ENABLED = false - World.deserialize serialized.serializedWorld, @shared.worldClassMap, @shared.lastSerializedWorldFrames, @finishBeholdingWorld(goalStates), serialized.startFrame, work.level, serialized.endFrame + World.deserialize serialized.serializedWorld, @shared.worldClassMap, @shared.lastSerializedWorldFrames, @finishBeholdingWorld(goalStates), serialized.startFrame, serialized.endFrame, work.level window.BOX2D_ENABLED = true @shared.lastSerializedWorldFrames = serialized.serializedWorld.frames diff --git a/app/lib/aether_utils.coffee b/app/lib/aether_utils.coffee index 8468bf29a..d3db579f9 100644 --- a/app/lib/aether_utils.coffee +++ b/app/lib/aether_utils.coffee @@ -51,3 +51,4 @@ functionParameters = initializeCentroids: [] update: [] getNearestEnemy: [] + die: [] diff --git a/app/styles/play/level/tome/spell_palette_entry.sass b/app/styles/play/level/tome/spell_palette_entry.sass index 62216d996..7ddba6bb9 100644 --- a/app/styles/play/level/tome/spell_palette_entry.sass +++ b/app/styles/play/level/tome/spell_palette_entry.sass @@ -49,11 +49,15 @@ body:not(.dialogue-view-active) .spell-palette-popover.popover right: 45% - min-width: 350px + min-width: 500px .spell-palette-popover.popover // Only those popovers which are our direct children (spell documentation) max-width: 600px + padding: 0 + border-image: url(/images/level/popover_background.png) 29 39 fill stretch + border-width: 15px 20px + @include box-shadow(0 0 0 #000) // Jiggle animation // TODO: consolidate with problem_alert.sass jiggle @@ -98,13 +102,8 @@ body:not(.dialogue-view-active) &:hover @include opacity(1) - padding: 0 - border-image: url(/images/level/popover_background.png) 29 39 fill stretch - border-width: 15px 20px - @include box-shadow(0 0 0 #000) - 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: Monaco, Menlo, Ubuntu Mono, Consolas, "source-code-pro", monospace !important font-variant: normal .popover-title @@ -129,6 +128,24 @@ body:not(.dialogue-view-active) &.right .arrow left: -3% + .docs-ace-container + padding: 2px 4px + border-radius: 4px + + &, .docs-ace + background-color: #f9f2f4 + font-size: 12px + font-family: Monaco, Menlo, Ubuntu Mono, Consolas, "source-code-pro", monospace !important + + .docs-ace + .ace_cursor, .ace_bracket + display: none + + code + color: black + font-family: Monaco, Menlo, Ubuntu Mono, Consolas, "source-code-pro", monospace !important + font-size: 12px + html.no-borderimage .spell-palette-popover.popover background: transparent url(/images/level/popover_background.png) 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 251d068ce..1271847b2 100644 --- a/app/templates/play/level/tome/spell_palette_entry_popover.jade +++ b/app/templates/play/level/tome/spell_palette_entry_popover.jade @@ -62,25 +62,28 @@ if !selectedMethod strong span(data-i18n="skill_docs.example") Example | : - div!= marked("```\n" + doc.example + "```") + .docs-ace-container + .docs-ace= doc.example else if doc.type == 'function' && argumentExamples.length p.example strong span(data-i18n="skill_docs.example") Example | : div - if language == 'javascript' - code= doc.owner + '.' + doc.name + '(' + argumentExamples.join(', ') + ');' - else if language == 'coffeescript' - code= doc.ownerName + (doc.ownerName == '@' ? '' : '.') + doc.name + ' ' + argumentExamples.join(', ') - else if language == 'python' - code= doc.ownerName + '.' + doc.name + '(' + argumentExamples.join(', ') + ')' - else if language == 'clojure' - code= '(.' + doc.name + ' ' + doc.ownerName + ' ' + argumentExamples.join(', ') + ')' - else if language == 'lua' - code= doc.ownerName + ':' + doc.name + '(' + argumentExamples.join(', ') + ')' - else if language == 'io' - code= (doc.ownerName == 'this' ? '' : doc.ownerName + ' ') + doc.name + '(' + argumentExamples.join(', ') + ')' + .docs-ace-container + .docs-ace + if language == 'javascript' + span= doc.owner + '.' + doc.name + '(' + argumentExamples.join(', ') + ');' + else if language == 'coffeescript' + span= doc.ownerName + (doc.ownerName == '@' ? '' : '.') + doc.name + ' ' + argumentExamples.join(', ') + else if language == 'python' + span= doc.ownerName + '.' + doc.name + '(' + argumentExamples.join(', ') + ')' + else if language == 'clojure' + span= '(.' + doc.name + ' ' + doc.ownerName + ' ' + argumentExamples.join(', ') + ')' + else if language == 'lua' + span= doc.ownerName + ':' + doc.name + '(' + argumentExamples.join(', ') + ')' + else if language == 'io' + span= (doc.ownerName == 'this' ? '' : doc.ownerName + ' ') + doc.name + '(' + argumentExamples.join(', ') + ')' if (doc.type != 'function' && doc.type != 'snippet') || doc.name == 'now' p.value diff --git a/app/views/editor/level/modals/GenerateTerrainModal.coffee b/app/views/editor/level/modals/GenerateTerrainModal.coffee index 56e67e5bc..100f3d0b6 100644 --- a/app/views/editor/level/modals/GenerateTerrainModal.coffee +++ b/app/views/editor/level/modals/GenerateTerrainModal.coffee @@ -15,6 +15,10 @@ clusters = { 'thangs': ['Tree 1', 'Tree 2', 'Tree 3', 'Tree 4'] 'margin': 0.5 } + 'tree_stands': { + 'thangs': ['Tree Stand 1', 'Tree Stand 2', 'Tree Stand 3', 'Tree Stand 4', 'Tree Stand 5', 'Tree Stand 6'] + 'margin': 3 + } 'shrubs': { 'thangs': ['Shrub 1', 'Shrub 2', 'Shrub 3'] 'margin': 0.5 @@ -165,10 +169,10 @@ presets = { 'grassy': { 'terrainName': 'Grass' 'type':'grassy' - 'borders':'trees' + 'borders':'tree_stands' 'borderNoise':1 - 'borderSize':0 - 'borderThickness':3 + 'borderSize':2 + 'borderThickness':2 'floors':'grass_floor' 'decorations': { 'hero': { diff --git a/app/views/play/WorldMapView.coffee b/app/views/play/WorldMapView.coffee index eac98c32a..79e6727ca 100644 --- a/app/views/play/WorldMapView.coffee +++ b/app/views/play/WorldMapView.coffee @@ -728,7 +728,6 @@ forest = [ description: 'Join forces with a new hero: Amara Arrowhead.' nextLevels: continue: 'swift-dagger' - disabled: not me.isAdmin() x: 64.37 y: 69.18 } @@ -740,7 +739,6 @@ forest = [ description: 'Deal damage from a distance with your new hero.' nextLevels: continue: 'shrapnel' - disabled: not me.isAdmin() x: 66 y: 75.61 } @@ -752,7 +750,6 @@ forest = [ description: 'Explore the explosive arts.' nextLevels: continue: 'coinucopia' - disabled: not me.isAdmin() x: 67 y: 81 } @@ -766,7 +763,6 @@ forest = [ description: 'Stand your ground against large ogres with a new hero: Ms. Hushbaum.' nextLevels: continue: 'touch-of-death' - disabled: not me.isAdmin() x: 64.37 y: 55.18 } @@ -778,7 +774,6 @@ forest = [ description: 'Learn your first spell to siphon life from your foes.' nextLevels: continue: 'bonemender' - disabled: not me.isAdmin() x: 65 y: 48 } @@ -790,7 +785,6 @@ forest = [ description: 'Cast regeneration on allied soldiers to withstand a siege.' nextLevels: continue: 'coinucopia' - disabled: not me.isAdmin() x: 66 y: 40 } diff --git a/app/views/play/level/modal/HeroVictoryModal.coffee b/app/views/play/level/modal/HeroVictoryModal.coffee index 6461c6210..84e77ef26 100644 --- a/app/views/play/level/modal/HeroVictoryModal.coffee +++ b/app/views/play/level/modal/HeroVictoryModal.coffee @@ -64,37 +64,24 @@ module.exports = class HeroVictoryModal extends ModalView thangType.project = ['original', 'rasterIcon', 'name', 'soundTriggers'] @thangTypes[thangTypeOriginal] = @supermodel.loadModel(thangType, 'thang').model - if achievementIDs.length - url = "/db/earned_achievement?view=get-by-achievement-ids&achievementIDs=#{achievementIDs.join(',')}" - earnedAchievements = new CocoCollection([], { - url: url - model: EarnedAchievement + @newEarnedAchievements = [] + for achievement in @achievements.models + continue unless achievement.completed + ea = new EarnedAchievement({ + collection: achievement.get('collection') + triggeredBy: @session.id + achievement: achievement.id }) - earnedAchievements.sizeShouldBe = achievementIDs.length - res = @supermodel.loadCollection(earnedAchievements, 'earned_achievements') - @earnedAchievements = res.model - @listenToOnce @earnedAchievements, 'sync', -> - @newEarnedAchievements = [] - recorded = (earned.get('achievement') for earned in @earnedAchievements.length) - for achievement in @achievements.models - continue unless achievement.completed - earnedObjects = [] - if achievement.id not in recorded - ea = new EarnedAchievement({ - collection: achievement.get('collection') - triggeredBy: @session.id - achievement: achievement.id - }) - ea.save() - @newEarnedAchievements.push ea - @listenToOnce ea, 'sync', -> - if _.all((ea.id for ea in @newEarnedAchievements)) - @listenToOnce me, 'sync', -> - @readyToContinue = true - @updateSavingProgressStatus() - me.fetch() unless me.loading - else - @readyToContinue = true + ea.save() + @newEarnedAchievements.push ea + @listenToOnce ea, 'sync', -> + if _.all((ea.id for ea in @newEarnedAchievements)) + @listenToOnce me, 'sync', -> + @readyToContinue = true + @updateSavingProgressStatus() + me.fetch() unless me.loading + + @readyToContinue = true if not @achievements.models.length getRenderData: -> c = super() diff --git a/app/views/play/level/tome/SpellPaletteEntryView.coffee b/app/views/play/level/tome/SpellPaletteEntryView.coffee index 80217af28..147e4024d 100644 --- a/app/views/play/level/tome/SpellPaletteEntryView.coffee +++ b/app/views/play/level/tome/SpellPaletteEntryView.coffee @@ -3,6 +3,7 @@ template = require 'templates/play/level/tome/spell_palette_entry' {me} = require 'lib/auth' filters = require 'lib/image_filter' DocFormatter = require './DocFormatter' +SpellView = require 'views/play/level/tome/SpellView' module.exports = class SpellPaletteEntryView extends CocoView tagName: 'div' # Could also try instead of
, but would need to adjust colors @@ -28,6 +29,7 @@ module.exports = class SpellPaletteEntryView extends CocoView @docFormatter = new DocFormatter options @doc = @docFormatter.doc @doc.initialHTML = @docFormatter.formatPopover() + @aceEditors = [] getRenderData: -> c = super() @@ -52,6 +54,31 @@ module.exports = class SpellPaletteEntryView extends CocoView Backbone.Mediator.publish 'audio-player:play-sound', trigger: "spell-palette-entry-open-#{soundIndex}", volume: 0.75 popover = @$el.data('bs.popover') popover?.$tip?.i18n() + codeLanguage = @options.language + oldEditor.destroy() for oldEditor in @aceEditors + @aceEditors = [] + aceEditors = @aceEditors + popover?.$tip?.find('.docs-ace').each -> + contents = $(@).text() + editor = ace.edit @ + editor.setOptions maxLines: Infinity + editor.setReadOnly true + editor.setTheme 'ace/theme/textmate' + editor.setShowPrintMargin false + editor.setShowFoldWidgets false + editor.setHighlightActiveLine false + editor.setHighlightActiveLine false + editor.setBehavioursEnabled false + editor.renderer.setShowGutter false + editor.setValue contents + editor.clearSelection() + session = editor.getSession() + session.setUseWorker false + session.setMode SpellView.editModes[codeLanguage] + session.setWrapLimitRange null + session.setUseWrapMode true + session.setNewLineMode 'unix' + aceEditors.push editor onMouseEnter: (e) -> # Make sure the doc has the updated Thang so it can regenerate its prop value @@ -121,4 +148,5 @@ module.exports = class SpellPaletteEntryView extends CocoView @togglePinned() if @popoverPinned @$el.popover 'destroy' @$el.off() + oldEditor.destroy() for oldEditor in @aceEditors super() diff --git a/app/views/play/level/tome/SpellView.coffee b/app/views/play/level/tome/SpellView.coffee index dc6c984d5..5e1fe8df2 100644 --- a/app/views/play/level/tome/SpellView.coffee +++ b/app/views/play/level/tome/SpellView.coffee @@ -20,7 +20,7 @@ module.exports = class SpellView extends CocoView eventsSuppressed: true writable: true - editModes: + @editModes: 'javascript': 'ace/mode/javascript' 'coffeescript': 'ace/mode/coffee' 'python': 'ace/mode/python' @@ -91,7 +91,7 @@ module.exports = class SpellView extends CocoView @aceSession = @ace.getSession() @aceDoc = @aceSession.getDocument() @aceSession.setUseWorker false - @aceSession.setMode @editModes[@spell.language] + @aceSession.setMode SpellView.editModes[@spell.language] @aceSession.setWrapLimitRange null @aceSession.setUseWrapMode true @aceSession.setNewLineMode 'unix' @@ -324,7 +324,7 @@ module.exports = class SpellView extends CocoView # window.zatannaInstance = @zatanna # window.snippetEntries = snippetEntries - lang = @editModes[e.language].substr 'ace/mode/'.length + lang = SpellView.editModes[e.language].substr 'ace/mode/'.length @zatanna.addSnippets snippetEntries, lang onMultiplayerChanged: -> @@ -904,8 +904,8 @@ module.exports = class SpellView extends CocoView onChangeLanguage: (e) -> return unless @spell.canWrite() - @aceSession.setMode @editModes[e.language] - @zatanna?.set 'language', @editModes[e.language].substr('ace/mode/') + @aceSession.setMode SpellView.editModes[e.language] + @zatanna?.set 'language', SpellView.editModes[e.language].substr('ace/mode/') wasDefault = @getSource() is @spell.originalSource @spell.setLanguage e.language @reloadCode true if wasDefault diff --git a/app/views/play/modal/PlayHeroesModal.coffee b/app/views/play/modal/PlayHeroesModal.coffee index ab7f66dab..3dec5461a 100644 --- a/app/views/play/modal/PlayHeroesModal.coffee +++ b/app/views/play/modal/PlayHeroesModal.coffee @@ -246,6 +246,7 @@ module.exports = class PlayHeroesModal extends ModalView heroEntry = @$el.find(".hero-item[data-hero-id='#{@visibleHero.get('original')}']") heroEntry.find('.hero-status-value').attr('data-i18n', 'play.available').i18n() heroEntry.removeClass 'locked purchasable' + @selectedHero = @visibleHero @rerenderFooter() Backbone.Mediator.publish 'store:hero-purchased', hero: @visibleHero, heroSlug: @visibleHero.get('slug') diff --git a/server/achievements/earned_achievement_handler.coffee b/server/achievements/earned_achievement_handler.coffee index 3b54add2c..19a3e2f4d 100644 --- a/server/achievements/earned_achievement_handler.coffee +++ b/server/achievements/earned_achievement_handler.coffee @@ -61,19 +61,45 @@ class EarnedAchievementHandler extends Handler EarnedAchievement.findOne q, (err, earned) -> callback(err, earned) }, (err, { achievement, trigger, earned } ) => return @sendDatabaseError(res, err) if err - if earned - return @sendSuccess(res, earned.toObject()) if not achievement return @sendNotFoundError(res, 'Could not find achievement.') - if not trigger + else if not trigger return @sendNotFoundError(res, 'Could not find trigger.') - if achievement.get('proportionalTo') + else if earned + achievementEarned = achievement.get('rewards') + actuallyEarned = earned.get('earnedRewards') + if not _.isEqual(achievementEarned, actuallyEarned) + earned.set('earnedRewards', achievementEarned) + earned.save((err) => + return @sendDatabaseError(res, err) if err + @upsertNonNumericRewards(req.user, achievement, (err) => + return @sendDatabaseError(res, err) if err + return @sendSuccess(res, earned.toObject()) + ) + ) + else + @upsertNonNumericRewards(req.user, achievement, (err) => + return @sendDatabaseError(res, err) if err + return @sendSuccess(res, earned.toObject()) + ) + else if achievement.get('proportionalTo') return @sendBadInputError(res, 'Cannot currently do this to repeatable docs...') - EarnedAchievement.createForAchievement(achievement, trigger, null, (earnedAchievementDoc) => - @sendCreated(res, earnedAchievementDoc.toObject()) - ) - + else + EarnedAchievement.createForAchievement(achievement, trigger, null, (earnedAchievementDoc) => + @sendCreated(res, earnedAchievementDoc.toObject()) + ) ) + + upsertNonNumericRewards: (user, achievement, done) -> + update = {} + for rewardType, rewards of achievement.get('rewards') ? {} + continue if rewardType is 'gems' + if rewards.length + update.$addToSet ?= {} + update.$addToSet["earned.#{rewardType}"] = $each: rewards + User.update {_id: user._id}, update, {}, (err, count) -> + log.error err if err? + done?(err) getByAchievementIDs: (req, res) -> query = { user: req.user._id+''}