This commit is contained in:
George Saines 2014-11-22 15:18:23 -08:00
commit 2875cb09a5
28 changed files with 429 additions and 196 deletions

View file

@ -6,8 +6,10 @@ CampaignList = require('views/play/WorldMapView').campaigns
options = options =
'default': 'default':
autocompleteFontSizePx: 16 autocompleteFontSizePx: 16
backspaceThrottle: false
'dungeon': 'dungeon':
autocompleteFontSizePx: 20 autocompleteFontSizePx: 20
backspaceThrottle: true
module.exports = CampaignOptions = module.exports = CampaignOptions =
getCampaignForSlug: (slug) -> getCampaignForSlug: (slug) ->

View file

@ -91,7 +91,7 @@ module.exports = LevelOptions =
hidesSay: true hidesSay: true
hidesCodeToolbar: true hidesCodeToolbar: true
hidesRealTimePlayback: true hidesRealTimePlayback: true
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic'} requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'tarnished-bronze-breastplate'}
restrictedGear: {feet: 'leather-boots'} restrictedGear: {feet: 'leather-boots'}
'the-first-kithmaze': 'the-first-kithmaze':
hidesRunShortcut: true hidesRunShortcut: true
@ -108,6 +108,7 @@ module.exports = LevelOptions =
hidesSay: true hidesSay: true
hidesCodeToolbar: true hidesCodeToolbar: true
hidesRealTimePlayback: true hidesRealTimePlayback: true
moveRightLoopSnippet: 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'] requiredCode: ['loop']
@ -123,6 +124,7 @@ module.exports = LevelOptions =
hidesSay: true hidesSay: true
hidesCodeToolbar: true hidesCodeToolbar: true
hidesRealTimePlayback: true hidesRealTimePlayback: true
moveRightLoopSnippet: 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'}
'dread-door': 'dread-door':
@ -137,14 +139,14 @@ module.exports = LevelOptions =
hidesSay: true hidesSay: true
hidesCodeToolbar: true hidesCodeToolbar: true
hidesRealTimePlayback: true hidesRealTimePlayback: true
requiredGear: {'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i', torso: 'leather-tunic'} requiredGear: {'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i', torso: 'tarnished-bronze-breastplate'}
restrictedGear: {feet: 'leather-boots'} restrictedGear: {feet: 'leather-boots'}
'master-of-names': 'master-of-names':
hidesHUD: true hidesHUD: true
hidesSay: true hidesSay: true
hidesCodeToolbar: true hidesCodeToolbar: true
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: 'tarnished-bronze-breastplate'}
restrictedGear: {feet: 'leather-boots'} restrictedGear: {feet: 'leather-boots'}
requiredCode: ['findNearestEnemy'] requiredCode: ['findNearestEnemy']
suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}] suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
@ -153,7 +155,7 @@ module.exports = LevelOptions =
hidesSay: true hidesSay: true
hidesCodeToolbar: true hidesCodeToolbar: true
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: 'tarnished-bronze-breastplate'}
restrictedGear: {feet: 'leather-boots'} restrictedGear: {feet: 'leather-boots'}
requiredCode: ['findNearestEnemy'] requiredCode: ['findNearestEnemy']
suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}] suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
@ -162,7 +164,7 @@ module.exports = LevelOptions =
hidesSay: true hidesSay: true
hidesCodeToolbar: true hidesCodeToolbar: true
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: 'tarnished-bronze-breastplate', eyes: 'crude-glasses'}
restrictedGear: {feet: 'leather-boots'} restrictedGear: {feet: 'leather-boots'}
suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}] suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
'tactical-strike': 'tactical-strike':
@ -170,7 +172,7 @@ module.exports = LevelOptions =
hidesSay: true hidesSay: true
hidesCodeToolbar: true hidesCodeToolbar: true
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: 'tarnished-bronze-breastplate', eyes: 'crude-glasses'}
restrictedGear: {feet: 'leather-boots'} restrictedGear: {feet: 'leather-boots'}
suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}] suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
'the-final-kithmaze': 'the-final-kithmaze':
@ -178,21 +180,21 @@ module.exports = LevelOptions =
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: 'tarnished-bronze-breastplate', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}] suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
'the-gauntlet': 'the-gauntlet':
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: 'tarnished-bronze-breastplate', '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}] suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
'kithgard-gates': 'kithgard-gates':
hidesSay: true hidesSay: true
hidesCodeToolbar: true hidesCodeToolbar: true
hidesRealTimePlayback: true hidesRealTimePlayback: true
requiredGear: {feet: 'simple-boots', 'right-hand': 'crude-builders-hammer', torso: 'leather-tunic'} requiredGear: {feet: 'simple-boots', 'right-hand': 'crude-builders-hammer', torso: 'tarnished-bronze-breastplate'}
restrictedGear: {'right-hand': 'simple-sword'} restrictedGear: {'right-hand': 'simple-sword'}
'defense-of-plainswood': 'defense-of-plainswood':
hidesRealTimePlayback: true hidesRealTimePlayback: true
@ -217,25 +219,51 @@ module.exports = LevelOptions =
hidesCodeToolbar: true hidesCodeToolbar: true
requiredGear: {feet: 'leather-boots', 'right-hand': 'crude-builders-hammer', 'programming-book': 'programmaticon-ii', eyes: 'crude-glasses'} requiredGear: {feet: 'leather-boots', 'right-hand': 'crude-builders-hammer', 'programming-book': 'programmaticon-ii', eyes: 'crude-glasses'}
restrictedGear: {feet: 'simple-boots', 'right-hand': 'simple-sword'} restrictedGear: {feet: 'simple-boots', 'right-hand': 'simple-sword'}
requiredCode: ['topEnemy']
'back-to-back': 'back-to-back':
hidesCodeToolbar: true hidesCodeToolbar: true
requiredGear: {feet: 'leather-boots', torso: 'leather-tunic', waist: 'leather-belt', 'programming-book': 'programmaticon-ii', eyes: 'crude-glasses', 'right-hand': 'simple-sword', 'left-hand': 'wooden-shield'} requiredGear: {feet: 'leather-boots', torso: 'tarnished-bronze-breastplate', waist: 'leather-belt', 'programming-book': 'programmaticon-ii', eyes: 'crude-glasses', 'right-hand': 'simple-sword', 'left-hand': 'wooden-shield'}
restrictedGear: {feet: 'simple-boots', 'right-hand': 'crude-builders-hammer'} restrictedGear: {feet: 'simple-boots', 'right-hand': 'crude-builders-hammer'}
'ogre-encampment': 'ogre-encampment':
requiredGear: {torso: 'leather-tunic', waist: 'leather-belt', 'programming-book': 'programmaticon-ii', eyes: 'crude-glasses', 'right-hand': 'simple-sword', 'left-hand': 'wooden-shield'} requiredGear: {torso: 'tarnished-bronze-breastplate', waist: 'leather-belt', 'programming-book': 'programmaticon-ii', eyes: 'crude-glasses', 'right-hand': 'simple-sword', 'left-hand': 'wooden-shield'}
restrictedGear: {feet: 'simple-boots', 'right-hand': 'crude-builders-hammer'} restrictedGear: {feet: 'simple-boots', 'right-hand': 'crude-builders-hammer'}
'woodland-cleaver': 'woodland-cleaver':
requiredGear: {torso: 'leather-tunic', waist: 'leather-belt', 'programming-book': 'programmaticon-ii', eyes: 'crude-glasses', 'right-hand': 'long-sword', 'left-hand': 'wooden-shield', wrists: 'sundial-wristwatch', feet: 'leather-boots'} requiredGear: {torso: 'tarnished-bronze-breastplate', waist: 'leather-belt', 'programming-book': 'programmaticon-ii', eyes: 'crude-glasses', 'right-hand': 'long-sword', 'left-hand': 'wooden-shield', wrists: 'sundial-wristwatch', feet: 'leather-boots'}
restrictedGear: {feet: 'simple-boots', 'right-hand': 'simple-sword'} restrictedGear: {feet: 'simple-boots', 'right-hand': 'simple-sword'}
'shield-rush': 'shield-rush':
requiredGear: {torso: 'leather-tunic', waist: 'leather-belt', 'programming-book': 'programmaticon-ii', eyes: 'crude-glasses', 'right-hand': 'long-sword', 'left-hand': 'bronze-shield', wrists: 'sundial-wristwatch'} requiredGear: {torso: 'tarnished-bronze-breastplate', waist: 'leather-belt', 'programming-book': 'programmaticon-ii', eyes: 'crude-glasses', 'right-hand': 'long-sword', 'left-hand': 'bronze-shield', wrists: 'sundial-wristwatch'}
restrictedGear: {'left-hand': 'wooden-shield'} restrictedGear: {'left-hand': 'wooden-shield'}
# Warrior branch
'peasant-protection': 'peasant-protection':
requiredGear: {torso: 'leather-tunic', waist: 'leather-belt', 'programming-book': 'programmaticon-ii', eyes: 'wooden-glasses', 'right-hand': 'long-sword', 'left-hand': 'bronze-shield', wrists: 'sundial-wristwatch'} requiredGear: {torso: 'tarnished-bronze-breastplate', waist: 'leather-belt', 'programming-book': 'programmaticon-ii', eyes: 'wooden-glasses', 'right-hand': 'long-sword', 'left-hand': 'bronze-shield', wrists: 'sundial-wristwatch'}
restrictedGear: {eyes: 'crude-glasses'} restrictedGear: {eyes: 'crude-glasses'}
'munchkin-swarm': 'munchkin-swarm':
requiredGear: {torso: 'leather-tunic', waist: 'leather-belt', 'programming-book': 'programmaticon-ii', eyes: 'wooden-glasses', 'right-hand': 'long-sword', 'left-hand': 'bronze-shield', wrists: 'sundial-wristwatch'} requiredGear: {torso: 'tarnished-bronze-breastplate', waist: 'leather-belt', 'programming-book': 'programmaticon-ii', eyes: 'wooden-glasses', 'right-hand': 'long-sword', 'left-hand': 'bronze-shield', wrists: 'sundial-wristwatch'}
restrictedGear: {} restrictedGear: {}
# Ranger branch
'munchkin-harvest':
requiredGear: {torso: 'tarnished-bronze-breastplate', waist: 'leather-belt', 'programming-book': 'programmaticon-ii', eyes: 'wooden-glasses', 'right-hand': 'long-sword', 'left-hand': 'bronze-shield', wrists: 'sundial-wristwatch'}
restrictedGear: {}
'swift-dagger':
requiredGear: {waist: 'leather-belt', 'programming-book': 'programmaticon-ii', eyes: 'wooden-glasses', 'right-hand': 'crude-crossbow', 'left-hand': 'crude-dagger', wrists: 'sundial-wristwatch'}
restrictedGear: {eyes: 'crude-glasses'}
'shrapnel':
requiredGear: {waist: 'leather-belt', 'programming-book': 'programmaticon-ii', eyes: 'wooden-glasses', 'right-hand': 'crude-crossbow', 'left-hand': 'weak-charge', wrists: 'sundial-wristwatch'}
restrictedGear: {eyes: 'crude-glasses', 'left-hand': 'crude-dagger'}
# Wizard branch
'arcane-ally':
requiredGear: {torso: 'tarnished-bronze-breastplate', waist: 'leather-belt', 'programming-book': 'programmaticon-ii', eyes: 'wooden-glasses', 'right-hand': 'long-sword', 'left-hand': 'bronze-shield', wrists: 'sundial-wristwatch'}
restrictedGear: {eyes: 'crude-glasses'}
'touch-of-death':
requiredGear: {waist: 'leather-belt', 'programming-book': 'programmaticon-ii', eyes: 'wooden-glasses', 'right-hand': 'enchanted-stick', 'left-hand': 'unholy-tome-i', wrists: 'sundial-wristwatch'}
restrictedGear: {}
'bonemender':
requiredGear: {waist: 'leather-belt', 'programming-book': 'programmaticon-ii', eyes: 'wooden-glasses', 'right-hand': 'enchanted-stick', 'left-hand': 'book-of-life-i', wrists: 'sundial-wristwatch'}
restrictedGear: {'left-hand': 'unholy-tome-i'}
'coinucopia': 'coinucopia':
requiredGear: {'programming-book': 'programmaticon-i', feet: 'leather-boots', 'programming-book': 'programmaticon-ii', flag: 'basic-flags'} requiredGear: {'programming-book': 'programmaticon-i', feet: 'leather-boots', 'programming-book': 'programmaticon-ii', flag: 'basic-flags'}
restrictedGear: {} restrictedGear: {}
@ -249,8 +277,11 @@ module.exports = LevelOptions =
requiredGear: {'programming-book': 'programmaticon-i', feet: 'leather-boots', 'programming-book': 'programmaticon-ii', flag: 'basic-flags', eyes: 'wooden-glasses', 'right-hand': 'crude-builders-hammer'} requiredGear: {'programming-book': 'programmaticon-i', feet: 'leather-boots', 'programming-book': 'programmaticon-ii', flag: 'basic-flags', eyes: 'wooden-glasses', 'right-hand': 'crude-builders-hammer'}
restrictedGear: {'right-hand': 'long-sword'} restrictedGear: {'right-hand': 'long-sword'}
'rich-forager': 'rich-forager':
requiredGear: {'programming-book': 'programmaticon-i', feet: 'leather-boots', 'programming-book': 'programmaticon-ii', flag: 'basic-flags', eyes: 'wooden-glasses', torso: 'leather-tunic', 'right-hand': 'long-sword', 'left-hand': 'bronze-shield'} requiredGear: {'programming-book': 'programmaticon-i', feet: 'leather-boots', 'programming-book': 'programmaticon-ii', flag: 'basic-flags', eyes: 'wooden-glasses', torso: 'tarnished-bronze-breastplate', 'right-hand': 'long-sword', 'left-hand': 'bronze-shield'}
restrictedGear: {'right-hand': 'crude-builders-hammer'} restrictedGear: {'right-hand': 'crude-builders-hammer'}
'multiplayer-treasure-grove': 'multiplayer-treasure-grove':
requiredGear: {'programming-book': 'programmaticon-i', feet: 'leather-boots', 'programming-book': 'programmaticon-ii', flag: 'basic-flags', eyes: 'wooden-glasses', torso: 'leather-tunic'} requiredGear: {'programming-book': 'programmaticon-i', feet: 'leather-boots', 'programming-book': 'programmaticon-ii', flag: 'basic-flags', eyes: 'wooden-glasses', torso: 'tarnished-bronze-breastplate'}
restrictedGear: {}
'siege-of-stonehold':
requiredGear: {}
restrictedGear: {} restrictedGear: {}

