2014-01-03 13:32:13 -05:00
Level = require ' models/Level '
2014-04-25 17:30:06 -04:00
LevelComponent = require ' models/LevelComponent '
LevelSystem = require ' models/LevelSystem '
Article = require ' models/Article '
2014-01-03 13:32:13 -05:00
LevelSession = require ' models/LevelSession '
ThangType = require ' models/ThangType '
2014-04-28 17:58:58 -04:00
ThangNamesCollection = require ' collections/ThangNamesCollection '
2014-04-25 17:30:06 -04:00
CocoClass = require ' lib/CocoClass '
AudioPlayer = require ' lib/AudioPlayer '
2014-01-03 13:32:13 -05:00
app = require ' application '
2014-01-14 16:16:30 -05:00
World = require ' lib/world/world '
2014-01-03 13:32:13 -05:00
# This is an initial stab at unifying loading and setup into a single place which can
# monitor everything and keep a LoadingScreen visible overall progress.
#
# Would also like to incorporate into here:
# * World Building
# * Sprite map generation
# * Connecting to Firebase
module.exports = class LevelLoader extends CocoClass
2014-01-07 02:45:33 -05:00
2014-02-15 18:44:45 -05:00
constructor: (options) ->
2014-04-28 17:58:58 -04:00
@t0 = new Date ( ) . getTime ( )
2014-01-03 13:32:13 -05:00
super ( )
2014-02-15 18:44:45 -05:00
@supermodel = options . supermodel
2014-05-14 12:24:52 -04:00
@ supermodel . setMaxProgress 0.2
2014-02-15 18:44:45 -05:00
@levelID = options . levelID
@sessionID = options . sessionID
@opponentSessionID = options . opponentSessionID
@team = options . team
@headless = options . headless
2014-03-12 20:51:09 -04:00
@spectateMode = options . spectateMode ? false
2014-02-15 18:44:45 -05:00
2014-08-07 21:27:47 -04:00
@worldNecessities = [ ]
@ listenTo @ supermodel , ' resource-loaded ' , @ onWorldNecessityLoaded
2014-04-25 17:30:06 -04:00
@ loadLevel ( )
2014-01-03 13:32:13 -05:00
@ loadAudio ( )
@ playJingle ( )
2014-04-29 19:54:57 -04:00
if @ supermodel . finished ( )
@ onSupermodelLoaded ( )
else
@ listenToOnce @ supermodel , ' loaded-all ' , @ onSupermodelLoaded
2014-01-07 02:45:33 -05:00
2014-09-01 23:53:53 -04:00
# Supermodel (Level) Loading
loadLevel: ->
@level = @ supermodel . getModel ( Level , @ levelID ) or new Level _id: @ levelID
if @ level . loaded
@ onLevelLoaded ( )
else
@level = @ supermodel . loadModel ( @ level , ' level ' ) . model
@ listenToOnce @ level , ' sync ' , @ onLevelLoaded
onLevelLoaded: ->
@ loadSession ( )
@ populateLevel ( )
2014-01-03 13:32:13 -05:00
# Session Loading
2014-01-07 02:45:33 -05:00
2014-01-03 13:32:13 -05:00
loadSession: ->
2014-03-18 14:52:23 -04:00
return if @ headless
2014-02-13 18:51:41 -05:00
if @ sessionID
url = " /db/level_session/ #{ @ sessionID } "
else
url = " /db/level/ #{ @ levelID } /session "
url += " ?team= #{ @ team } " if @ team
2014-02-15 18:44:45 -05:00
2014-05-07 16:28:42 -04:00
session = new LevelSession ( ) . setURL url
2014-06-30 22:16:26 -04:00
@sessionResource = @ supermodel . loadModel ( session , ' level_session ' , { cache: false } )
2014-05-21 15:53:28 -04:00
@session = @ sessionResource . model
2014-08-31 18:08:52 -04:00
if @ session . loaded
@ loadDependenciesForSession @ session
else
@ listenToOnce @ session , ' sync ' , ->
@session.url = -> ' /db/level.session/ ' + @ id
@ loadDependenciesForSession @ session
2014-08-24 15:33:46 -04:00
2014-02-13 19:42:35 -05:00
if @ opponentSessionID
2014-05-07 16:28:42 -04:00
opponentSession = new LevelSession ( ) . setURL " /db/level_session/ #{ @ opponentSessionID } "
2014-05-21 15:53:28 -04:00
@opponentSessionResource = @ supermodel . loadModel ( opponentSession , ' opponent_session ' )
@opponentSession = @ opponentSessionResource . model
2014-08-31 18:08:52 -04:00
if @ opponentSession . loaded
@ loadDependenciesForSession @ opponentSession
else
@ listenToOnce @ opponentSession , ' sync ' , @ loadDependenciesForSession
2014-08-24 15:33:46 -04:00
2014-08-15 15:41:29 -04:00
loadDependenciesForSession: (session) ->
2014-09-01 23:53:53 -04:00
return unless @ level . get ( ' type ' , true ) is ' hero '
heroConfig = session . get ( ' heroConfig ' )
2014-09-20 18:18:21 -04:00
heroConfig ? = me . get ( ' heroConfig ' )
heroConfig ? = { inventory: { } , thangType: ' 529ffbf1cf1818f2be000001 ' } # If we got here not from PlayLevelModal (like level editor preview), assign Tharin as the hero.
session . set ' heroConfig ' , heroConfig unless _ . isEqual heroConfig , session . get ( ' heroConfig ' )
2014-09-01 23:53:53 -04:00
url = " /db/thang.type/ #{ heroConfig . thangType } /version?project=name,components,original "
@ worldNecessities . push @ maybeLoadURL ( url , ThangType , ' thang ' )
for itemThangType in _ . values ( heroConfig . inventory )
url = " /db/thang.type/ #{ itemThangType } /version?project=name,components,original "
2014-08-07 21:27:47 -04:00
@ worldNecessities . push @ maybeLoadURL ( url , ThangType , ' thang ' )
2014-08-11 01:09:13 -04:00
2014-09-01 23:53:53 -04:00
# Grabbing the rest of the required data for the level
2014-04-29 19:43:46 -04:00
2014-04-25 17:30:06 -04:00
populateLevel: ->
thangIDs = [ ]
componentVersions = [ ]
systemVersions = [ ]
articleVersions = [ ]
2014-04-29 19:43:46 -04:00
2014-08-24 15:33:46 -04:00
flagThang = thangType: ' 53fa25f25bc220000052c2be ' , id: ' Placeholder Flag ' , components: [ ]
for thang in ( @ level . get ( ' thangs ' ) or [ ] ) . concat [ flagThang ]
2014-04-25 17:30:06 -04:00
thangIDs . push thang . thangType
2014-09-01 16:51:30 -04:00
@ loadThangsRequiredByLevelThang ( thang )
2014-04-25 17:30:06 -04:00
for comp in thang . components or [ ]
componentVersions . push _ . pick ( comp , [ ' original ' , ' majorVersion ' ] )
2014-04-29 19:43:46 -04:00
2014-04-25 17:30:06 -04:00
for system in @ level . get ( ' systems ' ) or [ ]
systemVersions . push _ . pick ( system , [ ' original ' , ' majorVersion ' ] )
if indieSprites = system ? . config ? . indieSprites
for indieSprite in indieSprites
thangIDs . push indieSprite . thangType
2014-04-29 19:43:46 -04:00
unless @ headless
for article in @ level . get ( ' documentation ' ) ? . generalArticles or [ ]
articleVersions . push _ . pick ( article , [ ' original ' , ' majorVersion ' ] )
2014-04-25 17:30:06 -04:00
objUniq = (array) -> _ . uniq array , false , (arg) -> JSON . stringify ( arg )
2014-04-28 17:58:58 -04:00
2014-05-02 13:31:20 -04:00
worldNecessities = [ ]
@thangIDs = _ . uniq thangIDs
2014-05-13 13:51:55 -04:00
@thangNames = new ThangNamesCollection ( @ thangIDs )
worldNecessities . push @ supermodel . loadCollection ( @ thangNames , ' thang_names ' )
2014-08-07 21:27:47 -04:00
@ listenToOnce @ thangNames , ' sync ' , @ onThangNamesLoaded
2014-05-21 15:53:28 -04:00
worldNecessities . push @ sessionResource if @ sessionResource ? . isLoading
worldNecessities . push @ opponentSessionResource if @ opponentSessionResource ? . isLoading
2014-05-02 15:32:41 -04:00
2014-04-25 17:30:06 -04:00
for obj in objUniq componentVersions
url = " /db/level.component/ #{ obj . original } /version/ #{ obj . majorVersion } "
2014-05-02 13:31:20 -04:00
worldNecessities . push @ maybeLoadURL ( url , LevelComponent , ' component ' )
2014-04-25 17:30:06 -04:00
for obj in objUniq systemVersions
url = " /db/level.system/ #{ obj . original } /version/ #{ obj . majorVersion } "
2014-05-02 13:31:20 -04:00
worldNecessities . push @ maybeLoadURL ( url , LevelSystem , ' system ' )
2014-04-25 17:30:06 -04:00
for obj in objUniq articleVersions
url = " /db/article/ #{ obj . original } /version/ #{ obj . majorVersion } "
@ maybeLoadURL url , Article , ' article '
if obj = @ level . get ' nextLevel '
url = " /db/level/ #{ obj . original } /version/ #{ obj . majorVersion } "
@ maybeLoadURL url , Level , ' level '
2014-08-07 17:22:43 -04:00
unless @ headless
2014-04-29 19:43:46 -04:00
wizard = ThangType . loadUniversalWizard ( )
@ supermodel . loadModel wizard , ' thang '
2014-05-02 15:32:41 -04:00
2014-08-07 21:27:47 -04:00
@worldNecessities = @ worldNecessities . concat worldNecessities
2014-08-11 01:09:13 -04:00
2014-09-01 16:51:30 -04:00
loadThangsRequiredByLevelThang: (levelThang) ->
@ loadThangsRequiredFromComponentList levelThang . components
loadThangsRequiredByThangType: (thangType) ->
@ loadThangsRequiredFromComponentList thangType . get ( ' components ' )
loadThangsRequiredFromComponentList: (components) ->
2014-09-01 23:07:50 -04:00
return unless components
2014-09-01 16:51:30 -04:00
requiredThangTypes = [ ]
for component in components when component . config
if component . original is LevelComponent . EquipsID
requiredThangTypes . push itemThangType for itemThangType in _ . values ( component . config . inventory ? { } )
else if component . config . requiredThangTypes
requiredThangTypes = requiredThangTypes . concat component . config . requiredThangTypes
for thangType in requiredThangTypes
url = " /db/thang.type/ #{ thangType } /version?project=name,components,original "
2014-08-28 18:19:04 -04:00
@ worldNecessities . push @ maybeLoadURL ( url , ThangType , ' thang ' )
2014-08-11 01:09:13 -04:00
2014-08-07 21:27:47 -04:00
onThangNamesLoaded: (thangNames) ->
2014-08-28 18:19:04 -04:00
for thangType in thangNames . models
@ loadDefaultComponentsForThangType ( thangType )
2014-09-01 16:51:30 -04:00
@ loadThangsRequiredByThangType ( thangType )
2014-08-11 01:09:13 -04:00
2014-08-07 21:27:47 -04:00
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 ' )
onWorldNecessityLoaded: (resource) ->
index = @ worldNecessities . indexOf ( resource )
2014-08-28 18:19:04 -04:00
if resource . name is ' thang '
2014-08-07 21:27:47 -04:00
@ loadDefaultComponentsForThangType ( resource . model )
2014-09-01 16:51:30 -04:00
@ loadThangsRequiredByThangType ( resource . model )
2014-08-11 01:09:13 -04:00
2014-08-07 21:27:47 -04:00
return unless index >= 0
@ worldNecessities . splice ( index , 1 )
@worldNecessities = ( r for r in @ worldNecessities when r ? )
@ onWorldNecessitiesLoaded ( ) if @ worldNecessities . length is 0
2014-05-02 13:31:20 -04:00
onWorldNecessitiesLoaded: =>
@ initWorld ( )
2014-05-14 12:24:52 -04:00
@ supermodel . clearMaxProgress ( )
2014-05-19 20:10:41 -04:00
@ trigger ' world-necessities-loaded '
2014-08-07 17:22:43 -04:00
return if @ headless
2014-05-15 14:27:51 -04:00
thangsToLoad = _ . uniq ( ( t . spriteName for t in @ world . thangs when t . exists ) )
2014-05-13 13:51:55 -04:00
nameModelTuples = ( [ thangType . get ( ' name ' ) , thangType ] for thangType in @ thangNames . models )
nameModelMap = _ . zipObject nameModelTuples
2014-09-16 23:19:52 -04:00
@ spriteSheetsToBuild ? = [ ]
2014-05-13 13:51:55 -04:00
for thangTypeName in thangsToLoad
2014-05-13 17:39:45 -04:00
thangType = nameModelMap [ thangTypeName ]
2014-08-25 18:39:47 -04:00
continue if not thangType or thangType . isFullyLoaded ( )
2014-05-13 17:39:45 -04:00
thangType . fetch ( )
thangType = @ supermodel . loadModel ( thangType , ' thang ' ) . model
2014-06-30 22:16:26 -04:00
res = @ supermodel . addSomethingResource ' sprite_sheet ' , 5
2014-05-13 17:39:45 -04:00
res.thangType = thangType
res . markLoading ( )
@ spriteSheetsToBuild . push res
2014-08-18 17:09:28 -04:00
@buildLoopInterval = setInterval @ buildLoop , 5 if @ spriteSheetsToBuild . length
2014-04-25 17:30:06 -04:00
maybeLoadURL: (url, Model, resourceName) ->
return if @ supermodel . getModel ( url )
2014-04-28 14:09:21 -04:00
model = new Model ( ) . setURL url
@ supermodel . loadModel ( model , resourceName )
2014-04-29 19:43:46 -04:00
2014-04-25 17:30:06 -04:00
onSupermodelLoaded: ->
2014-05-25 11:29:33 -04:00
return if @ destroyed
2014-05-13 17:39:45 -04:00
console . log ' SuperModel for Level loaded in ' , new Date ( ) . getTime ( ) - @ t0 , ' ms '
2014-01-03 13:32:13 -05:00
@ loadLevelSounds ( )
2014-04-25 17:30:06 -04:00
@ denormalizeSession ( )
2014-03-18 14:52:23 -04:00
app . tracker . updatePlayState ( @ level , @ session ) unless @ headless
2014-05-15 00:54:36 -04:00
2014-05-13 17:39:45 -04:00
buildLoop: =>
2014-05-20 13:46:52 -04:00
someLeft = false
2014-09-16 23:19:52 -04:00
for spriteSheetResource , i in @ spriteSheetsToBuild ? [ ]
2014-05-20 13:46:52 -04:00
continue if spriteSheetResource . spriteSheetKeys
someLeft = true
thangType = spriteSheetResource . thangType
if thangType . loaded and not thangType . loading
keys = @ buildSpriteSheetsForThangType spriteSheetResource . thangType
2014-05-20 14:25:32 -04:00
if keys and keys . length
@ listenTo spriteSheetResource . thangType , ' build-complete ' , @ onBuildComplete
spriteSheetResource.spriteSheetKeys = keys
else
spriteSheetResource . markLoaded ( )
2014-05-25 11:29:33 -04:00
2014-05-20 13:46:52 -04:00
clearInterval @ buildLoopInterval unless someLeft
onBuildComplete: (e) ->
resource = null
for resource in @ spriteSheetsToBuild
break if e . thangType is resource . thangType
2014-08-30 17:30:53 -04:00
return console . error ' Did not find spriteSheetToBuildResource for ' , e unless resource
2014-05-20 13:46:52 -04:00
resource.spriteSheetKeys = ( k for k in resource . spriteSheetKeys when k isnt e . key )
resource . markLoaded ( ) if resource . spriteSheetKeys . length is 0
2014-01-07 02:45:33 -05:00
2014-01-03 13:32:13 -05:00
denormalizeSession: ->
2014-03-18 14:52:23 -04:00
return if @ headless or @ sessionDenormalized or @ spectateMode
2014-01-03 13:32:13 -05:00
patch =
' levelName ' : @ level . get ( ' name ' )
' levelID ' : @ level . get ( ' slug ' ) or @ level . id
if me . id is @ session . get ' creator '
patch.creatorName = me . get ( ' name ' )
2014-02-26 20:06:21 -05:00
for key , value of patch
if @ session . get ( key ) is value
delete patch [ key ]
unless _ . isEmpty patch
@ session . set key , value for key , value of patch
tempSession = new LevelSession _id: @ session . id
tempSession . save ( patch , { patch: true } )
2014-01-03 13:32:13 -05:00
@sessionDenormalized = true
2014-01-16 14:37:04 -05:00
2014-02-28 01:14:52 -05:00
# Building sprite sheets
2014-04-25 17:30:06 -04:00
buildSpriteSheetsForThangType: (thangType) ->
2014-05-06 12:49:04 -04:00
return if @ headless
2014-05-02 20:03:30 -04:00
# TODO: Finish making sure the supermodel loads the raster image before triggering load complete, and that the cocosprite has access to the asset.
# if f = thangType.get('raster')
# queue = new createjs.LoadQueue()
# queue.loadFile('/file/'+f)
2014-04-25 17:30:06 -04:00
@ grabThangTypeTeams ( ) unless @ thangTypeTeams
2014-05-20 13:46:52 -04:00
keys = [ ]
2014-04-25 17:30:06 -04:00
for team in @ thangTypeTeams [ thangType . get ( ' original ' ) ] ? [ null ]
2014-05-20 13:46:52 -04:00
spriteOptions = { resolutionFactor: SPRITE_RESOLUTION_FACTOR , async: true }
2014-04-25 17:30:06 -04:00
if thangType . get ( ' kind ' ) is ' Floor '
spriteOptions.resolutionFactor = 2
if team and color = @ teamConfigs [ team ] ? . color
spriteOptions.colorConfig = team: color
2014-05-20 13:46:52 -04:00
key = @ buildSpriteSheet thangType , spriteOptions
if _ . isString ( key ) then keys . push key
keys
2014-04-25 17:30:06 -04:00
2014-02-28 01:14:52 -05:00
grabThangTypeTeams: ->
@ grabTeamConfigs ( )
@thangTypeTeams = { }
for thang in @ level . get ( ' thangs ' )
for component in thang . components
if team = component . config ? . team
@ thangTypeTeams [ thang . thangType ] ? = [ ]
@ thangTypeTeams [ thang . thangType ] . push team unless team in @ thangTypeTeams [ thang . thangType ]
break
@ thangTypeTeams
grabTeamConfigs: ->
for system in @ level . get ( ' systems ' )
if @teamConfigs = system . config ? . teamConfigs
break
unless @ teamConfigs
# Hack: pulled from Alliance System code. TODO: put in just one place.
2014-06-30 22:16:26 -04:00
@teamConfigs = { ' humans ' : { ' superteam ' : ' humans ' , ' color ' : { ' hue ' : 0 , ' saturation ' : 0.75 , ' lightness ' : 0.5 } , ' playable ' : true } , ' ogres ' : { ' superteam ' : ' ogres ' , ' color ' : { ' hue ' : 0.66 , ' saturation ' : 0.75 , ' lightness ' : 0.5 } , ' playable ' : false } , ' neutral ' : { ' superteam ' : ' neutral ' , ' color ' : { ' hue ' : 0.33 , ' saturation ' : 0.75 , ' lightness ' : 0.5 } } }
2014-02-28 01:14:52 -05:00
@ teamConfigs
2014-01-14 16:16:30 -05:00
buildSpriteSheet: (thangType, options) ->
if thangType . get ( ' name ' ) is ' Wizard '
options.colorConfig = me . get ( ' wizard ' ) ? . colorConfig or { }
2014-04-25 17:30:06 -04:00
thangType . buildSpriteSheet options
2014-01-16 14:37:04 -05:00
2014-02-28 01:14:52 -05:00
# World init
initWorld: ->
return if @ initialized
@initialized = true
2014-05-10 21:24:50 -04:00
@world = new World ( )
2014-05-15 17:54:31 -04:00
@world.levelSessionIDs = if @ opponentSessionID then [ @ sessionID , @ opponentSessionID ] else [ @ sessionID ]
2014-08-14 19:34:55 -04:00
serializedLevel = @ level . serialize ( @ supermodel , @ session )
2014-02-28 01:14:52 -05:00
@ world . loadFromLevel serializedLevel , false
2014-06-30 22:16:26 -04:00
console . log ' World has been initialized from level loader. '
2014-02-28 01:14:52 -05:00
2014-01-03 13:32:13 -05:00
# Initial Sound Loading
2014-09-01 23:53:53 -04:00
playJingle: ->
return if @ headless
# Apparently the jingle, when it tries to play immediately during all this loading, you can't hear it.
# Add the timeout to fix this weird behavior.
f = ->
jingles = [ ' ident_1 ' , ' ident_2 ' ]
AudioPlayer . playInterfaceSound jingles [ Math . floor Math . random ( ) * jingles . length ]
setTimeout f , 500
2014-01-03 13:32:13 -05:00
loadAudio: ->
2014-02-15 18:44:45 -05:00
return if @ headless
2014-06-30 22:16:26 -04:00
AudioPlayer . preloadInterfaceSounds [ ' victory ' ]
2014-01-07 02:45:33 -05:00
2014-01-03 13:32:13 -05:00
loadLevelSounds: ->
2014-02-15 18:44:45 -05:00
return if @ headless
2014-01-03 13:32:13 -05:00
scripts = @ level . get ' scripts '
return unless scripts
for script in scripts when script . noteChain
for noteGroup in script . noteChain when noteGroup . sprites
for sprite in noteGroup . sprites when sprite . say ? . sound
AudioPlayer . preloadSoundReference ( sprite . say . sound )
thangTypes = @ supermodel . getModels ( ThangType )
for thangType in thangTypes
for trigger , sounds of thangType . get ( ' soundTriggers ' ) or { } when trigger isnt ' say '
AudioPlayer . preloadSoundReference sound for sound in sounds
2014-01-07 02:45:33 -05:00
2014-01-03 13:32:13 -05:00
# everything else sound wise is loaded as needed as worlds are generated
2014-01-07 02:45:33 -05:00
2014-04-29 19:43:46 -04:00
progress: -> @ supermodel . progress
2014-05-15 00:54:36 -04:00
2014-05-13 17:39:45 -04:00
destroy: ->
clearInterval @ buildLoopInterval if @ buildLoopInterval
super ( )