diff --git a/app/core/treema-ext.coffee b/app/core/treema-ext.coffee index c29c087a9..35546dc00 100644 --- a/app/core/treema-ext.coffee +++ b/app/core/treema-ext.coffee @@ -313,7 +313,7 @@ class InternationalizationNode extends TreemaNode.nodeMap.object class LatestVersionCollection extends CocoCollection -class LatestVersionReferenceNode extends TreemaNode +module.exports.LatestVersionReferenceNode = class LatestVersionReferenceNode extends TreemaNode searchValueTemplate: '
' valueClass: 'treema-latest-version' url: '/db/article' @@ -383,7 +383,11 @@ class LatestVersionReferenceNode extends TreemaNode m = CocoModel.getReferencedModel(@getData(), @workingSchema) data = @getData() if _.isString data # LatestVersionOriginalReferenceNode just uses original - m = @settings.supermodel.getModelByOriginal(m.constructor, data) + if m.schema().properties.version + m = @settings.supermodel.getModelByOriginal(m.constructor, data) + else + # get by id + m = @settings.supermodel.getModel(m.constructor, data) else m = @settings.supermodel.getModelByOriginalAndMajorVersion(m.constructor, data.original, data.majorVersion) if @instance and not m @@ -434,7 +438,7 @@ class LatestVersionReferenceNode extends TreemaNode selected = @getSelectedResultEl() return not selected.length -class LatestVersionOriginalReferenceNode extends LatestVersionReferenceNode +module.exports.LatestVersionOriginalReferenceNode = class LatestVersionOriginalReferenceNode extends LatestVersionReferenceNode # Just for saving the original, not the major version. saveChanges: -> selected = @getSelectedResultEl() @@ -443,6 +447,15 @@ class LatestVersionOriginalReferenceNode extends LatestVersionReferenceNode @data = fullValue.attributes.original @instance = fullValue +module.exports.IDReferenceNode = class IDReferenceNode extends LatestVersionReferenceNode + # Just for saving the _id + saveChanges: -> + selected = @getSelectedResultEl() + return unless selected.length + fullValue = selected.data('value') + @data = fullValue.attributes._id + @instance = fullValue + class LevelComponentReferenceNode extends LatestVersionReferenceNode # HACK: this list of properties is needed by the thang components edit view and config views. # need a better way to specify this, or keep the search models from bleeding into those diff --git a/app/schemas/models/campaign.schema.coffee b/app/schemas/models/campaign.schema.coffee index 606267155..8820a1189 100644 --- a/app/schemas/models/campaign.schema.coffee +++ b/app/schemas/models/campaign.schema.coffee @@ -83,22 +83,27 @@ _.extend CampaignSchema.properties, { } requiredGear: { type: 'object', additionalProperties: { - type: 'string' # should be an originalID, denormalized on the editor side + type: 'array' + items: { type: 'string', links: [{rel: 'db', href: '/db/thang.type/{($)}/version'}], format: 'latest-version-original-reference' } }} restrictedGear: { type: 'object', additionalProperties: { - type: 'string' # should be an originalID, denormalized on the editor side + type: 'array' + items: { type: 'string', links: [{rel: 'db', href: '/db/thang.type/{($)}/version'}], format: 'latest-version-original-reference' } }} - allowedHeroes: { type: 'array', items: { - type: 'string' # should be an originalID, denormalized on the editor side + allowedHeroes: { type: 'array', items: { + type: 'string', links: [{rel: 'db', href: '/db/thang.type/{($)}/version'}], format: 'latest-version-original-reference' }} #- denormalized from Achievements - unlocks: { type: 'array', items: { + rewards: { type: 'array', items: { type: 'object' + additionalProperties: false properties: - original: { type: 'string' } - type: { enum: ['hero', 'item', 'level'] } - achievement: { type: 'string' } + achievement: { type: 'string', links: [{rel: 'db', href: '/db/achievement/{{$}}'}], format: 'achievement' } + item: { type: 'string', links: [{rel: 'db', href: '/db/thang.type/{($)}/version'}], format: 'latest-version-original-reference' } + hero: { type: 'string', links: [{rel: 'db', href: '/db/thang.type/{($)}/version'}], format: 'latest-version-original-reference' } + level: { type: 'string', links: [{rel: 'db', href: '/db/level/{($)}/version'}], format: 'latest-version-original-reference' } + type: { enum: ['heroes', 'items', 'levels'] } }} #- normal properties diff --git a/app/views/editor/campaign/CampaignEditorView.coffee b/app/views/editor/campaign/CampaignEditorView.coffee index 94d5d7bef..882fa748f 100644 --- a/app/views/editor/campaign/CampaignEditorView.coffee +++ b/app/views/editor/campaign/CampaignEditorView.coffee @@ -1,39 +1,157 @@ 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: -> - super(arguments...) + constructor: (options, campaignHandle) -> + super(options) - # 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) + # 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}) - collection = new CocoCollection({model: Level, url}) - collection.ur + #--------------- 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 @@ -51,17 +169,47 @@ module.exports = class CampaignEditorView extends RootView nodeClasses: levels: LevelsNode level: LevelNode - + achievement: AchievementNode + supermodel: @supermodel @treema = @$el.find('#campaign-treema').treema treemaOptions @treema.build() @treema.open() - @treema.childrenTreemas.levels.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' @@ -73,15 +221,12 @@ class LevelsNode extends TreemaObjectNode 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) @@ -92,11 +237,11 @@ class LevelNode extends TreemaObjectNode 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 + +class AchievementNode extends treemaExt.IDReferenceNode + buildSearchURL: (term) -> "#{@url}?term=#{term}&project=#{achievementProject.join(',')}" campaign = { name: 'Dungeon' diff --git a/server/campaigns/campaign_handler.coffee b/server/campaigns/campaign_handler.coffee index f252e734e..cc8217d81 100644 --- a/server/campaigns/campaign_handler.coffee +++ b/server/campaigns/campaign_handler.coffee @@ -36,7 +36,7 @@ CampaignHandler = class CampaignHandler extends Handler return @getRelatedAchievements(req, res, campaign, projection) if relationship is 'achievements' else super(arguments...) - + getRelatedLevels: (req, res, campaign, projection) -> levels = campaign.get('levels') or []