View file

@ -342,6 +342,7 @@ module.exports = Surface = class Surface extends CocoClass
@ended = true @ended = true
@setPaused true @setPaused true
Backbone.Mediator.publish 'surface:playback-ended', {} Backbone.Mediator.publish 'surface:playback-ended', {}
@updatePaths() # TODO: this is a hack to make sure paths are on the first time the level loads
else if @currentFrame < @world.totalFrames and @ended else if @currentFrame < @world.totalFrames and @ended
@ended = false @ended = false
@setPaused false @setPaused false
@ -586,7 +587,6 @@ module.exports = Surface = class Surface extends CocoClass
updatePaths: -> updatePaths: ->
return unless @options.paths and @heroLank return unless @options.paths and @heroLank
return unless me.isAdmin() # TODO: Fix world thang points, targets, then remove this
@hidePaths() @hidePaths()
return if @world.showPaths is 'never' return if @world.showPaths is 'never'
layerAdapter = @lankBoss.layerAdapters['Path'] layerAdapter = @lankBoss.layerAdapters['Path']

View file

@ -64,7 +64,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
next: "Выбрать" # Go from choose hero to choose inventory before playing a level next: "Выбрать" # Go from choose hero to choose inventory before playing a level
change_hero: "Выбрать героя" # Go back from choose inventory to choose hero change_hero: "Выбрать героя" # Go back from choose inventory to choose hero
choose_inventory: "Выбрать предметы" choose_inventory: "Выбрать предметы"
# buy_gems: "Buy Gems" buy_gems: "Купить самоцветы"
older_campaigns: "Старые кампании" older_campaigns: "Старые кампании"
anonymous: "Неизвестный игрок" anonymous: "Неизвестный игрок"
level_difficulty: "Сложность: " level_difficulty: "Сложность: "
@ -172,7 +172,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
medium: "Нормально" medium: "Нормально"
hard: "Сложно" hard: "Сложно"
player: "Игрок" player: "Игрок"
# player_level: "Level" # Like player level 5, not like level: Dungeons of Kithgard player_level: "Уровень" # Like player level 5, not like level: Dungeons of Kithgard
units: units:
second: "секунда" second: "секунда"
@ -315,17 +315,17 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
# equip: "Equip" # equip: "Equip"
# unequip: "Unequip" # unequip: "Unequip"
# buy_gems: buy_gems:
# few_gems: "A few gems" few_gems: "Немного самоцветов"
# pile_gems: "Pile of gems" pile_gems: "Кучка самоцветов"
# chest_gems: "Chest of gems" chest_gems: "Сундук с самоцветами"
choose_hero: choose_hero:
choose_hero: "Выберите героя" choose_hero: "Выберите героя"
programming_language: "Язык программирования" programming_language: "Язык программирования"
programming_language_description: "Какой язык программирования вы хотите использовать?" programming_language_description: "Какой язык программирования вы хотите использовать?"
# default: "Default" default: "По умолчанию"
# experimental: "Experimental" experimental: "Экспериментальный"
python_blurb: "Пусть простой, но мощный, Python - прекрасный язык программирования общего применения." python_blurb: "Пусть простой, но мощный, Python - прекрасный язык программирования общего применения."
javascript_blurb: "Язык для Сети." javascript_blurb: "Язык для Сети."
coffeescript_blurb: "Улучшенный синтаксис JavaScript." coffeescript_blurb: "Улучшенный синтаксис JavaScript."
@ -647,9 +647,9 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
diplomat_launch_url: "запуска в октябре" diplomat_launch_url: "запуска в октябре"
diplomat_introduction_suf: "было то, что есть значительная заинтересованность в CodeCombat в других странах! Мы создаём корпус переводчиков, стремящихся превратить один набор слов в другой набор слов для максимальной доступности CodeCombat по всему миру. Если вы любите видеть контент до официального выхода и получать эти уровни для ваших соотечественников как можно скорее, этот класс для вас." diplomat_introduction_suf: "было то, что есть значительная заинтересованность в CodeCombat в других странах! Мы создаём корпус переводчиков, стремящихся превратить один набор слов в другой набор слов для максимальной доступности CodeCombat по всему миру. Если вы любите видеть контент до официального выхода и получать эти уровни для ваших соотечественников как можно скорее, этот класс для вас."
diplomat_attribute_1: "Свободное владение английским языком и языком, на который вы хотели бы переводить. При передаче сложных идей важно иметь сильную хватку в обоих!" diplomat_attribute_1: "Свободное владение английским языком и языком, на который вы хотели бы переводить. При передаче сложных идей важно иметь сильную хватку в обоих!"
# diplomat_i18n_page_prefix: "You can start translating our levels by going to our" diplomat_i18n_page_prefix: "Вы можете начать переводить уровни, посетив нашу"
# diplomat_i18n_page: "translations page" diplomat_i18n_page: "страницу переводчиков"
# diplomat_i18n_page_suffix: ", or our interface and website on GitHub." diplomat_i18n_page_suffix: ", или перевести наш интерфейс и сайт на GitHub."
diplomat_join_pref_github: "Найдите файл локализации вашего языка " diplomat_join_pref_github: "Найдите файл локализации вашего языка "
diplomat_github_url: "на GitHub" diplomat_github_url: "на GitHub"
diplomat_join_suf_github: ", отредактируйте его онлайн и отправьте запрос на подтверждение изменений. Кроме того, установите флажок ниже, чтобы быть в курсе новых разработок интернационализации!" diplomat_join_suf_github: ", отредактируйте его онлайн и отправьте запрос на подтверждение изменений. Кроме того, установите флажок ниже, чтобы быть в курсе новых разработок интернационализации!"

