Extended the LevelLoader to load thang types and components dynamically for hero levels.

This commit is contained in:
Scott Erickson 2014-08-07 18:27:47 -07:00
parent 0c5364eebb
commit cc025942f8
12 changed files with 272 additions and 7 deletions

View file

@ -260,6 +260,32 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
return matching_requests;
};
this.sendResponses = function(responseMap) {
var urls = Object.keys(responseMap);
for(var i in urls) {
var url = urls[i];
var responseBody = responseMap[url];
var responded = false;
var requests = jasmine.Ajax.requests.all();
for(var j in requests) {
var request = requests[j];
if(request.url.startsWith(url)) {
request.response({status: 200, responseText: JSON.stringify(responseBody)});
responded = true;
break;
}
}
if(!responded) {
var allRequests = jasmine.Ajax.requests.all();
urls = [];
for(var k in allRequests) urls.push(allRequests[k].url);
console.error('could not find response for', url, 'in', urls, allRequests);
continue;
}
}
}
}
function RequestStub(url, stubData) {

View file

@ -33,6 +33,8 @@ module.exports = class LevelLoader extends CocoClass
@headless = options.headless
@spectateMode = options.spectateMode ? false
@worldNecessities = []
@listenTo @supermodel, 'resource-loaded', @onWorldNecessityLoaded
@loadSession()
@loadLevel()
@loadAudio()
@ -64,12 +66,23 @@ module.exports = class LevelLoader extends CocoClass
session = new LevelSession().setURL url
@sessionResource = @supermodel.loadModel(session, 'level_session', {cache: false})
@session = @sessionResource.model
@session.once 'sync', -> @url = -> '/db/level.session/' + @id
@listenToOnce @session, 'sync', @onSessionLoaded
if @opponentSessionID
opponentSession = new LevelSession().setURL "/db/level_session/#{@opponentSessionID}"
@opponentSessionResource = @supermodel.loadModel(opponentSession, 'opponent_session')
@opponentSession = @opponentSessionResource.model
@listenToOnce @opponentSession, 'sync', @onSessionLoaded
onSessionLoaded: (session) ->
session.url = -> '/db/level.session/' + @id
if heroConfig = session.get('heroConfig')
url = "/db/thang.type/#{heroConfig.thangType}/version?project=name,components"
@worldNecessities.push @maybeLoadURL(url, ThangType, 'thang')
for itemThangType in _.values(heroConfig.inventory)
url = "/db/thang.type/#{itemThangType}/version?project=name,components"
@worldNecessities.push @maybeLoadURL(url, ThangType, 'thang')
# Supermodel (Level) Loading
@ -92,6 +105,7 @@ module.exports = class LevelLoader extends CocoClass
for thang in @level.get('thangs') or []
thangIDs.push thang.thangType
@loadItemThangsEquippedByLevelThang(thang)
for comp in thang.components or []
componentVersions.push _.pick(comp, ['original', 'majorVersion'])
@ -112,6 +126,7 @@ module.exports = class LevelLoader extends CocoClass
@thangIDs = _.uniq thangIDs
@thangNames = new ThangNamesCollection(@thangIDs)
worldNecessities.push @supermodel.loadCollection(@thangNames, 'thang_names')
@listenToOnce @thangNames, 'sync', @onThangNamesLoaded
worldNecessities.push @sessionResource if @sessionResource?.isLoading
worldNecessities.push @opponentSessionResource if @opponentSessionResource?.isLoading
@ -132,8 +147,50 @@ module.exports = class LevelLoader extends CocoClass
wizard = ThangType.loadUniversalWizard()
@supermodel.loadModel wizard, 'thang'
jqxhrs = (resource.jqxhr for resource in worldNecessities when resource?.jqxhr)
$.when(jqxhrs...).done(@onWorldNecessitiesLoaded)
@worldNecessities = @worldNecessities.concat worldNecessities
loadItemThangsEquippedByLevelThang: (levelThang) ->
return unless levelThang.components
for component in levelThang.components
if component.original is LevelComponent.EquipsID and inventory = component.config?.inventory
for itemThangType in _.values(inventory)
url = "/db/thang.type/#{itemThangType}/version?project=name,components"
@worldNecessities.push @maybeLoadURL(url, ThangType, 'thang')
onThangNamesLoaded: (thangNames) ->
if @level.get('type') is 'hero'
for thangType in thangNames.models
@loadDefaultComponentsForThangType(thangType)
@loadEquippedItemsInheritedFromThangType(thangType)
loadDefaultComponentsForThangType: (thangType) ->
return unless components = thangType.get('components')
for component in components
url = "/db/level.component/#{component.original}/version/#{component.majorVersion}"
@worldNecessities.push @maybeLoadURL(url, LevelComponent, 'component')
loadEquippedItemsInheritedFromThangType: (thangType) ->
for levelThang in @level.get('thangs') or []
if levelThang.thangType is thangType.get('original')
levelThang = $.extend true, {}, levelThang
@level.denormalizeThang(levelThang, @supermodel)
equipsComponent = _.find levelThang.components, {original: LevelComponent.EquipsID}
inventory = equipsComponent.config?.inventory
continue unless inventory
for itemThangType in _.values inventory
url = "/db/thang.type/#{itemThangType}/version?project=name,components"
@worldNecessities.push @maybeLoadURL(url, ThangType, 'thang')
onWorldNecessityLoaded: (resource) ->
index = @worldNecessities.indexOf(resource)
if @level.get('type') is 'hero' and resource.name is 'thang'
@loadDefaultComponentsForThangType(resource.model)
@loadEquippedItemsInheritedFromThangType(resource.model)
return unless index >= 0
@worldNecessities.splice(index, 1)
@worldNecessities = (r for r in @worldNecessities when r?)
@onWorldNecessitiesLoaded() if @worldNecessities.length is 0
onWorldNecessitiesLoaded: =>
@initWorld()

View file

@ -32,6 +32,7 @@ module.exports = class Level extends CocoModel
o
denormalizeThang: (levelThang, supermodel) ->
levelThang.components ?= []
thangType = supermodel.getModelByOriginal(ThangType, levelThang.thangType)
configs = {}
for thangComponent in levelThang.components

View file

@ -3,6 +3,8 @@ CocoModel = require './CocoModel'
module.exports = class LevelComponent extends CocoModel
@className: 'LevelComponent'
@schema: require 'schemas/models/level_component'
@EquipsID: '53e217d253457600003e3ebb'
urlRoot: '/db/level.component'
set: (key, val, options) ->

View file

@ -170,6 +170,7 @@ module.exports = class SuperModel extends Backbone.Model
@num += r.value
_.defer @updateProgress
r.clean()
@trigger 'resource-loaded', r
onResourceFailed: (r) ->
return unless @resources[r.rid]

View file

@ -174,7 +174,6 @@ LevelThangSchema = c.object {
components: []
},
id: thang # TODO: figure out if we can make this unique and how to set dynamic defaults
# TODO: split thangType into 'original' and 'majorVersion' like the rest for consistency
thangType: c.objectId(links: [{rel: 'db', href: '/db/thang.type/{($)}/version'}], title: 'Thang Type', description: 'A reference to the original Thang template being configured.', format: 'thang-type')
components: c.array {title: 'Components', description: 'Thangs are configured by changing the Components attached to them.', uniqueItems: true, format: 'thang-components-array'}, ThangComponentSchema # TODO: uniqueness should be based on 'original', not whole thing
@ -239,7 +238,7 @@ _.extend LevelSchema.properties,
icon: {type: 'string', format: 'image-file', title: 'Icon'}
banner: {type: 'string', format: 'image-file', title: 'Banner'}
goals: c.array {title: 'Goals', description: 'An array of goals which are visible to the player and can trigger scripts.'}, GoalSchema
type: c.shortString(title: 'Type', description: 'What kind of level this is.', 'enum': ['campaign', 'ladder', 'ladder-tutorial'])
type: c.shortString(title: 'Type', description: 'What kind of level this is.', 'enum': ['campaign', 'ladder', 'ladder-tutorial', 'hero'])
showsGuide: c.shortString(title: 'Shows Guide', description: 'If the guide is shown at the beginning of the level.', 'enum': ['first-time', 'always'])
c.extendBasicProperties LevelSchema, 'level'

View file

@ -56,6 +56,10 @@ _.extend LevelSessionSchema.properties,
screenshot:
type: 'string'
heroConfig: c.object {},
inventory: c.object()
thangType: c.objectId()
state: c.object {},
complete:
type: 'boolean'

View file

@ -166,7 +166,9 @@ module.exports = class Handler
ids = ids.split(',') if _.isString ids
ids = _.uniq ids
project = {name:1, original:1, kind:1}
# HACK: levels loading thang types need the components returned as well
# Need a way to specify a projection for a query.
project = {name:1, original:1, kind:1, components: 1}
sort = {'version.major':-1, 'version.minor':-1}
makeFunc = (id) =>

View file

@ -0,0 +1,171 @@
Level = require 'models/Level'
LevelSession = require 'models/LevelSession'
SuperModel = require 'models/SuperModel'
LevelComponent = require 'models/LevelComponent'
LevelLoader = require 'lib/LevelLoader'
# LEVELS
levelWithOgreWithMace = {
type: 'hero'
thangs: [{
thangType: 'ogre'
components: [{
original: LevelComponent.EquipsID
majorVersion: 0
config: { inventory: { 'left-hand': 'mace' } }
}]
}]
}
levelWithShaman = {
type: 'hero'
thangs: [{
thangType: 'shaman'
}]
}
levelWithShamanWithSuperWand = {
type: 'hero'
thangs: [{
thangType: 'shaman'
components: [{
original: LevelComponent.EquipsID
majorVersion: 0
config: { inventory: { 'left-hand': 'super-wand' } }
}]
}]
}
# SESSIONS
sessionWithTharinWithHelmet = { heroConfig: { thangType: 'tharin', inventory: { 'head': 'helmet' }}}
# THANG TYPES
thangTypeOgreWithPhysicalComponent = {
name: 'Ogre'
original: 'ogre'
components: [{
original: 'physical'
majorVersion: 0
}]
}
thangTypeShamanWithWandEquipped = {
name: 'Shaman'
original: 'shaman'
components: [{
original: LevelComponent.EquipsID
majorVersion: 0
config: { inventory: { 'left-hand': 'wand' }}
}]
}
thangTypeTharinWithHealsComponent = {
name: 'Tharin'
original: 'tharin'
components: [{
original: 'heals'
majorVersion: 0
}]
}
thangTypeWand = {
name: 'Wand'
original: 'wand'
components: [{
original: 'poisons'
majorVersion: 0
}]
}
describe 'LevelLoader', ->
it 'loads hero and item thang types from heroConfig in the LevelSession', ->
new LevelLoader({supermodel:new SuperModel(), sessionID: 'id', levelID: 'id'})
responses = {
'/db/level_session/id': sessionWithTharinWithHelmet
}
jasmine.Ajax.requests.sendResponses(responses)
requests = jasmine.Ajax.requests.all()
urls = (r.url for r in requests)
expect('/db/thang.type/helmet/version?project=name,components' in urls).toBeTruthy()
expect('/db/thang.type/tharin/version?project=name,components' in urls).toBeTruthy()
it 'loads components for the hero in the heroConfig in the LevelSession', ->
new LevelLoader({supermodel:new SuperModel(), sessionID: 'id', levelID: 'id'})
responses = {
'/db/level_session/id': sessionWithTharinWithHelmet
'/db/thang.type/tharin/version?project=name,components': thangTypeTharinWithHealsComponent
}
jasmine.Ajax.requests.sendResponses(responses)
requests = jasmine.Ajax.requests.all()
urls = (r.url for r in requests)
expect('/db/level.component/heals/version/0' in urls).toBeTruthy()
it 'loads thangs for items that the level thangs have in their Equips component configs', ->
new LevelLoader({supermodel:supermodel = new SuperModel(), sessionID: 'id', levelID: 'id'})
responses = {
'/db/level/id': levelWithOgreWithMace
}
jasmine.Ajax.requests.sendResponses(responses)
requests = jasmine.Ajax.requests.all()
urls = (r.url for r in requests)
expect('/db/thang.type/mace/version?project=name,components' in urls).toBeTruthy()
it 'loads components which are inherited by level thangs from thang type default components', ->
new LevelLoader({supermodel:new SuperModel(), sessionID: 'id', levelID: 'id'})
responses =
'/db/level/id': levelWithOgreWithMace
'/db/thang.type/names': [thangTypeOgreWithPhysicalComponent]
jasmine.Ajax.requests.sendResponses(responses)
requests = jasmine.Ajax.requests.all()
urls = (r.url for r in requests)
expect('/db/level.component/physical/version/0' in urls).toBeTruthy()
it 'loads item thang types which are inherited by level thangs from thang type default equips component configs', ->
new LevelLoader({supermodel:new SuperModel(), sessionID: 'id', levelID: 'id'})
responses =
'/db/level/id': levelWithShaman
'/db/thang.type/names': [thangTypeShamanWithWandEquipped]
jasmine.Ajax.requests.sendResponses(responses)
requests = jasmine.Ajax.requests.all()
urls = (r.url for r in requests)
expect('/db/thang.type/wand/version?project=name,components' in urls).toBeTruthy()
it 'loads components for item thang types which are inherited by level thangs from thang type default equips component configs', ->
new LevelLoader({supermodel:new SuperModel(), sessionID: 'id', levelID: 'id'})
responses =
'/db/level/id': levelWithShaman
'/db/thang.type/names': [thangTypeShamanWithWandEquipped]
'/db/thang.type/wand/version?project=name,components': thangTypeWand
jasmine.Ajax.requests.sendResponses(responses)
requests = jasmine.Ajax.requests.all()
urls = (r.url for r in requests)
expect('/db/level.component/poisons/version/0' in urls).toBeTruthy()
it 'does not load item thang types from thang type equips component configs which are overriden by level thang equips component configs', ->
new LevelLoader({supermodel:new SuperModel(), sessionID: 'id', levelID: 'id'})
responses =
'/db/level/id': levelWithShamanWithSuperWand
'/db/thang.type/names': [thangTypeShamanWithWandEquipped]
jasmine.Ajax.requests.sendResponses(responses)
requests = jasmine.Ajax.requests.all()
urls = (r.url for r in requests)
expect('/db/thang.type/wand/version?project=name,components' in urls).toBeFalsy()

View file

@ -1,6 +1,6 @@
describe 'require', ->
it 'has no modules that error when you import them', ->
modules = window.require.list()
modules = window.require.list();
for module in modules
try
require(module)

2
test/app/utils.coffee Normal file
View file

@ -0,0 +1,2 @@
module.exports.sendTestResponses = (responseMap) ->