Merged in campaign editor and campaign view. Destroyed WorldMapView, CampaignOptions, and LevelOptions. Lots of stuff is now stored in the database instead of code. Cleaned up a few unneeded old features. Fixed some problems with checking permissions on first rather than latest versions of documents.
Before Width: | Height: | Size: 329 KiB |
Before Width: | Height: | Size: 552 KiB |
Before Width: | Height: | Size: 346 KiB |
Before Width: | Height: | Size: 614 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 441 KiB |
Before Width: | Height: | Size: 763 KiB |
Before Width: | Height: | Size: 4.2 KiB |
|
@ -86,11 +86,11 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
|
||||
'play-old': go('play/MainPlayView') # This used to be 'play'.
|
||||
'play': go('play/CampaignView')
|
||||
'play/:map': go('play/CampaignView')
|
||||
'play/ladder/:levelID': go('ladder/LadderView')
|
||||
'play/ladder': go('ladder/MainLadderView')
|
||||
'play/level/:levelID': go('play/level/PlayLevelView')
|
||||
'play/spectate/:levelID': go('play/SpectateView')
|
||||
'play/:map': go('play/WorldMapView')
|
||||
|
||||
'preview': go('HomeView')
|
||||
|
||||
|
|
|
@ -140,3 +140,24 @@ if document?
|
|||
module.exports.replaceText = (elems, text) ->
|
||||
elem[TEXT] = text for elem in elems
|
||||
null
|
||||
|
||||
# Add a stylesheet rule
|
||||
# http://stackoverflow.com/questions/524696/how-to-create-a-style-tag-with-javascript/26230472#26230472
|
||||
# Don't use wantonly, or we'll have to implement a simple mechanism for clearing out old rules.
|
||||
if document?
|
||||
module.exports.injectCSS = ((doc) ->
|
||||
# wrapper for all injected styles and temp el to create them
|
||||
wrap = doc.createElement("div")
|
||||
temp = doc.createElement("div")
|
||||
# rules like "a {color: red}" etc.
|
||||
return (cssRules) ->
|
||||
# append wrapper to the body on the first call
|
||||
unless wrap.id
|
||||
wrap.id = "injected-css"
|
||||
wrap.style.display = "none"
|
||||
doc.body.appendChild wrap
|
||||
# <br> for IE: http://goo.gl/vLY4x7
|
||||
temp.innerHTML = "<br><style>" + cssRules + "</style>"
|
||||
wrap.appendChild temp.children[1]
|
||||
return
|
||||
)(document)
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
CampaignList = require('views/play/WorldMapView').campaigns
|
||||
|
||||
# TODO: Is this file structured correctly?
|
||||
|
||||
# Per-campaign options, with default fallback set
|
||||
options =
|
||||
'default':
|
||||
autocompleteFontSizePx: 16
|
||||
backspaceThrottle: false
|
||||
lockDefaultCode: false
|
||||
'dungeon':
|
||||
autocompleteFontSizePx: 20
|
||||
backspaceThrottle: true
|
||||
lockDefaultCode: true
|
||||
|
||||
module.exports = CampaignOptions =
|
||||
getCampaignForSlug: (slug) ->
|
||||
return unless slug
|
||||
for campaign in CampaignList
|
||||
for level in campaign.levels
|
||||
return campaign.id if level.id is slug
|
||||
|
||||
getOption: (levelSlug, option) ->
|
||||
return unless levelSlug and option
|
||||
return unless campaign = CampaignOptions.getCampaignForSlug levelSlug
|
||||
return options[campaign]?[option] if options[campaign]?[option]?
|
||||
return options.default[option] if options.default[option]?
|
|
@ -1,367 +0,0 @@
|
|||
module.exports = LevelOptions =
|
||||
'dungeons-of-kithgard':
|
||||
disableSpaces: true
|
||||
helpVideos: [
|
||||
{style: 'original', URL: '//player.vimeo.com/video/114921603'}
|
||||
{style: 'scripted', URL: '//player.vimeo.com/video/114729726'}
|
||||
{style: 'eccentric', URL: '//player.vimeo.com/video/114729725'}
|
||||
{style: 'edited', URL: '//player.vimeo.com/video/114729724'}
|
||||
]
|
||||
hidesSubmitUntilRun: true
|
||||
hidesPlayButton: true
|
||||
hidesRunShortcut: true
|
||||
hidesHUD: true
|
||||
hidesSay: true
|
||||
hidesCodeToolbar: true
|
||||
hidesRealTimePlayback: true
|
||||
requiredGear: {feet: 'simple-boots'}
|
||||
restrictedGear: {feet: 'leather-boots'}
|
||||
requiredCode: ['moveRight']
|
||||
'gems-in-the-deep':
|
||||
disableSpaces: true
|
||||
helpVideos: [{style: 'original', URL: '//player.vimeo.com/video/114730449'}]
|
||||
hidesSubmitUntilRun: true
|
||||
hidesPlayButton: true
|
||||
hidesRunShortcut: true
|
||||
hidesHUD: true
|
||||
hidesSay: true
|
||||
hidesCodeToolbar: true
|
||||
hidesRealTimePlayback: true
|
||||
requiredGear: {feet: 'simple-boots'}
|
||||
restrictedGear: {feet: 'leather-boots'}
|
||||
'shadow-guard':
|
||||
disableSpaces: true
|
||||
helpVideos: [{style: 'original', URL: '//player.vimeo.com/video/114734163'}]
|
||||
hidesSubmitUntilRun: true
|
||||
hidesPlayButton: true
|
||||
hidesRunShortcut: true
|
||||
hidesHUD: true
|
||||
hidesSay: true
|
||||
hidesCodeToolbar: true
|
||||
hidesRealTimePlayback: true
|
||||
requiredGear: {feet: 'simple-boots'}
|
||||
restrictedGear: {feet: 'leather-boots', 'right-hand': 'simple-sword'}
|
||||
'kounter-kithwise':
|
||||
disableSpaces: true
|
||||
helpVideos: [{style: 'original', URL: '//player.vimeo.com/video/114734160'}]
|
||||
hidesPlayButton: true
|
||||
hidesRunShortcut: true
|
||||
hidesHUD: true
|
||||
hidesSay: true
|
||||
hidesCodeToolbar: true
|
||||
hidesRealTimePlayback: true
|
||||
requiredGear: {feet: 'simple-boots'}
|
||||
restrictedGear: {feet: 'leather-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i'}
|
||||
'crawlways-of-kithgard':
|
||||
helpVideos: [{style: 'original', URL: '//player.vimeo.com/video/114734162'}]
|
||||
hidesPlayButton: true
|
||||
hidesRunShortcut: true
|
||||
hidesHUD: true
|
||||
hidesSay: true
|
||||
hidesCodeToolbar: true
|
||||
hidesRealTimePlayback: true
|
||||
requiredGear: {feet: 'simple-boots'}
|
||||
restrictedGear: {feet: 'leather-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i'}
|
||||
'forgetful-gemsmith':
|
||||
disableSpaces: true
|
||||
helpVideos: [{style: 'original', URL: '//player.vimeo.com/video/114734165'}]
|
||||
hidesPlayButton: true
|
||||
hidesRunShortcut: true
|
||||
hidesHUD: true
|
||||
hidesSay: true
|
||||
hidesCodeToolbar: true
|
||||
hidesRealTimePlayback: true
|
||||
requiredGear: {feet: 'simple-boots'}
|
||||
restrictedGear: {feet: 'leather-boots', 'programming-book': 'programmaticon-i'}
|
||||
'true-names':
|
||||
disableSpaces: true
|
||||
helpVideos: [{style: 'original', URL: '//player.vimeo.com/video/114734166'}]
|
||||
hidesPlayButton: true
|
||||
hidesRunShortcut: true
|
||||
hidesHUD: true
|
||||
hidesSay: true
|
||||
hidesCodeToolbar: true
|
||||
hidesRealTimePlayback: true
|
||||
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', waist: 'leather-belt'}
|
||||
restrictedGear: {feet: 'leather-boots', 'programming-book': 'programmaticon-i'}
|
||||
requiredCode: ['Brak']
|
||||
'favorable-odds':
|
||||
disableSpaces: true
|
||||
helpVideos: [{style: 'original', URL: '//player.vimeo.com/video/114734656'}]
|
||||
hidesPlayButton: true
|
||||
hidesRunShortcut: true
|
||||
hidesHUD: true
|
||||
hidesSay: true
|
||||
hidesCodeToolbar: true
|
||||
hidesRealTimePlayback: true
|
||||
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword'}
|
||||
restrictedGear: {feet: 'leather-boots', 'programming-book': 'programmaticon-i'}
|
||||
'the-raised-sword':
|
||||
disableSpaces: true
|
||||
helpVideos: [{style: 'original', URL: '//player.vimeo.com/video/114734655'}]
|
||||
hidesPlayButton: true
|
||||
hidesRunShortcut: true
|
||||
hidesHUD: true
|
||||
hidesSay: true
|
||||
hidesCodeToolbar: true
|
||||
hidesRealTimePlayback: true
|
||||
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'tarnished-bronze-breastplate'}
|
||||
restrictedGear: {feet: 'leather-boots', 'programming-book': 'programmaticon-i'}
|
||||
'riddling-kithmaze':
|
||||
hidesRunShortcut: true
|
||||
hidesHUD: true
|
||||
hidesSay: true
|
||||
hidesCodeToolbar: true
|
||||
hidesRealTimePlayback: true
|
||||
requiredGear: {feet: 'simple-boots', 'programming-book': 'programmaticon-i'}
|
||||
restrictedGear: {feet: 'leather-boots'}
|
||||
requiredCode: ['loop']
|
||||
'haunted-kithmaze':
|
||||
helpVideos: [
|
||||
{style: 'original', URL: '//player.vimeo.com/video/114921605'}
|
||||
{style: 'scripted', URL: '//player.vimeo.com/video/114730074'}
|
||||
{style: 'eccentric', URL: '//player.vimeo.com/video/114729727'}
|
||||
{style: 'edited', URL: '//player.vimeo.com/video/114729723'}
|
||||
]
|
||||
hidesRunShortcut: true
|
||||
hidesHUD: true
|
||||
hidesSay: true
|
||||
hidesCodeToolbar: true
|
||||
hidesRealTimePlayback: true
|
||||
moveRightLoopSnippet: true
|
||||
requiredGear: {feet: 'simple-boots', 'programming-book': 'programmaticon-i'}
|
||||
restrictedGear: {feet: 'leather-boots'}
|
||||
requiredCode: ['loop']
|
||||
'descending-further':
|
||||
hidesHUD: true
|
||||
hidesSay: true
|
||||
hidesCodeToolbar: true
|
||||
hidesRealTimePlayback: true
|
||||
requiredGear: {feet: 'simple-boots', 'programming-book': 'programmaticon-i'}
|
||||
restrictedGear: {feet: 'leather-boots'}
|
||||
'the-second-kithmaze':
|
||||
helpVideos: [{style: 'original', URL: '//player.vimeo.com/video/114899761'}]
|
||||
hidesHUD: true
|
||||
hidesSay: true
|
||||
hidesCodeToolbar: true
|
||||
hidesRealTimePlayback: true
|
||||
moveRightLoopSnippet: true
|
||||
requiredGear: {feet: 'simple-boots', 'programming-book': 'programmaticon-i'}
|
||||
restrictedGear: {feet: 'leather-boots'}
|
||||
'dread-door':
|
||||
hidesHUD: true
|
||||
hidesSay: true
|
||||
hidesCodeToolbar: true
|
||||
hidesRealTimePlayback: true
|
||||
requiredGear: {'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i'}
|
||||
restrictedGear: {feet: 'leather-boots'}
|
||||
'known-enemy':
|
||||
hidesHUD: true
|
||||
hidesSay: true
|
||||
hidesCodeToolbar: true
|
||||
hidesRealTimePlayback: true
|
||||
requiredGear: {'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i', torso: 'tarnished-bronze-breastplate'}
|
||||
restrictedGear: {feet: 'leather-boots'}
|
||||
suspectCode: [{name: 'enemy-in-quotes', pattern: /['"]enemy/m}] # '
|
||||
'master-of-names':
|
||||
hidesHUD: true
|
||||
hidesSay: true
|
||||
hidesCodeToolbar: true
|
||||
hidesRealTimePlayback: true
|
||||
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses', torso: 'tarnished-bronze-breastplate'}
|
||||
restrictedGear: {feet: 'leather-boots'}
|
||||
requiredCode: ['findNearestEnemy']
|
||||
suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
|
||||
'lowly-kithmen':
|
||||
hidesHUD: true
|
||||
hidesSay: true
|
||||
hidesCodeToolbar: true
|
||||
hidesRealTimePlayback: true
|
||||
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses', torso: 'tarnished-bronze-breastplate'}
|
||||
restrictedGear: {feet: 'leather-boots'}
|
||||
requiredCode: ['findNearestEnemy']
|
||||
suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
|
||||
'closing-the-distance':
|
||||
hidesHUD: true
|
||||
hidesSay: true
|
||||
hidesCodeToolbar: true
|
||||
hidesRealTimePlayback: true
|
||||
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'tarnished-bronze-breastplate', eyes: 'crude-glasses'}
|
||||
restrictedGear: {feet: 'leather-boots'}
|
||||
suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
|
||||
'tactical-strike':
|
||||
hidesHUD: true
|
||||
hidesSay: true
|
||||
hidesCodeToolbar: true
|
||||
hidesRealTimePlayback: true
|
||||
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'tarnished-bronze-breastplate', eyes: 'crude-glasses'}
|
||||
restrictedGear: {feet: 'leather-boots'}
|
||||
suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
|
||||
'the-final-kithmaze':
|
||||
hidesHUD: true
|
||||
hidesSay: true
|
||||
hidesCodeToolbar: true
|
||||
hidesRealTimePlayback: true
|
||||
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'tarnished-bronze-breastplate', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
|
||||
suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
|
||||
'the-gauntlet':
|
||||
hidesHUD: true
|
||||
hidesSay: true
|
||||
hidesCodeToolbar: true
|
||||
hidesRealTimePlayback: true
|
||||
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'tarnished-bronze-breastplate', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
|
||||
restrictedGear: {feet: 'leather-boots', 'right-hand': 'crude-builders-hammer'}
|
||||
suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
|
||||
'kithgard-gates':
|
||||
hidesSay: true
|
||||
hidesCodeToolbar: true
|
||||
hidesRealTimePlayback: true
|
||||
requiredGear: {feet: 'simple-boots', 'right-hand': 'crude-builders-hammer', torso: 'tarnished-bronze-breastplate'}
|
||||
restrictedGear: {'right-hand': 'simple-sword'}
|
||||
'defense-of-plainswood':
|
||||
hidesRealTimePlayback: true
|
||||
hidesCodeToolbar: true
|
||||
requiredGear: {feet: 'simple-boots', 'right-hand': 'crude-builders-hammer'}
|
||||
restrictedGear: {'right-hand': 'simple-sword'}
|
||||
'winding-trail':
|
||||
hidesRealTimePlayback: true
|
||||
hidesCodeToolbar: true
|
||||
requiredGear: {feet: 'leather-boots', 'right-hand': 'crude-builders-hammer'}
|
||||
restrictedGear: {feet: 'simple-boots', 'right-hand': 'simple-sword'}
|
||||
'patrol-buster':
|
||||
hidesRealTimePlayback: true
|
||||
hidesCodeToolbar: true
|
||||
requiredGear: {feet: 'leather-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-ii', eyes: 'crude-glasses'}
|
||||
restrictedGear: {feet: 'simple-boots', 'right-hand': 'crude-builders-hammer', 'programming-book': 'programmaticon-i'}
|
||||
'endangered-burl':
|
||||
hidesRealTimePlayback: true
|
||||
hidesCodeToolbar: true
|
||||
requiredGear: {feet: 'leather-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-ii', eyes: 'crude-glasses'}
|
||||
restrictedGear: {feet: 'simple-boots', 'right-hand': 'crude-builders-hammer', 'programming-book': 'programmaticon-i'}
|
||||
'village-guard':
|
||||
hidesCodeToolbar: true
|
||||
lockDefaultCode: true
|
||||
requiredGear: {feet: 'leather-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-ii', eyes: 'crude-glasses'}
|
||||
restrictedGear: {feet: 'simple-boots', 'right-hand': 'crude-builders-hammer', 'programming-book': 'programmaticon-i'}
|
||||
'thornbush-farm':
|
||||
hidesCodeToolbar: true
|
||||
lockDefaultCode: true
|
||||
requiredGear: {feet: 'leather-boots', 'right-hand': 'crude-builders-hammer', 'programming-book': 'programmaticon-ii', eyes: 'crude-glasses'}
|
||||
restrictedGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i'}
|
||||
requiredCode: ['topEnemy']
|
||||
'back-to-back':
|
||||
hidesCodeToolbar: true
|
||||
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', 'programming-book': 'programmaticon-i'}
|
||||
'ogre-encampment':
|
||||
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', 'programming-book': 'programmaticon-i'}
|
||||
'woodland-cleaver':
|
||||
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', 'programming-book': 'programmaticon-i'}
|
||||
'shield-rush':
|
||||
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', 'programming-book': 'programmaticon-i'}
|
||||
|
||||
# Warrior branch
|
||||
'peasant-protection':
|
||||
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', 'programming-book': 'programmaticon-i'}
|
||||
'munchkin-swarm':
|
||||
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: {'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
|
||||
|
||||
# Ranger branch
|
||||
'munchkin-harvest':
|
||||
requiredGear: {waist: 'leather-belt', 'programming-book': 'programmaticon-ii', eyes: 'wooden-glasses', 'right-hand': 'long-sword', 'left-hand': 'bronze-shield', wrists: 'sundial-wristwatch'}
|
||||
restrictedGear: {'programming-book': 'programmaticon-i'}
|
||||
allowedHeroes: ['captain', 'knight', 'samurai']
|
||||
'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', 'programming-book': 'programmaticon-i'}
|
||||
allowedHeroes: ['ninja', 'trapper', 'forest-archer']
|
||||
'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', 'programming-book': 'programmaticon-i'}
|
||||
allowedHeroes: ['ninja', 'trapper', 'forest-archer']
|
||||
|
||||
# Wizard branch
|
||||
'arcane-ally':
|
||||
requiredGear: {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', 'programming-book': 'programmaticon-i'}
|
||||
allowedHeroes: ['captain', 'knight', 'samurai']
|
||||
'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: {'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
|
||||
allowedHeroes: ['librarian', 'potion-master', 'sorcerer']
|
||||
'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', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
|
||||
requiredCode: ['canCast']
|
||||
allowedHeroes: ['librarian', 'potion-master', 'sorcerer']
|
||||
|
||||
'coinucopia':
|
||||
requiredGear: {'programming-book': 'programmaticon-ii', feet: 'leather-boots', flag: 'basic-flags'}
|
||||
restrictedGear: {'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
|
||||
'copper-meadows':
|
||||
requiredGear: {'programming-book': 'programmaticon-ii', feet: 'leather-boots', flag: 'basic-flags', eyes: 'wooden-glasses'}
|
||||
restrictedGear: {'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
|
||||
'drop-the-flag':
|
||||
requiredGear: {'programming-book': 'programmaticon-ii', feet: 'leather-boots', flag: 'basic-flags', eyes: 'wooden-glasses', 'right-hand': 'crude-builders-hammer'}
|
||||
restrictedGear: {'right-hand': 'long-sword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
|
||||
'deadly-pursuit':
|
||||
requiredGear: {'programming-book': 'programmaticon-ii', feet: 'leather-boots', flag: 'basic-flags', eyes: 'wooden-glasses', 'right-hand': 'crude-builders-hammer'}
|
||||
restrictedGear: {'right-hand': 'long-sword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
|
||||
'rich-forager':
|
||||
requiredGear: {'programming-book': 'programmaticon-ii', feet: 'leather-boots', flag: 'basic-flags', eyes: 'wooden-glasses', torso: 'tarnished-bronze-breastplate', 'right-hand': 'long-sword', 'left-hand': 'bronze-shield'}
|
||||
restrictedGear: {'right-hand': 'crude-builders-hammer', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
|
||||
'multiplayer-treasure-grove':
|
||||
requiredGear: {'programming-book': 'programmaticon-ii', feet: 'leather-boots', flag: 'basic-flags', eyes: 'wooden-glasses', torso: 'tarnished-bronze-breastplate'}
|
||||
restrictedGear: {'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
|
||||
'siege-of-stonehold':
|
||||
requiredGear: {}
|
||||
restrictedGear: {}
|
||||
|
||||
# Desert
|
||||
'the-dunes':
|
||||
requiredGear: {}
|
||||
restrictedGear: {}
|
||||
'the-mighty-sand-yak':
|
||||
requiredGear: {neck: 'rough-sense-stone'}
|
||||
restrictedGear: {flag: 'basic-flags'}
|
||||
'oasis':
|
||||
requiredGear: {neck: 'rough-sense-stone'}
|
||||
restrictedGear: {flag: 'basic-flags'}
|
||||
'sarven-road':
|
||||
requiredGear: {neck: 'rough-sense-stone'}
|
||||
restrictedGear: {flag: 'basic-flags'}
|
||||
'sarven-gaps':
|
||||
requiredGear: {'right-hand': 'crude-builders-hammer', neck: 'rough-sense-stone'}
|
||||
restrictedGear: {flag: 'basic-flags'}
|
||||
'thunderhooves':
|
||||
requiredGear: {'right-hand': 'crude-builders-hammer', neck: 'rough-sense-stone'}
|
||||
restrictedGear: {flag: 'basic-flags'}
|
||||
'medical-attention':
|
||||
requiredGear: {'right-hand': 'long-sword', neck: 'polished-sense-stone'}
|
||||
restrictedGear: {'right-hand': 'crude-builders-hammer', flag: 'basic-flags', neck: 'rough-sense-stone'}
|
||||
'minesweeper':
|
||||
requiredGear: {neck: 'polished-sense-stone'}
|
||||
restrictedGear: {flag: 'basic-flags', neck: 'rough-sense-stone'}
|
||||
'sarven-sentry':
|
||||
requiredGear: {'right-hand': 'crude-builders-hammer', flag: 'basic-flags', neck: 'polished-sense-stone'}
|
||||
restrictedGear: {}
|
||||
'keeping-time':
|
||||
requiredGear: {wrists: 'simple-wristwatch'}
|
||||
restrictedGear: {wrists: 'sundial-wristwatch'}
|
||||
'hoarding-gold':
|
||||
requiredGear: {neck: 'quartz-sense-stone'}
|
||||
restrictedGear: {neck: 'polished-sense-stone'}
|
||||
'decoy-drill':
|
||||
requiredGear: {'right-hand': 'wooden-builders-hammer', neck: 'quartz-sense-stone'}
|
||||
restrictedGear: {neck: 'polished-sense-stone'}
|
||||
'yakstraction':
|
||||
requiredGear: {'right-hand': 'wooden-builders-hammer', flag: 'basic-flags'}
|
||||
restrictedGear: {'right-hand': 'crude-builders-hammer'}
|
||||
'sarven-brawl':
|
||||
requiredGear: {}
|
||||
restrictedGear: {}
|
|
@ -1,10 +1,10 @@
|
|||
CocoClass = require 'core/CocoClass'
|
||||
PlayHeroesModal = require 'views/play/modal/PlayHeroesModal'
|
||||
InventoryModal = require 'views/play/menu/InventoryModal'
|
||||
Level = require 'models/Level'
|
||||
LevelSession = require 'models/LevelSession'
|
||||
SuperModel = require 'models/SuperModel'
|
||||
ThangType = require 'models/ThangType'
|
||||
LevelOptions = require 'lib/LevelOptions'
|
||||
|
||||
lastHeroesEarned = me.get('earned')?.heroes ? []
|
||||
lastHeroesPurchased = me.get('purchased')?.heroes ? []
|
||||
|
@ -22,27 +22,39 @@ module.exports = class LevelSetupManager extends CocoClass
|
|||
@loadSession()
|
||||
|
||||
loadSession: ->
|
||||
url = "/db/level/#{@options.levelID}/session"
|
||||
#url += "?team=#{@team}" if @options.team # TODO: figure out how to get the teams for multiplayer PVP hero style
|
||||
@session = new LevelSession().setURL url
|
||||
levelURL = "/db/level/#{@options.levelID}"
|
||||
@level = new Level().setURL levelURL
|
||||
@level = @supermodel.loadModel(@level, 'level').model
|
||||
onLevelSync = ->
|
||||
return if @destroyed
|
||||
if @waitingToLoadModals
|
||||
@waitingToLoadModals = false
|
||||
@loadModals()
|
||||
onLevelSync.call @ if @level.loaded
|
||||
|
||||
sessionURL = "#{levelURL}/session"
|
||||
#sessionURL += "?team=#{@team}" if @options.team # TODO: figure out how to get the teams for multiplayer PVP hero style
|
||||
@session = new LevelSession().setURL sessionURL
|
||||
onSessionSync = ->
|
||||
return if @destroyed
|
||||
@session.url = -> '/db/level.session/' + @id
|
||||
@fillSessionWithDefaults()
|
||||
@listenToOnce @session, 'sync', onSessionSync
|
||||
@session = @supermodel.loadModel(@session, 'level_session').model
|
||||
if @session.loaded
|
||||
onSessionSync.call @
|
||||
onSessionSync.call @ if @session.loaded
|
||||
|
||||
fillSessionWithDefaults: ->
|
||||
heroConfig = _.merge {}, me.get('heroConfig'), @session.get('heroConfig')
|
||||
@session.set('heroConfig', heroConfig)
|
||||
@loadModals()
|
||||
if @level.loaded
|
||||
@loadModals()
|
||||
else
|
||||
@waitingToLoadModals = true
|
||||
|
||||
loadModals: ->
|
||||
# build modals and prevent them from disappearing.
|
||||
@heroesModal = new PlayHeroesModal({supermodel: @supermodel, session: @session, confirmButtonI18N: 'play.next', levelID: @options.levelID, hadEverChosenHero: @options.hadEverChosenHero})
|
||||
@inventoryModal = new InventoryModal({supermodel: @supermodel, session: @session, levelID: @options.levelID})
|
||||
@heroesModal = new PlayHeroesModal({supermodel: @supermodel, session: @session, confirmButtonI18N: 'play.next', level: @level, hadEverChosenHero: @options.hadEverChosenHero})
|
||||
@inventoryModal = new InventoryModal({supermodel: @supermodel, session: @session, level: @level})
|
||||
@heroesModalDestroy = @heroesModal.destroy
|
||||
@inventoryModalDestroy = @inventoryModal.destroy
|
||||
@heroesModal.destroy = @inventoryModal.destroy = _.noop
|
||||
|
@ -62,7 +74,7 @@ module.exports = class LevelSetupManager extends CocoClass
|
|||
not _.isEqual(lastHeroesPurchased, me.get('purchased')?.heroes ? []))
|
||||
console.log 'Showing hero picker because heroes earned/purchased has changed.'
|
||||
firstModal = @heroesModal
|
||||
else if allowedHeroSlugs = LevelOptions[@options.levelID]?.allowedHeroes
|
||||
else if allowedHeroSlugs = @level.get 'allowedHeroes'
|
||||
unless _.find(allowedHeroSlugs, (slug) -> ThangType.heroes[slug] is me.get('heroConfig')?.thangType)
|
||||
firstModal = @heroesModal
|
||||
lastHeroesEarned = me.get('earned')?.heroes ? []
|
||||
|
|
|
@ -5,11 +5,11 @@ c.extendNamedProperties CampaignSchema # name first
|
|||
|
||||
_.extend CampaignSchema.properties, {
|
||||
i18n: {type: 'object', title: 'i18n', format: 'i18n', props: ['name', 'body']}
|
||||
|
||||
|
||||
ambientSound: c.object {},
|
||||
mp3: { type: 'string', format: 'sound-file' }
|
||||
ogg: { type: 'string', format: 'sound-file' }
|
||||
|
||||
|
||||
backgroundImage: c.array {}, {
|
||||
type: 'object'
|
||||
additionalProperties: false
|
||||
|
@ -20,7 +20,7 @@ _.extend CampaignSchema.properties, {
|
|||
}
|
||||
backgroundColor: { type: 'string' }
|
||||
backgroundColorTransparent: { type: 'string' }
|
||||
|
||||
|
||||
adjacentCampaigns: { type: 'object', format: 'campaigns', additionalProperties: {
|
||||
title: 'Campaign'
|
||||
type: 'object'
|
||||
|
@ -32,7 +32,7 @@ _.extend CampaignSchema.properties, {
|
|||
description: { type: 'string', format: 'hidden' }
|
||||
i18n: { type: 'object', format: 'hidden' }
|
||||
slug: { type: 'string', format: 'hidden' }
|
||||
|
||||
|
||||
#- normal properties
|
||||
position: c.point2d()
|
||||
rotation: { type: 'number', format: 'degrees' }
|
||||
|
@ -40,13 +40,13 @@ _.extend CampaignSchema.properties, {
|
|||
showIfUnlocked: { type: 'string', links: [{rel: 'db', href: '/db/level/{($)}/version'}], format: 'latest-version-original-reference' }
|
||||
}
|
||||
}}
|
||||
|
||||
|
||||
levels: { type: 'object', format: 'levels', additionalProperties: {
|
||||
title: 'Level'
|
||||
type: 'object'
|
||||
format: 'level'
|
||||
additionalProperties: false
|
||||
|
||||
|
||||
# key is the original property
|
||||
properties: {
|
||||
#- denormalized from Level
|
||||
|
@ -58,6 +58,7 @@ _.extend CampaignSchema.properties, {
|
|||
original: { type: 'string', format: 'hidden' }
|
||||
adventurer: { type: 'boolean' }
|
||||
practice: { type: 'boolean' }
|
||||
adminOnly: { type: 'boolean' }
|
||||
disableSpaces: { type: 'boolean' }
|
||||
hidesSubmitUntilRun: { type: 'boolean' }
|
||||
hidesPlayButton: { type: 'boolean' }
|
||||
|
@ -72,8 +73,8 @@ _.extend CampaignSchema.properties, {
|
|||
realTimeSpeedFactor: { type: 'number' }
|
||||
autocompleteFontSizePx: { type: 'number' }
|
||||
|
||||
requiredCode: c.array {}, {
|
||||
type: 'string'
|
||||
requiredCode: c.array {}, {
|
||||
type: 'string'
|
||||
}
|
||||
suspectCode: c.array {}, {
|
||||
type: 'object'
|
||||
|
@ -82,17 +83,17 @@ _.extend CampaignSchema.properties, {
|
|||
pattern: { type: 'string' }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
requiredGear: { type: 'object', additionalProperties: {
|
||||
type: 'array'
|
||||
items: { type: 'string', links: [{rel: 'db', href: '/db/thang.type/{($)}/version'}], format: 'latest-version-original-reference' }
|
||||
}}
|
||||
restrictedGear: { type: 'object', additionalProperties: {
|
||||
type: 'array'
|
||||
type: 'array'
|
||||
items: { type: 'string', links: [{rel: 'db', href: '/db/thang.type/{($)}/version'}], format: 'latest-version-original-reference' }
|
||||
}}
|
||||
allowedHeroes: { type: 'array', items: {
|
||||
type: 'string', links: [{rel: 'db', href: '/db/thang.type/{($)}/version'}], format: 'latest-version-original-reference'
|
||||
allowedHeroes: { type: 'array', items: {
|
||||
type: 'string', links: [{rel: 'db', href: '/db/thang.type/{($)}/version'}], format: 'latest-version-original-reference'
|
||||
}}
|
||||
|
||||
#- denormalized from Achievements
|
||||
|
@ -106,6 +107,7 @@ _.extend CampaignSchema.properties, {
|
|||
level: { type: 'string', links: [{rel: 'db', href: '/db/level/{($)}/version'}], format: 'latest-version-original-reference' }
|
||||
type: { enum: ['heroes', 'items', 'levels'] }
|
||||
}}
|
||||
campaign: c.shortString title: 'Campaign', description: 'Which campaign this level is part of (like "desert").', format: 'hidden' # Automatically set by campaign editor.
|
||||
|
||||
#- normal properties
|
||||
position: c.point2d()
|
||||
|
|
|
@ -295,10 +295,15 @@ _.extend LevelSchema.properties,
|
|||
showsGuide: c.shortString(title: 'Shows Guide', description: 'If the guide is shown at the beginning of the level.', 'enum': ['first-time', 'always'])
|
||||
requiresSubscription: {title: 'Requires Subscription', description: 'Whether this level is available to subscribers only.', type: 'boolean'}
|
||||
tasks: c.array {title: 'Tasks', description: 'Tasks to be completed for this level.', default: (name: t for t in defaultTasks)}, c.task
|
||||
helpVideos: c.array {title: 'Help Videos'}, c.object {default: {style: 'eccentric', url: '', free: false}},
|
||||
style: c.shortString title: 'Style', description: 'Like: original, eccentric, scripted, edited, etc.'
|
||||
free: {type: 'boolean', title: 'Free', description: 'Whether this video is freely available to all players without a subscription.'}
|
||||
url: c.url {title: 'URL', description: 'Link to the video on Vimeo.'}
|
||||
|
||||
# Admin flags
|
||||
adventurer: { type: 'boolean' }
|
||||
practice: { type: 'boolean' }
|
||||
adminOnly: { type: 'boolean' }
|
||||
disableSpaces: { type: 'boolean' }
|
||||
hidesSubmitUntilRun: { type: 'boolean' }
|
||||
hidesPlayButton: { type: 'boolean' }
|
||||
|
@ -323,15 +328,18 @@ _.extend LevelSchema.properties,
|
|||
}
|
||||
}
|
||||
requiredGear: { type: 'object', additionalProperties: {
|
||||
type: 'string'
|
||||
type: 'array'
|
||||
items: { type: 'string', links: [{rel: 'db', href: '/db/thang.type/{($)}/version'}], format: 'latest-version-original-reference' }
|
||||
}}
|
||||
restrictedGear: { type: 'object', additionalProperties: {
|
||||
type: 'string'
|
||||
type: 'array'
|
||||
items: { type: 'string', links: [{rel: 'db', href: '/db/thang.type/{($)}/version'}], format: 'latest-version-original-reference' }
|
||||
}}
|
||||
allowedHeroes: { type: 'array', items: {
|
||||
type: 'string'
|
||||
type: 'string', links: [{rel: 'db', href: '/db/thang.type/{($)}/version'}], format: 'latest-version-original-reference'
|
||||
}}
|
||||
|
||||
campaign: c.shortString title: 'Campaign', description: 'Which campaign this level is part of (like "desert").', format: 'hidden' # Automatically set by campaign editor.
|
||||
|
||||
c.extendBasicProperties LevelSchema, 'level'
|
||||
c.extendSearchableProperties LevelSchema
|
||||
c.extendVersionedProperties LevelSchema, 'level'
|
||||
|
|
|
@ -105,8 +105,6 @@ module.exports =
|
|||
'playback:ended-changed': c.object {required: ['ended']},
|
||||
ended: {type: 'boolean'}
|
||||
|
||||
'level:play-next-level': c.object {}
|
||||
|
||||
'level:toggle-playing': c.object {}
|
||||
|
||||
'level:toggle-grid': c.object {}
|
||||
|
|
|
@ -2,17 +2,9 @@
|
|||
@import "app/styles/bootstrap/variables"
|
||||
|
||||
$mapHeight: 1536
|
||||
$forestMapWidth: 2500
|
||||
$dungeonMapWidth: 2350
|
||||
$desertMapWidth: 2350
|
||||
$desertMapSeaBackground: rgba(113, 186, 208, 1)
|
||||
$desertMapSeaBackgroundTransparent: rgba(113, 186, 208, 0)
|
||||
$forestMapSeaBackground: rgba(113, 186, 208, 1)
|
||||
$forestMapSeaBackgroundTransparent: rgba(113, 186, 208, 0)
|
||||
$dungeonMapCaveBackground: rgba(68, 54, 45, 1)
|
||||
$dungeonMapCaveBackgroundTransparent: rgba(68, 54, 45, 0)
|
||||
$mapWidth: 2350
|
||||
$levelDotWidth: 2%
|
||||
$levelDotHeight: $levelDotWidth * $forestMapWidth / $mapHeight
|
||||
$levelDotHeight: $levelDotWidth * $mapWidth / $mapHeight
|
||||
$levelDotZ: $levelDotHeight * 0.25
|
||||
$levelDotHoverZ: $levelDotZ * 2
|
||||
$levelDotShadowWidth: 0.8 * $levelDotWidth
|
||||
|
@ -63,51 +55,6 @@ $gameControlMargin: 30px
|
|||
&.left-gradient
|
||||
left: 0
|
||||
|
||||
&.desert
|
||||
background-color: $desertMapSeaBackground
|
||||
|
||||
.top-gradient
|
||||
background: linear-gradient(to bottom, $desertMapSeaBackground 0%, $desertMapSeaBackgroundTransparent 100%)
|
||||
|
||||
.right-gradient
|
||||
background: linear-gradient(to left, $desertMapSeaBackground 0%, $desertMapSeaBackgroundTransparent 100%)
|
||||
|
||||
.bottom-gradient
|
||||
background: linear-gradient(to top, $desertMapSeaBackground 0%, $desertMapSeaBackgroundTransparent 100%)
|
||||
|
||||
.left-gradient
|
||||
background: linear-gradient(to right, $desertMapSeaBackground 0%, $desertMapSeaBackgroundTransparent 100%)
|
||||
|
||||
&.forest
|
||||
background-color: $forestMapSeaBackground
|
||||
|
||||
.top-gradient
|
||||
background: linear-gradient(to bottom, $forestMapSeaBackground 0%, $forestMapSeaBackgroundTransparent 100%)
|
||||
|
||||
.right-gradient
|
||||
background: linear-gradient(to left, $forestMapSeaBackground 0%, $forestMapSeaBackgroundTransparent 100%)
|
||||
|
||||
.bottom-gradient
|
||||
background: linear-gradient(to top, $forestMapSeaBackground 0%, $forestMapSeaBackgroundTransparent 100%)
|
||||
|
||||
.left-gradient
|
||||
background: linear-gradient(to right, $forestMapSeaBackground 0%, $forestMapSeaBackgroundTransparent 100%)
|
||||
|
||||
&.dungeon
|
||||
background-color: $dungeonMapCaveBackground
|
||||
|
||||
.top-gradient
|
||||
background: linear-gradient(to bottom, $dungeonMapCaveBackground 0%, $dungeonMapCaveBackgroundTransparent 100%)
|
||||
|
||||
.right-gradient
|
||||
background: linear-gradient(to left, $dungeonMapCaveBackground 0%, $dungeonMapCaveBackgroundTransparent 100%)
|
||||
|
||||
.bottom-gradient
|
||||
background: linear-gradient(to top, $dungeonMapCaveBackground 0%, $dungeonMapCaveBackgroundTransparent 100%)
|
||||
|
||||
.left-gradient
|
||||
background: linear-gradient(to right, $dungeonMapCaveBackground 0%, $dungeonMapCaveBackgroundTransparent 100%)
|
||||
|
||||
.map
|
||||
position: relative
|
||||
|
||||
|
@ -117,21 +64,6 @@ $gameControlMargin: 30px
|
|||
background-size: 100%
|
||||
@include user-select(none)
|
||||
|
||||
&.map-dungeon
|
||||
background-image: url('/images/pages/play/map_dungeon_1920.jpg')
|
||||
@media screen and ( max-width: 1366px )
|
||||
background-image: url('/images/pages/play/map_dungeon_1366.jpg')
|
||||
|
||||
&.map-forest
|
||||
background-image: url('/images/pages/play/map_forest_1920.jpg')
|
||||
@media screen and ( max-width: 1366px )
|
||||
background-image: url('/images/pages/play/map_forest_1366.jpg')
|
||||
|
||||
&.map-desert
|
||||
background-image: url('/images/pages/play/map_desert_1920.jpg')
|
||||
@media screen and ( max-width: 1366px )
|
||||
background-image: url('/images/pages/play/map_desert_1366.jpg')
|
||||
|
||||
.level, .level-shadow
|
||||
position: absolute
|
||||
border-radius: 100%
|
||||
|
@ -301,27 +233,6 @@ $gameControlMargin: 30px
|
|||
|
||||
&:hover
|
||||
text-decoration: none
|
||||
|
||||
&#desert-link
|
||||
left: 90%
|
||||
top: 18.5%
|
||||
transform: scaleY(-1.5) scaleX(1.5)
|
||||
|
||||
&#forest-back-link
|
||||
left: 2%
|
||||
top: 70.5%
|
||||
transform: rotate(216deg)
|
||||
|
||||
&#forest-link
|
||||
left: 94.5%
|
||||
top: 7%
|
||||
transform: rotate(-35deg)
|
||||
|
||||
&#dungeon-link
|
||||
left: 9%
|
||||
top: 54.5%
|
||||
transform: rotate(180deg)
|
||||
color: fuchsia
|
||||
|
||||
.game-controls
|
||||
position: absolute
|
||||
|
|
|
@ -19,10 +19,6 @@
|
|||
float: right
|
||||
margin-left: 10px
|
||||
|
||||
.next-level-button, .world-map-button
|
||||
float: right
|
||||
margin-left: 10px
|
||||
|
||||
.rating
|
||||
float: left
|
||||
position: relative
|
||||
|
|
|
@ -3,24 +3,23 @@
|
|||
.gradient.vertical-gradient.right-gradient
|
||||
.gradient.horizontal-gradient.bottom-gradient
|
||||
.gradient.vertical-gradient.left-gradient
|
||||
.map-background(class="map-"+mapType alt="", draggable="false")
|
||||
.map-background(alt="", draggable="false")
|
||||
|
||||
each level in levels
|
||||
if !level.hidden
|
||||
- var next = nextLevel && level.slug === nextLevel;
|
||||
div(style="left: #{level.position.x}%; bottom: #{level.position.y}%; background-color: #{level.color}", class="level" + (next ? " next" : "") + (level.disabled ? " disabled" : "") + (level.locked ? " locked" : "") + " " + levelStatusMap[level.original] || "", data-level-id=level.original, title=level.name + (level.disabled ? ' (Coming Soon to Adventurers)' : ''))
|
||||
div(style="left: #{level.position.x}%; bottom: #{level.position.y}%; background-color: #{level.color}", class="level" + (level.next ? " next" : "") + (level.disabled ? " disabled" : "") + (level.locked ? " locked" : "") + " " + levelStatusMap[level.slug] || "", data-level-slug=level.slug, title=level.name + (level.disabled ? ' (Coming Soon to Adventurers)' : ''))
|
||||
if level.unlocksHero && !level.unlockedHero
|
||||
img.hero-portrait(src=level.unlocksHero.img)
|
||||
a(href=level.type == 'hero' ? '#' : level.disabled ? "/play" : "/play/#{level.levelPath || 'level'}/#{level.original}", disabled=level.disabled, data-level-id=level.original, data-level-path=level.levelPath || 'level', data-level-name=level.name)
|
||||
a(href=level.type == 'hero' ? '#' : level.disabled ? "/play" : "/play/#{level.levelPath || 'level'}/#{level.slug}", disabled=level.disabled, data-level-slug=level.slug, data-level-path=level.levelPath || 'level', data-level-name=level.name)
|
||||
if level.requiresSubscription
|
||||
img.star(src="/images/pages/play/star.png")
|
||||
if levelStatusMap[level.original] === 'complete'
|
||||
if levelStatusMap[level.slug] === 'complete'
|
||||
img.banner(src="/images/pages/play/level-banner-complete.png")
|
||||
if levelStatusMap[level.original] === 'started'
|
||||
if levelStatusMap[level.slug] === 'started'
|
||||
img.banner(src="/images/pages/play/level-banner-started.png")
|
||||
div(style="left: #{level.position.x}%; bottom: #{level.position.y}%", class="level-shadow" + (next ? " next" : "") + " " + levelStatusMap[level.original] || "")
|
||||
.level-info-container(data-level-id=level.original, data-level-path=level.levelPath || 'level', data-level-name=level.name)
|
||||
div(class="level-info " + (levelStatusMap[level.original] || ""))
|
||||
div(style="left: #{level.position.x}%; bottom: #{level.position.y}%", class="level-shadow" + (level.next ? " next" : "") + " " + levelStatusMap[level.slug] || "")
|
||||
.level-info-container(data-level-slug=level.slug, data-level-path=level.levelPath || 'level', data-level-name=level.name)
|
||||
div(class="level-info " + (levelStatusMap[level.slug] || ""))
|
||||
h3= level.name + (level.disabled ? " (Coming soon!)" : (level.locked ? " (Locked)" : ""))
|
||||
.level-description= level.description
|
||||
if level.disabled
|
||||
|
@ -30,7 +29,7 @@
|
|||
strong(data-i18n="play.awaiting_levels_adventurer") Sign up as an Adventurer
|
||||
span.spl(data-i18n="play.awaiting_levels_adventurer_suffix") to be the first to play new levels.
|
||||
|
||||
- var playCount = levelPlayCountMap[level.original]
|
||||
- var playCount = levelPlayCountMap[level.slug]
|
||||
if playCount && playCount.sessions > 20
|
||||
div
|
||||
span.spr #{playCount.sessions}
|
||||
|
@ -42,8 +41,8 @@
|
|||
button.btn.btn-success.btn-lg.start-level(data-i18n="common.play") Play
|
||||
|
||||
for adjacentCampaign in adjacentCampaigns
|
||||
a
|
||||
span.glyphicon.glyphicon-share-alt.campaign-switch(href="/play/"+adjacentCampaign.slug, style=adjacentCampaign.style, title=adjacentCampaign.name, data-campaign-id=adjacentCampaign.id)
|
||||
a(href=(editorMode ? "/editor/campaign/" : "/play/") + adjacentCampaign.slug)
|
||||
span.glyphicon.glyphicon-share-alt.campaign-switch(style=adjacentCampaign.style, title=adjacentCampaign.name, data-campaign-id=adjacentCampaign.id)
|
||||
|
||||
.game-controls.header-font
|
||||
button.btn.items(data-toggle='coco-modal', data-target='play/modal/PlayItemsModal', data-i18n="[title]play.items")
|
||||
|
|
|
@ -15,10 +15,6 @@ block modal-footer-content
|
|||
.ladder-submission-view
|
||||
else if level.get('type') === 'ladder'
|
||||
a.btn.btn-primary(href="/play/ladder/#{level.get('slug')}#my-matches", data-dismiss="modal", data-i18n="play_level.victory_return_to_ladder") Return to Ladder
|
||||
else if level.get('type', true) === 'hero'
|
||||
a.btn.btn-success.world-map-button(href="/play-hero", data-dismiss="modal", data-i18n="play_level.victory_play_continue") Continue
|
||||
else if hasNextLevel
|
||||
button.btn.btn-success.next-level-button(data-dismiss="modal", data-i18n="play_level.victory_play_next_level") Play Next Level
|
||||
else
|
||||
a.btn.btn-primary(href="/", data-dismiss="modal", data-i18n="play_level.victory_go_home") Go Home
|
||||
if me.get('anonymous')
|
||||
|
@ -44,9 +40,3 @@ block modal-footer-content
|
|||
.fb-like(data-href="https://www.facebook.com/codecombat", data-send="false", data-layout="button_count", data-width="350", data-show-faces="true", data-ref="coco_victory_#{fbRef}")
|
||||
a.twitter-follow-button(href="https://twitter.com/CodeCombat", data-show-count="true", data-show-screen-name="false", data-dnt="true", data-align="right", data-i18n="nav.twitter_follow") Follow
|
||||
iframe.github-star-button(src="http://ghbtns.com/github-btn.html?user=codecombat&repo=codecombat&type=watch&count=true", allowtransparency="true", frameborder="0", scrolling="0", width="110", height="20")
|
||||
|
||||
if showHourOfCodeDoneButton
|
||||
h3.pull-left(data-i18n="play_level.victory_hour_of_code_done") Are You Done?
|
||||
a(href="http://code.org/api/hour/finish")
|
||||
strong(data-i18n="play_level.victory_hour_of_code_done_yes") Yes, I'm finished with my Hour of Code!
|
||||
img(src="/images/level/csedweek-logo-final-small.jpg", alt="CS Ed Week Hour of Code", title="I'm finished with my Hour of Code", width=80)
|
||||
|
|
|
@ -53,6 +53,6 @@ module.exports = class HomeView extends RootView
|
|||
if elapsed < 5 * 60 * 1000
|
||||
me.set 'hourOfCode', true
|
||||
me.patch()
|
||||
# We may also insert the tracking pixel for everyone on the WorldMapView so as to count directly-linked visitors.
|
||||
# We may also insert the tracking pixel for everyone on the CampaignView so as to count directly-linked visitors.
|
||||
$('body').append($('<img src="http://code.org/api/hour/begin_codecombat.png" style="visibility: hidden;">'))
|
||||
application.tracker?.trackEvent 'Hour of Code Begin', {}
|
||||
|
|
|
@ -92,7 +92,7 @@ module.exports = class AuthModal extends ModalView
|
|||
Backbone.Mediator.publish "auth:signed-up", {}
|
||||
window.tracker?.trackEvent 'Finished Signup', label: 'CodeCombat'
|
||||
@enableModalInProgress(@$el)
|
||||
createUser userObject, null, window.nextLevelURL
|
||||
createUser userObject, null, window.nextURL
|
||||
|
||||
onLoggingInWithFacebook: (e) ->
|
||||
modal = $('.modal:visible', @$el)
|
||||
|
|
|
@ -12,61 +12,19 @@ RelatedAchievementsCollection = require 'collections/RelatedAchievementsCollecti
|
|||
CampaignLevelView = require './CampaignLevelView'
|
||||
|
||||
achievementProject = ['related', 'rewards', 'name', 'slug']
|
||||
thangTypeProject = ['name', 'original', 'slug']
|
||||
thangTypeProject = ['name', 'original']
|
||||
|
||||
module.exports = class CampaignEditorView extends RootView
|
||||
id: "campaign-editor-view"
|
||||
template: require 'templates/editor/campaign/campaign-editor-view'
|
||||
className: 'editor'
|
||||
|
||||
|
||||
events:
|
||||
'click #save-button': 'onClickSaveButton'
|
||||
|
||||
constructor: (options, @campaignHandle) ->
|
||||
super(options)
|
||||
|
||||
# MIGRATION CODE
|
||||
# for level in levels
|
||||
# _.extend level, options[level.id]
|
||||
# level.slug = level.id
|
||||
# delete level.id
|
||||
# delete level.nextLevels
|
||||
# level.position = { x: level.x, y: level.y }
|
||||
# delete level.x
|
||||
# delete level.y
|
||||
# if level.unlocksHero
|
||||
# level.unlocks = [{
|
||||
# original: level.unlocksHero.originalID
|
||||
# type: 'hero'
|
||||
# }]
|
||||
# delete level.unlocksHero
|
||||
# campaign.levels[level.original] = level
|
||||
# @campaign = new Campaign(campaign)
|
||||
#------------------------------------------------
|
||||
|
||||
@campaign = new Campaign({_id:@campaignHandle})
|
||||
|
||||
#--------------- temporary migration to change thang type slugs to originals
|
||||
#- should keep around though for loading the names of items and heroes that are referenced
|
||||
#- just load names instead of slugs, though
|
||||
@sluggyThangs = new Backbone.Collection()
|
||||
@listenToOnce @campaign, 'sync', ->
|
||||
slugs = []
|
||||
for level in _.values(@campaign.get('levels'))
|
||||
slugs = slugs.concat(_.values(level.requiredGear)) if level.requiredGear
|
||||
slugs = slugs.concat(_.values(level.restrictedGear)) if level.restrictedGear
|
||||
slugs = slugs.concat(level.allowedHeroes) if level.allowedHeroes
|
||||
slugs = _.uniq _.flatten slugs
|
||||
for slug in slugs
|
||||
thangType = new ThangType()
|
||||
thangType.setProjection(thangTypeProject)
|
||||
if utils.isID slug
|
||||
thangType.setURL("/db/thang.type/#{slug}/version")
|
||||
else
|
||||
thangType.setURL("/db/thang.type/#{slug}")
|
||||
@supermodel.loadModel(thangType, 'thang')
|
||||
@sluggyThangs.add(thangType)
|
||||
#---------------
|
||||
@supermodel.loadModel(@campaign, 'campaign')
|
||||
|
||||
@levels = new CocoCollection([], {
|
||||
|
@ -82,28 +40,41 @@ module.exports = class CampaignEditorView extends RootView
|
|||
project: achievementProject
|
||||
})
|
||||
@supermodel.loadCollection(@achievements, 'achievements')
|
||||
|
||||
|
||||
@toSave = new Backbone.Collection()
|
||||
@listenToOnce @campaign ,'sync', @loadThangTypeNames
|
||||
@listenToOnce @campaign, 'sync', @onFundamentalLoaded
|
||||
@listenToOnce @levels, 'sync', @onFundamentalLoaded
|
||||
@listenToOnce @achievements, 'sync', @onFundamentalLoaded
|
||||
|
||||
loadThangTypeNames: ->
|
||||
# Load the names of the ThangTypes that this level's Treema nodes might want to display.
|
||||
originals = []
|
||||
for level in _.values(@campaign.get('levels'))
|
||||
originals = originals.concat(_.values(level.requiredGear)) if level.requiredGear
|
||||
originals = originals.concat(_.values(level.restrictedGear)) if level.restrictedGear
|
||||
originals = originals.concat(level.allowedHeroes) if level.allowedHeroes
|
||||
originals = _.uniq _.flatten originals
|
||||
for original in originals
|
||||
thangType = new ThangType()
|
||||
thangType.setProjection(thangTypeProject)
|
||||
thangType.setURL("/db/thang.type/#{original}/version")
|
||||
@supermodel.loadModel(thangType, 'thang')
|
||||
|
||||
onFundamentalLoaded: ->
|
||||
# load any levels which
|
||||
# Load any levels which haven't been denormalized into our campaign.
|
||||
return unless @campaign.loaded and @levels.loaded and @achievements.loaded
|
||||
for level in _.values(@campaign.get('levels'))
|
||||
model = @levels.findWhere(original: level.original)
|
||||
if not model
|
||||
model = new Level({})
|
||||
model.setProjection Campaign.denormalizedLevelProperties
|
||||
model.setURL("/db/level/#{level.original}/version")
|
||||
@levels.add @supermodel.loadModel(model, 'level').model
|
||||
achievements = new RelatedAchievementsCollection level.original
|
||||
achievements.setProjection achievementProject
|
||||
@supermodel.loadCollection achievements, 'achievements'
|
||||
@listenToOnce achievements, 'sync', ->
|
||||
@achievements.add(achievements.models)
|
||||
|
||||
continue if model = @levels.findWhere(original: level.original)
|
||||
model = new Level({})
|
||||
model.setProjection Campaign.denormalizedLevelProperties
|
||||
model.setURL("/db/level/#{level.original}/version")
|
||||
@levels.add @supermodel.loadModel(model, 'level').model
|
||||
achievements = new RelatedAchievementsCollection level.original
|
||||
achievements.setProjection achievementProject
|
||||
@supermodel.loadCollection achievements, 'achievements'
|
||||
@listenToOnce achievements, 'sync', ->
|
||||
@achievements.add(achievements.models)
|
||||
|
||||
onLoaded: ->
|
||||
@toSave.add @campaign if @campaign.hasLocalChanges()
|
||||
|
@ -113,38 +84,6 @@ module.exports = class CampaignEditorView extends RootView
|
|||
campaignLevel = campaignLevels[levelOriginal]
|
||||
continue if not campaignLevel
|
||||
|
||||
#--------------- temporary migrations
|
||||
if campaignLevel.restrictedGear
|
||||
for slot, value of campaignLevel.restrictedGear
|
||||
if _.isString(value)
|
||||
campaignLevel.restrictedGear[slot] = [value]
|
||||
#
|
||||
if campaignLevel.requiredGear
|
||||
for slot, value of campaignLevel.requiredGear
|
||||
if _.isString(value)
|
||||
campaignLevel.requiredGear[slot] = [value]
|
||||
#
|
||||
if campaignLevel.requiredGear
|
||||
for gear in _.values(campaignLevel.requiredGear)
|
||||
for slug, index in gear
|
||||
thang = @sluggyThangs.findWhere({slug: slug})
|
||||
continue unless thang
|
||||
gear[index] = thang.get('original')
|
||||
#
|
||||
if campaignLevel.restrictedGear
|
||||
for gear in _.values(campaignLevel.restrictedGear)
|
||||
for slug, index in gear
|
||||
thang = @sluggyThangs.findWhere({slug: slug})
|
||||
continue unless thang
|
||||
gear[index] = thang.get('original')
|
||||
#
|
||||
if campaignLevel.allowedHeroes
|
||||
for slug, index in campaignLevel.allowedHeroes
|
||||
thang = @sluggyThangs.findWhere({slug: slug})
|
||||
continue unless thang
|
||||
level.allowedHeroes[index] = thang.get('original')
|
||||
#---------------
|
||||
|
||||
$.extend campaignLevel, _.omit(level.attributes, '_id')
|
||||
achievements = @achievements.where {'related': levelOriginal}
|
||||
rewards = []
|
||||
|
@ -152,38 +91,39 @@ module.exports = class CampaignEditorView extends RootView
|
|||
for rewardType, rewardArray of achievement.get('rewards')
|
||||
for reward in rewardArray
|
||||
rewardObject = { achievement: achievement.id }
|
||||
|
||||
|
||||
if rewardType is 'heroes'
|
||||
rewardObject.hero = reward
|
||||
thangType = new ThangType({}, {project: thangTypeProject})
|
||||
thangType.setURL("/db/thang.type/#{reward}/version")
|
||||
@supermodel.loadModel(thangType, 'thang')
|
||||
|
||||
|
||||
if rewardType is 'levels'
|
||||
rewardObject.level = reward
|
||||
if not @levels.findWhere({original: reward})
|
||||
level = new Level({}, {project: Campaign.denormalizedLevelProperties})
|
||||
level.setURL("/db/level/#{reward}/version")
|
||||
@supermodel.loadModel(level, 'level')
|
||||
|
||||
|
||||
if rewardType is 'items'
|
||||
rewardObject.item = reward
|
||||
thangType = new ThangType({}, {project: thangTypeProject})
|
||||
thangType.setURL("/db/thang.type/#{reward}/version")
|
||||
@supermodel.loadModel(thangType, 'thang')
|
||||
|
||||
|
||||
rewards.push rewardObject
|
||||
campaignLevel.rewards = rewards
|
||||
delete campaignLevel.unlocks
|
||||
campaignLevel.campaign = @campaign.get 'slug'
|
||||
campaignLevels[levelOriginal] = campaignLevel
|
||||
|
||||
|
||||
@campaign.set('levels', campaignLevels)
|
||||
|
||||
|
||||
for level in _.values campaignLevels
|
||||
model = @levels.findWhere {original: level.original}
|
||||
model.set key, level[key] for key in Campaign.denormalizedLevelProperties
|
||||
# @toSave.add model if model.hasLocalChanges()
|
||||
# @updateRewardsForLevel model, level.rewards
|
||||
@toSave.add model if model.hasLocalChanges()
|
||||
@updateRewardsForLevel model, level.rewards
|
||||
|
||||
super()
|
||||
|
||||
|
@ -195,12 +135,13 @@ module.exports = class CampaignEditorView extends RootView
|
|||
onClickSaveButton: ->
|
||||
@toSave.set @toSave.filter (m) -> m.hasLocalChanges()
|
||||
@openModalView new SaveCampaignModal({}, @toSave)
|
||||
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
treemaOptions =
|
||||
schema: Campaign.schema
|
||||
data: $.extend({}, @campaign.attributes)
|
||||
filePath: "db/campaign/#{@campaign.get('_id')}"
|
||||
callbacks:
|
||||
change: @onTreemaChanged
|
||||
select: @onTreemaSelectionChanged
|
||||
|
@ -224,7 +165,7 @@ module.exports = class CampaignEditorView extends RootView
|
|||
@listenTo @campaignView, 'adjacent-campaign-moved', @onAdjacentCampaignMoved
|
||||
@listenTo @campaignView, 'level-clicked', @onCampaignLevelClicked
|
||||
@insertSubView @campaignView
|
||||
|
||||
|
||||
onTreemaChanged: (e, nodes) =>
|
||||
for node in nodes
|
||||
path = node.getPath()
|
||||
|
@ -233,12 +174,12 @@ module.exports = class CampaignEditorView extends RootView
|
|||
original = parts[2]
|
||||
level = @supermodel.getModelByOriginal Level, original
|
||||
campaignLevel = @treema.get "/levels/#{original}"
|
||||
|
||||
|
||||
@updateRewardsForLevel level, campaignLevel.rewards
|
||||
|
||||
|
||||
level.set key, campaignLevel[key] for key in Campaign.denormalizedLevelProperties
|
||||
@toSave.add level if level.hasLocalChanges()
|
||||
|
||||
|
||||
@toSave.add @campaign
|
||||
@campaign.set key, value for key, value of @treema.data
|
||||
@campaignView.setCampaign(@campaign)
|
||||
|
@ -270,16 +211,16 @@ module.exports = class CampaignEditorView extends RootView
|
|||
rewardSubset = (r for r in rewards when r.achievement is achievement.id)
|
||||
oldRewards = achievement.get 'rewards'
|
||||
newRewards = {}
|
||||
|
||||
|
||||
heroes = _.compact((r.hero for r in rewardSubset))
|
||||
newRewards.heroes = heroes if heroes.length
|
||||
|
||||
|
||||
items = _.compact((r.item for r in rewardSubset))
|
||||
newRewards.items = items if items.length
|
||||
|
||||
levels = _.compact((r.level for r in rewardSubset))
|
||||
newRewards.levels = levels if levels.length
|
||||
|
||||
|
||||
newRewards.gems = oldRewards.gems if oldRewards.gems
|
||||
achievement.set 'rewards', newRewards
|
||||
if achievement.hasLocalChanges()
|
||||
|
@ -288,7 +229,7 @@ module.exports = class CampaignEditorView extends RootView
|
|||
class LevelsNode extends TreemaObjectNode
|
||||
valueClass: 'treema-levels'
|
||||
@levels: {}
|
||||
|
||||
|
||||
buildValueForDisplay: (valEl, data) ->
|
||||
@buildValueForDisplaySimply valEl, ''+_.size(data)
|
||||
|
||||
|
@ -310,7 +251,7 @@ class LevelNode extends TreemaObjectNode
|
|||
valueClass: 'treema-level'
|
||||
buildValueForDisplay: (valEl, data) ->
|
||||
@buildValueForDisplaySimply valEl, data.name
|
||||
|
||||
|
||||
populateData: ->
|
||||
return if @data.name?
|
||||
data = _.pick LevelsNode.levels[@keyForParent].attributes, Campaign.denormalizedLevelProperties
|
||||
|
@ -328,7 +269,7 @@ class CampaignsNode extends TreemaObjectNode
|
|||
childSource: (req, res) =>
|
||||
s = new Backbone.Collection([], {model:Campaign})
|
||||
s.url = '/db/campaign'
|
||||
s.fetch({data: {term:req.term, project: campaign.denormalizedCampaignProperties}})
|
||||
s.fetch({data: {term:req.term, project: Campaign.denormalizedCampaignProperties}})
|
||||
s.once 'sync', (collection) ->
|
||||
CampaignsNode.campaigns[campaign.id] = campaign for campaign in collection.models
|
||||
mapped = ({label: r.get('name'), value: r.id} for r in collection.models)
|
||||
|
@ -344,601 +285,6 @@ class CampaignNode extends TreemaObjectNode
|
|||
return if @data.name?
|
||||
data = _.pick CampaignsNode.campaigns[@keyForParent].attributes, Campaign.denormalizedCampaignProperties
|
||||
_.extend @data, data
|
||||
|
||||
|
||||
class AchievementNode extends treemaExt.IDReferenceNode
|
||||
buildSearchURL: (term) -> "#{@url}?term=#{term}&project=#{achievementProject.join(',')}"
|
||||
|
||||
|
||||
|
||||
|
||||
#campaign = {
|
||||
# name: 'Dungeon'
|
||||
# levels: {}
|
||||
#}
|
||||
#
|
||||
#
|
||||
#levels = [
|
||||
# {
|
||||
# name: 'Dungeons of Kithgard'
|
||||
# type: 'hero'
|
||||
# id: 'dungeons-of-kithgard'
|
||||
# original: '5411cb3769152f1707be029c'
|
||||
# description: 'Grab the gem, but touch nothing else. Start here.'
|
||||
# x: 14
|
||||
# y: 15.5
|
||||
# nextLevels:
|
||||
# continue: 'gems-in-the-deep'
|
||||
# }
|
||||
# {
|
||||
# name: 'Gems in the Deep'
|
||||
# type: 'hero'
|
||||
# id: 'gems-in-the-deep'
|
||||
# original: '54173c90844506ae0195a0b4'
|
||||
# description: 'Quickly collect the gems; you will need them.'
|
||||
# x: 29
|
||||
# y: 12
|
||||
# nextLevels:
|
||||
# continue: 'shadow-guard'
|
||||
# }
|
||||
# {
|
||||
# name: 'Shadow Guard'
|
||||
# type: 'hero'
|
||||
# id: 'shadow-guard'
|
||||
# original: '54174347844506ae0195a0b8'
|
||||
# description: 'Evade the Kithgard minion.'
|
||||
# x: 40.54
|
||||
# y: 11.03
|
||||
# nextLevels:
|
||||
# continue: 'forgetful-gemsmith'
|
||||
# }
|
||||
# {
|
||||
# name: 'Kounter Kithwise'
|
||||
# type: 'hero'
|
||||
# id: 'kounter-kithwise'
|
||||
# original: '54527a6257e83800009730c7'
|
||||
# description: 'Practice your evasion skills with more guards.'
|
||||
# x: 35.37
|
||||
# y: 20.61
|
||||
# nextLevels:
|
||||
# continue: 'crawlways-of-kithgard'
|
||||
# practice: true
|
||||
# requiresSubscription: true
|
||||
# }
|
||||
# {
|
||||
# name: 'Crawlways of Kithgard'
|
||||
# type: 'hero'
|
||||
# id: 'crawlways-of-kithgard'
|
||||
# original: '545287ef57e83800009730d5'
|
||||
# description: 'Dart in and grab the gem–at the right moment.'
|
||||
# x: 36.48
|
||||
# y: 29.03
|
||||
# nextLevels:
|
||||
# continue: 'forgetful-gemsmith'
|
||||
# practice: true
|
||||
# requiresSubscription: true
|
||||
# }
|
||||
# {
|
||||
# name: 'Forgetful Gemsmith'
|
||||
# type: 'hero'
|
||||
# id: 'forgetful-gemsmith'
|
||||
# original: '544a98f62d002f0000fe331a'
|
||||
# description: 'Grab even more gems as you practice moving.'
|
||||
# x: 54.98
|
||||
# y: 10.53
|
||||
# nextLevels:
|
||||
# continue: 'true-names'
|
||||
# }
|
||||
# {
|
||||
# name: 'True Names'
|
||||
# type: 'hero'
|
||||
# id: 'true-names'
|
||||
# original: '541875da4c16460000ab990f'
|
||||
# description: 'Learn an enemy\'s true name to defeat it.'
|
||||
# x: 68.44
|
||||
# y: 10.70
|
||||
# nextLevels:
|
||||
# continue: 'the-raised-sword'
|
||||
# unlocksHero: {
|
||||
# img: '/file/db/thang.type/53e12be0d042f23505c3023b/portrait.png'
|
||||
# originalID: '53e12be0d042f23505c3023b'
|
||||
# }
|
||||
# }
|
||||
# {
|
||||
# name: 'Favorable Odds'
|
||||
# type: 'hero'
|
||||
# id: 'favorable-odds'
|
||||
# original: '5452972f57e83800009730de'
|
||||
# description: 'Test out your battle skills by defeating more munchkins.'
|
||||
# x: 88.25
|
||||
# y: 14.92
|
||||
# nextLevels:
|
||||
# continue: 'the-raised-sword'
|
||||
# practice: true
|
||||
# requiresSubscription: true
|
||||
# }
|
||||
# {
|
||||
# name: 'The Raised Sword'
|
||||
# type: 'hero'
|
||||
# id: 'the-raised-sword'
|
||||
# original: '5418aec24c16460000ab9aa6'
|
||||
# description: 'Learn to equip yourself for combat.'
|
||||
# x: 81.51
|
||||
# y: 17.92
|
||||
# nextLevels:
|
||||
# continue: 'haunted-kithmaze'
|
||||
# }
|
||||
# {
|
||||
# name: 'Haunted Kithmaze'
|
||||
# type: 'hero'
|
||||
# id: 'haunted-kithmaze'
|
||||
# original: '545a5914d820eb0000f6dc0a'
|
||||
# description: 'The builders of Kithgard constructed many mazes to confuse travelers.'
|
||||
# x: 78
|
||||
# y: 29
|
||||
# nextLevels:
|
||||
# continue: 'the-second-kithmaze'
|
||||
# }
|
||||
# {
|
||||
# name: 'Riddling Kithmaze'
|
||||
# type: 'hero'
|
||||
# id: 'riddling-kithmaze'
|
||||
# original: '5418b9d64c16460000ab9ab4'
|
||||
# description: 'If at first you go astray, change your loop to find the way.'
|
||||
# x: 69.97
|
||||
# y: 28.03
|
||||
# nextLevels:
|
||||
# continue: 'descending-further'
|
||||
# practice: true
|
||||
# requiresSubscription: true
|
||||
# }
|
||||
# {
|
||||
# name: 'Descending Further'
|
||||
# type: 'hero'
|
||||
# id: 'descending-further'
|
||||
# original: '5452a84d57e83800009730e4'
|
||||
# description: 'Another day, another maze.'
|
||||
# x: 61.68
|
||||
# y: 22.80
|
||||
# nextLevels:
|
||||
# continue: 'the-second-kithmaze'
|
||||
# practice: true
|
||||
# requiresSubscription: true
|
||||
# }
|
||||
# {
|
||||
# name: 'The Second Kithmaze'
|
||||
# type: 'hero'
|
||||
# id: 'the-second-kithmaze'
|
||||
# original: '5418cf256bae62f707c7e1c3'
|
||||
# description: 'Many have tried, few have found their way through this maze.'
|
||||
# x: 54.49
|
||||
# y: 26.49
|
||||
# nextLevels:
|
||||
# continue: 'dread-door'
|
||||
# }
|
||||
# {
|
||||
# name: 'Dread Door'
|
||||
# type: 'hero'
|
||||
# id: 'dread-door'
|
||||
# original: '5418d40f4c16460000ab9ac2'
|
||||
# description: 'Behind a dread door lies a chest full of riches.'
|
||||
# x: 60.52
|
||||
# y: 33.70
|
||||
# nextLevels:
|
||||
# continue: 'known-enemy'
|
||||
# }
|
||||
# {
|
||||
# name: 'Known Enemy'
|
||||
# type: 'hero'
|
||||
# id: 'known-enemy'
|
||||
# original: '5452adea57e83800009730ee'
|
||||
# description: 'Begin to use variables in your battles.'
|
||||
# x: 67
|
||||
# y: 39
|
||||
# nextLevels:
|
||||
# continue: 'master-of-names'
|
||||
# }
|
||||
# {
|
||||
# name: 'Master of Names'
|
||||
# type: 'hero'
|
||||
# id: 'master-of-names'
|
||||
# original: '5452c3ce57e83800009730f7'
|
||||
# description: 'Use your glasses to defend yourself from the Kithmen.'
|
||||
# x: 75
|
||||
# y: 46
|
||||
# nextLevels:
|
||||
# continue: 'lowly-kithmen'
|
||||
# }
|
||||
# {
|
||||
# name: 'Lowly Kithmen'
|
||||
# type: 'hero'
|
||||
# id: 'lowly-kithmen'
|
||||
# original: '541b24511ccc8eaae19f3c1f'
|
||||
# description: 'Now that you can see them, they\'re everywhere!'
|
||||
# x: 85
|
||||
# y: 40
|
||||
# nextLevels:
|
||||
# continue: 'closing-the-distance'
|
||||
# }
|
||||
# {
|
||||
# name: 'Closing the Distance'
|
||||
# type: 'hero'
|
||||
# id: 'closing-the-distance'
|
||||
# original: '541b288e1ccc8eaae19f3c25'
|
||||
# description: 'Kithmen are not the only ones to stand in your way.'
|
||||
# x: 93
|
||||
# y: 47
|
||||
# nextLevels:
|
||||
# continue: 'the-final-kithmaze'
|
||||
# }
|
||||
# {
|
||||
# name: 'Tactical Strike'
|
||||
# type: 'hero'
|
||||
# id: 'tactical-strike'
|
||||
# original: '5452cfa706a59e000067e4f5'
|
||||
# description: 'They\'re, uh, coming right for us! Sneak up behind them.'
|
||||
# x: 83.23
|
||||
# y: 52.73
|
||||
# nextLevels:
|
||||
# continue: 'the-final-kithmaze'
|
||||
# practice: true
|
||||
# requiresSubscription: true
|
||||
# }
|
||||
# {
|
||||
# name: 'The Final Kithmaze'
|
||||
# type: 'hero'
|
||||
# id: 'the-final-kithmaze'
|
||||
# original: '541b434e1ccc8eaae19f3c33'
|
||||
# description: 'To escape you must find your way through an Elder Kithman\'s maze.'
|
||||
# x: 86.95
|
||||
# y: 64.70
|
||||
# nextLevels:
|
||||
# continue: 'kithgard-gates'
|
||||
# }
|
||||
# {
|
||||
# name: 'The Gauntlet'
|
||||
# type: 'hero'
|
||||
# id: 'the-gauntlet'
|
||||
# original: '5452d8b906a59e000067e4fa'
|
||||
# description: 'Rush for the stairs, battling foes at every turn.'
|
||||
# x: 76.50
|
||||
# y: 72.69
|
||||
# nextLevels:
|
||||
# continue: 'kithgard-gates'
|
||||
# practice: true
|
||||
# requiresSubscription: true
|
||||
# }
|
||||
# {
|
||||
# name: 'Kithgard Gates'
|
||||
# type: 'hero'
|
||||
# id: 'kithgard-gates'
|
||||
# original: '541c9a30c6362edfb0f34479'
|
||||
# description: 'Escape the Kithgard dungeons and don\'t let the guardians get you.'
|
||||
# x: 89
|
||||
# y: 82
|
||||
# nextLevels:
|
||||
# continue: 'defense-of-plainswood'
|
||||
# }
|
||||
# {
|
||||
# name: 'Cavern Survival'
|
||||
# type: 'hero-ladder'
|
||||
# id: 'cavern-survival'
|
||||
# original: '544437e0645c0c0000c3291d'
|
||||
# description: 'Stay alive longer than your opponent amidst hordes of ogres!'
|
||||
# x: 17.54
|
||||
# y: 78.39
|
||||
# }
|
||||
#]
|
||||
#
|
||||
#options =
|
||||
# 'dungeons-of-kithgard':
|
||||
# disableSpaces: true
|
||||
# hidesSubmitUntilRun: true
|
||||
# hidesPlayButton: true
|
||||
# hidesRunShortcut: true
|
||||
# hidesHUD: true
|
||||
# hidesSay: true
|
||||
# hidesCodeToolbar: true
|
||||
# hidesRealTimePlayback: true
|
||||
# requiredGear: {feet: 'simple-boots'}
|
||||
# restrictedGear: {feet: 'leather-boots'}
|
||||
# requiredCode: ['moveRight']
|
||||
# 'gems-in-the-deep':
|
||||
# disableSpaces: true
|
||||
# hidesSubmitUntilRun: true
|
||||
# hidesPlayButton: true
|
||||
# hidesRunShortcut: true
|
||||
# hidesHUD: true
|
||||
# hidesSay: true
|
||||
# hidesCodeToolbar: true
|
||||
# hidesRealTimePlayback: true
|
||||
# requiredGear: {feet: 'simple-boots'}
|
||||
# restrictedGear: {feet: 'leather-boots'}
|
||||
# 'shadow-guard':
|
||||
# disableSpaces: true
|
||||
# hidesSubmitUntilRun: true
|
||||
# hidesPlayButton: true
|
||||
# hidesRunShortcut: true
|
||||
# hidesHUD: true
|
||||
# hidesSay: true
|
||||
# hidesCodeToolbar: true
|
||||
# hidesRealTimePlayback: true
|
||||
# requiredGear: {feet: 'simple-boots'}
|
||||
# restrictedGear: {feet: 'leather-boots', 'right-hand': 'simple-sword'}
|
||||
# 'kounter-kithwise':
|
||||
# disableSpaces: true
|
||||
# hidesPlayButton: true
|
||||
# hidesRunShortcut: true
|
||||
# hidesHUD: true
|
||||
# hidesSay: true
|
||||
# hidesCodeToolbar: true
|
||||
# hidesRealTimePlayback: true
|
||||
# requiredGear: {feet: 'simple-boots'}
|
||||
# restrictedGear: {feet: 'leather-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i'}
|
||||
# 'crawlways-of-kithgard':
|
||||
# hidesPlayButton: true
|
||||
# hidesRunShortcut: true
|
||||
# hidesHUD: true
|
||||
# hidesSay: true
|
||||
# hidesCodeToolbar: true
|
||||
# hidesRealTimePlayback: true
|
||||
# requiredGear: {feet: 'simple-boots'}
|
||||
# restrictedGear: {feet: 'leather-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i'}
|
||||
# 'forgetful-gemsmith':
|
||||
# disableSpaces: true
|
||||
# hidesPlayButton: true
|
||||
# hidesRunShortcut: true
|
||||
# hidesHUD: true
|
||||
# hidesSay: true
|
||||
# hidesCodeToolbar: true
|
||||
# hidesRealTimePlayback: true
|
||||
# requiredGear: {feet: 'simple-boots'}
|
||||
# restrictedGear: {feet: 'leather-boots', 'programming-book': 'programmaticon-i'}
|
||||
# 'true-names':
|
||||
# disableSpaces: true
|
||||
# hidesPlayButton: true
|
||||
# hidesRunShortcut: true
|
||||
# hidesHUD: true
|
||||
# hidesSay: true
|
||||
# hidesCodeToolbar: true
|
||||
# hidesRealTimePlayback: true
|
||||
# requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', waist: 'leather-belt'}
|
||||
# restrictedGear: {feet: 'leather-boots', 'programming-book': 'programmaticon-i'}
|
||||
# requiredCode: ['Brak']
|
||||
# 'favorable-odds':
|
||||
# disableSpaces: true
|
||||
# hidesPlayButton: true
|
||||
# hidesRunShortcut: true
|
||||
# hidesHUD: true
|
||||
# hidesSay: true
|
||||
# hidesCodeToolbar: true
|
||||
# hidesRealTimePlayback: true
|
||||
# requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword'}
|
||||
# restrictedGear: {feet: 'leather-boots', 'programming-book': 'programmaticon-i'}
|
||||
# 'the-raised-sword':
|
||||
# disableSpaces: true
|
||||
# hidesPlayButton: true
|
||||
# hidesRunShortcut: true
|
||||
# hidesHUD: true
|
||||
# hidesSay: true
|
||||
# hidesCodeToolbar: true
|
||||
# hidesRealTimePlayback: true
|
||||
# requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'tarnished-bronze-breastplate'}
|
||||
# restrictedGear: {feet: 'leather-boots', 'programming-book': 'programmaticon-i'}
|
||||
# 'the-first-kithmaze':
|
||||
# hidesRunShortcut: true
|
||||
# hidesHUD: true
|
||||
# hidesSay: true
|
||||
# hidesCodeToolbar: true
|
||||
# hidesRealTimePlayback: true
|
||||
# requiredGear: {feet: 'simple-boots', 'programming-book': 'programmaticon-i'}
|
||||
# restrictedGear: {feet: 'leather-boots'}
|
||||
# requiredCode: ['loop']
|
||||
# 'haunted-kithmaze':
|
||||
# hidesRunShortcut: true
|
||||
# hidesHUD: true
|
||||
# hidesSay: true
|
||||
# hidesCodeToolbar: true
|
||||
# hidesRealTimePlayback: true
|
||||
# moveRightLoopSnippet: true
|
||||
# requiredGear: {feet: 'simple-boots', 'programming-book': 'programmaticon-i'}
|
||||
# restrictedGear: {feet: 'leather-boots'}
|
||||
# requiredCode: ['loop']
|
||||
# 'descending-further':
|
||||
# hidesHUD: true
|
||||
# hidesSay: true
|
||||
# hidesCodeToolbar: true
|
||||
# hidesRealTimePlayback: true
|
||||
# requiredGear: {feet: 'simple-boots', 'programming-book': 'programmaticon-i'}
|
||||
# restrictedGear: {feet: 'leather-boots'}
|
||||
# 'the-second-kithmaze':
|
||||
# hidesHUD: true
|
||||
# hidesSay: true
|
||||
# hidesCodeToolbar: true
|
||||
# hidesRealTimePlayback: true
|
||||
# moveRightLoopSnippet: true
|
||||
# requiredGear: {feet: 'simple-boots', 'programming-book': 'programmaticon-i'}
|
||||
# restrictedGear: {feet: 'leather-boots'}
|
||||
# 'dread-door':
|
||||
# hidesHUD: true
|
||||
# hidesSay: true
|
||||
# hidesCodeToolbar: true
|
||||
# hidesRealTimePlayback: true
|
||||
# requiredGear: {'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i'}
|
||||
# restrictedGear: {feet: 'leather-boots'}
|
||||
# 'known-enemy':
|
||||
# hidesHUD: true
|
||||
# hidesSay: true
|
||||
# hidesCodeToolbar: true
|
||||
# hidesRealTimePlayback: true
|
||||
# requiredGear: {'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i', torso: 'tarnished-bronze-breastplate'}
|
||||
# restrictedGear: {feet: 'leather-boots'}
|
||||
# suspectCode: [{name: 'enemy-in-quotes', pattern: '[\'"]enemy'}] # '
|
||||
# 'master-of-names':
|
||||
# hidesHUD: true
|
||||
# hidesSay: true
|
||||
# hidesCodeToolbar: true
|
||||
# hidesRealTimePlayback: true
|
||||
# requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses', torso: 'tarnished-bronze-breastplate'}
|
||||
# restrictedGear: {feet: 'leather-boots'}
|
||||
# requiredCode: ['findNearestEnemy']
|
||||
# suspectCode: [{name: 'lone-find-nearest-enemy', pattern: '^[ ]*(self|this|@)?[:.]?findNearestEnemy()'}]
|
||||
# 'lowly-kithmen':
|
||||
# hidesHUD: true
|
||||
# hidesSay: true
|
||||
# hidesCodeToolbar: true
|
||||
# hidesRealTimePlayback: true
|
||||
# requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses', torso: 'tarnished-bronze-breastplate'}
|
||||
# restrictedGear: {feet: 'leather-boots'}
|
||||
# requiredCode: ['findNearestEnemy']
|
||||
# suspectCode: [{name: 'lone-find-nearest-enemy', pattern: '^[ ]*(self|this|@)?[:.]?findNearestEnemy()'}]
|
||||
# 'closing-the-distance':
|
||||
# hidesHUD: true
|
||||
# hidesSay: true
|
||||
# hidesCodeToolbar: true
|
||||
# hidesRealTimePlayback: true
|
||||
# requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'tarnished-bronze-breastplate', eyes: 'crude-glasses'}
|
||||
# restrictedGear: {feet: 'leather-boots'}
|
||||
# suspectCode: [{name: 'lone-find-nearest-enemy', pattern: '^[ ]*(self|this|@)?[:.]?findNearestEnemy()'}]
|
||||
# 'tactical-strike':
|
||||
# hidesHUD: true
|
||||
# hidesSay: true
|
||||
# hidesCodeToolbar: true
|
||||
# hidesRealTimePlayback: true
|
||||
# requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'tarnished-bronze-breastplate', eyes: 'crude-glasses'}
|
||||
# restrictedGear: {feet: 'leather-boots'}
|
||||
# suspectCode: [{name: 'lone-find-nearest-enemy', pattern: '^[ ]*(self|this|@)?[:.]?findNearestEnemy()'}]
|
||||
# 'the-final-kithmaze':
|
||||
# hidesHUD: true
|
||||
# hidesSay: true
|
||||
# hidesCodeToolbar: true
|
||||
# hidesRealTimePlayback: true
|
||||
# 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()'}]
|
||||
# 'the-gauntlet':
|
||||
# hidesHUD: true
|
||||
# hidesSay: true
|
||||
# hidesCodeToolbar: true
|
||||
# hidesRealTimePlayback: true
|
||||
# requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'tarnished-bronze-breastplate', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
|
||||
# restrictedGear: {feet: 'leather-boots', 'right-hand': 'crude-builders-hammer'}
|
||||
# suspectCode: [{name: 'lone-find-nearest-enemy', pattern: '^[ ]*(self|this|@)?[:.]?findNearestEnemy()'}]
|
||||
# 'kithgard-gates':
|
||||
# hidesSay: true
|
||||
# hidesCodeToolbar: true
|
||||
# hidesRealTimePlayback: true
|
||||
# requiredGear: {feet: 'simple-boots', 'right-hand': 'crude-builders-hammer', torso: 'tarnished-bronze-breastplate'}
|
||||
# restrictedGear: {'right-hand': 'simple-sword'}
|
||||
# 'defense-of-plainswood':
|
||||
# hidesRealTimePlayback: true
|
||||
# hidesCodeToolbar: true
|
||||
# requiredGear: {feet: 'simple-boots', 'right-hand': 'crude-builders-hammer'}
|
||||
# restrictedGear: {'right-hand': 'simple-sword'}
|
||||
# 'winding-trail':
|
||||
# hidesRealTimePlayback: true
|
||||
# hidesCodeToolbar: true
|
||||
# requiredGear: {feet: 'leather-boots', 'right-hand': 'crude-builders-hammer'}
|
||||
# restrictedGear: {feet: 'simple-boots', 'right-hand': 'simple-sword'}
|
||||
# 'patrol-buster':
|
||||
# hidesRealTimePlayback: true
|
||||
# hidesCodeToolbar: true
|
||||
# requiredGear: {feet: 'leather-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-ii', eyes: 'crude-glasses'}
|
||||
# restrictedGear: {feet: 'simple-boots', 'right-hand': 'crude-builders-hammer', 'programming-book': 'programmaticon-i'}
|
||||
# 'endangered-burl':
|
||||
# hidesRealTimePlayback: true
|
||||
# hidesCodeToolbar: true
|
||||
# requiredGear: {feet: 'leather-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-ii', eyes: 'crude-glasses'}
|
||||
# restrictedGear: {feet: 'simple-boots', 'right-hand': 'crude-builders-hammer', 'programming-book': 'programmaticon-i'}
|
||||
# 'village-guard':
|
||||
# hidesCodeToolbar: true
|
||||
# lockDefaultCode: true
|
||||
# requiredGear: {feet: 'leather-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-ii', eyes: 'crude-glasses'}
|
||||
# restrictedGear: {feet: 'simple-boots', 'right-hand': 'crude-builders-hammer', 'programming-book': 'programmaticon-i'}
|
||||
# 'thornbush-farm':
|
||||
# hidesCodeToolbar: true
|
||||
# lockDefaultCode: true
|
||||
# requiredGear: {feet: 'leather-boots', 'right-hand': 'crude-builders-hammer', 'programming-book': 'programmaticon-ii', eyes: 'crude-glasses'}
|
||||
# restrictedGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i'}
|
||||
# requiredCode: ['topEnemy']
|
||||
# 'back-to-back':
|
||||
# hidesCodeToolbar: true
|
||||
# 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', 'programming-book': 'programmaticon-i'}
|
||||
# 'ogre-encampment':
|
||||
# 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', 'programming-book': 'programmaticon-i'}
|
||||
# 'woodland-cleaver':
|
||||
# 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', 'programming-book': 'programmaticon-i'}
|
||||
# 'shield-rush':
|
||||
# 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', 'programming-book': 'programmaticon-i'}
|
||||
#
|
||||
## Warrior branch
|
||||
# 'peasant-protection':
|
||||
# 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', 'programming-book': 'programmaticon-i'}
|
||||
# 'munchkin-swarm':
|
||||
# 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: {'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
|
||||
#
|
||||
## Ranger branch
|
||||
# 'munchkin-harvest':
|
||||
# requiredGear: {waist: 'leather-belt', 'programming-book': 'programmaticon-ii', eyes: 'wooden-glasses', 'right-hand': 'long-sword', 'left-hand': 'bronze-shield', wrists: 'sundial-wristwatch'}
|
||||
# restrictedGear: {'programming-book': 'programmaticon-i'}
|
||||
# allowedHeroes: ['captain', 'knight', 'samurai']
|
||||
# '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', 'programming-book': 'programmaticon-i'}
|
||||
# allowedHeroes: ['ninja', 'trapper', 'forest-archer']
|
||||
# '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', 'programming-book': 'programmaticon-i'}
|
||||
# allowedHeroes: ['ninja', 'trapper', 'forest-archer']
|
||||
#
|
||||
## Wizard branch
|
||||
# 'arcane-ally':
|
||||
# requiredGear: {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', 'programming-book': 'programmaticon-i'}
|
||||
# allowedHeroes: ['captain', 'knight', 'samurai']
|
||||
# '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: {'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
|
||||
# allowedHeroes: ['librarian', 'potion-master', 'sorcerer']
|
||||
# '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', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
|
||||
# requiredCode: ['canCast']
|
||||
# allowedHeroes: ['librarian', 'potion-master', 'sorcerer']
|
||||
#
|
||||
# 'coinucopia':
|
||||
# requiredGear: {'programming-book': 'programmaticon-ii', feet: 'leather-boots', flag: 'basic-flags'}
|
||||
# restrictedGear: {'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
|
||||
# 'copper-meadows':
|
||||
# requiredGear: {'programming-book': 'programmaticon-ii', feet: 'leather-boots', flag: 'basic-flags', eyes: 'wooden-glasses'}
|
||||
# restrictedGear: {'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
|
||||
# 'drop-the-flag':
|
||||
# requiredGear: {'programming-book': 'programmaticon-ii', feet: 'leather-boots', flag: 'basic-flags', eyes: 'wooden-glasses', 'right-hand': 'crude-builders-hammer'}
|
||||
# restrictedGear: {'right-hand': 'long-sword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
|
||||
# 'deadly-pursuit':
|
||||
# requiredGear: {'programming-book': 'programmaticon-ii', feet: 'leather-boots', flag: 'basic-flags', eyes: 'wooden-glasses', 'right-hand': 'crude-builders-hammer'}
|
||||
# restrictedGear: {'right-hand': 'long-sword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
|
||||
# 'rich-forager':
|
||||
# requiredGear: {'programming-book': 'programmaticon-ii', feet: 'leather-boots', flag: 'basic-flags', eyes: 'wooden-glasses', torso: 'tarnished-bronze-breastplate', 'right-hand': 'long-sword', 'left-hand': 'bronze-shield'}
|
||||
# restrictedGear: {'right-hand': 'crude-builders-hammer', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
|
||||
# 'multiplayer-treasure-grove':
|
||||
# requiredGear: {'programming-book': 'programmaticon-ii', feet: 'leather-boots', flag: 'basic-flags', eyes: 'wooden-glasses', torso: 'tarnished-bronze-breastplate'}
|
||||
# restrictedGear: {'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
|
||||
# 'siege-of-stonehold':
|
||||
# requiredGear: {}
|
||||
# restrictedGear: {}
|
||||
#
|
||||
## Desert
|
||||
# 'the-dunes':
|
||||
# requiredGear: {}
|
||||
# restrictedGear: {}
|
||||
# 'the-mighty-sand-yak':
|
||||
# requiredGear: {}
|
||||
# restrictedGear: {}
|
||||
# 'oasis':
|
||||
# requiredGear: {}
|
||||
# restrictedGear: {}
|
||||
|
|
|
@ -15,7 +15,7 @@ module.exports = class SettingsTabView extends CocoView
|
|||
editableSettings: [
|
||||
'name', 'description', 'documentation', 'nextLevel', 'background', 'victory', 'i18n', 'icon', 'goals',
|
||||
'type', 'terrain', 'showsGuide', 'banner', 'employerDescription', 'loadingTip', 'requiresSubscription',
|
||||
'tasks'
|
||||
'tasks', 'helpVideos'
|
||||
]
|
||||
|
||||
subscriptions:
|
||||
|
|
|
@ -24,7 +24,7 @@ class LevelSessionsCollection extends CocoCollection
|
|||
super()
|
||||
@url = "/db/user/#{me.id}/level.sessions?project=state.complete,levelID"
|
||||
|
||||
module.exports = class WorldMapView extends RootView
|
||||
module.exports = class CampaignView extends RootView
|
||||
id: 'campaign-view'
|
||||
template: template
|
||||
|
||||
|
@ -41,16 +41,13 @@ module.exports = class WorldMapView extends RootView
|
|||
'click #volume-button': 'onToggleVolume'
|
||||
|
||||
constructor: (options, @terrain='dungeon') ->
|
||||
if options and application.isIPAdApp # TODO: later only clear the SuperModel if it has received a memory warning (not in app store yet)
|
||||
options.supermodel = null
|
||||
super options
|
||||
options ?= {}
|
||||
|
||||
|
||||
@campaign = new Campaign({_id:@terrain})
|
||||
@campaign = @supermodel.loadModel(@campaign, 'campaign').model
|
||||
|
||||
@editorMode = options.editorMode
|
||||
@nextLevel = @getQueryVariable 'next'
|
||||
@levelStatusMap = {}
|
||||
@levelPlayCountMap = {}
|
||||
@sessions = @supermodel.loadCollection(new LevelSessionsCollection(), 'your_sessions', null, 0).model
|
||||
|
@ -71,9 +68,8 @@ module.exports = class WorldMapView extends RootView
|
|||
@supermodel.loadCollection(@earnedAchievements, 'achievements')
|
||||
|
||||
@listenToOnce @sessions, 'sync', @onSessionsLoaded
|
||||
@getLevelPlayCounts()
|
||||
@listenToOnce @campaign, 'sync', @getLevelPlayCounts
|
||||
$(window).on 'resize', @onWindowResize
|
||||
@playAmbientSound()
|
||||
@probablyCachedMusic = storage.load("loaded-menu-music")
|
||||
musicDelay = if @probablyCachedMusic then 1000 else 10000
|
||||
@playMusicTimeout = _.delay (=> @playMusic() unless @destroyed), musicDelay
|
||||
|
@ -103,7 +99,6 @@ module.exports = class WorldMapView extends RootView
|
|||
super()
|
||||
|
||||
getLevelPlayCounts: ->
|
||||
return # TODO: Either use the campaign object instead of hardcoded data or get the data some other way
|
||||
return unless me.isAdmin()
|
||||
success = (levelPlayCounts) =>
|
||||
return if @destroyed
|
||||
|
@ -111,13 +106,10 @@ module.exports = class WorldMapView extends RootView
|
|||
@levelPlayCountMap[level._id] = playtime: level.playtime, sessions: level.sessions
|
||||
@render() if @fullyRendered
|
||||
|
||||
levelIDs = []
|
||||
for campaign in campaigns
|
||||
for level in campaign.levels
|
||||
levelIDs.push level.id
|
||||
levelSlugs = (level.slug for levelID, level of @campaign.get 'levels')
|
||||
levelPlayCountsRequest = @supermodel.addRequestResource 'play_counts', {
|
||||
url: '/db/level/-/play_counts'
|
||||
data: {ids: levelIDs}
|
||||
data: {ids: levelSlugs}
|
||||
method: 'POST'
|
||||
success: success
|
||||
}, 0
|
||||
|
@ -128,7 +120,7 @@ module.exports = class WorldMapView extends RootView
|
|||
@fullyRendered = true
|
||||
@render()
|
||||
@preloadTopHeroes() unless me.get('heroConfig')?.thangType
|
||||
|
||||
|
||||
setCampaign: (@campaign) ->
|
||||
@render()
|
||||
|
||||
|
@ -143,12 +135,10 @@ module.exports = class WorldMapView extends RootView
|
|||
for level in context.levels
|
||||
level.position ?= { x: 10, y: 10 }
|
||||
level.locked = not me.ownsLevel level.original
|
||||
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 @levelStatusMap[level.id] in ['started', 'complete']
|
||||
level.locked = false if @levelStatusMap[level.slug] in ['started', 'complete']
|
||||
level.locked = false if me.get('slug') is 'nick'
|
||||
level.locked = false if @editorMode
|
||||
level.disabled = false if @levelStatusMap[level.id] in ['started', 'complete']
|
||||
level.disabled = true if not me.isAdmin() and level.adminOnly and not @levelStatusMap[level.slug] in ['started', 'complete']
|
||||
level.color = 'rgb(255, 80, 60)'
|
||||
if level.requiresSubscription
|
||||
level.color = 'rgb(80, 130, 200)'
|
||||
|
@ -163,13 +153,10 @@ module.exports = class WorldMapView extends RootView
|
|||
context.levelPlayCountMap = @levelPlayCountMap
|
||||
context.isIPadApp = application.isIPadApp
|
||||
context.mapType = _.string.slugify @terrain
|
||||
context.nextLevel = @nextLevel
|
||||
context.forestIsAvailable = Level.levels['defense-of-plainswood'] in (me.get('earned')?.levels or [])
|
||||
context.desertIsAvailable = Level.levels['the-mighty-sand-yak'] in (me.get('earned')?.levels or [])
|
||||
context.requiresSubscription = @requiresSubscription
|
||||
context.editorMode = @editorMode
|
||||
context.adjacentCampaigns = _.filter _.values(_.cloneDeep(@campaign.get('adjacentCampaigns') or {})), (ac) ->
|
||||
return false if ac.showIfUnlocked and ac.showIfUnlocked not in (me.get('unlocked')?.levels or [])
|
||||
return false if ac.showIfUnlocked and ac.showIfUnlocked not in me.levels()
|
||||
ac.name = utils.i18n ac, 'name'
|
||||
ac.description = utils.i18n ac, 'description'
|
||||
styles = []
|
||||
|
@ -194,19 +181,14 @@ module.exports = class WorldMapView extends RootView
|
|||
bg = $('.map-background')
|
||||
x = ($(@).offset().left - bg.offset().left + $(@).outerWidth() / 2) / bg.width()
|
||||
y = 1 - ($(@).offset().top - bg.offset().top + $(@).outerHeight() / 2) / bg.height()
|
||||
e = { position: { x: (100 * x), y: (100 * y) }, levelOriginal: $(@).data('level-id'), campaignID: $(@).data('campaign-id') }
|
||||
e = { position: { x: (100 * x), y: (100 * y) }, levelOriginal: $(@).data('level-slug'), campaignID: $(@).data('campaign-id') }
|
||||
view.trigger 'level-moved', e if e.levelOriginal
|
||||
view.trigger 'adjacent-campaign-moved', e if e.campaignID
|
||||
@$el.addClass _.string.slugify @terrain
|
||||
@updateVolume()
|
||||
@updateHero()
|
||||
unless window.currentModal or not @fullyRendered
|
||||
@highlightElement '.level.next', delay: 500, duration: 60000, rotation: 0, sides: ['top']
|
||||
if levelID = @$el.find('.level.next').data('level-id')
|
||||
@$levelInfo = @$el.find(".level-info-container[data-level-id=#{levelID}]").show() unless @editorMode
|
||||
pos = @$el.find('.level.next').offset()
|
||||
@adjustLevelInfoPosition pageX: pos.left, pageY: pos.top
|
||||
@manuallyPositionedLevelInfoID = levelID
|
||||
@applyCampaignStyles()
|
||||
|
||||
afterInsert: ->
|
||||
super()
|
||||
|
@ -217,12 +199,29 @@ module.exports = class WorldMapView extends RootView
|
|||
authModal.mode = 'signup'
|
||||
@openModalView authModal
|
||||
|
||||
applyCampaignStyles: ->
|
||||
return unless @campaign.loaded
|
||||
if (backgrounds = @campaign.get 'backgroundImage') and backgrounds.length
|
||||
backgrounds = _.sortBy backgrounds, 'width'
|
||||
backgrounds.reverse()
|
||||
rules = []
|
||||
for background, i in backgrounds
|
||||
rule = "#campaign-view .map-background { background-image: url(/file/#{background.image}); }"
|
||||
rule = "@media screen and (max-width: #{background.width}px) { #{rule} }" if i
|
||||
rules.push rule
|
||||
utils.injectCSS rules.join('\n')
|
||||
if backgroundColor = @campaign.get 'backgroundColor'
|
||||
backgroundColorTransparent = @campaign.get 'backgroundColorTransparent'
|
||||
@$el.css 'background-color', backgroundColor
|
||||
for pos in ['top', 'right', 'bottom', 'left']
|
||||
@$el.find(".#{pos}-gradient").css 'background-image', "linear-gradient(to #{pos}, #{backgroundColorTransparent} 0%, #{backgroundColor} 100%)"
|
||||
@playAmbientSound()
|
||||
|
||||
onSessionsLoaded: (e) ->
|
||||
return if @editorMode
|
||||
for session in @sessions.models
|
||||
@levelStatusMap[session.get('levelID')] = if session.get('state')?.complete then 'complete' else 'started'
|
||||
if @nextLevel and @levelStatusMap[@nextLevel] is 'complete'
|
||||
@nextLevel = null
|
||||
# TODO: add level.next = true for the next level they should do
|
||||
@render()
|
||||
|
||||
onClickMap: (e) ->
|
||||
|
@ -237,18 +236,18 @@ module.exports = class WorldMapView extends RootView
|
|||
e.stopPropagation()
|
||||
@$levelInfo?.hide()
|
||||
levelElement = $(e.target).parents('.level')
|
||||
levelID = levelElement.data('level-id')
|
||||
levelSlug = levelElement.data('level-slug')
|
||||
if @editorMode
|
||||
return @trigger 'level-clicked', levelID
|
||||
level = _.find _.values(@campaign.get('levels')), id: levelID
|
||||
return @trigger 'level-clicked', levelSlug
|
||||
level = _.find _.values(@campaign.get('levels')), slug: levelSlug
|
||||
if application.isIPadApp
|
||||
@$levelInfo = @$el.find(".level-info-container[data-level-id=#{levelID}]").show()
|
||||
@$levelInfo = @$el.find(".level-info-container[data-level-slug=#{levelSlug}]").show()
|
||||
@adjustLevelInfoPosition e
|
||||
@endHighlight()
|
||||
else
|
||||
if level.requiresSubscription and @requiresSubscription and not @levelStatusMap[level.id] and not level.adventurer
|
||||
if level.requiresSubscription and @requiresSubscription and not @levelStatusMap[level.slug] and not level.adventurer
|
||||
@openModalView new SubscribeModal()
|
||||
window.tracker?.trackEvent 'Show subscription modal', category: 'Subscription', label: 'map level clicked', level: levelID
|
||||
window.tracker?.trackEvent 'Show subscription modal', category: 'Subscription', label: 'map level clicked', level: levelSlug
|
||||
else if $(e.target).attr('disabled')
|
||||
Backbone.Mediator.publish 'router:navigate', route: '/contribute/adventurer'
|
||||
return
|
||||
|
@ -256,40 +255,36 @@ module.exports = class WorldMapView extends RootView
|
|||
return
|
||||
else
|
||||
@startLevel levelElement
|
||||
window.tracker?.trackEvent 'Clicked Level', category: 'World Map', levelID: levelID, ['Google Analytics']
|
||||
window.tracker?.trackEvent 'Clicked Level', category: 'World Map', levelID: levelSlug, ['Google Analytics']
|
||||
|
||||
onClickStartLevel: (e) ->
|
||||
levelElement = $(e.target).parents('.level-info-container')
|
||||
@startLevel levelElement
|
||||
window.tracker?.trackEvent 'Clicked Start Level', category: 'World Map', levelID: levelElement.data('level-id'), ['Google Analytics']
|
||||
window.tracker?.trackEvent 'Clicked Start Level', category: 'World Map', levelID: levelElement.data('level-slug'), ['Google Analytics']
|
||||
|
||||
startLevel: (levelElement) ->
|
||||
@setupManager?.destroy()
|
||||
@setupManager = new LevelSetupManager supermodel: @supermodel, levelID: levelElement.data('level-id'), levelPath: levelElement.data('level-path'), levelName: levelElement.data('level-name'), hadEverChosenHero: @hadEverChosenHero, parent: @
|
||||
@setupManager = new LevelSetupManager supermodel: @supermodel, levelID: levelElement.data('level-slug'), levelPath: levelElement.data('level-path'), levelName: levelElement.data('level-name'), hadEverChosenHero: @hadEverChosenHero, parent: @
|
||||
@setupManager.open()
|
||||
@$levelInfo?.hide()
|
||||
|
||||
onMouseEnterLevel: (e) ->
|
||||
return if application.isIPadApp
|
||||
return if @editorMode
|
||||
levelID = $(e.target).parents('.level').data('level-id')
|
||||
return if @manuallyPositionedLevelInfoID and levelID isnt @manuallyPositionedLevelInfoID
|
||||
@$levelInfo = @$el.find(".level-info-container[data-level-id=#{levelID}]").show()
|
||||
levelSlug = $(e.target).parents('.level').data('level-slug')
|
||||
@$levelInfo = @$el.find(".level-info-container[data-level-slug=#{levelSlug}]").show()
|
||||
@adjustLevelInfoPosition e
|
||||
@endHighlight()
|
||||
@manuallyPositionedLevelInfoID = false
|
||||
|
||||
onMouseLeaveLevel: (e) ->
|
||||
return if application.isIPadApp
|
||||
levelID = $(e.target).parents('.level').data('level-id')
|
||||
return if @manuallyPositionedLevelInfoID and levelID isnt @manuallyPositionedLevelInfoID
|
||||
@$el.find(".level-info-container[data-level-id='#{levelID}']").hide()
|
||||
@manuallyPositionedLevelInfoID = null
|
||||
levelSlug = $(e.target).parents('.level').data('level-slug')
|
||||
@$el.find(".level-info-container[data-level-slug='#{levelSlug}']").hide()
|
||||
@$levelInfo = null
|
||||
|
||||
onMouseMoveMap: (e) ->
|
||||
return if application.isIPadApp
|
||||
@adjustLevelInfoPosition e unless @manuallyPositionedLevelInfoID
|
||||
@adjustLevelInfoPosition e
|
||||
|
||||
adjustLevelInfoPosition: (e) ->
|
||||
return unless @$levelInfo
|
||||
|
@ -329,8 +324,8 @@ module.exports = class WorldMapView extends RootView
|
|||
|
||||
playAmbientSound: ->
|
||||
return if @ambientSound
|
||||
return unless file = {dungeon: 'ambient-dungeon', forest: 'ambient-map-grass', desert: 'ambient-desert'}[@terrain]
|
||||
src = "/file/interface/#{file}#{AudioPlayer.ext}"
|
||||
return unless file = @campaign.get('ambientSound')?[AudioPlayer.ext.substr 1]
|
||||
src = "/file/#{file}"
|
||||
unless AudioPlayer.getStatus(src)?.loaded
|
||||
AudioPlayer.preloadSound src
|
||||
Backbone.Mediator.subscribeOnce 'audio-player:loaded', @playAmbientSound, @
|
||||
|
@ -381,4 +376,4 @@ module.exports = class WorldMapView extends RootView
|
|||
for slug, original of ThangType.heroes when original is hero
|
||||
@$el.find('.player-hero-icon').removeClass().addClass('player-hero-icon ' + slug)
|
||||
return
|
||||
console.error "WorldMapView hero update couldn't find hero slug for original:", hero
|
||||
console.error "CampaignView hero update couldn't find hero slug for original:", hero
|
||||
|
|
|
@ -7,7 +7,6 @@ RealTimeModel = require 'models/RealTimeModel'
|
|||
RealTimeCollection = require 'collections/RealTimeCollection'
|
||||
LevelSetupManager = require 'lib/LevelSetupManager'
|
||||
GameMenuModal = require 'views/play/menu/GameMenuModal'
|
||||
CampaignOptions = require 'lib/CampaignOptions'
|
||||
|
||||
module.exports = class ControlBarView extends CocoView
|
||||
id: 'control-bar-view'
|
||||
|
@ -69,8 +68,8 @@ module.exports = class ControlBarView extends CocoView
|
|||
@homeViewArgs.push levelID
|
||||
else if @level.get('type', true) in ['hero', 'hero-coop']
|
||||
@homeLink = c.homeLink = '/play'
|
||||
@homeViewClass = 'views/play/WorldMapView'
|
||||
campaign = CampaignOptions.getCampaignForSlug @level.get 'slug'
|
||||
@homeViewClass = 'views/play/CampaignView'
|
||||
campaign = @level.get 'campaign'
|
||||
if campaign isnt 'dungeon'
|
||||
@homeLink += '/' + campaign
|
||||
@homeViewArgs.push campaign
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
CocoView = require 'views/core/CocoView'
|
||||
template = require 'templates/play/level/hud'
|
||||
prop_template = require 'templates/play/level/hud_prop'
|
||||
LevelOptions = require 'lib/LevelOptions'
|
||||
utils = require 'core/utils'
|
||||
|
||||
module.exports = class LevelHUDView extends CocoView
|
||||
|
@ -23,7 +22,7 @@ module.exports = class LevelHUDView extends CocoView
|
|||
afterRender: ->
|
||||
super()
|
||||
@$el.addClass 'no-selection'
|
||||
if LevelOptions[@options.level.get('slug')]?.hidesHUD
|
||||
if @options.level.get('hidesHUD')
|
||||
@hidesHUD = true
|
||||
@$el.addClass 'hide-hud-properties'
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
CocoView = require 'views/core/CocoView'
|
||||
template = require 'templates/play/level/playback'
|
||||
{me} = require 'core/auth'
|
||||
LevelOptions = require 'lib/LevelOptions'
|
||||
|
||||
module.exports = class LevelPlaybackView extends CocoView
|
||||
id: 'playback-view'
|
||||
|
@ -67,7 +66,7 @@ module.exports = class LevelPlaybackView extends CocoView
|
|||
@goto = t 'play_level.time_goto'
|
||||
@current = t 'play_level.time_current'
|
||||
@total = t 'play_level.time_total'
|
||||
@$el.find('#play-button').css('visibility', 'hidden') if LevelOptions[@options.levelID]?.hidesPlayButton # Don't show for first few levels, confuses new players.
|
||||
@$el.find('#play-button').css('visibility', 'hidden') if @options.level.get 'hidesPlayButton' # Don't show for first few levels, confuses new players.
|
||||
|
||||
updatePopupContent: ->
|
||||
@timePopup?.updateContent "<h2>#{@timeToString @newTime}</h2>#{@formatTime(@current, @currentTime)}<br/>#{@formatTime(@total, @totalTime)}"
|
||||
|
|
|
@ -62,7 +62,6 @@ module.exports = class PlayLevelView extends RootView
|
|||
'god:infinite-loop': 'onInfiniteLoop'
|
||||
'level:reload-from-data': 'onLevelReloadFromData'
|
||||
'level:reload-thang-type': 'onLevelReloadThangType'
|
||||
'level:play-next-level': 'onPlayNextLevel'
|
||||
'level:session-will-save': 'onSessionWillSave'
|
||||
'level:started': 'onLevelStarted'
|
||||
'level:loading-view-unveiling': 'onLoadingViewUnveiling'
|
||||
|
@ -235,7 +234,7 @@ module.exports = class PlayLevelView extends RootView
|
|||
|
||||
insertSubviews: ->
|
||||
@insertSubView @tome = new TomeView levelID: @levelID, session: @session, otherSession: @otherSession, thangs: @world.thangs, supermodel: @supermodel, level: @level
|
||||
@insertSubView new LevelPlaybackView session: @session, levelID: @levelID, level: @level
|
||||
@insertSubView new LevelPlaybackView session: @session, level: @level
|
||||
@insertSubView new GoalsView {}
|
||||
@insertSubView new LevelFlagsView levelID: @levelID, world: @world if @$el.hasClass 'flags'
|
||||
@insertSubView new GoldView {}
|
||||
|
@ -427,7 +426,7 @@ module.exports = class PlayLevelView extends RootView
|
|||
victoryModal = new ModalClass(options)
|
||||
@openModalView(victoryModal)
|
||||
if me.get('anonymous')
|
||||
window.nextLevelURL = @getNextLevelURL() # Signup will go here on completion instead of reloading.
|
||||
window.nextURL = '/play/' + (@level.get('campaign') ? '') # Signup will go here on completion instead of reloading.
|
||||
|
||||
onRestartLevel: ->
|
||||
@tome.reloadAllCode()
|
||||
|
@ -440,23 +439,6 @@ module.exports = class PlayLevelView extends RootView
|
|||
@openModalView new InfiniteLoopModal()
|
||||
application.tracker?.trackEvent 'Saw Initial Infinite Loop', category: 'Play Level', level: @level.get('name'), label: @level.get('name')
|
||||
|
||||
onPlayNextLevel: ->
|
||||
nextLevelID = @getNextLevelID()
|
||||
nextLevelURL = @getNextLevelURL()
|
||||
Backbone.Mediator.publish 'router:navigate', {
|
||||
route: nextLevelURL,
|
||||
viewClass: PlayLevelView,
|
||||
viewArgs: [{supermodel: if @hasReceivedMemoryWarning then null else @supermodel}, nextLevelID]}
|
||||
|
||||
getNextLevelID: ->
|
||||
for campaign in require('views/play/WorldMapView').campaigns
|
||||
for level in campaign.levels
|
||||
return level.nextLevels?.continue if level.id is @level.get('slug')
|
||||
|
||||
getNextLevelURL: ->
|
||||
return null unless @getNextLevelID()
|
||||
"/play/level/#{@getNextLevelID()}"
|
||||
|
||||
onHighlightDOM: (e) -> @highlightElement e.selector, delay: e.delay, sides: e.sides, offset: e.offset, rotation: e.rotation
|
||||
|
||||
onEndHighlight: -> @endHighlight()
|
||||
|
@ -554,7 +536,7 @@ module.exports = class PlayLevelView extends RootView
|
|||
delete window.world # not sure where this is set, but this is one way to clean it up
|
||||
@bus?.destroy()
|
||||
#@instance.save() unless @instance.loading
|
||||
delete window.nextLevelURL
|
||||
delete window.nextURL
|
||||
console.profileEnd?() if PROFILE_ME
|
||||
@onRealTimeMultiplayerLevelUnloaded()
|
||||
super()
|
||||
|
|
|
@ -9,7 +9,6 @@ utils = require 'core/utils'
|
|||
ThangType = require 'models/ThangType'
|
||||
LadderSubmissionView = require 'views/play/common/LadderSubmissionView'
|
||||
AudioPlayer = require 'lib/AudioPlayer'
|
||||
CampaignOptions = require 'lib/CampaignOptions'
|
||||
User = require 'models/User'
|
||||
utils = require 'core/utils'
|
||||
|
||||
|
@ -115,7 +114,6 @@ module.exports = class HeroVictoryModal extends ModalView
|
|||
c.me = me
|
||||
c.readyToRank = @level.get('type', true) is 'hero-ladder' and @session.readyToRank()
|
||||
c.level = @level
|
||||
@continueLevelLink = @getNextLevelLink 'continue'
|
||||
|
||||
elapsed = (new Date() - new Date(me.get('dateCreated')))
|
||||
isHourOfCode = me.get('hourOfCode') or elapsed < 120 * 60 * 1000
|
||||
|
@ -301,34 +299,21 @@ module.exports = class HeroVictoryModal extends ModalView
|
|||
else
|
||||
AudioPlayer.playSound name, 1
|
||||
|
||||
getLevelInfoForSlug: (slug) ->
|
||||
for campaign in require('views/play/WorldMapView').campaigns
|
||||
for level in campaign.levels
|
||||
return level if level.id is slug
|
||||
|
||||
getNextLevelCampaign: ->
|
||||
# Wouldn't handle skipping/more practice across campaign boundaries, but we don't do that.
|
||||
campaign = CampaignOptions.getCampaignForSlug @level.get 'slug'
|
||||
if nextLevelSlug = @getNextLevel 'continue'
|
||||
campaign = CampaignOptions.getCampaignForSlug nextLevelSlug
|
||||
campaign or 'dungeon'
|
||||
{'kithgard-gates': 'forest', 'siege-of-stonehold': 'desert'}[@level.get('slug')] or @level.get 'campaign' # Much easier to just keep this updated than to dynamically figure it out.
|
||||
link += '/' + nextCampaign unless nextCampaign is 'dungeon'
|
||||
|
||||
getNextLevelLink: (type) ->
|
||||
getNextLevelLink: ->
|
||||
link = '/play'
|
||||
nextCampaign = @getNextLevelCampaign()
|
||||
link += '/' + nextCampaign unless nextCampaign is 'dungeon'
|
||||
return link unless nextLevel = @getNextLevel type
|
||||
"#{link}?next=#{nextLevel}"
|
||||
|
||||
getNextLevel: (type) ->
|
||||
levelInfo = @getLevelInfoForSlug @level.get 'slug'
|
||||
levelInfo?.nextLevels?[type] # 'continue'; TODO: refactor to not have the object and just use single nextLevel property
|
||||
link
|
||||
|
||||
onClickContinue: (e) ->
|
||||
@playSound 'menu-button-click'
|
||||
nextLevelLink = @continueLevelLink
|
||||
nextLevelLink = @getNextLevelLink()
|
||||
# Preserve the supermodel as we navigate back to the world map.
|
||||
Backbone.Mediator.publish 'router:navigate', route: nextLevelLink, viewClass: require('views/play/WorldMapView'), viewArgs: [{supermodel: if @options.hasReceivedMemoryWarning then null else @supermodel}, @getNextLevelCampaign()]
|
||||
Backbone.Mediator.publish 'router:navigate', route: nextLevelLink, viewClass: require('views/play/CampaignView'), viewArgs: [{supermodel: if @options.hasReceivedMemoryWarning then null else @supermodel}, @getNextLevelCampaign()]
|
||||
|
||||
onClickReturnToLadder: (e) ->
|
||||
@playSound 'menu-button-click'
|
||||
|
|
|
@ -14,8 +14,6 @@ module.exports = class VictoryModal extends ModalView
|
|||
'ladder:game-submitted': 'onGameSubmitted'
|
||||
|
||||
events:
|
||||
'click .next-level-button': 'onPlayNextLevel'
|
||||
'click .world-map-button': 'onClickWorldMap'
|
||||
'click .sign-up-button': 'onClickSignupButton'
|
||||
|
||||
# review events
|
||||
|
@ -26,9 +24,6 @@ module.exports = class VictoryModal extends ModalView
|
|||
@$el.find('.review').show()
|
||||
'keypress .review textarea': -> @saveReviewEventually()
|
||||
|
||||
shortcuts:
|
||||
'enter': -> 'onPlayNextLevel'
|
||||
|
||||
constructor: (options) ->
|
||||
application.router.initializeSocialMediaServices()
|
||||
victory = options.level.get('victory', true)
|
||||
|
@ -61,15 +56,6 @@ module.exports = class VictoryModal extends ModalView
|
|||
@feedback.set('level', {majorVersion: @level.get('version').major, original: @level.get('original')})
|
||||
@showStars()
|
||||
|
||||
onPlayNextLevel: ->
|
||||
@saveReview() if @$el.find('.review textarea').val()
|
||||
Backbone.Mediator.publish 'level:play-next-level', {}
|
||||
|
||||
onClickWorldMap: (e) ->
|
||||
e.preventDefault()
|
||||
e.stopImmediatePropagation()
|
||||
Backbone.Mediator.publish 'router:navigate', route: '/play', viewClass: require('views/play/WorldMapView'), viewArgs: [{supermodel: @supermodel}]
|
||||
|
||||
onClickSignupButton: (e) ->
|
||||
e.preventDefault()
|
||||
window.tracker?.trackEvent 'Started Signup', category: 'Play Level', label: 'Victory Modal', level: @level.get('slug')
|
||||
|
@ -83,23 +69,10 @@ module.exports = class VictoryModal extends ModalView
|
|||
c = super()
|
||||
c.body = @body
|
||||
c.me = me
|
||||
c.hasNextLevel = _.isObject(@level.get('nextLevel'))
|
||||
c.levelName = utils.i18n @level.attributes, 'name'
|
||||
c.level = @level
|
||||
if c.level.get('type') in ['ladder', 'hero-ladder']
|
||||
if c.level.get('type') is 'ladder'
|
||||
c.readyToRank = @session.readyToRank()
|
||||
if me.get 'hourOfCode'
|
||||
# Show the Hour of Code "I'm Done" tracking pixel after they played for 30 minutes
|
||||
elapsed = (new Date() - new Date(me.get('dateCreated')))
|
||||
enough = not c.hasNextLevel or elapsed >= 30 * 60 * 1000
|
||||
if enough and not me.get('hourOfCodeComplete')
|
||||
$('body').append($('<img src="http://code.org/api/hour/finish_codecombat.png" style="visibility: hidden;">'))
|
||||
me.set 'hourOfCodeComplete', true
|
||||
me.patch()
|
||||
window.tracker?.trackEvent 'Hour of Code Finish', {}
|
||||
# Show the "I'm done" button if they get to the end, unless it's been over two hours
|
||||
tooMuch = elapsed >= 120 * 60 * 1000
|
||||
c.showHourOfCodeDoneButton = not c.hasNextLevel and not tooMuch
|
||||
c
|
||||
|
||||
afterRender: ->
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
CocoView = require 'views/core/CocoView'
|
||||
template = require 'templates/play/level/tome/cast_button'
|
||||
{me} = require 'core/auth'
|
||||
LevelOptions = require 'lib/LevelOptions'
|
||||
|
||||
module.exports = class CastButtonView extends CocoView
|
||||
id: 'cast-button-view'
|
||||
|
@ -25,9 +24,7 @@ module.exports = class CastButtonView extends CocoView
|
|||
constructor: (options) ->
|
||||
super options
|
||||
@spells = options.spells
|
||||
@levelID = options.levelID
|
||||
@castShortcut = '⇧↵'
|
||||
@levelOptions = LevelOptions[@options.levelID] ? {}
|
||||
|
||||
getRenderData: (context={}) ->
|
||||
context = super context
|
||||
|
@ -46,11 +43,11 @@ module.exports = class CastButtonView extends CocoView
|
|||
#delay = me.get('autocastDelay') # No more autocast
|
||||
delay = 90019001
|
||||
@setAutocastDelay delay
|
||||
if @levelOptions.hidesSubmitUntilRun or @levelOptions.hidesRealTimePlayback
|
||||
if @options.level.get('hidesSubmitUntilRun') or @options.level.get('hidesRealTimePlayback')
|
||||
@$el.find('.submit-button').hide() # Hide Submit for the first few until they run it once.
|
||||
if @options.session.get('state')?.complete and @levelOptions.hidesRealTimePlayback
|
||||
if @options.session.get('state')?.complete and @options.level.get 'hidesRealTimePlayback'
|
||||
@$el.find('.done-button').show()
|
||||
if @options.levelID is 'thornbush-farm'# and not @options.session.get('state')?.complete
|
||||
if @options.level.get('slug') is 'thornbush-farm'# and not @options.session.get('state')?.complete
|
||||
@$el.find('.submit-button').hide() # Hide submit until first win so that script can explain it.
|
||||
|
||||
attachTo: (spellView) ->
|
||||
|
@ -92,16 +89,16 @@ module.exports = class CastButtonView extends CocoView
|
|||
@winnable = winnable
|
||||
@$el.toggleClass 'winnable', @winnable
|
||||
Backbone.Mediator.publish 'tome:winnability-updated', winnable: @winnable
|
||||
if @levelOptions.hidesRealTimePlayback
|
||||
if @options.level.get 'hidesRealTimePlayback'
|
||||
@$el.find('.done-button').toggle @winnable
|
||||
else if @winnable and @options.levelID is 'thornbush-farm'
|
||||
else if @winnable and @options.level.get('slug') is 'thornbush-farm'
|
||||
@$el.find('.submit-button').show() # Hide submit until first win so that script can explain it.
|
||||
|
||||
onGoalsCalculated: (e) ->
|
||||
# When preloading, with real-time playback enabled, we highlight the submit button when we think they'll win.
|
||||
return unless e.preload
|
||||
return if @levelOptions.hidesRealTimePlayback
|
||||
return if @options.levelID is 'thornbush-farm' # Don't show it until they actually win for this first one.
|
||||
return if @options.level.get 'hidesRealTimePlayback'
|
||||
return if @options.level.get('slug') is 'thornbush-farm' # Don't show it until they actually win for this first one.
|
||||
@onNewGoalStates e
|
||||
|
||||
updateCastButton: ->
|
||||
|
@ -116,7 +113,7 @@ module.exports = class CastButtonView extends CocoView
|
|||
castText = $.i18n.t('play_level.tome_cast_button_running')
|
||||
else if castable or true
|
||||
castText = $.i18n.t('play_level.tome_cast_button_run')
|
||||
unless @levelOptions.hidesRunShortcut # Hide for first few.
|
||||
unless @options.level.get 'hidesRunShortcut' # Hide for first few.
|
||||
castText += ' ' + @castShortcut
|
||||
else
|
||||
castText = $.i18n.t('play_level.tome_cast_button_ran')
|
||||
|
|
|
@ -5,7 +5,6 @@ filters = require 'lib/image_filter'
|
|||
SpellPaletteEntryView = require './SpellPaletteEntryView'
|
||||
LevelComponent = require 'models/LevelComponent'
|
||||
ThangType = require 'models/ThangType'
|
||||
LevelOptions = require 'lib/LevelOptions'
|
||||
GameMenuModal = require 'views/play/menu/GameMenuModal'
|
||||
|
||||
N_ROWS = 4
|
||||
|
@ -221,7 +220,7 @@ module.exports = class SpellPaletteView extends CocoView
|
|||
# Assign any unassigned properties to the hero itself.
|
||||
for owner, storage of propStorage
|
||||
for prop in _.reject(@thang[storage] ? [], (prop) -> itemsByProp[prop] or prop[0] is '_') # no private properties
|
||||
if prop is 'say' and LevelOptions[@options.level.get('slug')]?.hidesSay # Hide for Dungeon Campaign
|
||||
if prop is 'say' and @options.level.get 'hidesSay' # Hide for Dungeon Campaign
|
||||
continue
|
||||
propsByItem['Hero'] ?= []
|
||||
propsByItem['Hero'].push owner: owner, prop: prop, item: itemThangTypes[@thang.spriteName]
|
||||
|
@ -284,7 +283,7 @@ module.exports = class SpellPaletteView extends CocoView
|
|||
entry.destroy() for entry in @entries
|
||||
@createPalette()
|
||||
@render()
|
||||
|
||||
|
||||
onClickHelp: (e) ->
|
||||
application.tracker?.trackEvent 'Spell palette help clicked', levelID: @level.get('slug')
|
||||
@openModalView new GameMenuModal showTab: 'guide', level: @level, session: @session, supermodel: @supermodel
|
||||
|
|
|
@ -9,8 +9,6 @@ SpellDebugView = require './SpellDebugView'
|
|||
SpellToolbarView = require './SpellToolbarView'
|
||||
LevelComponent = require 'models/LevelComponent'
|
||||
UserCodeProblem = require 'models/UserCodeProblem'
|
||||
CampaignOptions = require 'lib/CampaignOptions'
|
||||
LevelOptions = require 'lib/LevelOptions'
|
||||
|
||||
module.exports = class SpellView extends CocoView
|
||||
id: 'spell-view'
|
||||
|
@ -199,7 +197,7 @@ module.exports = class SpellView extends CocoView
|
|||
bindKey: {win: 'Ctrl-Shift-M', mac: 'Command-Shift-M|Ctrl-Shift-M'}
|
||||
exec: -> Backbone.Mediator.publish 'tome:toggle-maximize', {}
|
||||
addCommand
|
||||
# TODO: Restrict to beginner campaign levels, possibly with a CampaignOptions similar to LevelOptions
|
||||
# TODO: Restrict to beginner campaign levels like we do backspaceThrottle
|
||||
name: 'enter-skip-delimiters'
|
||||
bindKey: 'Enter|Return'
|
||||
exec: =>
|
||||
|
@ -216,40 +214,36 @@ module.exports = class SpellView extends CocoView
|
|||
name: 'disable-spaces'
|
||||
bindKey: 'Space'
|
||||
exec: =>
|
||||
return @ace.execCommand 'insertstring', ' ' unless LevelOptions[@options.level.get('slug')]?.disableSpaces
|
||||
return @ace.execCommand 'insertstring', ' ' unless @options.level.get 'disableSpaces'
|
||||
line = @aceDoc.getLine @ace.getCursorPosition().row
|
||||
return @ace.execCommand 'insertstring', ' ' if @singleLineCommentRegex().test line
|
||||
|
||||
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')
|
||||
if @options.level.get 'backspaceThrottle'
|
||||
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.
|
||||
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"
|
||||
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: ->
|
||||
|
@ -259,7 +253,7 @@ module.exports = class SpellView extends CocoView
|
|||
|
||||
lockDefaultCode: (force=false) ->
|
||||
# TODO: Lock default indent for an empty line?
|
||||
return unless LevelOptions[@options.level.get('slug')]?.lockDefaultCode or CampaignOptions?.getOption?(@options?.level?.get?('slug'), 'lockDefaultCode')
|
||||
return unless @options.level.get('lockDefaultCode')
|
||||
return unless @spell.source is @spell.originalSource or force
|
||||
|
||||
console.info 'Locking down default code.'
|
||||
|
@ -374,7 +368,7 @@ module.exports = class SpellView extends CocoView
|
|||
# TODO: Turn on more autocompletion based on level sophistication
|
||||
# TODO: E.g. using the language default snippets yields a bunch of crazy non-beginner suggestions
|
||||
# TODO: Options logic shouldn't exist both here and in updateAutocomplete()
|
||||
popupFontSizePx = CampaignOptions.getOption(@options.level.get('slug'), 'autocompleteFontSizePx') ? 16
|
||||
popupFontSizePx = @options.level.get('autocompleteFontSizePx') ? 16
|
||||
@zatanna = new Zatanna @ace,
|
||||
basic: false
|
||||
liveCompletion: false
|
||||
|
@ -411,7 +405,7 @@ module.exports = class SpellView extends CocoView
|
|||
return (owner is 'this' or owner is 'more') and (not doc.owner? or doc.owner is 'this')
|
||||
if doc?.snippets?[e.language]
|
||||
content = doc.snippets[e.language].code
|
||||
if /loop/.test(content) and LevelOptions[@options.level.get('slug')]?.moveRightLoopSnippet
|
||||
if /loop/.test(content) and @options.level.get 'moveRightLoopSnippet'
|
||||
# Replace default loop snippet with an embedded moveRight()
|
||||
content = switch e.language
|
||||
when 'python' then 'loop:\n self.moveRight()\n ${1:}'
|
||||
|
@ -618,8 +612,8 @@ module.exports = class SpellView extends CocoView
|
|||
_.throttle @updateLines, 500
|
||||
_.throttle @hideProblemAlert, 500
|
||||
]
|
||||
onSignificantChange.push _.debounce @checkRequiredCode, 750 if LevelOptions[@options.level.get('slug')]?.requiredCode
|
||||
onSignificantChange.push _.debounce @checkSuspectCode, 750 if LevelOptions[@options.level.get('slug')]?.suspectCode
|
||||
onSignificantChange.push _.debounce @checkRequiredCode, 750 if @options.level.get 'requiredCode'
|
||||
onSignificantChange.push _.debounce @checkSuspectCode, 750 if @options.level.get 'suspectCode'
|
||||
@onCodeChangeMetaHandler = =>
|
||||
return if @eventsSuppressed
|
||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'code-change', volume: 0.5
|
||||
|
@ -924,7 +918,7 @@ module.exports = class SpellView extends CocoView
|
|||
@aceSession.removeGutterDecoration row, 'executed'
|
||||
@decoratedGutter[row] = ''
|
||||
lastExecuted = _.last executed
|
||||
showToolbarView = executed.length and @spellThang.castAether.metrics.statementsExecuted > 3 and not LevelOptions[@options.level.get('slug')]?.hidesCodeToolbar # Hide for a while
|
||||
showToolbarView = executed.length and @spellThang.castAether.metrics.statementsExecuted > 3 and not @options.level.get 'hidesCodeToolbar' # Hide for a while
|
||||
showToolbarView = false # TODO: fix toolbar styling in new design to have some space for it
|
||||
|
||||
if showToolbarView
|
||||
|
@ -1059,7 +1053,7 @@ module.exports = class SpellView extends CocoView
|
|||
checkRequiredCode: =>
|
||||
return if @destroyed
|
||||
source = @getSource().replace @singleLineCommentRegex(), ''
|
||||
requiredCodeFragments = LevelOptions[@options.level.get('slug')].requiredCode
|
||||
requiredCodeFragments = @options.level.get 'requiredCode'
|
||||
for requiredCodeFragment in requiredCodeFragments
|
||||
# Could make this obey regular expressions like suspectCode if needed
|
||||
if source.indexOf(requiredCodeFragment) is -1
|
||||
|
@ -1071,10 +1065,11 @@ module.exports = class SpellView extends CocoView
|
|||
checkSuspectCode: =>
|
||||
return if @destroyed
|
||||
source = @getSource().replace @singleLineCommentRegex(), ''
|
||||
suspectCodeFragments = LevelOptions[@options.level.get('slug')].suspectCode
|
||||
suspectCodeFragments = @options.level.get 'suspectCode'
|
||||
detectedSuspectCodeFragmentNames = []
|
||||
for suspectCodeFragment in suspectCodeFragments
|
||||
if suspectCodeFragment.pattern.test source
|
||||
pattern = new RegExp suspectCodeFragment.pattern, 'm'
|
||||
if pattern.test source
|
||||
@warnedCodeFragments ?= {}
|
||||
unless @warnedCodeFragments[suspectCodeFragment.name]
|
||||
Backbone.Mediator.publish 'tome:suspect-code-fragment-added', codeFragment: suspectCodeFragment.name, codeLanguage: @spell.language
|
||||
|
|
|
@ -62,7 +62,7 @@ module.exports = class TomeView extends CocoView
|
|||
programmableThangs = _.filter @options.thangs, 'isProgrammable'
|
||||
@createSpells programmableThangs, programmableThangs[0]?.world # Do before spellList, thangList, and castButton
|
||||
@spellList = @insertSubView new SpellListView spells: @spells, supermodel: @supermodel, level: @options.level
|
||||
@castButton = @insertSubView new CastButtonView spells: @spells, levelID: @options.levelID, session: @options.session
|
||||
@castButton = @insertSubView new CastButtonView spells: @spells, level: @options.level, session: @options.session
|
||||
@teamSpellMap = @generateTeamSpellMap(@spells)
|
||||
unless programmableThangs.length
|
||||
@cast()
|
||||
|
|
|
@ -2,7 +2,6 @@ CocoView = require 'views/core/CocoView'
|
|||
template = require 'templates/play/menu/guide-view'
|
||||
Article = require 'models/Article'
|
||||
utils = require 'core/utils'
|
||||
LevelOptions = require 'lib/LevelOptions'
|
||||
|
||||
# let's implement this once we have the docs database schema set up
|
||||
|
||||
|
@ -15,12 +14,12 @@ module.exports = class LevelGuideView extends CocoView
|
|||
|
||||
constructor: (options) ->
|
||||
@levelID = options.level.get('slug')
|
||||
@helpVideos = LevelOptions[@levelID]?.helpVideos ? []
|
||||
@helpVideos = options.level.get 'helpVideos'
|
||||
@trackedHelpVideoStart = @trackedHelpVideoFinish = false
|
||||
|
||||
|
||||
# A/B Testing video tutorial styles
|
||||
@helpVideosIndex = me.getVideoTutorialStylesIndex(@helpVideos.length)
|
||||
|
||||
|
||||
@firstOnly = options.firstOnly
|
||||
@docs = options?.docs ? options.level.get('documentation') ? {}
|
||||
general = @docs.generalArticles or []
|
||||
|
@ -88,7 +87,7 @@ module.exports = class LevelGuideView extends CocoView
|
|||
unless @trackedHelpVideoStart
|
||||
window.tracker?.trackEvent 'Start help video', level: @levelID, style: @helpVideos[@helpVideosIndex].style
|
||||
@trackedHelpVideoStart = true
|
||||
|
||||
|
||||
onFinishHelpVideo: ->
|
||||
unless @trackedHelpVideoFinish
|
||||
window.tracker?.trackEvent 'Finish help video', level: @levelID, style: @helpVideos[@helpVideosIndex].style
|
||||
|
@ -96,56 +95,8 @@ module.exports = class LevelGuideView extends CocoView
|
|||
|
||||
setupVideoPlayer: () ->
|
||||
return unless @helpVideos.length > 0
|
||||
|
||||
# TODO: run A/B test for different video styles
|
||||
|
||||
helpVideoURL = @helpVideos[@helpVideosIndex].URL
|
||||
if helpVideoURL.toLowerCase().indexOf('youtube') >= 0
|
||||
@setupYouTubeVideoPlayer helpVideoURL
|
||||
else if helpVideoURL.toLowerCase().indexOf('vimeo') >= 0
|
||||
@setupVimeoVideoPlayer helpVideoURL
|
||||
|
||||
setupYouTubeVideoPlayer: (helpVideoURL) ->
|
||||
# Setup YouTube iframe player
|
||||
# https://developers.google.com/youtube/iframe_api_reference
|
||||
# TODO: Can't load a YouTube video twice in one level
|
||||
# TODO: window.onYouTubeIframeAPIReady is only called once
|
||||
# TODO: Consider ripping out YouTube support and migrating all videos to Vimeo
|
||||
|
||||
onPlayerStateChange = (e) =>
|
||||
if e.data is 1
|
||||
@onStartHelpVideo()
|
||||
else if e.data is 0
|
||||
@onFinishHelpVideo()
|
||||
|
||||
createPlayer = =>
|
||||
new YT.Player 'help-video-player', {
|
||||
height: @helpVideoHeight,
|
||||
width: @helpVideoWidth,
|
||||
videoId: videoID,
|
||||
events: {
|
||||
'onStateChange': onPlayerStateChange
|
||||
}
|
||||
}
|
||||
|
||||
if matchVideoID = helpVideoURL.match /www\.youtube\.com\/embed\/(bHaeKdMPZrA)/
|
||||
videoID = matchVideoID[1]
|
||||
else
|
||||
console.warn "Unable to read video ID from help video."
|
||||
# TODO: Default to dungeons-of-kithgard?
|
||||
videoID = 'bHaeKdMPZrA'
|
||||
|
||||
# Add method that will be called by YouTube iframe player when ready
|
||||
window.onYouTubeIframeAPIReady = =>
|
||||
createPlayer()
|
||||
|
||||
# Add YouTube video player iframe script if necessary
|
||||
if YT?.Player?
|
||||
createPlayer()
|
||||
else
|
||||
tag = document.createElement('script')
|
||||
tag.src = "https://www.youtube.com/iframe_api"
|
||||
@$el.find('#help-video-heading').after(tag)
|
||||
helpVideoURL = @helpVideos[@helpVideosIndex].url
|
||||
@setupVimeoVideoPlayer helpVideoURL
|
||||
|
||||
setupVimeoVideoPlayer: (helpVideoURL) ->
|
||||
# Setup Vimeo player
|
||||
|
@ -166,7 +117,6 @@ module.exports = class LevelGuideView extends CocoView
|
|||
# Vimeo player is ready, can now hook up other events
|
||||
# https://developer.vimeo.com/player/js-api#events
|
||||
player = $('#help-video-player')[0]
|
||||
helpVideoURL = 'http:' + helpVideoURL unless helpVideoURL.indexOf('http') is 0
|
||||
player.contentWindow.postMessage JSON.stringify(method: 'addEventListener', value: 'play'), helpVideoURL
|
||||
player.contentWindow.postMessage JSON.stringify(method: 'addEventListener', value: 'finish'), helpVideoURL
|
||||
else if data.event is 'play'
|
||||
|
|
|
@ -8,7 +8,6 @@ ItemView = require './ItemView'
|
|||
SpriteBuilder = require 'lib/sprites/SpriteBuilder'
|
||||
ItemDetailsView = require 'views/play/modal/ItemDetailsView'
|
||||
Purchase = require 'models/Purchase'
|
||||
LevelOptions = require 'lib/LevelOptions'
|
||||
BuyGemsModal = require 'views/play/modal/BuyGemsModal'
|
||||
|
||||
hasGoneFullScreenOnce = false
|
||||
|
@ -94,9 +93,13 @@ module.exports = class InventoryModal extends ModalView
|
|||
locked = not (item.get('original') in me.items())
|
||||
#locked = false if me.get('slug') is 'nick'
|
||||
|
||||
if not item.getFrontFacingStats().props.length and not _.size(item.getFrontFacingStats().stats) and locked # Temp: while there are placeholder items
|
||||
null # Don't put into a collection
|
||||
if locked and item.get('slug') in _.values(LevelOptions[@options.levelID]?.requiredGear ? {})
|
||||
required = item.get('slug') in _.flatten(_.values(@options.level.get('requiredGear') ? {}))
|
||||
restricted = item.get('slug') in _.flatten(_.values(@options.level.get('restrictedGear') ? {}))
|
||||
placeholder = not item.getFrontFacingStats().props.length and not _.size(item.getFrontFacingStats().stats)
|
||||
|
||||
if placeholder and locked # The item is not complete, so don't put it into a collection.
|
||||
null
|
||||
else if locked and required
|
||||
item.classes.push 'locked'
|
||||
@itemGroups.requiredPurchaseItems.add item
|
||||
else if locked and item.get('slug') isnt 'simple-boots'
|
||||
|
@ -106,7 +109,7 @@ module.exports = class InventoryModal extends ModalView
|
|||
null
|
||||
else
|
||||
@itemGroups.lockedItems.add(item)
|
||||
else if item.get('slug') in _.values(LevelOptions[@options.levelID]?.restrictedGear ? {})
|
||||
else if restricted
|
||||
@itemGroups.restrictedItems.add(item)
|
||||
item.classes.push 'restricted'
|
||||
else
|
||||
|
@ -361,8 +364,8 @@ module.exports = class InventoryModal extends ModalView
|
|||
|
||||
requireLevelEquipment: ->
|
||||
# This is temporary, until we have a more general way of awarding items and configuring required/restricted items per level.
|
||||
requiredGear = LevelOptions[@options.levelID]?.requiredGear ? {}
|
||||
restrictedGear = LevelOptions[@options.levelID]?.restrictedGear ? {}
|
||||
requiredGear = @options.level.get('requiredGear') ? {}
|
||||
restrictedGear = @options.level.get('restrictedGear') ? {}
|
||||
if @inserted
|
||||
if @supermodel.finished()
|
||||
equipment = @getCurrentEquipmentConfig() # Make sure @equipment is updated
|
||||
|
@ -379,33 +382,43 @@ module.exports = class InventoryModal extends ModalView
|
|||
console.log 'Unequipping', itemModel.get('heroClass'), 'item', itemModel.get('name'), 'from slot due to class restrictions.'
|
||||
@unequipItemFromSlot @$el.find(".item-slot[data-slot='#{slot}']")
|
||||
delete equipment[slot]
|
||||
for slot, item of restrictedGear
|
||||
equipped = equipment[slot]
|
||||
if equipped and equipped is gear[restrictedGear[slot]]
|
||||
console.log 'Unequipping restricted item', restrictedGear[slot], 'for', slot, 'before level', @options.levelID
|
||||
@unequipItemFromSlot @$el.find(".item-slot[data-slot='#{slot}']")
|
||||
delete equipment[slot]
|
||||
for slot, item of requiredGear
|
||||
for slot, items of restrictedGear
|
||||
items = [items] if _.isString items
|
||||
for item in items
|
||||
item = gear[item] unless item.length is 24 # Temp: until migration to DB data is done
|
||||
equipped = equipment[slot]
|
||||
if equipped and equipped is item
|
||||
console.log 'Unequipping restricted item', equipped, 'for', slot, 'before level', @options.level.get('slug')
|
||||
@unequipItemFromSlot @$el.find(".item-slot[data-slot='#{slot}']")
|
||||
delete equipment[slot]
|
||||
for slot, items of requiredGear
|
||||
items = [items] if _.isString items # Temp: until migration to arrays is done
|
||||
item = items[0] # TODO: look for the last one that they own, or the first one if they don't own any.
|
||||
# TODO: require them to have one of the given items, not just either the item or anything except all these exceptions.
|
||||
slug = gearSlugs[item]
|
||||
if item.length isnt 24 # Temp: until migration to DB data is done
|
||||
[item, slug] = [gear[item], item]
|
||||
#console.log 'requiring', item, slug, 'for', slot, 'and have', equipment[slot]
|
||||
if (slot in ['right-hand', 'left-hand', 'head', 'torso']) and not (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'])) and not (item in ['crude-builders-hammer', 'wooden-builders-hammer'])
|
||||
(heroClass is 'Ranger' and @options.level.get('slug') in ['swift-dagger', 'shrapnel']) or
|
||||
(heroClass is 'Wizard' and @options.level.get('slug') in ['touch-of-death', 'bonemender'])) and not (slug in ['crude-builders-hammer', 'wooden-builders-hammer'])
|
||||
# After they switch to a ranger or wizard, we stop being so finicky about class-specific gear.
|
||||
continue
|
||||
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
|
||||
continue if slug is 'tarnished-bronze-breastplate' and inWorldMap and @options.level.get('slug') is 'the-raised-sword' # Don't tell them they need it until they need it in the level
|
||||
equipped = equipment[slot]
|
||||
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 in ['simple-sword', 'long-sword', 'roughedge', 'sharpened-sword'] and equipped is gear['crude-builders-hammer']) or
|
||||
(item is 'leather-boots' and equipped is gear['simple-boots']) or
|
||||
(item is 'simple-boots' and equipped is gear['leather-boots'])
|
||||
(slug is 'crude-builders-hammer' and equipped in [gear['simple-sword'], gear['long-sword'], gear['sharpened-sword'], gear['roughedge']]) or
|
||||
(slug in ['simple-sword', 'long-sword', 'roughedge', 'sharpened-sword'] and equipped is gear['crude-builders-hammer']) or
|
||||
(slug is 'leather-boots' and equipped is gear['simple-boots']) or
|
||||
(slug is 'simple-boots' and equipped is gear['leather-boots'])
|
||||
)
|
||||
itemModel = @items.findWhere {slug: item}
|
||||
itemModel = @items.findWhere {slug: slug}
|
||||
continue unless itemModel
|
||||
availableSlotSelector = "#unequipped .item[data-item-id='#{itemModel.id}']"
|
||||
@highlightElement availableSlotSelector, delay: 500, sides: ['right'], rotation: Math.PI / 2
|
||||
@$el.find(availableSlotSelector).addClass 'should-equip'
|
||||
@$el.find("#equipped div[data-slot='#{slot}']").addClass 'should-equip'
|
||||
@remainingRequiredEquipment.push slot: slot, item: gear[item]
|
||||
@remainingRequiredEquipment.push slot: slot, item: item
|
||||
if hadRequired and not @remainingRequiredEquipment.length
|
||||
@endHighlight()
|
||||
@highlightElement '#play-level-button', duration: 5000
|
||||
|
@ -639,3 +652,5 @@ gear =
|
|||
'quartz-sense-stone': '54693240a2b1f53ce79443c5'
|
||||
'wooden-builders-hammer': '54694ba3a2b1f53ce794444d'
|
||||
'simple-wristwatch': '54693797a2b1f53ce79443e9'
|
||||
|
||||
gearSlugs = _.invert gear
|
||||
|
|
|
@ -8,7 +8,6 @@ AudioPlayer = require 'lib/AudioPlayer'
|
|||
utils = require 'core/utils'
|
||||
BuyGemsModal = require 'views/play/modal/BuyGemsModal'
|
||||
Purchase = require 'models/Purchase'
|
||||
LevelOptions = require 'lib/LevelOptions'
|
||||
|
||||
module.exports = class PlayHeroesModal extends ModalView
|
||||
className: 'modal fade play-modal'
|
||||
|
@ -55,8 +54,8 @@ module.exports = class PlayHeroesModal extends ModalView
|
|||
original = hero.get('original')
|
||||
hero.locked = not me.ownsHero(original)
|
||||
hero.purchasable = hero.locked and (original in (me.get('earned')?.heroes ? []))
|
||||
if @options.levelID and allowedHeroSlugs = LevelOptions[@options.levelID]?.allowedHeroes
|
||||
hero.restricted = not (hero.get('slug') in allowedHeroSlugs)
|
||||
if @options.level and allowedHeroes = @options.level.get 'allowedHeroes'
|
||||
hero.restricted = not (hero.get('original') in allowedHeroes)
|
||||
hero.class = (hero.get('heroClass') or 'warrior').toLowerCase()
|
||||
hero.stats = hero.getHeroStats()
|
||||
|
||||
|
|
|
@ -36,23 +36,30 @@ CampaignHandler = class CampaignHandler extends Handler
|
|||
return @getRelatedAchievements(req, res, campaign, projection) if relationship is 'achievements'
|
||||
else
|
||||
super(arguments...)
|
||||
|
||||
|
||||
|
||||
getRelatedLevels: (req, res, campaign, projection) ->
|
||||
extraProjectionProps = []
|
||||
if projection
|
||||
# Make sure that permissions and version are fetched, but not sent back if they didn't ask for them.
|
||||
extraProjectionProps.push 'permissions' unless projection.permissions
|
||||
extraProjectionProps.push 'version' unless projection.version
|
||||
projection.permissions = 1
|
||||
projection.version = 1
|
||||
|
||||
levels = campaign.get('levels') or []
|
||||
|
||||
|
||||
f = (levelOriginal) ->
|
||||
(callback) ->
|
||||
query = { original: mongoose.Types.ObjectId(levelOriginal) }
|
||||
sort = { 'version.major': -1, 'version.minor': -1 }
|
||||
Level.findOne(query, projection).sort(sort).exec callback
|
||||
|
||||
|
||||
fetches = (f(level.original) for level in _.values(levels))
|
||||
async.parallel fetches, (err, levels) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
return @sendSuccess(res, (level.toObject() for level in levels))
|
||||
|
||||
|
||||
filteredLevels = (_.omit(level.toObject(), extraProjectionProps) for level in levels)
|
||||
return @sendSuccess(res, filteredLevels)
|
||||
|
||||
getRelatedAchievements: (req, res, campaign, projection) ->
|
||||
levels = campaign.get('levels') or []
|
||||
|
||||
|
@ -67,4 +74,8 @@ CampaignHandler = class CampaignHandler extends Handler
|
|||
return @sendDatabaseError(res, err) if err
|
||||
return @sendSuccess(res, (achievement.toObject() for achievement in achievements))
|
||||
|
||||
onPutSuccess: (req, doc) ->
|
||||
docLink = "http://codecombat.com#{req.headers['x-current-path']}"
|
||||
@sendChangedHipChatMessage creator: req.user, target: doc, docLink: docLink
|
||||
|
||||
module.exports = new CampaignHandler()
|
||||
|
|
|
@ -314,10 +314,17 @@ module.exports = class Handler
|
|||
projection = {}
|
||||
fields = if req.query.project is 'true' then _.keys(PROJECT) else req.query.project.split(',')
|
||||
projection[field] = 1 for field in fields
|
||||
# Make sure that permissions and version are fetched, but not sent back if they didn't ask for them.
|
||||
extraProjectionProps = []
|
||||
extraProjectionProps.push 'permissions' unless projection.permissions
|
||||
extraProjectionProps.push 'version' unless projection.version
|
||||
projection.permissions = 1
|
||||
projection.version = 1
|
||||
args.push projection
|
||||
@modelClass.findOne(args...).sort(sort).exec (err, doc) =>
|
||||
return @sendNotFoundError(res) unless doc?
|
||||
return @sendForbiddenError(res) unless @hasAccessToDocument(req, doc)
|
||||
doc = _.omit doc, extraProjectionProps if extraProjectionProps?
|
||||
res.send(doc)
|
||||
res.end()
|
||||
|
||||
|
@ -343,6 +350,7 @@ module.exports = class Handler
|
|||
return @sendBadInputError(res, err.errors) if err?.valid is false
|
||||
return @sendDatabaseError(res, err) if err
|
||||
@sendSuccess(res, @formatEntity(req, document))
|
||||
@onPutSuccess(req, document)
|
||||
|
||||
post: (req, res) ->
|
||||
if @modelClass.schema.uses_coco_versions
|
||||
|
@ -362,6 +370,7 @@ module.exports = class Handler
|
|||
@onPostSuccess(req, document)
|
||||
|
||||
onPostSuccess: (req, doc) ->
|
||||
onPutSuccess: (req, doc) ->
|
||||
|
||||
###
|
||||
TODO: think about pulling some common stuff out of postFirstVersion/postNewVersion
|
||||
|
|
|
@ -33,6 +33,7 @@ LevelHandler = class LevelHandler extends Handler
|
|||
'requiresSubscription'
|
||||
'adventurer'
|
||||
'practice'
|
||||
'adminOnly'
|
||||
'disableSpaces'
|
||||
'hidesSubmitUntilRun'
|
||||
'hidesPlayButton'
|
||||
|
@ -52,6 +53,8 @@ LevelHandler = class LevelHandler extends Handler
|
|||
'restrictedGear'
|
||||
'allowedHeroes'
|
||||
'tasks'
|
||||
'helpVideos'
|
||||
'campaign'
|
||||
]
|
||||
|
||||
postEditableProperties: ['name']
|
||||
|
@ -93,7 +96,7 @@ LevelHandler = class LevelHandler extends Handler
|
|||
Session.findOne(sessionQuery).exec (err, doc) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
return @sendSuccess(res, doc) if doc?
|
||||
return @sendPaymentRequiredError(res, err) if (not req.user.isPremium()) and level.get('requiresSubscription')
|
||||
return @sendPaymentRequiredError(res, err) if (not req.user.isPremium()) and level.get('requiresSubscription') and not level.get('adventurer')
|
||||
@createAndSaveNewSession sessionQuery, req, res
|
||||
|
||||
createAndSaveNewSession: (sessionQuery, req, res) =>
|
||||
|
|
|
@ -309,17 +309,17 @@ module.exports.SearchablePlugin = (schema, options) ->
|
|||
next()
|
||||
|
||||
module.exports.TranslationCoveragePlugin = (schema, options) ->
|
||||
|
||||
|
||||
schema.uses_coco_translation_coverage = true
|
||||
schema.set('autoIndex', true)
|
||||
|
||||
|
||||
index = {}
|
||||
|
||||
|
||||
if schema.uses_coco_versions
|
||||
if not schema.uses_coco_names
|
||||
throw Error('If using translation coverage and versioning, should also use names for indexing.')
|
||||
index.slug = 1
|
||||
|
||||
|
||||
index.i18nCoverage = 1
|
||||
|
||||
schema.index(index, {sparse: true, name: 'translation coverage index', background: true})
|
||||
|
||||
schema.index(index, {sparse: true, name: 'translation coverage index', background: true})
|
||||
|
|
|
@ -23,7 +23,7 @@ campaign = {
|
|||
name: 'Campaign'
|
||||
levels: {}
|
||||
}
|
||||
|
||||
|
||||
levelURL = getURL('/db/level')
|
||||
achievementURL = getURL('/db/achievement')
|
||||
campaignURL = getURL('/db/campaign')
|
||||
|
@ -47,7 +47,7 @@ describe '/db/campaign', ->
|
|||
request.post {uri: achievementURL, json: achievement}, (err, res, body) ->
|
||||
achievement = body
|
||||
done()
|
||||
|
||||
|
||||
it 'can create campaigns', (done) ->
|
||||
for level in levels.reverse()
|
||||
campaign.levels[level.original] = _.pick level, campaignLevelProperties
|
||||
|
@ -55,7 +55,7 @@ describe '/db/campaign', ->
|
|||
expect(res.statusCode).toBe(200)
|
||||
campaign = body
|
||||
done()
|
||||
|
||||
|
||||
describe '/db/campaign/.../levels', ->
|
||||
it 'fetches the levels in a campaign', (done) ->
|
||||
url = getURL("/db/campaign/#{campaign._id}/levels")
|
||||
|
@ -65,7 +65,7 @@ describe '/db/campaign/.../levels', ->
|
|||
expect(body.length).toBe(2)
|
||||
expect(_.difference(['level-1', 'level-2'],(level.slug for level in body)).length).toBe(0)
|
||||
done()
|
||||
|
||||
|
||||
describe '/db/campaign/.../achievements', ->
|
||||
it 'fetches the achievements in the levels in a campaign', (done) ->
|
||||
url = getURL("/db/campaign/#{campaign._id}/achievements")
|
||||
|
@ -74,4 +74,3 @@ describe '/db/campaign/.../achievements', ->
|
|||
body = JSON.parse(body)
|
||||
expect(body.length).toBe(1)
|
||||
done()
|
||||
|