mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-03-14 07:00:01 -04:00
Added basic campaign view, working on campaign handlers.
This commit is contained in:
parent
0b81796333
commit
1cc6a97e43
16 changed files with 999 additions and 45 deletions
|
@ -66,6 +66,7 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
'editor/level/:levelID': go('editor/level/LevelEditView')
|
||||
'editor/thang': go('editor/thang/ThangTypeSearchView')
|
||||
'editor/thang/:thangID': go('editor/thang/ThangTypeEditView')
|
||||
'editor/campaign/:campaignID': go('editor/campaign/CampaignEditorView')
|
||||
|
||||
'employers': go('EmployersView')
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
CocoModel = require './CocoModel'
|
||||
schema = require 'schemas/models/campaign.schema'
|
||||
|
||||
module.exports = class Campaign extends CocoModel
|
||||
@className: 'Campaign'
|
||||
@schema: require 'schemas/models/campaign.schema'
|
||||
@schema: schema
|
||||
urlRoot: '/db/campaign'
|
||||
saveBackups: true
|
||||
@denormalizedLevelProperties: _.keys(_.omit(schema.properties.levels.additionalProperties.properties, ['unlocks', 'position']))
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
|
||||
c = require './../schemas'
|
||||
|
||||
CampaignSchema = c.object()
|
||||
c.extendNamedProperties CampaignSchema # name first
|
||||
|
||||
_.extend CampaignSchema.properties,
|
||||
_.extend CampaignSchema.properties, {
|
||||
i18n: {type: 'object', title: 'i18n', format: 'i18n', props: ['name', 'body']}
|
||||
|
||||
ambientSound: c.object {},
|
||||
|
@ -12,8 +11,12 @@ _.extend CampaignSchema.properties,
|
|||
ogg: { type: 'string', format: 'sound-file' }
|
||||
|
||||
backgroundImage: c.array {}, {
|
||||
image: { type: 'string', format: 'image-file' }
|
||||
width: { type: 'number' }
|
||||
type: 'object'
|
||||
additionalProperties: false
|
||||
properties: {
|
||||
image: { type: 'string', format: 'image-file' }
|
||||
width: { type: 'number' }
|
||||
}
|
||||
}
|
||||
backgroundColor: { type: 'string' }
|
||||
backgroundColorTransparent: { type: 'string' }
|
||||
|
@ -33,18 +36,22 @@ _.extend CampaignSchema.properties,
|
|||
}
|
||||
}}
|
||||
|
||||
levels: { type: 'object', additionalItems: {
|
||||
levels: { type: 'object', format: 'levels', additionalProperties: {
|
||||
title: 'Level'
|
||||
type: 'object'
|
||||
format: 'level'
|
||||
additionalProperties: false
|
||||
|
||||
# key is the original property
|
||||
properties: {
|
||||
#- denormalized from Level
|
||||
# TODO: take these properties from the Level schema and put them into schema references, use them here
|
||||
name: { readOnly: true }
|
||||
description: { readOnly: true }
|
||||
name: { type: 'string', format: 'hidden' }
|
||||
description: { type: 'string', format: 'hidden' }
|
||||
requiresSubscription: { type: 'boolean' }
|
||||
type: {'enum': ['campaign', 'ladder', 'ladder-tutorial', 'hero', 'hero-ladder', 'hero-coop']}
|
||||
slug: { readOnly: true }
|
||||
original: { readOnly: true }
|
||||
slug: { type: 'string', format: 'hidden' }
|
||||
original: { type: 'string', format: 'hidden' }
|
||||
adventurer: { type: 'boolean' }
|
||||
practice: { type: 'boolean' }
|
||||
|
||||
|
@ -85,20 +92,23 @@ _.extend CampaignSchema.properties,
|
|||
type: 'string' # should be an originalID, denormalized on the editor side
|
||||
}}
|
||||
|
||||
#- denormalized/restructured from Achievements
|
||||
nextLevels: c.array {}
|
||||
unlocksHero: c.object { readOnly: true }, {
|
||||
image: { type: 'string', format: 'image-file' }
|
||||
original: { type: 'string' }
|
||||
}
|
||||
#- denormalized from Achievements
|
||||
unlocks: { type: 'array', items: {
|
||||
type: 'object'
|
||||
properties:
|
||||
original: { type: 'string' }
|
||||
type: { enum: ['hero', 'item', 'level'] }
|
||||
achievement: { type: 'string' }
|
||||
}}
|
||||
|
||||
#- normal properties
|
||||
position: c.point2d()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
c.extendBasicProperties CampaignSchema, 'campaign'
|
||||
c.extendTranslationCoverageProperties CampaignSchema
|
||||
|
||||
|
|
|
@ -253,6 +253,40 @@ _.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'}
|
||||
|
||||
# Admin flags
|
||||
disableSpaces: { type: 'boolean' }
|
||||
hidesSubmitUntilRun: { type: 'boolean' }
|
||||
hidesPlayButton: { type: 'boolean' }
|
||||
hidesRunShortcut: { type: 'boolean' }
|
||||
hidesHUD: { type: 'boolean' }
|
||||
hidesSay: { type: 'boolean' }
|
||||
hidesCodeToolbar: { type: 'boolean' }
|
||||
hidesRealTimePlayback: { type: 'boolean' }
|
||||
backspaceThrottle: { type: 'boolean' }
|
||||
lockDefaultCode: { type: 'boolean' }
|
||||
moveRightLoopSnippet: { type: 'boolean' }
|
||||
realTimeSpeedFactor: { type: 'number' }
|
||||
autocompleteFontSizePx: { type: 'number' }
|
||||
requiredCode: c.array {}, {
|
||||
type: 'string'
|
||||
}
|
||||
suspectCode: c.array {}, {
|
||||
type: 'object'
|
||||
properties: {
|
||||
name: { type: 'string' }
|
||||
pattern: { type: 'string' }
|
||||
}
|
||||
}
|
||||
requiredGear: { type: 'object', additionalProperties: {
|
||||
type: 'string'
|
||||
}}
|
||||
restrictedGear: { type: 'object', additionalProperties: {
|
||||
type: 'string'
|
||||
}}
|
||||
allowedHeroes: { type: 'array', items: {
|
||||
type: 'string'
|
||||
}}
|
||||
|
||||
c.extendBasicProperties LevelSchema, 'level'
|
||||
c.extendSearchableProperties LevelSchema
|
||||
c.extendVersionedProperties LevelSchema, 'level'
|
||||
|
|
15
app/styles/editor/campaign/campaign-editor-view.sass
Normal file
15
app/styles/editor/campaign/campaign-editor-view.sass
Normal file
|
@ -0,0 +1,15 @@
|
|||
#campaign-editor-view
|
||||
#left-column
|
||||
position: absolute
|
||||
top: 0
|
||||
bottom: 0
|
||||
left: 0
|
||||
right: 0
|
||||
margin-right: 1200px
|
||||
|
||||
#right-column
|
||||
position: absolute
|
||||
top: 0
|
||||
bottom: 0
|
||||
right: 0
|
||||
width: 1200px
|
44
app/templates/editor/campaign/campaign-editor-view.jade
Normal file
44
app/templates/editor/campaign/campaign-editor-view.jade
Normal file
|
@ -0,0 +1,44 @@
|
|||
extends /templates/base
|
||||
|
||||
block header
|
||||
if campaign.loading
|
||||
nav.navbar.navbar-default(role='navigation')#campaign-editor-top-nav
|
||||
.container-fluid
|
||||
ul.nav.navbar-nav
|
||||
li
|
||||
a(href="/")
|
||||
span.glyphicon-home.glyphicon
|
||||
else
|
||||
nav.navbar.navbar-default(role='navigation')#campaign-editor-top-nav
|
||||
ul.nav.navbar-nav
|
||||
li
|
||||
a(href="/")
|
||||
span.glyphicon-home.glyphicon
|
||||
|
||||
ul.nav.navbar-nav.navbar-right
|
||||
if authorized
|
||||
li#save-button
|
||||
a
|
||||
span.glyphicon-floppy-disk.glyphicon
|
||||
li.dropdown
|
||||
a(data-toggle='dropdown')
|
||||
span.glyphicon-chevron-down.glyphicon
|
||||
ul.dropdown-menu
|
||||
li.dropdown-header Actions
|
||||
li(class=anonymous ? "disabled": "")
|
||||
a(data-toggle="coco-modal", data-target="modal/RevertModal", data-i18n="editor.revert")#revert-button Revert
|
||||
li(class=anonymous ? "disabled": "")
|
||||
a(data-i18n="editor.pop_i18n")#pop-level-i18n-button Populate i18n
|
||||
li.divider
|
||||
li.dropdown-header Info
|
||||
|
||||
block outer_content
|
||||
.outer-content
|
||||
#left-column
|
||||
#campaign-treema
|
||||
|
||||
#right-column
|
||||
#world-map-view
|
||||
#campaign-level-view
|
||||
|
||||
block footer
|
|
@ -8,7 +8,7 @@
|
|||
- var seenNext = nextLevel;
|
||||
each level in campaign.levels
|
||||
if !level.hidden
|
||||
- var next = level.id == nextLevel || (!seenNext && levelStatusMap[level.id] != "complete" && !level.locked && !level.disabled);
|
||||
- var next = level.id == nextLevel || (!seenNext && levelStatusMap[level.id] != "complete" && !level.locked && !level.disabled && !editorMode);
|
||||
- seenNext = seenNext || next;
|
||||
div(style="left: #{level.x}%; bottom: #{level.y}%; background-color: #{level.color}", class="level" + (next ? " next" : "") + (level.disabled ? " disabled" : "") + (level.locked ? " locked" : "") + " " + levelStatusMap[level.id] || "", data-level-id=level.id, title=level.name + (level.disabled ? ' (Coming Soon to Adventurers)' : ''))
|
||||
if level.unlocksHero && !level.unlockedHero
|
||||
|
|
691
app/views/editor/campaign/CampaignEditorView.coffee
Normal file
691
app/views/editor/campaign/CampaignEditorView.coffee
Normal file
|
@ -0,0 +1,691 @@
|
|||
RootView = require 'views/core/RootView'
|
||||
Campaign = require 'models/Campaign'
|
||||
Level = require 'models/Level'
|
||||
WorldMapView = require 'views/play/WorldMapView'
|
||||
CocoCollection = require 'collections/CocoCollection'
|
||||
|
||||
module.exports = class CampaignEditorView extends RootView
|
||||
id: "campaign-editor-view"
|
||||
template: require 'templates/editor/campaign/campaign-editor-view'
|
||||
className: 'editor'
|
||||
|
||||
constructor: ->
|
||||
super(arguments...)
|
||||
|
||||
# TODO: move the outputted data to the db, and load the Campaign objects instead
|
||||
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)
|
||||
#------------------------------------------------
|
||||
|
||||
collection = new CocoCollection({model: Level, url})
|
||||
collection.ur
|
||||
|
||||
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
|
||||
|
||||
|
||||
@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
|
||||
|
||||
|
||||
class LevelsNode extends TreemaObjectNode
|
||||
valueClass: 'treema-levels'
|
||||
@levels: {}
|
||||
|
||||
buildValueForDisplay: (valEl, data) ->
|
||||
@buildValueForDisplaySimply valEl, ''+_.size(data)
|
||||
|
||||
childPropertiesAvailable: -> @childSource
|
||||
|
||||
childSource: (req, res) =>
|
||||
console.log 'calling child source!', req
|
||||
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
|
||||
console.log 'results!', collection.models
|
||||
mapped = ({label: r.get('name'), value: r.get('original')} for r in collection.models)
|
||||
console.log 'mapped', mapped
|
||||
res(mapped)
|
||||
|
||||
|
||||
class LevelNode extends TreemaObjectNode
|
||||
valueClass: 'treema-level'
|
||||
buildValueForDisplay: (valEl, data) ->
|
||||
@buildValueForDisplaySimply valEl, data.name
|
||||
|
||||
populateData: ->
|
||||
return if @data.name?
|
||||
console.log 'how do I do this?', @data, @keyForParent, LevelsNode.levels
|
||||
data = _.pick LevelsNode.levels[@keyForParent].attributes, Campaign.denormalizedLevelProperties
|
||||
_.extend @data, data
|
||||
console.log 'extended to data', data
|
||||
console.log 'now data is', @data
|
||||
|
||||
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: {}
|
|
@ -43,6 +43,8 @@ module.exports = class WorldMapView extends RootView
|
|||
options.supermodel = null
|
||||
@terrain ?= 'dungeon' # or 'forest', 'desert'
|
||||
super options
|
||||
options ?= {}
|
||||
@editorMode = options.editorMode
|
||||
@nextLevel = @getQueryVariable 'next'
|
||||
@levelStatusMap = {}
|
||||
@levelPlayCountMap = {}
|
||||
|
@ -137,12 +139,12 @@ module.exports = class WorldMapView extends RootView
|
|||
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 me.get('slug') is 'nick'
|
||||
level.locked = false if @editorMode
|
||||
level.disabled = false if @levelStatusMap[level.id] in ['started', 'complete']
|
||||
level.color = 'rgb(255, 80, 60)'
|
||||
if level.requiresSubscription
|
||||
level.color = 'rgb(80, 130, 200)'
|
||||
if level.unlocksHero
|
||||
level.color = 'rgb(0,0,0)'
|
||||
level.unlockedHero = level.unlocksHero.originalID in (me.get('earned')?.heroes or [])
|
||||
level.hidden = level.locked or level.disabled
|
||||
|
||||
|
@ -159,6 +161,7 @@ module.exports = class WorldMapView extends RootView
|
|||
context.forestIsAvailable = @startedForestLevel or (Level.levels['defense-of-plainswood'] in (me.get('earned')?.levels or []))
|
||||
context.desertIsAvailable = @startedDesertLevel or (Level.levels['the-mighty-sand-yak'] in (me.get('earned')?.levels or []))
|
||||
context.requiresSubscription = @requiresSubscription
|
||||
context.editorMode = @editorMode
|
||||
context
|
||||
|
||||
afterRender: ->
|
||||
|
@ -179,7 +182,7 @@ module.exports = class WorldMapView extends RootView
|
|||
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()
|
||||
@$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
|
||||
|
@ -194,6 +197,10 @@ module.exports = class WorldMapView extends RootView
|
|||
@openModalView authModal
|
||||
|
||||
onSessionsLoaded: (e) ->
|
||||
if @editorMode
|
||||
@startedForestLevel = true
|
||||
@startedDesertLevel = true
|
||||
return
|
||||
forestLevels = (f.id for f in forest)
|
||||
desertLevels = (f.id for f in desert)
|
||||
for session in @sessions.models
|
||||
|
@ -252,6 +259,7 @@ module.exports = class WorldMapView extends RootView
|
|||
|
||||
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()
|
||||
|
@ -290,8 +298,8 @@ module.exports = class WorldMapView extends RootView
|
|||
mapHeight = iPadHeight = 1536
|
||||
mapWidth = {dungeon: 2350, forest: 2500, desert: 2350}[@terrain] or 2350
|
||||
aspectRatio = mapWidth / mapHeight
|
||||
pageWidth = $(window).width()
|
||||
pageHeight = $(window).height()
|
||||
pageWidth = @$el.width()
|
||||
pageHeight = @$el.height()
|
||||
widthRatio = pageWidth / mapWidth
|
||||
heightRatio = pageHeight / mapHeight
|
||||
# Make sure we can see the whole map, fading to background in one dimension.
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
Campaign = require './Campaign'
|
||||
Handler = require '../commons/Handler'
|
||||
|
||||
CampaignHandler = class CampaignHandler extends Handler
|
||||
modelClass: Campaign
|
||||
editableProperties: [
|
||||
'name'
|
||||
'i18n'
|
||||
'i18nCoverage'
|
||||
'ambientSound'
|
||||
'backgroundImage'
|
||||
'backgroundColor'
|
||||
'backgroundColorTransparent'
|
||||
'adjacentCampaigns'
|
||||
'levels'
|
||||
]
|
||||
jsonSchema: require '../../app/schemas/models/campaign.schema'
|
||||
|
||||
hasAccess: (req) ->
|
||||
req.method is 'GET' or req.user?.isAdmin()
|
||||
|
||||
module.exports = new CampaignHandler()
|
70
server/campaigns/campaign_handler.coffee
Normal file
70
server/campaigns/campaign_handler.coffee
Normal file
|
@ -0,0 +1,70 @@
|
|||
Campaign = require './Campaign'
|
||||
Level = require '../levels/Level'
|
||||
Achievement = require '../achievements/Achievement'
|
||||
Handler = require '../commons/Handler'
|
||||
async = require 'async'
|
||||
mongoose = require 'mongoose'
|
||||
|
||||
CampaignHandler = class CampaignHandler extends Handler
|
||||
modelClass: Campaign
|
||||
editableProperties: [
|
||||
'name'
|
||||
'i18n'
|
||||
'i18nCoverage'
|
||||
'ambientSound'
|
||||
'backgroundImage'
|
||||
'backgroundColor'
|
||||
'backgroundColorTransparent'
|
||||
'adjacentCampaigns'
|
||||
'levels'
|
||||
]
|
||||
jsonSchema: require '../../app/schemas/models/campaign.schema'
|
||||
|
||||
hasAccess: (req) ->
|
||||
req.method is 'GET' or req.user?.isAdmin()
|
||||
|
||||
getByRelationship: (req, res, args...) ->
|
||||
relationship = args[1]
|
||||
if relationship in ['levels', 'achievements']
|
||||
projection = {}
|
||||
if req.query.project
|
||||
projection[field] = 1 for field in req.query.project.split(',')
|
||||
@getDocumentForIdOrSlug args[0], (err, campaign) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
return @sendNotFoundError(res) unless campaign?
|
||||
return @getRelatedLevels(req, res, campaign, projection) if relationship is 'levels'
|
||||
return @getRelatedAchievements(req, res, campaign, projection) if relationship is 'achievements'
|
||||
else
|
||||
super(arguments...)
|
||||
|
||||
|
||||
getRelatedLevels: (req, res, campaign, projection) ->
|
||||
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))
|
||||
|
||||
|
||||
getRelatedAchievements: (req, res, campaign, projection) ->
|
||||
levels = campaign.get('levels') or []
|
||||
|
||||
f = (levelOriginal) ->
|
||||
(callback) ->
|
||||
query = { related: levelOriginal }
|
||||
Achievement.find(query, projection).exec callback
|
||||
|
||||
fetches = (f(level.original) for level in _.values(levels))
|
||||
async.parallel fetches, (err, achievementses) =>
|
||||
achievements = _.flatten(achievementses)
|
||||
return @sendDatabaseError(res, err) if err
|
||||
return @sendSuccess(res, (achievement.toObject() for achievement in achievements))
|
||||
|
||||
module.exports = new CampaignHandler()
|
|
@ -1,6 +1,7 @@
|
|||
module.exports.handlers =
|
||||
'analytics_users_active': 'analytics/analytics_users_active_handler'
|
||||
'article': 'articles/article_handler'
|
||||
'campaign': 'campaigns/campaign_handler'
|
||||
'level': 'levels/level_handler'
|
||||
'level_component': 'levels/components/level_component_handler'
|
||||
'level_feedback': 'levels/feedbacks/level_feedback_handler'
|
||||
|
|
|
@ -31,6 +31,28 @@ LevelHandler = class LevelHandler extends Handler
|
|||
'i18nCoverage'
|
||||
'loadingTip'
|
||||
'requiresSubscription'
|
||||
'disableSpaces'
|
||||
'hidesSubmitUntilRun'
|
||||
'hidesPlayButton'
|
||||
'hidesRunShortcut'
|
||||
'hidesHUD'
|
||||
'hidesSay'
|
||||
'hidesCodeToolbar'
|
||||
'hidesRealTimePlayback'
|
||||
'backspaceThrottle'
|
||||
'lockDefaultCode'
|
||||
'moveRightLoopSnippet'
|
||||
'realTimeSpeedFactor'
|
||||
'autocompleteFontSizePx'
|
||||
'requiredCode'
|
||||
'suspectCode'
|
||||
'requiredGear'
|
||||
'restrictedGear'
|
||||
'allowedHeroes'
|
||||
]
|
||||
|
||||
adminEditableProperties: [
|
||||
[]
|
||||
]
|
||||
|
||||
postEditableProperties: ['name']
|
||||
|
|
|
@ -26,6 +26,7 @@ GLOBAL.tv4 = require 'tv4' # required for TreemaUtils to work
|
|||
models_path = [
|
||||
'../../server/analytics/AnalyticsUsersActive'
|
||||
'../../server/articles/Article'
|
||||
'../../server/campaigns/Campaign'
|
||||
'../../server/levels/Level'
|
||||
'../../server/levels/components/LevelComponent'
|
||||
'../../server/levels/systems/LevelSystem'
|
||||
|
|
77
test/server/functional/campaign_handler.spec.coffee
Normal file
77
test/server/functional/campaign_handler.spec.coffee
Normal file
|
@ -0,0 +1,77 @@
|
|||
require '../common'
|
||||
|
||||
levels = [
|
||||
{
|
||||
name: 'Level 1'
|
||||
description: 'This is the first level.'
|
||||
disableSpaces: true
|
||||
icon: 'somestringyoudontneed.png'
|
||||
}
|
||||
{
|
||||
name: 'Level 2'
|
||||
description: 'This is the second level.'
|
||||
requiresSubscription: true
|
||||
backspaceThrottle: true
|
||||
}
|
||||
]
|
||||
|
||||
achievement = {
|
||||
name: 'Level 1 Complete'
|
||||
}
|
||||
|
||||
campaign = {
|
||||
name: 'Campaign'
|
||||
levels: {}
|
||||
}
|
||||
|
||||
levelURL = getURL('/db/level')
|
||||
achievementURL = getURL('/db/achievement')
|
||||
campaignURL = getURL('/db/campaign')
|
||||
campaignSchema = require '../../../app/schemas/models/campaign.schema'
|
||||
campaignLevelProperties = _.keys(campaignSchema.properties.levels.additionalProperties.properties)
|
||||
|
||||
describe '/db/campaign', ->
|
||||
it 'prepares the db first', (done) ->
|
||||
clearModels [Achievement, Campaign, Level, User], (err) ->
|
||||
expect(err).toBeNull()
|
||||
loginAdmin (admin) ->
|
||||
levels[0].permissions = levels[1].permissions = [{target: admin._id, access: 'owner'}]
|
||||
request.post {uri: levelURL, json: levels[0]}, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
levels[0] = body
|
||||
request.post {uri: levelURL, json: levels[1]}, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
levels[1] = body
|
||||
achievement.related = levels[0].original
|
||||
achievement.rewards = { levels: [levels[1].original] }
|
||||
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
|
||||
request.post {uri: campaignURL, json: campaign}, (err, res, body) ->
|
||||
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")
|
||||
request.get {uri: url}, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
body = JSON.parse(body)
|
||||
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")
|
||||
request.get {uri: url}, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(200)
|
||||
body = JSON.parse(body)
|
||||
expect(body.length).toBe(1)
|
||||
done()
|
||||
|
Loading…
Reference in a new issue