Merge branch 'master' of https://github.com/codecombat/codecombat
This commit is contained in:
commit
2875cb09a5
28 changed files with 429 additions and 196 deletions
app
lib
locale
schemas/subscriptions
styles/play/level
templates/play
views
server
test/server/functional
|
@ -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) ->
|
||||||
|
|
|
@ -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: {}
|
||||||
|
|
|
@ -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']
|
||||||
|
|
|
@ -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: ", отредактируйте его онлайн и отправьте запрос на подтверждение изменений. Кроме того, установите флажок ниже, чтобы быть в курсе новых разработок интернационализации!"
|
||||||
|
|
|
@ -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']},
|
||||||
|
|
|
@ -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'}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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}))
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) ->
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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())
|
||||||
)
|
)
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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) ->
|
||||||
|
|
Reference in a new issue