View file

@ -8,6 +8,8 @@ module.exports =
'auth:logging-in-with-facebook': c.object {} 'auth:logging-in-with-facebook': c.object {}
'auth:signed-up': c.object {}
'auth:logging-out': c.object {} 'auth:logging-out': c.object {}
'auth:logged-in-with-facebook': c.object {title: 'Facebook logged in', description: 'Published when you successfully logged in with Facebook', required: ['response']}, 'auth:logged-in-with-facebook': c.object {title: 'Facebook logged in', description: 'Published when you successfully logged in with Facebook', required: ['response']},

View file

@ -29,6 +29,9 @@ module.exports =
'modal:closed': c.object {} 'modal:closed': c.object {}
'modal:open-modal-view': c.object {required: ['modalPath']},
modalPath: {type: 'string'}
'router:navigate': c.object {required: ['route']}, 'router:navigate': c.object {required: ['route']},
route: {type: 'string'} route: {type: 'string'}
view: {type: 'object'} view: {type: 'object'}
@ -49,11 +52,15 @@ module.exports =
progress: {type: 'number', minimum: 0, maximum: 1} progress: {type: 'number', minimum: 0, maximum: 1}
'buy-gems-modal:update-products': { } 'buy-gems-modal:update-products': { }
'buy-gems-modal:purchase-initiated': c.object {required: ['productID']}, 'buy-gems-modal:purchase-initiated': c.object {required: ['productID']},
productID: { type: 'string' } productID: { type: 'string' }
'stripe:received-token': c.object { required: ['token'] }, 'stripe:received-token': c.object { required: ['token'] },
token: { type: 'object', properties: { token: { type: 'object', properties: {
id: {type: 'string'} id: {type: 'string'}
}} }}
'store:item-purchased': c.object {required: ['item', 'itemSlug']},
item: {type: 'object'}
itemSlug: {type: 'string'}

View file

@ -54,9 +54,12 @@
font-size: 18px font-size: 18px
line-height: 20px line-height: 20px
strong strong, a
color: #FFCCAA color: #FFCCAA
a
text-decoration: underline
.hud-hint .hud-hint
font-weight: normal font-weight: normal
color: #ddd color: #ddd

View file

@ -66,7 +66,7 @@
&.disabled &.disabled
@include opacity(0.8) @include opacity(0.8)
.ace_cursor, .executing, .ace_active-line, .ace_gutter-active-line .ace_cursor, .executing, .ace_active-line, .ace_gutter-active-line
@include opacity(0.2) @include opacity(0.1)
.ace_gutter .ace_gutter
background-color: transparent background-color: transparent

View file

@ -45,11 +45,15 @@
// color: rgb(197, 6, 11) // color: rgb(197, 6, 11)
color: rgb(243, 169, 49) color: rgb(243, 169, 49)
body:not(.dialogue-view-active)
.spell-palette-popover.popover
right: 45%
min-width: 350px
.spell-palette-popover.popover .spell-palette-popover.popover
// Only those popovers which are our direct children (spell documentation) // Only those popovers which are our direct children (spell documentation)
max-width: 600px max-width: 600px
right: 45%
min-width: 350px
&.pinned &.pinned
left: auto !important left: auto !important

View file

@ -49,11 +49,11 @@
span.cost span.cost
img(src="/images/common/gem.png", draggable="false") img(src="/images/common/gem.png", draggable="false")
span.big-font= item.get('gems') span.big-font= item.get('gems')
if item.equippable if item.unequippable
// Temp, while we only have Warriors: prevent them from buying non-Warrior stuff // Temp, while we only have Warriors: prevent them from buying non-Warrior stuff
button.btn.unlock-button.big-font(data-i18n="play.unlock", disabled=!item.affordable, data-item-id=item.id)
else
span.big-font.unequippable= item.get('heroClass') span.big-font.unequippable= item.get('heroClass')
else
button.btn.unlock-button.big-font(data-i18n="play.unlock", disabled=!item.affordable, data-item-id=item.id)
.clearfix .clearfix
#item-details-view #item-details-view

View file

@ -43,7 +43,7 @@
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") 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 || me.get('iosIdentifierForVendor') || isIPadApp
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.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")

View file

@ -289,5 +289,4 @@ module.exports.ItemThangTypeNode = ItemThangTypeNode = class ItemThangTypeNode e
processThangType: (thangType) -> processThangType: (thangType) ->
return unless itemComponent = _.find thangType.get('components'), {original: LevelComponent.ItemID} return unless itemComponent = _.find thangType.get('components'), {original: LevelComponent.ItemID}
return unless itemComponent.config?.slots?.length @constructor.thangTypes.push name: thangType.get('name'), original: thangType.get('original'), slots: itemComponent.config?.slots ? ['right-hand']
@constructor.thangTypes.push name: thangType.get('name'), original: thangType.get('original'), slots: itemComponent.config.slots

View file

