RootView = require 'views/core/RootView' Campaign = require 'models/Campaign' Level = require 'models/Level' Achievement = require 'models/Achievement' ThangType = require 'models/ThangType' WorldMapView = require 'views/play/WorldMapView' 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] ? {} #--------------- 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 achievement: AchievementNode supermodel: @supermodel @treema = @$el.find('#campaign-treema').treema treemaOptions @treema.build() @treema.open() @treema.childrenTreemas.levels?.open() worldMapView = new WorldMapView({supermodel: @supermodel, editorMode: true}, 'dungeon') worldMapView.highlightElement = _.noop # make it stop @insertSubView worldMapView 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 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) -> LevelsNode.levels[level.get('original')] = level for level in collection.models 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 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: {}