diff --git a/app/lib/LevelLoader.coffee b/app/lib/LevelLoader.coffee index 60f93cb21..5a04c070d 100644 --- a/app/lib/LevelLoader.coffee +++ b/app/lib/LevelLoader.coffee @@ -31,6 +31,7 @@ module.exports = class LevelLoader extends CocoClass @opponentSessionID = options.opponentSessionID @team = options.team @headless = options.headless + @inLevelEditor = options.inLevelEditor @spectateMode = options.spectateMode ? false @worldNecessities = [] @@ -83,6 +84,7 @@ module.exports = class LevelLoader extends CocoClass for itemThangType in _.values(heroConfig.inventory) url = "/db/thang.type/#{itemThangType}/version?project=name,components,original" @worldNecessities.push @maybeLoadURL(url, ThangType, 'thang') + @populateHero() if @level?.loaded # Supermodel (Level) Loading @@ -96,6 +98,7 @@ module.exports = class LevelLoader extends CocoClass onLevelLoaded: -> @populateLevel() + @populateHero() if @session?.loaded populateLevel: -> thangIDs = [] @@ -149,6 +152,16 @@ module.exports = class LevelLoader extends CocoClass @worldNecessities = @worldNecessities.concat worldNecessities + populateHero: -> + return if @inLevelEditor + return unless @level.get('type') is 'hero' and hero = _.find @level.get('thangs'), id: 'Hero Placeholder' + heroConfig = @session.get('heroConfig') + hero.thangType = heroConfig.thangType + hero.inventory = heroConfig.inventory # Will take effect in Level's denormalizeThang + hero.placeholderComponents = hero.components # Will be replaced in Level's denormalizeThang + hero.components = [] + #hero.id = ... ? # What do we want to do about this? + loadItemThangsEquippedByLevelThang: (levelThang) -> return unless levelThang.components for component in levelThang.components @@ -178,7 +191,7 @@ module.exports = class LevelLoader extends CocoClass levelThang = $.extend true, {}, levelThang @level.denormalizeThang(levelThang, @supermodel) equipsComponent = _.find levelThang.components, {original: LevelComponent.EquipsID} - inventory = equipsComponent.config?.inventory + inventory = equipsComponent?.config?.inventory continue unless inventory for itemThangType in _.values inventory url = "/db/thang.type/#{itemThangType}/version?project=name,components,original" diff --git a/app/models/Level.coffee b/app/models/Level.coffee index d2972366b..27210b1a6 100644 --- a/app/models/Level.coffee +++ b/app/models/Level.coffee @@ -9,8 +9,7 @@ module.exports = class Level extends CocoModel urlRoot: '/db/level' serialize: (supermodel) -> - # o = _.cloneDeep @attributes # slow in level editor when there are hundreds of Thangs - o = $.extend true, {}, @attributes + o = @denormalize supermodel # Figure out Components o.levelComponents = _.cloneDeep (lc.attributes for lc in supermodel.getModels LevelComponent) @@ -31,8 +30,10 @@ module.exports = class Level extends CocoModel denormalize: (supermodel) -> o = $.extend true, {}, @attributes - for levelThang in o.thangs - @denormalizeThang(levelThang, supermodel) + if @get('type') is 'hero' + # TOOD: figure out if/when/how we are doing this for non-Hero levels that aren't expecting denormalization. + for levelThang in o.thangs + @denormalizeThang(levelThang, supermodel) o denormalizeThang: (levelThang, supermodel) -> @@ -41,16 +42,38 @@ module.exports = class Level extends CocoModel configs = {} for thangComponent in levelThang.components configs[thangComponent.original] = thangComponent + placeholders = {} + for thangComponent in levelThang.placeholderComponents ? [] + placeholders[thangComponent.original] = thangComponent for defaultThangComponent in thangType.get('components') if levelThangComponent = configs[defaultThangComponent.original] - # take the thang type default components and merge level-specific component config into it + # Take the ThangType default Components and merge level-specific Component config into it copy = $.extend true, {}, defaultThangComponent.config levelThangComponent.config = _.merge copy, levelThangComponent.config else - # just add the component as is - levelThang.components.push $.extend true, {}, defaultThangComponent + # Just add the Component as is + levelThangComponent = $.extend true, {}, defaultThangComponent + levelThang.components.push levelThangComponent + + if placeholderComponent = placeholders[defaultThangComponent.original] + placeholderConfig = placeholderComponent.config ? {} + if placeholderConfig.pos # Pull in Physical pos x and y + levelThangComponent.config.pos ?= {} + levelThangComponent.config.pos.x = placeholderConfig.pos.x + levelThangComponent.config.pos.y = placeholderConfig.pos.y + else if placeholderConfig.team # Pull in Allied team + levelThangComponent.config.team = placeholderConfig.team + else if placeholderConfig.programmableMethods + # Take the ThangType default Programmable and merge level-specific Component config into it + copy = $.extend true, {}, placeholderConfig + levelThangComponent.config = _.merge copy, levelThangComponent.config + + if levelThang.inventory and equips = _.find levelThang.components, {original: LevelComponent.EquipsID} + # inventory is assigned from the LevelSession in LevelLoader's populateHero + equips.config.inventory = $.extend true, {}, levelThang.inventory + sortSystems: (levelSystems, systemModels) -> [sorted, originalsSeen] = [[], {}] diff --git a/app/schemas/models/level_session.coffee b/app/schemas/models/level_session.coffee index 575937993..235052c53 100644 --- a/app/schemas/models/level_session.coffee +++ b/app/schemas/models/level_session.coffee @@ -55,10 +55,13 @@ _.extend LevelSessionSchema.properties, screenshot: type: 'string' - - heroConfig: c.object {}, - inventory: c.object() - thangType: c.objectId() + + heroConfig: c.object {description: 'Which hero the player is using, equipped with what inventory.'}, + inventory: + type: 'object' + description: 'The inventory of the hero: slots to item ThangTypes.' + additionalProperties: c.objectId(description: 'An item ThangType.') + thangType: c.objectId(links: [{rel: 'db', href: '/db/thang.type/{($)}/version'}], title: 'Thang Type', description: 'The ThangType of the hero.', format: 'thang-type') state: c.object {}, complete: diff --git a/app/views/editor/component/ThangComponentsEditView.coffee b/app/views/editor/component/ThangComponentsEditView.coffee index 2459439c9..eb72a84f4 100644 --- a/app/views/editor/component/ThangComponentsEditView.coffee +++ b/app/views/editor/component/ThangComponentsEditView.coffee @@ -12,8 +12,8 @@ nodes = require '../level/treema_nodes' ThangType = require 'models/ThangType' CocoCollection = require 'collections/CocoCollection' -class ThangTypeSearchCollection extends CocoCollection - url: '/db/thang.type?project=original,name,version,slug,kind,components' +class ItemThangTypeSearchCollection extends CocoCollection + url: '/db/thang.type?view=items&project=original,name,version,slug,kind,components' model: ThangType module.exports = class ThangComponentsEditView extends CocoView @@ -32,7 +32,7 @@ module.exports = class ThangComponentsEditView extends CocoView @level = options.level @loadComponents(@components) # Need to grab the ThangTypes so that we can autocomplete items in inventory based on them. - @thangTypes = @supermodel.loadCollection(new ThangTypeSearchCollection(), 'thangs').model + @itemThangTypes = @supermodel.loadCollection(new ItemThangTypeSearchCollection(), 'thangs').model loadComponents: (components) -> for componentRef in components diff --git a/app/views/editor/level/LevelEditView.coffee b/app/views/editor/level/LevelEditView.coffee index bf6a3166d..f7ee96c76 100644 --- a/app/views/editor/level/LevelEditView.coffee +++ b/app/views/editor/level/LevelEditView.coffee @@ -47,7 +47,7 @@ module.exports = class LevelEditView extends RootView super options @supermodel.shouldSaveBackups = (model) -> model.constructor.className in ['Level', 'LevelComponent', 'LevelSystem', 'ThangType'] - @levelLoader = new LevelLoader supermodel: @supermodel, levelID: @levelID, headless: true + @levelLoader = new LevelLoader supermodel: @supermodel, levelID: @levelID, headless: true, inLevelEditor: true @level = @levelLoader.level @files = new DocumentFiles(@levelLoader.level) @supermodel.loadCollection(@files, 'file_names') diff --git a/app/views/editor/level/thangs/ThangsTabView.coffee b/app/views/editor/level/thangs/ThangsTabView.coffee index 5bf39f4e4..e6537a84f 100644 --- a/app/views/editor/level/thangs/ThangsTabView.coffee +++ b/app/views/editor/level/thangs/ThangsTabView.coffee @@ -335,8 +335,8 @@ module.exports = class ThangsTabView extends CocoView adjustThangPos: (sprite, thang, pos) -> snap = sprite?.data?.snap or sprite?.thangType?.get('snap') or {x: 0.01, y: 0.01} # Centimeter resolution by default - pos.x = Math.round((pos.x - thang.width / 2) / snap.x) * snap.x + thang.width / 2 - pos.y = Math.round((pos.y - thang.height / 2) / snap.y) * snap.y + thang.height / 2 + pos.x = Math.round((pos.x - (thang.width ? 1) / 2) / snap.x) * snap.x + (thang.width ? 1) / 2 + pos.y = Math.round((pos.y - (thang.height ? 1) / 2) / snap.y) * snap.y + (thang.height ? 1) / 2 pos.z = thang.depth / 2 thang.pos = pos @surface.spriteBoss.update true # Make sure Obstacle layer resets cache diff --git a/app/views/game-menu/InventoryView.coffee b/app/views/game-menu/InventoryView.coffee index 4ba26573d..ab74fa46d 100644 --- a/app/views/game-menu/InventoryView.coffee +++ b/app/views/game-menu/InventoryView.coffee @@ -9,32 +9,32 @@ module.exports = class InventoryView extends CocoView id: 'inventory-view' className: 'tab-pane' template: template - slots: ["head","eyes","neck","torso","wrists","gloves","left-ring","right-ring","right-hand","left-hand","waist","feet","spellbook","programming-book","pet","minion","misc-0","misc-1","misc-2","misc-3","misc-4"] - + slots: ['head', 'eyes', 'neck', 'torso', 'wrists', 'gloves', 'left-ring', 'right-ring', 'right-hand', 'left-hand', 'waist', 'feet', 'spellbook', 'programming-book', 'pet', 'minion', 'misc-0', 'misc-1', 'misc-2', 'misc-3', 'misc-4'] + events: 'click .item-slot': 'onItemSlotClick' 'click #available-equipment .list-group-item': 'onAvailableItemClick' 'dblclick #available-equipment .list-group-item': 'onAvailableItemDoubleClick' 'dblclick .item-slot .item-view': 'onEquippedItemDoubleClick' 'click #swap-button': 'onClickSwapButton' - + shortcuts: 'esc': 'clearSelection' - + initialize: (options) -> super(arguments...) - @items = new CocoCollection([], { model: ThangType }) - @equipment = options.equipment or {} + @items = new CocoCollection([], {model: ThangType}) + @equipment = options.equipment or @options.session?.get('heroConfig')?.inventory or {} @items.url = '/db/thang.type?view=items&project=name,description,components,original' @supermodel.loadCollection(@items, 'items') - + onLoaded: -> super() getRenderData: (context={}) -> context = super(context) context.equipped = _.values(@equipment) - context.items = @items.models + context.items = @items.models for item in @items.models item.classes = item.getAllowedSlots() @@ -50,7 +50,7 @@ module.exports = class InventoryView extends CocoView afterRender: -> super() return unless @supermodel.finished() - + keys = (item.id for item in @items.models) itemMap = _.zipObject keys, @items.models @@ -58,7 +58,7 @@ module.exports = class InventoryView extends CocoView for slottedItemStub in @$el.find('.replace-me') itemID = $(slottedItemStub).data('item-id') item = itemMap[itemID] - itemView = new ItemView({item:item, includes:{name:true}}) + itemView = new ItemView({item: item, includes: {name: true}}) itemView.render() $(slottedItemStub).replaceWith(itemView.$el) @registerSubView(itemView) @@ -66,13 +66,13 @@ module.exports = class InventoryView extends CocoView for availableItemEl in @$el.find('#available-equipment .list-group-item') itemID = $(availableItemEl).data('item-id') item = itemMap[itemID] - itemView = new ItemView({item:item, includes:{name:true}}) + itemView = new ItemView({item: item, includes: {name: true}}) itemView.render() $(availableItemEl).append(itemView.$el) @registerSubView(itemView) - + @delegateEvents() - + clearSelection: -> @$el.find('.panel-info').removeClass('panel-info') @$el.find('.list-group-item').removeClass('active') @@ -85,7 +85,7 @@ module.exports = class InventoryView extends CocoView @unselectAllAvailableEquipment() if slot.hasClass('disabled') @selectSlot(slot) unless wasActive and not $(e.target).closest('.item-view')[0] @onSelectionChanged() - + onAvailableItemClick: (e) -> itemContainer = $(e.target).closest('.list-group-item') @unselectAllAvailableEquipment() @@ -98,7 +98,7 @@ module.exports = class InventoryView extends CocoView @unequipItemFromSlot(slot) @equipSelectedItemToSlot(slot) @onSelectionChanged() - + onEquippedItemDoubleClick: (e) -> @unselectAllAvailableEquipment() slot = $(e.target).closest('.item-slot') @@ -115,31 +115,31 @@ module.exports = class InventoryView extends CocoView @selectAvailableItem(itemContainer) @selectSlot(slot) @onSelectionChanged() - - getSelectedSlot: -> + + getSelectedSlot: -> @$el.find('#equipped .item-slot.panel-info') - - unselectAllAvailableEquipment: -> + + unselectAllAvailableEquipment: -> @$el.find('#available-equipment .list-group-item').removeClass('active') - + unselectAllSlots: -> @$el.find('#equipped .panel').removeClass('panel-info') - + selectSlot: (slot) -> slot.addClass('panel-info') - + getSlot: (name) -> @$el.find(".item-slot[data-slot=#{name}]") - + getSelectedAvailableItemContainer: -> @$el.find('#available-equipment .list-group-item.active') - + getAvailableItemContainer: (itemID) -> @$el.find("#available-equipment .list-group-item[data-item-id='#{itemID}']") - + selectAvailableItem: (itemContainer) -> itemContainer?.addClass('active') - + unequipItemFromSlot: (slot) -> itemIDToUnequip = slot.find('.item-view').data('item-id') return unless itemIDToUnequip @@ -157,22 +157,22 @@ module.exports = class InventoryView extends CocoView slotContainer.html(newItemHTML) slotContainer.find('.item-view').data('item-id', selectedItemContainer.find('.item-view').data('item-id')) @$el.find('.list-group-item').removeClass('active') - + onSelectionChanged: -> @$el.find('.item-slot').show() - + selectedSlot = @$el.find('.panel.panel-info') selectedItem = @$el.find('#available-equipment .list-group-item.active') - + if selectedSlot.length @$el.find('#available-equipment .list-group-item').hide() @$el.find("#available-equipment .list-group-item.#{selectedSlot.data('slot')}").show() - + selectedSlotItemID = selectedSlot.find('.item-view').data('item-id') if selectedSlotItemID item = _.find @items.models, {id:selectedSlotItemID} @showSelectedSlotItem(item) - + else @hideSelectedSlotItem() @@ -183,7 +183,7 @@ module.exports = class InventoryView extends CocoView @$el.find('.item-slot').removeClass('disabled') if selectedItem.length item = _.find @items.models, {id:selectedItem.find('.item-view').data('item-id')} - + # update which slots are enabled allowedSlots = item.getAllowedSlots() for slotEl in @$el.find('.item-slot') @@ -192,12 +192,12 @@ module.exports = class InventoryView extends CocoView $(slotEl).addClass('disabled') @showSelectedAvailableItem(item) - + else @hideSelectedAvailableItem() - + @delegateEvents() - + showSelectedSlotItem: (item) -> if not @selectedEquippedItemView @selectedEquippedItemView = new ItemView({ @@ -208,10 +208,10 @@ module.exports = class InventoryView extends CocoView @selectedEquippedItemView.$el.show() @selectedEquippedItemView.item = item @selectedEquippedItemView.render() - + hideSelectedSlotItem: -> @selectedEquippedItemView?.$el.hide() - + showSelectedAvailableItem: (item) -> if not @selectedAvailableItemView @selectedAvailableItemView = new ItemView({ @@ -222,7 +222,7 @@ module.exports = class InventoryView extends CocoView @selectedAvailableItemView.$el.show() @selectedAvailableItemView.item = item @selectedAvailableItemView.render() - + hideSelectedAvailableItem: -> @selectedAvailableItemView?.$el.hide() @@ -234,5 +234,14 @@ module.exports = class InventoryView extends CocoView continue unless slotItemID item = _.find @items.models, {id:slotItemID} config[slotName] = item.get('original') - - config \ No newline at end of file + + config + + onHidden: -> + inventory = @getCurrentEquipmentConfig() + heroConfig = @options.session.get('heroConfig') ? {} + unless _.isEqual inventory, heroConfig.inventory + heroConfig.inventory = inventory + heroConfig.thangType ?= '529ffbf1cf1818f2be000001' # Temp: assign Tharin as the hero + @options.session.set 'heroConfig', heroConfig + @options.session.patch() diff --git a/bin/coco-brunch b/bin/coco-brunch index 9afe5e7c7..d06dab2e8 100755 --- a/bin/coco-brunch +++ b/bin/coco-brunch @@ -9,7 +9,7 @@ import sys current_directory = os.path.dirname(os.path.realpath(sys.argv[0])) coco_path = os.getenv("COCO_DIR",os.path.join(current_directory,os.pardir)) brunch_path = coco_path + os.sep + "node_modules" + os.sep + ".bin" + os.sep + "brunch" -subprocess.Popen("ulimit -n 100000",shell=True) +subprocess.Popen("ulimit -n 10000", shell=True) while True: print("Starting brunch. After the first compile, it'll keep running and watch for changes.") call(brunch_path + " w",shell=True,cwd=coco_path) diff --git a/server/levels/sessions/LevelSession.coffee b/server/levels/sessions/LevelSession.coffee index 81f23a7e7..329914f01 100644 --- a/server/levels/sessions/LevelSession.coffee +++ b/server/levels/sessions/LevelSession.coffee @@ -45,7 +45,7 @@ LevelSessionSchema.pre 'save', (next) -> LevelSessionSchema.statics.privateProperties = ['code', 'submittedCode', 'unsubscribed'] LevelSessionSchema.statics.editableProperties = ['multiplayer', 'players', 'code', 'codeLanguage', 'completed', 'state', 'levelName', 'creatorName', 'levelID', 'screenshot', - 'chat', 'teamSpells', 'submitted', 'submittedCodeLanguage', 'unsubscribed', 'playtime'] + 'chat', 'teamSpells', 'submitted', 'submittedCodeLanguage', 'unsubscribed', 'playtime', 'heroConfig'] LevelSessionSchema.statics.jsonSchema = jsonschema LevelSessionSchema.index {user: 1, changed: -1}, {sparse: true, name: 'last played index'}