@ -84,7 +84,7 @@ module.exports = class InventoryModal extends ModalView
# sort into one of the four groups # sort into one of the four groups
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) and not locked # 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
@ -247,6 +247,8 @@ module.exports = class InventoryModal extends ModalView
@delegateEvents() @delegateEvents()
@setUpDraggableEventsForAvailableEquipment() @setUpDraggableEventsForAvailableEquipment()
@itemDetailsView.setItem(item) @itemDetailsView.setItem(item)
Backbone.Mediator.publish 'store:item-purchased', item: item, itemSlug: item.get('slug')
else else
button.addClass('confirm').text($.i18n.t('play.confirm')) button.addClass('confirm').text($.i18n.t('play.confirm'))
@$el.one 'click', (e) -> @$el.one 'click', (e) ->
@ -378,15 +380,19 @@ module.exports = class InventoryModal extends ModalView
unless itemModel and heroClass in itemModel.classes unless itemModel and heroClass in itemModel.classes
console.log 'Unequipping', itemModel.get('heroClass'), 'item', itemModel.get('name'), 'from slot due to class restrictions.' console.log 'Unequipping', itemModel.get('heroClass'), 'item', itemModel.get('name'), 'from slot due to class restrictions.'
@unequipItemFromSlot @$el.find(".item-slot[data-slot='#{slot}']") @unequipItemFromSlot @$el.find(".item-slot[data-slot='#{slot}']")
delete equipment[slot]
for slot, item of restrictedGear for slot, item of restrictedGear
equipped = equipment[slot] equipped = equipment[slot]
if equipped and equipped is gear[restrictedGear[slot]] if equipped and equipped is gear[restrictedGear[slot]]
console.log 'Unequipping restricted item', restrictedGear[slot], 'for', slot, 'before level', @options.levelID console.log 'Unequipping restricted item', restrictedGear[slot], 'for', slot, 'before level', @options.levelID
@unequipItemFromSlot @$el.find(".item-slot[data-slot='#{slot}']") @unequipItemFromSlot @$el.find(".item-slot[data-slot='#{slot}']")
if heroClass is 'Warrior' delete equipment[slot]
if (heroClass is 'Warrior' or
(heroClass is 'Ranger' and @options.levelID in ['swift-dagger', 'shrapnel']) or
(heroClass is 'Wizard' and @options.levelID in ['touch-of-death', 'bonemender']))
# After they switch to a ranger or wizard, we stop being so finicky about gear. # After they switch to a ranger or wizard, we stop being so finicky about gear.
for slot, item of requiredGear for slot, item of requiredGear
#continue if item is 'leather-tunic' and inWorldMap and @options.levelID is 'the-raised-sword' # Don't tell them they need it until they need it in the level # ... when we make it so that you can buy it continue if item is 'tarnished-bronze-breastplate' and inWorldMap and @options.levelID is 'the-raised-sword' # Don't tell them they need it until they need it in the level
equipped = equipment[slot] equipped = equipment[slot]
continue if equipped and not ( continue if equipped and not (
(item is 'crude-builders-hammer' and equipped in [gear['simple-sword'], gear['long-sword'], gear['sharpened-sword'], gear['roughedge']]) or (item is 'crude-builders-hammer' and equipped in [gear['simple-sword'], gear['long-sword'], gear['sharpened-sword'], gear['roughedge']]) or
@ -471,10 +477,11 @@ module.exports = class InventoryModal extends ModalView
gear = gear =
'simple-boots': '53e237bf53457600003e3f05' 'simple-boots': '53e237bf53457600003e3f05'
'simple-sword': '53e218d853457600003e3ebe' 'simple-sword': '53e218d853457600003e3ebe'
'leather-tunic': '53e22eac53457600003e3efc' 'tarnished-bronze-breastplate': '53e22eac53457600003e3efc'
'leather-boots': '53e2384453457600003e3f07' 'leather-boots': '53e2384453457600003e3f07'
'leather-belt': '5437002a7beba4a82024a97d' 'leather-belt': '5437002a7beba4a82024a97d'
'programmaticon-i': '53e4108204c00d4607a89f78' 'programmaticon-i': '53e4108204c00d4607a89f78'
'programmaticon-ii': '546e25d99df4a17d0d449be1'
'crude-glasses': '53e238df53457600003e3f0b' 'crude-glasses': '53e238df53457600003e3f0b'
'crude-builders-hammer': '53f4e6e3d822c23505b74f42' 'crude-builders-hammer': '53f4e6e3d822c23505b74f42'
'long-sword': '544d7d1f8494308424f564a3' 'long-sword': '544d7d1f8494308424f564a3'
@ -484,3 +491,9 @@ gear =
'basic-flags': '545bacb41e649a4495f887da' 'basic-flags': '545bacb41e649a4495f887da'
'roughedge': '544d7d918494308424f564a7' 'roughedge': '544d7d918494308424f564a7'
'sharpened-sword': '544d7deb8494308424f564ab' 'sharpened-sword': '544d7deb8494308424f564ab'
'crude-crossbow': '544d7ffd8494308424f564c3'
'crude-dagger': '544d952b8494308424f56517'
'weak-charge': '544d957d8494308424f5651f'
'enchanted-stick': '544d87188494308424f564f1'
'unholy-tome-i': '546374bc3839c6e02811d308'
'book-of-life-i': '546375653839c6e02811d30b'

View file

@ -32,6 +32,7 @@ module.exports = class RootView extends CocoView
subscriptions: subscriptions:
'achievements:new': 'handleNewAchievements' 'achievements:new': 'handleNewAchievements'
'modal:open-modal-view': 'onOpenModalView'
showNewAchievement: (achievement, earnedAchievement) -> showNewAchievement: (achievement, earnedAchievement) ->
return if achievement.get('collection') is 'level.sessions' return if achievement.get('collection') is 'level.sessions'
@ -64,6 +65,10 @@ module.exports = class RootView extends CocoView
window.tracker?.trackEvent 'Homepage', Action: anchorText, ['Google Analytics'] if @id is 'home-view' and anchorText window.tracker?.trackEvent 'Homepage', Action: anchorText, ['Google Analytics'] if @id is 'home-view' and anchorText
@toggleModal e @toggleModal e
onOpenModalView: (e) ->
return console.error "Couldn't find modalPath #{e.modalPath}" unless e.modalPath and ModalClass = require e.modalPath
@openModalView new ModalClass {}
showLoading: ($el) -> showLoading: ($el) ->
$el ?= @$el.find('.main-content-area') $el ?= @$el.find('.main-content-area')
super($el) super($el)

View file

@ -90,6 +90,7 @@ module.exports = class AuthModal extends ModalView
userObject.emails.generalNews.enabled = subscribe userObject.emails.generalNews.enabled = subscribe
res = tv4.validateMultiple userObject, User.schema res = tv4.validateMultiple userObject, User.schema
return forms.applyErrorsToForm(@$el, res.errors) unless res.valid return forms.applyErrorsToForm(@$el, res.errors) unless res.valid
Backbone.Mediator.publish "auth:signed-up", {}
window.tracker?.trackEvent 'Finished Signup' window.tracker?.trackEvent 'Finished Signup'
@enableModalInProgress(@$el) @enableModalInProgress(@$el)
createUser userObject, null, window.nextLevelURL createUser userObject, null, window.nextLevelURL
@ -125,7 +126,7 @@ module.exports = class AuthModal extends ModalView
onClickGPlusLogin: -> onClickGPlusLogin: ->
step.done = false for step in @gplusAuthSteps step.done = false for step in @gplusAuthSteps
handler = application.gplusHandler handler = application.gplusHandler
@listenToOnce handler, 'logged-in', -> @listenToOnce handler, 'logged-in', ->
@gplusAuthSteps[0].done = true @gplusAuthSteps[0].done = true
@renderGPlusAuthChecklist() @renderGPlusAuthChecklist()
@ -141,7 +142,7 @@ module.exports = class AuthModal extends ModalView
@listenToOnce handler, 'logging-into-codecombat', -> @listenToOnce handler, 'logging-into-codecombat', ->
@gplusAuthSteps[3].done = true @gplusAuthSteps[3].done = true
@renderGPlusAuthChecklist() @renderGPlusAuthChecklist()
renderGPlusAuthChecklist: -> renderGPlusAuthChecklist: ->
template = require 'templates/modal/auth-modal-gplus-checklist' template = require 'templates/modal/auth-modal-gplus-checklist'
el = $(template({steps: @gplusAuthSteps})) el = $(template({steps: @gplusAuthSteps}))

View file

@ -40,7 +40,7 @@ module.exports = class WorldMapView extends RootView
@levelStatusMap = {} @levelStatusMap = {}
@levelPlayCountMap = {} @levelPlayCountMap = {}
@sessions = @supermodel.loadCollection(new LevelSessionsCollection(), 'your_sessions', null, 0).model @sessions = @supermodel.loadCollection(new LevelSessionsCollection(), 'your_sessions', null, 0).model
# Temporary attempt to make sure all earned rewards are accounted for. Figure out a better solution... # Temporary attempt to make sure all earned rewards are accounted for. Figure out a better solution...
@earnedAchievements = new CocoCollection([], {url: '/db/earned_achievement', model:EarnedAchievement, project: ['earnedRewards']}) @earnedAchievements = new CocoCollection([], {url: '/db/earned_achievement', model:EarnedAchievement, project: ['earnedRewards']})
@listenToOnce @earnedAchievements, 'sync', -> @listenToOnce @earnedAchievements, 'sync', ->
@ -56,7 +56,7 @@ module.exports = class WorldMapView extends RootView
earned[group].push(reward) earned[group].push(reward)
addedSomething = true addedSomething = true
@supermodel.loadCollection(@earnedAchievements, 'achievements') @supermodel.loadCollection(@earnedAchievements, 'achievements')
@listenToOnce @sessions, 'sync', @onSessionsLoaded @listenToOnce @sessions, 'sync', @onSessionsLoaded
@getLevelPlayCounts() @getLevelPlayCounts()
$(window).on 'resize', @onWindowResize $(window).on 'resize', @onWindowResize
@ -110,6 +110,7 @@ module.exports = class WorldMapView extends RootView
window.levelUnlocksNotWorking = true if level.locked and level.id is @nextLevel # Temporary window.levelUnlocksNotWorking = true if level.locked and level.id is @nextLevel # Temporary
level.locked = false if window.levelUnlocksNotWorking # Temporary; also possible in HeroVictoryModal level.locked = false if window.levelUnlocksNotWorking # Temporary; also possible in HeroVictoryModal
level.locked = false if @levelStatusMap[level.id] in ['started', 'complete'] level.locked = false if @levelStatusMap[level.id] in ['started', 'complete']
level.locked = false if me.get('slug') is 'nick'
level.disabled = false if @levelStatusMap[level.id] in ['started', 'complete'] level.disabled = false if @levelStatusMap[level.id] in ['started', 'complete']
level.color = 'rgb(255, 80, 60)' level.color = 'rgb(255, 80, 60)'
if level.practice if level.practice
@ -136,7 +137,7 @@ module.exports = class WorldMapView extends RootView
if levelID = @$el.find('.level.next').data('level-id') if levelID = @$el.find('.level.next').data('level-id')
@$levelInfo = @$el.find(".level-info-container[data-level-id=#{levelID}]").show() @$levelInfo = @$el.find(".level-info-container[data-level-id=#{levelID}]").show()
pos = @$el.find('.level.next').offset() pos = @$el.find('.level.next').offset()
@adjustLevelInfoPosition pageX: pos.left, pageY: pos.top + 250 @adjustLevelInfoPosition pageX: pos.left, pageY: pos.top
@manuallyPositionedLevelInfoID = levelID @manuallyPositionedLevelInfoID = levelID
afterInsert: -> afterInsert: ->
@ -203,6 +204,7 @@ module.exports = class WorldMapView extends RootView
@$levelInfo = @$el.find(".level-info-container[data-level-id=#{levelID}]").show() @$levelInfo = @$el.find(".level-info-container[data-level-id=#{levelID}]").show()
@adjustLevelInfoPosition e @adjustLevelInfoPosition e
@endHighlight() @endHighlight()
@manuallyPositionedLevelInfoID = false
onMouseLeaveLevel: (e) -> onMouseLeaveLevel: (e) ->
return if application.isIPadApp return if application.isIPadApp
@ -688,7 +690,9 @@ forest = [
continue: 'peasant-protection' continue: 'peasant-protection'
x: 58.54 x: 58.54
y: 66.73 y: 66.73
} }
# Warrior branch
{ {
name: 'Peasant Protection' name: 'Peasant Protection'
type: 'hero' type: 'hero'
@ -709,6 +713,77 @@ forest = [
x: 71.19 x: 71.19
y: 63.61 y: 63.61
} }
# Ranger branch
{
name: 'Munchkin Harvest'
type: 'hero'
id: 'munchkin-harvest'
description: 'Join forces with a new hero: Amara Arrowhead.'
nextLevels:
continue: 'swift-dagger'
disabled: not me.isAdmin()
x: 64.37
y: 69.18
}
{
name: 'Swift Dagger'
type: 'hero'
id: 'swift-dagger'
description: 'Deal damage from a distance with your new hero.'
nextLevels:
continue: 'shrapnel'
disabled: not me.isAdmin()
x: 66
y: 75.61
}
{
name: 'Shrapnel'
type: 'hero'
id: 'shrapnel'
description: 'Explore the explosive arts.'
nextLevels:
continue: 'coinucopia'
disabled: not me.isAdmin()
x: 67
y: 81
}
# Wizard branch
{
name: 'Arcane Ally'
type: 'hero'
id: 'arcane-ally'
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
}
{
name: 'Touch of Death'
type: 'hero'
id: 'touch-of-death'
description: 'Learn your first spell to siphon life from your foes.'
nextLevels:
continue: 'bonemender'
disabled: not me.isAdmin()
x: 65
y: 48
}
{
name: 'Bonemender'
type: 'hero'
id: 'bonemender'
description: 'Cast regeneration on allied soldiers to withstand a siege.'
nextLevels:
continue: 'coinucopia'
disabled: not me.isAdmin()
x: 66
y: 40
}
{ {
name: 'Coinucopia' name: 'Coinucopia'
type: 'hero' type: 'hero'
@ -759,6 +834,17 @@ forest = [
x: 77.54 x: 77.54
y: 25.94 y: 25.94
} }
{
name: 'Siege of Stonehold'
type: 'hero'
id: 'siege-of-stonehold'
description: 'Unlock the desert world, if you are strong enough to win this epic battle!'
#nextLevels:
# continue: ''
disabled: not me.isAdmin()
x: 77.54
y: 25.94
}
{ {
name: 'Multiplayer Treasure Grove' name: 'Multiplayer Treasure Grove'
type: 'hero-ladder' type: 'hero-ladder'

View file

@ -15,17 +15,22 @@ module.exports = class LevelDialogueView extends CocoView
events: events:
'click': 'onClick' 'click': 'onClick'
'click a': 'onClickLink'
onClick: (e) -> onClick: (e) ->
Backbone.Mediator.publish 'tome:focus-editor', {} Backbone.Mediator.publish 'tome:focus-editor', {}
onFrameChanged: (e) -> onClickLink: (e) ->
@timeProgress = e.progress route = $(e.target).attr('href')
@update() if route and /item-store/.test route
PlayItemsModal = require 'views/play/modal/PlayItemsModal'
@openModalView new PlayItemsModal supermodel: @supermodal
e.stopPropagation()
onSpriteDialogue: (e) -> onSpriteDialogue: (e) ->
return unless e.message return unless e.message
@$el.addClass 'active speaking' @$el.addClass 'active speaking'
$('body').addClass('dialogue-view-active')
@setMessage e.message, e.mood, e.responses @setMessage e.message, e.mood, e.responses
window.tracker?.trackEvent 'Heard Sprite', {message: e.message, label: e.message}, ['Google Analytics'] window.tracker?.trackEvent 'Heard Sprite', {message: e.message, label: e.message}, ['Google Analytics']
@ -35,6 +40,7 @@ module.exports = class LevelDialogueView extends CocoView
onSpriteClearDialogue: -> onSpriteClearDialogue: ->
@$el.removeClass 'active speaking' @$el.removeClass 'active speaking'
$('body').removeClass('dialogue-view-active')
setMessage: (message, mood, responses) -> setMessage: (message, mood, responses) ->
message = marked message message = marked message

View file

@ -56,6 +56,7 @@ module.exports = class LevelHUDView extends CocoView
setThang: (thang, thangType) -> setThang: (thang, thangType) ->
if not thang? and not @thang? then return if not thang? and not @thang? then return
if thang? and @thang? and thang.id is @thang.id then return if thang? and @thang? and thang.id is @thang.id then return
if thang? and @hidesHUD and thang.id isnt 'Hero Placeholder' then return # Don't let them find the names of their opponents this way
@thang = thang @thang = thang
@thangType = thangType @thangType = thangType
return unless @thang return unless @thang

View file

@ -78,6 +78,7 @@ module.exports = class PlayLevelView extends RootView
'real-time-multiplayer:left-game': 'onRealTimeMultiplayerLeftGame' 'real-time-multiplayer:left-game': 'onRealTimeMultiplayerLeftGame'
'real-time-multiplayer:manual-cast': 'onRealTimeMultiplayerCast' 'real-time-multiplayer:manual-cast': 'onRealTimeMultiplayerCast'
'ipad:memory-warning': 'onIPadMemoryWarning' 'ipad:memory-warning': 'onIPadMemoryWarning'
'store:item-purchased': 'onItemPurchased'
events: events:
'click #level-done-button': 'onDonePressed' 'click #level-done-button': 'onDonePressed'
@ -431,7 +432,6 @@ module.exports = class PlayLevelView extends RootView
application.tracker?.trackEvent 'Saw Victory', application.tracker?.trackEvent 'Saw Victory',
level: @level.get('name') level: @level.get('name')
label: @level.get('name') label: @level.get('name')
getDirectFirstGroup: me.getDirectFirstGroup()
application.tracker?.trackTiming victoryTime, 'Level Victory Time', @levelID, @levelID, 100 application.tracker?.trackTiming victoryTime, 'Level Victory Time', @levelID, @levelID, 100
showVictory: -> showVictory: ->
@ -906,3 +906,13 @@ module.exports = class PlayLevelView extends RootView
onIPadMemoryWarning: (e) -> onIPadMemoryWarning: (e) ->
@hasReceivedMemoryWarning = true @hasReceivedMemoryWarning = true
onItemPurchased: (e) ->
heroConfig = @session.get('heroConfig') ? {}
inventory = heroConfig.inventory ? {}
slot = e.item.getAllowedSlots()[0]
if slot and not inventory[slot]
# Open up the inventory modal so they can equip the new item
@setupManager?.destroy()
@setupManager = new LevelSetupManager({supermodel: @supermodel, levelID: @levelID, parent: @, session: @session, hadEverChosenHero: true})
@setupManager.open()

View file

@ -148,6 +148,7 @@ module.exports = class CastButtonView extends CocoView
onLeftRealTimeMultiplayerGame: (e) -> onLeftRealTimeMultiplayerGame: (e) ->
@inRealTimeMultiplayerSession = false @inRealTimeMultiplayerSession = false
# https://mixpanel.com/report/227350/segmentation/#action:segment,arb_event:'Saw%20Victory',bool_op:or,chart_type:bar,from_date:-9,segfilter:!((filter:(operand:!('Ogre%20Encampment'),operator:%3D%3D),property:level,selected_property_type:string,type:string),(property:castButtonTextGroup,selected_property_type:number,type:number)),segment_type:number,to_date:0,type:unique,unit:day
initButtonTextABTest: -> initButtonTextABTest: ->
return if me.isAdmin() return if me.isAdmin()
return unless $.i18n.lng() is 'en-US' return unless $.i18n.lng() is 'en-US'

View file

@ -37,10 +37,11 @@ module.exports = class SpellPaletteEntryView extends CocoView
afterRender: -> afterRender: ->
super() super()
@$el.addClass(@doc.type) @$el.addClass(@doc.type)
placement = -> if $('body').hasClass('dialogue-view-active') then 'top' else 'left'
@$el.popover( @$el.popover(
animation: false animation: false
html: true html: true
placement: 'left' placement: placement
trigger: 'manual' # Hover, until they click, which will then pin it until unclick. trigger: 'manual' # Hover, until they click, which will then pin it until unclick.
content: @docFormatter.formatPopover() content: @docFormatter.formatPopover()
container: 'body' container: 'body'

View file

@ -131,13 +131,12 @@ module.exports = class SpellView extends CocoView
addCommand addCommand
name: 'toggle-playing' name: 'toggle-playing'
bindKey: {win: 'Ctrl-P', mac: 'Command-P|Ctrl-P'} bindKey: {win: 'Ctrl-P', mac: 'Command-P|Ctrl-P'}
readOnly: true
exec: -> Backbone.Mediator.publish 'level:toggle-playing', {} exec: -> Backbone.Mediator.publish 'level:toggle-playing', {}
addCommand addCommand
name: 'end-current-script' name: 'end-current-script'
bindKey: {win: 'Shift-Space', mac: 'Shift-Space'} bindKey: {win: 'Shift-Space', mac: 'Shift-Space'}
# passEvent: true # https://github.com/ajaxorg/ace/blob/master/lib/ace/keyboard/keybinding.js#L114 readOnly: true
# No easy way to selectively cancel shift+space, since we don't get access to the event.
# Maybe we could temporarily set ourselves to read-only if we somehow know that a script is active?
exec: => exec: =>
if @scriptRunning if @scriptRunning
Backbone.Mediator.publish 'level:shift-space-pressed', {} Backbone.Mediator.publish 'level:shift-space-pressed', {}
@ -147,34 +146,44 @@ module.exports = class SpellView extends CocoView
addCommand addCommand
name: 'end-all-scripts' name: 'end-all-scripts'
bindKey: {win: 'Escape', mac: 'Escape'} bindKey: {win: 'Escape', mac: 'Escape'}
exec: -> Backbone.Mediator.publish 'level:escape-pressed', {} readOnly: true
exec: ->
console.log 'esc pressed'
Backbone.Mediator.publish 'level:escape-pressed', {}
addCommand addCommand
name: 'toggle-grid' name: 'toggle-grid'
bindKey: {win: 'Ctrl-G', mac: 'Command-G|Ctrl-G'} bindKey: {win: 'Ctrl-G', mac: 'Command-G|Ctrl-G'}
readOnly: true
exec: -> Backbone.Mediator.publish 'level:toggle-grid', {} exec: -> Backbone.Mediator.publish 'level:toggle-grid', {}
addCommand addCommand
name: 'toggle-debug' name: 'toggle-debug'
bindKey: {win: 'Ctrl-\\', mac: 'Command-\\|Ctrl-\\'} bindKey: {win: 'Ctrl-\\', mac: 'Command-\\|Ctrl-\\'}
readOnly: true
exec: -> Backbone.Mediator.publish 'level:toggle-debug', {} exec: -> Backbone.Mediator.publish 'level:toggle-debug', {}
addCommand addCommand
name: 'toggle-pathfinding' name: 'toggle-pathfinding'
bindKey: {win: 'Ctrl-O', mac: 'Command-O|Ctrl-O'} bindKey: {win: 'Ctrl-O', mac: 'Command-O|Ctrl-O'}
readOnly: true
exec: -> Backbone.Mediator.publish 'level:toggle-pathfinding', {} exec: -> Backbone.Mediator.publish 'level:toggle-pathfinding', {}
addCommand addCommand
name: 'level-scrub-forward' name: 'level-scrub-forward'
bindKey: {win: 'Ctrl-]', mac: 'Command-]|Ctrl-]'} bindKey: {win: 'Ctrl-]', mac: 'Command-]|Ctrl-]'}
readOnly: true
exec: -> Backbone.Mediator.publish 'level:scrub-forward', {} exec: -> Backbone.Mediator.publish 'level:scrub-forward', {}
addCommand addCommand
name: 'level-scrub-back' name: 'level-scrub-back'
bindKey: {win: 'Ctrl-[', mac: 'Command-[|Ctrl-]'} bindKey: {win: 'Ctrl-[', mac: 'Command-[|Ctrl-]'}
readOnly: true
exec: -> Backbone.Mediator.publish 'level:scrub-back', {} exec: -> Backbone.Mediator.publish 'level:scrub-back', {}
addCommand addCommand
name: 'spell-step-forward' name: 'spell-step-forward'
bindKey: {win: 'Ctrl-Alt-]', mac: 'Command-Alt-]|Ctrl-Alt-]'} bindKey: {win: 'Ctrl-Alt-]', mac: 'Command-Alt-]|Ctrl-Alt-]'}
readOnly: true
exec: -> Backbone.Mediator.publish 'tome:spell-step-forward', {} exec: -> Backbone.Mediator.publish 'tome:spell-step-forward', {}
addCommand addCommand
name: 'spell-step-backward' name: 'spell-step-backward'
bindKey: {win: 'Ctrl-Alt-[', mac: 'Command-Alt-[|Ctrl-Alt-]'} bindKey: {win: 'Ctrl-Alt-[', mac: 'Command-Alt-[|Ctrl-Alt-]'}
readOnly: true
exec: -> Backbone.Mediator.publish 'tome:spell-step-backward', {} exec: -> Backbone.Mediator.publish 'tome:spell-step-backward', {}
addCommand addCommand
name: 'spell-beautify' name: 'spell-beautify'
@ -207,6 +216,37 @@ module.exports = class SpellView extends CocoView
name: 'disable-spaces' name: 'disable-spaces'
bindKey: 'Space' bindKey: 'Space'
exec: => @ace.execCommand 'insertstring', ' ' unless LevelOptions[@options.level.get('slug')]?.disableSpaces exec: => @ace.execCommand 'insertstring', ' ' unless LevelOptions[@options.level.get('slug')]?.disableSpaces
addCommand
name: 'throttle-backspaces'
bindKey: 'Backspace'
exec: =>
# Throttle the backspace speed
# Slow to 500ms when whitespace at beginning of line is first encountered
# Slow to 100ms for remaining whitespace at beginning of line
# Rough testing showed backspaces happen at 150ms when tapping.
# Backspace speed varies by system when holding, 30ms on fastest Macbook setting.
unless CampaignOptions?.getOption?(@options?.level?.get?('slug'), 'backspaceThrottle')
@ace.remove "left"
return
nowDate = Date.now()
if @aceSession.selection.isEmpty()
cursor = @ace.getCursorPosition()
line = @aceDoc.getLine(cursor.row)
if /^\s*$/.test line.substring(0, cursor.column)
@backspaceThrottleMs ?= 500
# console.log "SpellView @backspaceThrottleMs=#{@backspaceThrottleMs}"
# console.log 'SpellView lastBackspace diff', nowDate - @lastBackspace if @lastBackspace?
if not @lastBackspace? or nowDate - @lastBackspace > @backspaceThrottleMs
@backspaceThrottleMs = 100
@lastBackspace = nowDate
@ace.remove "left"
return
@backspaceThrottleMs = null
@lastBackspace = nowDate
@ace.remove "left"
fillACE: -> fillACE: ->
@ace.setValue @spell.source @ace.setValue @spell.source
@ -253,8 +293,15 @@ module.exports = class SpellView extends CocoView
return true if doc.owner is owner return true if doc.owner is owner
return (owner is 'this' or owner is 'more') and (not doc.owner? or doc.owner is 'this') return (owner is 'this' or owner is 'more') and (not doc.owner? or doc.owner is 'this')
if doc?.snippets?[e.language] if doc?.snippets?[e.language]
content = doc.snippets[e.language].code
if /loop/.test(content) and LevelOptions[@options.level.get('slug')]?.moveRightLoopSnippet
# Replace default loop snippet with an embedded moveRight()
content = switch e.language
when 'python' then 'loop:\n self.moveRight()\n ${1:}'
when 'javascript' then 'loop {\n this.moveRight();\n ${1:}\n}'
else content
entry = entry =
content: doc.snippets[e.language].code content: content
meta: 'press tab' meta: 'press tab'
name: doc.name name: doc.name
tabTrigger: doc.snippets[e.language].tab tabTrigger: doc.snippets[e.language].tab
@ -810,14 +857,16 @@ module.exports = class SpellView extends CocoView
onDisableControls: (e) -> @toggleControls e, false onDisableControls: (e) -> @toggleControls e, false
onEnableControls: (e) -> @toggleControls e, @writable onEnableControls: (e) -> @toggleControls e, @writable
toggleControls: (e, enabled) -> toggleControls: (e, enabled) ->
return if @destroyed
return if e?.controls and not ('editor' in e.controls) return if e?.controls and not ('editor' in e.controls)
return if enabled is @controlsEnabled return if enabled is @controlsEnabled
@controlsEnabled = enabled and @writable @controlsEnabled = enabled and @writable
disabled = not enabled disabled = not enabled
$('body').focus() if disabled and $(document.activeElement).is('.ace_text-input') wasFocused = @ace.isFocused()
@ace.setReadOnly disabled @ace.setReadOnly disabled
@ace[if disabled then 'setStyle' else 'unsetStyle'] 'disabled' @ace[if disabled then 'setStyle' else 'unsetStyle'] 'disabled'
@toggleBackground() @toggleBackground()
$('body').focus() if disabled and wasFocused
toggleBackground: => toggleBackground: =>
# TODO: make the background an actual background and do the CSS trick # TODO: make the background an actual background and do the CSS trick

View file

@ -90,7 +90,7 @@ module.exports = class PlayItemsModal extends ModalView
model.silhouetted = not model.owned and model.isSilhouettedItem() model.silhouetted = not model.owned and model.isSilhouettedItem()
model.level = model.levelRequiredForItem() if model.get('tier')? model.level = model.levelRequiredForItem() if model.get('tier')?
model.unequippable = not ('Warrior' in model.getAllowedHeroClasses()) # Temp: while there are no wizards/rangers model.unequippable = not ('Warrior' in model.getAllowedHeroClasses()) # Temp: while there are no wizards/rangers
model.comingSoon = not model.getFrontFacingStats().props.length and not _.size model.getFrontFacingStats().stats and not model.owned # Temp: while there are placeholder items model.comingSoon = not model.getFrontFacingStats().props.length and not _.size(model.getFrontFacingStats().stats) and not model.owned # Temp: while there are placeholder items
@idToItem[model.id] = model @idToItem[model.id] = model
if needMore if needMore
@ -112,6 +112,7 @@ module.exports = class PlayItemsModal extends ModalView
@$el.find('.nano:visible').nanoScroller({alwaysVisible: true}) @$el.find('.nano:visible').nanoScroller({alwaysVisible: true})
@itemDetailsView = new ItemDetailsView() @itemDetailsView = new ItemDetailsView()
@insertSubView(@itemDetailsView) @insertSubView(@itemDetailsView)
@$el.find("a[href='#item-category-armor']").click() # Start on armor tab, if it's there.
onHidden: -> onHidden: ->
super() super()
@ -156,6 +157,8 @@ module.exports = class PlayItemsModal extends ModalView
#- ...then rerender key bits #- ...then rerender key bits
@renderSelectors(".item[data-item-id='#{item.id}']", "#gems-count") @renderSelectors(".item[data-item-id='#{item.id}']", "#gems-count")
@itemDetailsView.render() @itemDetailsView.render()
Backbone.Mediator.publish 'store:item-purchased', item: item, itemSlug: item.get('slug')
else else
button.addClass('confirm').text($.i18n.t('play.confirm')) button.addClass('confirm').text($.i18n.t('play.confirm'))
@$el.one 'click', (e) -> @$el.one 'click', (e) ->

View file

@ -1,6 +1,7 @@
mongoose = require 'mongoose' mongoose = require 'mongoose'
jsonschema = require '../../app/schemas/models/earned_achievement' jsonschema = require '../../app/schemas/models/earned_achievement'
util = require '../../app/lib/utils' util = require '../../app/lib/utils'
log = require 'winston'
EarnedAchievementSchema = new mongoose.Schema({ EarnedAchievementSchema = new mongoose.Schema({
notified: notified:
@ -68,6 +69,6 @@ EarnedAchievementSchema.statics.createForAchievement = (achievement, doc, origin
(new EarnedAchievement(earned)).save (err, doc) -> (new EarnedAchievement(earned)).save (err, doc) ->
return log.error err if err? return log.error err if err?
earnedPoints = worth earnedPoints = worth
wrapUp(doc) wrapUp(doc)
module.exports = EarnedAchievement = mongoose.model('EarnedAchievement', EarnedAchievementSchema) module.exports = EarnedAchievement = mongoose.model('EarnedAchievement', EarnedAchievementSchema)

View file

@ -67,7 +67,7 @@ class EarnedAchievementHandler extends Handler
if achievement.get('proportionalTo') if achievement.get('proportionalTo')
return @sendBadInputError(res, 'Cannot currently do this to repeatable docs...') return @sendBadInputError(res, 'Cannot currently do this to repeatable docs...')
EarnedAchievement.createForAchievement(achievement, trigger, null, (earnedAchievementDoc) => EarnedAchievement.createForAchievement(achievement, trigger, null, (earnedAchievementDoc) =>
@sendSuccess(res, earnedAchievementDoc.toObject()) @sendCreated(res, earnedAchievementDoc.toObject())
) )
) )

View file

@ -88,10 +88,13 @@ PaymentHandler = class PaymentHandler extends Handler
#- Check existence #- Check existence
transactionID = transaction.transaction_id transactionID = transaction.transaction_id
criteria = { recipient: req.user._id, 'ios.transactionID': transactionID } criteria = { 'ios.transactionID': transactionID }
Payment.findOne(criteria).exec((err, payment) => Payment.findOne(criteria).exec((err, payment) =>
if payment if payment
unless payment.get('recipient').equals(req.user._id)
return @sendForbiddenError(res)
@recalculateGemsFor(req.user, (err) => @recalculateGemsFor(req.user, (err) =>
return @sendDatabaseError(res, err) if err return @sendDatabaseError(res, err) if err
@sendSuccess(res, @formatEntity(req, payment)) @sendSuccess(res, @formatEntity(req, payment))

View file

@ -122,134 +122,133 @@ describe 'Achievement', ->
# TODO: Took level achievements out of this auto achievement business, so fix these tests # TODO: Took level achievements out of this auto achievement business, so fix these tests
#describe 'Achieving Achievements', -> describe 'Level Session Achievement', ->
# it 'wait for achievements to be loaded', (done) -> it 'does not generate earned achievements automatically, they need to be created manually', (done) ->
# Achievement.loadAchievements (achievements) -> unittest.getNormalJoe (joe) ->
# expect(Object.keys(achievements).length).toBe(1) session = new LevelSession
# permissions: simplePermissions
# loadedAchievements = Achievement.getLoadedAchievements() creator: joe._id
# expect(Object.keys(loadedAchievements).length).toBe(1) level: original: 'dungeon-arena'
# done() session.save (err, session) ->
# expect(err).toBeNull()
# it 'saving an object that should trigger an unlockable achievement', (done) -> expect(session).toBeDefined()
# unittest.getNormalJoe (joe) -> expect(session.creator).toBe(session.creator)
# session = new LevelSession
# permissions: simplePermissions EarnedAchievement.find {}, (err, earnedAchievements) ->
# creator: joe._id expect(err).toBeNull()
# level: original: 'dungeon-arena' expect(earnedAchievements.length).toBe(0)
# session.save (err, doc) ->
# expect(err).toBeNull() json = {achievement: unlockable._id, triggeredBy: session._id, collection: 'level.sessions'}
# expect(doc).toBeDefined() request.post {uri: getURL('/db/earned_achievement'), json: json}, (err, res, body) ->
# expect(doc.creator).toBe(session.creator) expect(res.statusCode).toBe(201)
# done() expect(body.achievement).toBe unlockable._id+''
# expect(body.user).toBe joe._id.toHexString()
# it 'verify that an unlockable achievement has been earned', (done) -> expect(body.notified).toBeFalsy()
# unittest.getNormalJoe (joe) -> expect(body.earnedPoints).toBe unlockable.worth
# EarnedAchievement.find {}, (err, docs) -> expect(body.achievedAmount).toBeUndefined()
# expect(err).toBeNull() expect(body.previouslyAchievedAmount).toBeUndefined()
# expect(docs.length).toBe(1) done()
# achievement = docs[0]
# expect(achievement).toBeDefined()
#
# expect(achievement.get 'achievement').toBe unlockable._id
# expect(achievement.get 'user').toBe joe._id.toHexString()
# expect(achievement.get 'notified').toBeFalsy()
# expect(achievement.get 'earnedPoints').toBe unlockable.worth
# expect(achievement.get 'achievedAmount').toBeUndefined()
# expect(achievement.get 'previouslyAchievedAmount').toBeUndefined()
# done()
#
# it 'saving an object that should trigger a repeatable achievement', (done) ->
# unittest.getNormalJoe (joe) ->
# expect(joe.get 'simulatedBy').toBeFalsy()
# joe.set('simulatedBy', 2)
# joe.save (err, doc) ->
# expect(err).toBeNull()
# done()
#
# it 'verify that a repeatable achievement has been earned', (done) ->
# unittest.getNormalJoe (joe) ->
# EarnedAchievement.find {achievementName: repeatable.name}, (err, docs) ->
# expect(err).toBeNull()
# expect(docs.length).toBe(1)
# achievement = docs[0]
#
# expect(achievement.get 'achievement').toBe repeatable._id
# expect(achievement.get 'user').toBe joe._id.toHexString()
# expect(achievement.get 'notified').toBeFalsy()
# expect(achievement.get 'earnedPoints').toBe 2 * repeatable.worth
# expect(achievement.get 'achievedAmount').toBe 2
# expect(achievement.get 'previouslyAchievedAmount').toBeFalsy()
# done()
#
#
# it 'verify that the repeatable achievement with complex exp has been earned', (done) ->
# unittest.getNormalJoe (joe) ->
# EarnedAchievement.find {achievementName: diminishing.name}, (err, docs) ->
# expect(err).toBeNull()
# expect(docs.length).toBe 1
# achievement = docs[0]
#
# expect(achievement.get 'achievedAmount').toBe 2
# expect(achievement.get 'earnedPoints').toBe (Math.log(.5 * (2 + .5)) + 1) * diminishing.worth
#
# done()
#describe 'Recalculate Achievements', ->
# EarnedAchievementHandler = require '../../../server/achievements/earned_achievement_handler' describe 'Achieving Achievements', ->
# it 'wait for achievements to be loaded', (done) ->
# it 'remove earned achievements', (done) -> Achievement.loadAchievements (achievements) ->
# clearModels [EarnedAchievement], (err) -> expect(Object.keys(achievements).length).toBe(1)
# expect(err).toBeNull()
# EarnedAchievement.find {}, (err, earned) -> loadedAchievements = Achievement.getLoadedAchievements()
# expect(earned.length).toBe 0 expect(Object.keys(loadedAchievements).length).toBe(1)
# done()
# User.update {}, {$set: {points: 0}}, {multi:true}, (err) ->
# expect(err).toBeNull() it 'saving an object that should trigger a repeatable achievement', (done) ->
# done() unittest.getNormalJoe (joe) ->
# expect(joe.get 'simulatedBy').toBeFalsy()
# it 'can not be accessed by regular users', (done) -> joe.set('simulatedBy', 2)
# loginJoe -> request.post {uri:getURL '/admin/earned_achievement/recalculate'}, (err, res, body) -> joe.save (err, doc) ->
# expect(res.statusCode).toBe 403 expect(err).toBeNull()
# done() done()
#
# it 'can recalculate a selection of achievements', (done) -> it 'verify that a repeatable achievement has been earned', (done) ->
# loginAdmin -> unittest.getNormalJoe (joe) ->
# EarnedAchievementHandler.constructor.recalculate ['dungeon-arena-started'], -> EarnedAchievement.find {achievementName: repeatable.name}, (err, docs) ->
# EarnedAchievement.find {}, (err, earnedAchievements) -> expect(err).toBeNull()
# expect(earnedAchievements.length).toBe 1 expect(docs.length).toBe(1)
# achievement = docs[0]
# # Recalculate again, doesn't change a thing
# EarnedAchievementHandler.constructor.recalculate ['dungeon-arena-started'], -> expect(achievement.get 'achievement').toBe repeatable._id
# EarnedAchievement.find {}, (err, earnedAchievements) -> expect(achievement.get 'user').toBe joe._id.toHexString()
# expect(earnedAchievements.length).toBe 1 expect(achievement.get 'notified').toBeFalsy()
# expect(achievement.get 'earnedPoints').toBe 2 * repeatable.worth
# unittest.getNormalJoe (joe) -> expect(achievement.get 'achievedAmount').toBe 2
# User.findById joe.get('id'), (err, guy) -> expect(achievement.get 'previouslyAchievedAmount').toBeFalsy()
# expect(err).toBeNull() done()
# expect(guy.get 'points').toBe unlockable.worth
# done() it 'verify that the repeatable achievement with complex exp has been earned', (done) ->
# unittest.getNormalJoe (joe) ->
# it 'can recalculate all achievements', (done) -> EarnedAchievement.find {achievementName: diminishing.name}, (err, docs) ->
# loginAdmin -> expect(err).toBeNull()
# Achievement.count {}, (err, count) -> expect(docs.length).toBe 1
# expect(count).toBe 3 achievement = docs[0]
# EarnedAchievementHandler.constructor.recalculate ->
# EarnedAchievement.find {}, (err, earnedAchievements) -> expect(achievement.get 'achievedAmount').toBe 2
# expect(earnedAchievements.length).toBe 3 expect(achievement.get 'earnedPoints').toBe (Math.log(.5 * (2 + .5)) + 1) * diminishing.worth
# unittest.getNormalJoe (joe) ->
# User.findById joe.get('id'), (err, guy) -> done()
# expect(err).toBeNull()
# expect(guy.get 'points').toBe unlockable.worth + 2 * repeatable.worth + (Math.log(.5 * (2 + .5)) + 1) * diminishing.worth describe 'Recalculate Achievements', ->
# done() EarnedAchievementHandler = require '../../../server/achievements/earned_achievement_handler'
#
# it 'cleaning up test: deleting all Achievements and related', (done) -> it 'remove earned achievements', (done) ->
# clearModels [Achievement, EarnedAchievement, LevelSession], (err) -> clearModels [EarnedAchievement], (err) ->
# expect(err).toBeNull() expect(err).toBeNull()
# EarnedAchievement.find {}, (err, earned) ->
# # reset achievements in memory as well expect(earned.length).toBe 0
# Achievement.resetAchievements()
# loadedAchievements = Achievement.getLoadedAchievements() User.update {}, {$set: {points: 0}}, {multi:true}, (err) ->
# expect(Object.keys(loadedAchievements).length).toBe(0) expect(err).toBeNull()
# done()
# done()
it 'can not be accessed by regular users', (done) ->
loginJoe -> request.post {uri:getURL '/admin/earned_achievement/recalculate'}, (err, res, body) ->
expect(res.statusCode).toBe 403
done()
it 'can recalculate a selection of achievements', (done) ->
loginAdmin ->
EarnedAchievementHandler.constructor.recalculate ['dungeon-arena-started'], ->
EarnedAchievement.find {}, (err, earnedAchievements) ->
expect(earnedAchievements.length).toBe 1
# Recalculate again, doesn't change a thing
EarnedAchievementHandler.constructor.recalculate ['dungeon-arena-started'], ->
EarnedAchievement.find {}, (err, earnedAchievements) ->
expect(earnedAchievements.length).toBe 1
unittest.getNormalJoe (joe) ->
User.findById joe.get('id'), (err, guy) ->
expect(err).toBeNull()
expect(guy.get 'points').toBe unlockable.worth
done()
it 'can recalculate all achievements', (done) ->
loginAdmin ->
Achievement.count {}, (err, count) ->
expect(count).toBe 3
EarnedAchievementHandler.constructor.recalculate ->
EarnedAchievement.find {}, (err, earnedAchievements) ->
expect(earnedAchievements.length).toBe 3
unittest.getNormalJoe (joe) ->
User.findById joe.get('id'), (err, guy) ->
expect(err).toBeNull()
expect(guy.get 'points').toBe unlockable.worth + 2 * repeatable.worth + (Math.log(.5 * (2 + .5)) + 1) * diminishing.worth
done()
it 'cleaning up test: deleting all Achievements and related', (done) ->
clearModels [Achievement, EarnedAchievement, LevelSession], (err) ->
expect(err).toBeNull()
# reset achievements in memory as well
Achievement.resetAchievements()
loadedAchievements = Achievement.getLoadedAchievements()
expect(Object.keys(loadedAchievements).length).toBe(0)
done()

View file

@ -52,6 +52,12 @@ describe '/db/payment', ->
done() done()
) )
it 'prevents other users from reusing payment receipts', (done) ->
loginSam ->
request.post {uri: paymentURL, json: firstApplePayment}, (err, res, body) ->
expect(res.statusCode).toBe 403
done()
it 'processes only the transactionID that is given', (done) -> it 'processes only the transactionID that is given', (done) ->
loginJoe -> loginJoe ->
request.post {uri: paymentURL, json: secondApplePayment}, (err, res, body) -> request.post {uri: paymentURL, json: secondApplePayment}, (err, res, body) ->