mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-27 09:35:39 -05:00
Extended the LevelLoader to load thang types and components dynamically for hero levels.
This commit is contained in:
parent
0c5364eebb
commit
cc025942f8
12 changed files with 272 additions and 7 deletions
|
@ -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) {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) ->
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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) =>
|
||||
|
|
171
test/app/lib/LevelLoader.spec.coffee
Normal file
171
test/app/lib/LevelLoader.spec.coffee
Normal 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()
|
|
@ -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
2
test/app/utils.coffee
Normal file
|
@ -0,0 +1,2 @@
|
|||
module.exports.sendTestResponses = (responseMap) ->
|
||||
|
Loading…
Reference in a new issue