codecombat/app/views/editor/campaign/CampaignEditorView.coffee

896 lines
34 KiB
CoffeeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

RootView = require 'views/core/RootView'
Campaign = require 'models/Campaign'
Level = require 'models/Level'
Achievement = require 'models/Achievement'
ThangType = require 'models/ThangType'
CampaignView = require 'views/play/CampaignView'
CocoCollection = require 'collections/CocoCollection'
treemaExt = require 'core/treema-ext'
utils = require 'core/utils'
achievementProject = ['related', 'rewards', 'name', 'slug']
thangTypeProject = ['name', 'original', 'slug']
module.exports = class CampaignEditorView extends RootView
id: "campaign-editor-view"
template: require 'templates/editor/campaign/campaign-editor-view'
className: 'editor'
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([], {
model: Level
url: "/db/campaign/#{@campaignHandle}/levels"
project: Campaign.denormalizedLevelProperties
})
@supermodel.loadCollection(@levels, 'levels')
@achievements = new CocoCollection([], {
model: Achievement
url: "/db/campaign/#{@campaignHandle}/achievements"
project: achievementProject
})
@supermodel.loadCollection(@achievements, 'achievements')
@toSave = new Backbone.Collection()
onLoaded: ->
campaignLevels = $.extend({}, @campaign.get('levels'))
for level in @levels.models
levelOriginal = level.get('original')
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 = []
for achievement in achievements
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
campaignLevels[levelOriginal] = campaignLevel
@campaign.set('levels', campaignLevels)
super()
getRenderData: ->
c = super()
c.campaign = @campaign
c
afterRender: ->
super()
treemaOptions =
schema: Campaign.schema
data: $.extend({}, @campaign.attributes)
callbacks:
change: @onTreemaChanged
select: @onTreemaSelectionChanged
dblclick: @onTreemaDoubleClicked
nodeClasses:
levels: LevelsNode
level: LevelNode
campaigns: CampaignsNode
campaign: CampaignNode
achievement: AchievementNode
supermodel: @supermodel
@treema = @$el.find('#campaign-treema').treema treemaOptions
@treema.build()
@treema.open()
@treema.childrenTreemas.levels?.open()
@campaignView = new CampaignView({editorMode: true, supermodel: @supermodel}, @campaignHandle)
@campaignView.highlightElement = _.noop # make it stop
@listenTo @campaignView, 'level-moved', @onCampaignLevelMoved
@listenTo @campaignView, 'adjacent-campaign-moved', @onAdjacentCampaignMoved
@listenTo @campaignView, 'level-clicked', @onCampaignLevelClicked
@insertSubView @campaignView
onTreemaChanged: (e, nodes) =>
for node in nodes
path = node.getPath()
if _.string.startsWith path, '/levels/'
parts = path.split('/')
original = parts[2]
level = @supermodel.getModelByOriginal Level, original
campaignLevel = @treema.get "/levels/#{original}"
if 'rewards' in parts
rewardsData = @
@updateRewardsForLevel level, campaignLevel.rewards
for key in Campaign.denormalizedLevelProperties
level.set key, campaignLevel[key]
@toSave.add level
@toSave.add @campaign
@campaign.set key, value for key, value of @treema.data
@campaignView.setCampaign(@campaign)
onCampaignLevelMoved: (e) ->
path = "levels/#{e.levelOriginal}/position"
@treema.set path, e.position
onAdjacentCampaignMoved: (e) ->
path = "adjacentCampaigns/#{e.campaignID}/position"
@treema.set path, e.position
onCampaignLevelClicked: (levelOriginal) ->
return unless levelTreema = @treema.childrenTreemas?.levels?.childrenTreemas?[levelOriginal]
levelTreema.select()
# levelTreema.open()
updateRewardsForLevel: (level, rewards) ->
achievements = @supermodel.getModels(Achievement)
achievements = (a for a in achievements when a.get('related') is level.get('original'))
for achievement in achievements
rewardSubset = (r for r in rewards when r.achievement is achievement._id)
newRewards = {}
newRewards.heroes = _.compact((r.hero for r in rewards))
newRewards.items = _.compact((r.item for r in rewards))
newRewards.levels = _.compact((r.level for r in rewards))
achievement.set 'rewards', newRewards
class LevelsNode extends TreemaObjectNode
valueClass: 'treema-levels'
@levels: {}
buildValueForDisplay: (valEl, data) ->
@buildValueForDisplaySimply valEl, ''+_.size(data)
childPropertiesAvailable: -> @childSource
childSource: (req, res) =>
s = new Backbone.Collection([], {model:Level})
s.url = '/db/level'
s.fetch({data: {term:req.term, project: Campaign.denormalizedLevelProperties.join(',')}})
s.once 'sync', (collection) =>
for level in collection.models
LevelsNode.levels[level.get('original')] = level
@settings.supermodel.registerModel level
mapped = ({label: r.get('name'), value: r.get('original')} for r in collection.models)
res(mapped)
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
_.extend @data, data
class CampaignsNode extends TreemaObjectNode
valueClass: 'treema-campaigns'
@campaigns: {}
buildValueForDisplay: (valEl, data) ->
@buildValueForDisplaySimply valEl, ''+_.size(data)
childPropertiesAvailable: -> @childSource
childSource: (req, res) =>
s = new Backbone.Collection([], {model:Campaign})
s.url = '/db/campaign'
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)
console.log 'campaigns is now', CampaignsNode.campaigns
res(mapped)
class CampaignNode extends TreemaObjectNode
valueClass: 'treema-campaign'
buildValueForDisplay: (valEl, data) ->
console.log 'build value for display?', data
@buildValueForDisplaySimply valEl, data.name
populateData: ->
return if @data.name?
console.log 'key for parent', @keyForParent, CampaignsNode.campaigns[@keyForParent].attributes, Campaign.denormalizedCampaignProperties
data = _.pick CampaignsNode.campaigns[@keyForParent].attributes, Campaign.denormalizedCampaignProperties
console.log 'data?', data
_.extend @data, data
console.log 'extended 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 gemat 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: {}