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
2014-11-28 20:49:41 -05:00
CocoClass = require ' core/CocoClass '
2014-04-25 17:30:06 -04:00
AudioPlayer = require ' lib/AudioPlayer '
2014-11-28 20:49:41 -05:00
app = require ' core/application '
2014-01-14 16:16:30 -05:00
World = require ' lib/world/world '
2015-11-19 17:49:47 -05:00
utils = require ' core/utils '
2014-01-03 13:32:13 -05:00
2016-06-17 18:15:13 -04:00
LOG = false
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
2016-08-05 16:23:44 -04:00
# LevelLoader depends on SuperModel retrying timed out requests, as these occasionally happen during play.
# If LevelLoader ever moves away from SuperModel, it will have to manage its own retries.
2016-08-09 15:53:53 -04:00
reportedLoadErrorAlready = false
2014-01-03 13:32:13 -05:00
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
2015-02-28 00:37:29 -05:00
@sessionless = options . sessionless
2016-04-07 22:06:57 -04:00
@fakeSessionConfig = options . fakeSessionConfig
2014-03-12 20:51:09 -04:00
@spectateMode = options . spectateMode ? false
2015-02-04 14:31:43 -05:00
@observing = options . observing
2015-11-12 14:00:54 -05:00
@courseID = options . courseID
2014-02-15 18:44:45 -05:00
2014-08-07 21:27:47 -04:00
@worldNecessities = [ ]
@ listenTo @ supermodel , ' resource-loaded ' , @ onWorldNecessityLoaded
2016-06-06 22:42:57 -04:00
@ listenTo @ supermodel , ' failed ' , @ onWorldNecessityLoadFailed
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
2016-08-09 15:53:53 -04:00
@loadTimeoutID = setTimeout @ reportLoadError . bind ( @ ) , 30000
2014-04-29 19:54:57 -04:00
@ listenToOnce @ supermodel , ' loaded-all ' , @ onSupermodelLoaded
2014-01-07 02:45:33 -05:00
2014-09-01 23:53:53 -04:00
# Supermodel (Level) Loading
2016-07-14 11:58:43 -04:00
2016-07-13 16:28:54 -04:00
loadWorldNecessities: ->
# TODO: Actually trigger loading, instead of in the constructor
new Promise ( (resolve, reject) =>
2016-07-13 16:52:22 -04:00
return resolve ( @ ) if @ world
2016-07-13 16:28:54 -04:00
@ once ' world-necessities-loaded ' , => resolve ( @ )
@ once ' world-necessity-load-failed ' , ({resource}) ->
{ jqxhr } = resource
reject ( { message: jqxhr . responseJSON ? . message or jqxhr . responseText or ' Unknown Error ' } )
)
2014-09-01 23:53:53 -04:00
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
2016-08-23 19:02:03 -04:00
2016-08-09 15:53:53 -04:00
reportLoadError: ->
2016-08-17 19:54:32 -04:00
window . tracker ? . trackEvent ' LevelLoadError ' ,
2016-08-15 16:34:06 -04:00
category: ' Error ' ,
levelSlug: @ work ? . level ? . slug ,
unloaded: JSON . stringify ( @ supermodel . report ( ) . map (m) -> _ . result ( m . model , ' url ' ) )
2014-09-01 23:53:53 -04:00
onLevelLoaded: ->
2016-07-14 11:58:43 -04:00
if not @ sessionless and @ level . isType ( ' hero ' , ' hero-ladder ' , ' hero-coop ' , ' course ' )
2016-06-20 17:26:45 -04:00
@sessionDependenciesRegistered = { }
2016-07-14 15:34:22 -04:00
if @ level . isType ( ' web-dev ' )
@headless = true
2016-07-14 15:47:25 -04:00
if @ sessionless
# When loading a web-dev level in the level editor, pretend it's a normal hero level so we can put down our placeholder Thang.
# TODO: avoid this whole roundabout Thang-based way of doing web-dev levels
originalGet = @ level . get
@level.get = ->
return ' hero ' if arguments [ 0 ] is ' type '
originalGet . apply @ , arguments
2016-07-15 19:57:39 -04:00
if ( @ courseID and not @ level . isType ( ' course ' , ' course-ladder ' , ' game-dev ' , ' web-dev ' ) ) or window . serverConfig . picoCTF
2015-11-12 12:57:34 -05:00
# Because we now use original hero levels for both hero and course levels, we fake being a course level in this context.
originalGet = @ level . get
@level.get = ->
return ' course ' if arguments [ 0 ] is ' type '
originalGet . apply @ , arguments
2016-02-17 14:33:50 -05:00
if window . serverConfig . picoCTF
@ supermodel . addRequestResource ( url: ' /picoctf/problems ' , success: (picoCTFProblems) =>
@ level ? . picoCTFProblem = _ . find picoCTFProblems , pid: @ level . get ( ' picoCTFProblem ' )
) . load ( )
2016-04-07 22:06:57 -04:00
if @ sessionless
null
else if @ fakeSessionConfig ?
@ loadFakeSession ( )
else
@ loadSession ( )
2014-09-01 23:53:53 -04:00
@ populateLevel ( )
2014-01-03 13:32:13 -05:00
# Session Loading
2014-01-07 02:45:33 -05:00
2016-04-07 22:06:57 -04:00
loadFakeSession: ->
initVals =
level:
original: @ level . get ( ' original ' )
majorVersion: @ level . get ( ' version ' ) . major
creator: me . id
state:
complete: false
scripts: { }
permissions: [
{ target: me . id , access: ' owner ' }
{ target: ' public ' , access: ' write ' }
]
codeLanguage: @ fakeSessionConfig . codeLanguage or me . get ( ' aceConfig ' ) ? . language or ' python '
_id: ' A Fake Session ID '
@session = new LevelSession initVals
@session.loaded = true
@ fakeSessionConfig . callback ? @ session , @ level
# TODO: set the team if we need to, for multiplayer
# TODO: just finish the part where we make the submit button do what is right when we are fake
# TODO: anything else to make teacher session-less play make sense when we are fake
# TODO: make sure we are not actually calling extra save/patch/put things throwing warnings because we know we are fake and so we shouldn't try to do that
for method in [ ' save ' , ' patch ' , ' put ' ]
@ session [ method ] = -> console . error " We shouldn ' t be doing a session. #{ method } , since it ' s a fake session. "
@session.fake = true
@ loadDependenciesForSession @ session
2014-01-03 13:32:13 -05:00
loadSession: ->
2014-02-13 18:51:41 -05:00
if @ sessionID
2014-09-21 23:19:27 -04:00
url = " /db/level.session/ #{ @ sessionID } "
2016-05-05 19:56:58 -04:00
url += " ?interpret=true " if @ spectateMode
2014-02-13 18:51:41 -05:00
else
url = " /db/level/ #{ @ levelID } /session "
url += " ?team= #{ @ team } " if @ team
2015-11-12 14:00:54 -05:00
url += " ?course= #{ @ courseID } " if @ courseID
2014-02-15 18:44:45 -05:00
2014-05-07 16:28:42 -04:00
session = new LevelSession ( ) . setURL url
2016-08-23 14:48:02 -04:00
if @ headless and not @ level . isType ( ' web-dev ' )
2016-08-23 19:02:03 -04:00
session.project = [ ' creator ' , ' team ' , ' heroConfig ' , ' codeLanguage ' , ' submittedCodeLanguage ' , ' state ' , ' submittedCode ' , ' submitted ' ]
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-10-20 00:56:26 -04:00
if @ opponentSessionID
2016-05-24 15:00:04 -04:00
opponentURL = " /db/level.session/ #{ @ opponentSessionID } ?interpret=true "
2016-05-04 01:26:48 -04:00
opponentSession = new LevelSession ( ) . setURL opponentURL
2015-04-08 15:00:12 -04:00
opponentSession.project = session . project if @ headless
2014-11-17 18:07:10 -05:00
@opponentSessionResource = @ supermodel . loadModel ( opponentSession , ' opponent_session ' , { cache: false } )
2014-10-20 00:56:26 -04:00
@opponentSession = @ opponentSessionResource . model
2014-08-31 18:08:52 -04:00
if @ session . loaded
2014-09-21 23:19:27 -04:00
@ session . setURL ' /db/level.session/ ' + @ session . id
2014-08-31 18:08:52 -04:00
@ loadDependenciesForSession @ session
else
@ listenToOnce @ session , ' sync ' , ->
2014-09-21 23:19:27 -04:00
@ session . setURL ' /db/level.session/ ' + @ session . id
2014-08-31 18:08:52 -04:00
@ loadDependenciesForSession @ session
2014-10-20 00:56:26 -04:00
if @ opponentSession
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) ->
2016-06-17 18:15:13 -04:00
console . log " Loading dependencies for session: " , session if LOG
2015-01-31 13:04:02 -05:00
if me . id isnt session . get ' creator '
session.patch = session.save = -> console . error " Not saving session, since we didn ' t create it. "
2015-11-19 17:49:47 -05:00
else if codeLanguage = utils . getQueryVariable ' codeLanguage '
session . set ' codeLanguage ' , codeLanguage
2015-02-12 21:40:38 -05:00
@ loadCodeLanguagesForSession session
2016-05-04 01:26:48 -04:00
if compressed = session . get ' interpret '
uncompressed = LZString . decompressFromUTF16 compressed
code = session . get ' code '
code [ if session . get ( ' team ' ) is ' humans ' then ' hero-placeholder ' else ' hero-placeholder-1 ' ] . plan = uncompressed
session . set ' code ' , code
session . unset ' interpret '
2016-05-24 15:00:04 -04:00
if session . get ( ' codeLanguage ' ) in [ ' io ' , ' clojure ' ]
session . set ' codeLanguage ' , ' python '
2014-09-22 23:15:51 -04:00
if session is @ session
2015-01-30 17:45:34 -05:00
@ addSessionBrowserInfo session
2014-11-17 18:07:10 -05:00
# hero-ladder games require the correct session team in level:loaded
team = @ team ? @ session . get ( ' team ' )
Backbone . Mediator . publish ' level:loaded ' , level: @ level , team: team
2014-09-22 23:15:51 -04:00
Backbone . Mediator . publish ' level:session-loaded ' , level: @ level , session: @ session
2014-10-18 21:57:33 -04:00
@ consolidateFlagHistory ( ) if @ opponentSession ? . loaded
2014-10-20 00:56:26 -04:00
else if session is @ opponentSession
2014-10-18 21:57:33 -04:00
@ consolidateFlagHistory ( ) if @ session . loaded
2016-07-14 11:58:43 -04:00
if @ level . isType ( ' course ' ) # course-ladder is hard to handle because there's 2 sessions
2016-06-20 13:47:15 -04:00
heroThangType = me . get ( ' heroConfig ' ) ? . thangType or ThangType . heroes . captain
console . log " Course mode, loading custom hero: " , heroThangType if LOG
url = " /db/thang.type/ #{ heroThangType } /version "
2016-05-25 18:24:51 -04:00
if heroResource = @ maybeLoadURL ( url , ThangType , ' thang ' )
2016-06-17 18:15:13 -04:00
console . log " Pushing resource: " , heroResource if LOG
2016-05-25 18:24:51 -04:00
@ worldNecessities . push heroResource
2016-06-14 16:03:42 -04:00
@ sessionDependenciesRegistered [ session . id ] = true
2016-09-11 12:16:50 -04:00
unless @ level . isType ( ' hero ' , ' hero-ladder ' , ' hero-coop ' )
# Return before loading heroConfig ThangTypes. Finish if all world necessities were completed by the time the session loaded.
if @ checkAllWorldNecessitiesRegisteredAndLoaded ( )
@ onWorldNecessitiesLoaded ( )
2016-05-25 18:24:51 -04:00
return
2016-09-11 12:16:50 -04:00
# Load the ThangTypes needed for the session's heroConfig for these types of levels
2014-09-01 23:53:53 -04:00
heroConfig = session . get ( ' heroConfig ' )
2014-10-20 00:56:26 -04:00
heroConfig ? = me . get ( ' heroConfig ' ) if session is @ session and not @ headless
2014-11-29 11:43:40 -05:00
heroConfig ? = { }
heroConfig . inventory ? = feet: ' 53e237bf53457600003e3f05 ' # If all else fails, assign simple boots.
heroConfig . thangType ? = ' 529ffbf1cf1818f2be000001 ' # If all else fails, assign Tharin as the hero.
2014-09-20 18:18:21 -04:00
session . set ' heroConfig ' , heroConfig unless _ . isEqual heroConfig , session . get ( ' heroConfig ' )
2014-09-21 23:49:45 -04:00
url = " /db/thang.type/ #{ heroConfig . thangType } /version "
2014-09-23 02:01:19 -04:00
if heroResource = @ maybeLoadURL ( url , ThangType , ' thang ' )
@ worldNecessities . push heroResource
else
heroThangType = @ supermodel . getModel url
@ loadDefaultComponentsForThangType heroThangType
@ loadThangsRequiredByThangType heroThangType
2014-09-01 23:53:53 -04:00
for itemThangType in _ . values ( heroConfig . inventory )
2014-10-19 20:38:10 -04:00
url = " /db/thang.type/ #{ itemThangType } /version?project=name,components,original,rasterIcon,kind "
2014-09-23 02:01:19 -04:00
if itemResource = @ maybeLoadURL ( url , ThangType , ' thang ' )
@ worldNecessities . push itemResource
else
itemThangType = @ supermodel . getModel url
@ loadDefaultComponentsForThangType itemThangType
@ loadThangsRequiredByThangType itemThangType
2014-10-20 00:56:26 -04:00
@ sessionDependenciesRegistered [ session . id ] = true
2014-11-10 17:48:39 -05:00
if _ . size ( @ sessionDependenciesRegistered ) is 2 and @ checkAllWorldNecessitiesRegisteredAndLoaded ( )
2014-10-20 00:56:26 -04:00
@ onWorldNecessitiesLoaded ( )
2014-08-11 01:09:13 -04:00
2015-02-12 21:40:38 -05:00
loadCodeLanguagesForSession: (session) ->
codeLanguages = _ . uniq _ . filter [ session . get ( ' codeLanguage ' ) or ' python ' , session . get ( ' submittedCodeLanguage ' ) ]
for codeLanguage in codeLanguages
do (codeLanguage) =>
modulePath = " vendor/aether- #{ codeLanguage } "
return unless application . moduleLoader ? . load modulePath
languageModuleResource = @ supermodel . addSomethingResource ' language_module '
onModuleLoaded = (e) ->
return unless e . id is modulePath
languageModuleResource . markLoaded ( )
@ stopListening application . moduleLoader , ' loaded ' , onModuleLoaded # listenToOnce might work here instead, haven't tried
@ listenTo application . moduleLoader , ' loaded ' , onModuleLoaded
2015-01-30 17:45:34 -05:00
addSessionBrowserInfo: (session) ->
2015-01-31 13:04:02 -05:00
return unless me . id is session . get ' creator '
2015-01-30 17:45:34 -05:00
return unless $ . browser ?
browser = { }
browser [ ' desktop ' ] = $ . browser . desktop if $ . browser . desktop
browser [ ' name ' ] = $ . browser . name if $ . browser . name
browser [ ' platform ' ] = $ . browser . platform if $ . browser . platform
browser [ ' version ' ] = $ . browser . version if $ . browser . version
session . set ' browser ' , browser
2016-04-07 22:06:57 -04:00
session . patch ( ) unless session . fake
2015-01-30 17:45:34 -05:00
2014-10-18 21:57:33 -04:00
consolidateFlagHistory: ->
state = @ session . get ( ' state ' ) ? { }
myFlagHistory = _ . filter state . flagHistory ? [ ] , team: @ session . get ( ' team ' )
opponentFlagHistory = _ . filter @ opponentSession . get ( ' state ' ) ? . flagHistory ? [ ] , team: @ opponentSession . get ( ' team ' )
state.flagHistory = myFlagHistory . concat opponentFlagHistory
@ session . set ' state ' , state
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 '
2014-12-01 16:46:55 -05:00
if obj = @ level . get ' nextLevel ' # TODO: update to get next level from campaigns, not this old property
2014-04-25 17:30:06 -04:00
url = " /db/level/ #{ obj . original } /version/ #{ obj . majorVersion } "
@ maybeLoadURL url , Level , ' level '
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
2015-04-10 14:27:13 -04:00
extantRequiredThangTypes = _ . filter requiredThangTypes
if extantRequiredThangTypes . length < requiredThangTypes . length
console . error " Some Thang had a blank required ThangType in components list: " , components
for thangType in extantRequiredThangTypes
2015-12-18 16:26:47 -05:00
url = " /db/thang.type/ #{ thangType } /version?project=name,components,original,rasterIcon,kind,prerenderedSpriteSheetData "
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-11-10 17:48:39 -05:00
@thangNamesLoaded = true
@ onWorldNecessitiesLoaded ( ) if @ checkAllWorldNecessitiesRegisteredAndLoaded ( )
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 ? )
2014-11-10 17:48:39 -05:00
@ onWorldNecessitiesLoaded ( ) if @ checkAllWorldNecessitiesRegisteredAndLoaded ( )
2016-07-13 16:28:54 -04:00
onWorldNecessityLoadFailed: (event) ->
2016-08-09 15:53:53 -04:00
@ reportLoadError ( )
2016-07-13 16:28:54 -04:00
@ trigger ( ' world-necessity-load-failed ' , event )
2016-06-06 22:42:57 -04:00
2014-11-10 17:48:39 -05:00
checkAllWorldNecessitiesRegisteredAndLoaded: ->
return false unless _ . filter ( @ worldNecessities ) . length is 0
return false unless @ thangNamesLoaded
2015-02-28 00:37:29 -05:00
return false if @ sessionDependenciesRegistered and not @ sessionDependenciesRegistered [ @ session . id ] and not @ sessionless
return false if @ sessionDependenciesRegistered and @ opponentSession and not @ sessionDependenciesRegistered [ @ opponentSession . id ] and not @ sessionless
2014-11-10 17:48:39 -05:00
true
2014-05-02 13:31:20 -04:00
2014-10-20 00:56:26 -04:00
onWorldNecessitiesLoaded: ->
2016-06-17 18:15:13 -04:00
console . log " World necessities loaded. " if LOG
2014-05-02 13:31:20 -04:00
@ 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
2014-09-25 13:47:53 -04:00
# for thangTypeName in thangsToLoad
# thangType = nameModelMap[thangTypeName]
# continue if not thangType or thangType.isFullyLoaded()
# thangType.fetch()
# thangType = @supermodel.loadModel(thangType, 'thang').model
# res = @supermodel.addSomethingResource 'sprite_sheet', 5
# res.thangType = thangType
# res.markLoading()
# @spriteSheetsToBuild.push res
2014-05-13 17:39:45 -04:00
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: ->
2016-08-09 15:53:53 -04:00
clearTimeout @ loadTimeoutID
2014-05-25 11:29:33 -04:00
return if @ destroyed
2016-06-17 18:15:13 -04:00
console . log ' SuperModel for Level loaded in ' , new Date ( ) . getTime ( ) - @ t0 , ' ms ' if LOG
2014-01-03 13:32:13 -05:00
@ loadLevelSounds ( )
2014-04-25 17:30:06 -04:00
@ denormalizeSession ( )
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: ->
2016-07-17 03:53:17 -04:00
return if @ sessionDenormalized or @ spectateMode or @ sessionless or me . isSessionless ( )
return if @ headless and not @ level . isType ( ' web-dev ' )
2016-03-30 19:20:37 -04:00
# This is a way (the way?) PUT /db/level.sessions/undefined was happening
# See commit c242317d9
return if not @ session . id
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
2014-10-27 19:09:52 -04:00
tempSession . save ( patch , { patch: true , type: ' PUT ' } )
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 ' )
2016-07-14 11:58:43 -04:00
if @ level . isType ( ' hero ' , ' course ' ) and thang . id is ' Hero Placeholder '
2014-11-23 23:04:07 -05:00
continue # No team colors for heroes on single-player levels
2014-02-28 01:14:52 -05:00
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
2016-07-14 15:34:22 -04:00
return if @ level . isType ( ' web-dev ' )
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-10-18 20:32:01 -04:00
@world.submissionCount = @ session ? . get ( ' state ' ) ? . submissionCount ? 0
@world.flagHistory = @ session ? . get ( ' state ' ) ? . flagHistory ? [ ]
2015-01-05 13:44:17 -05:00
@world.difficulty = @ session ? . get ( ' state ' ) ? . difficulty ? 0
2015-02-04 14:31:43 -05:00
if @ observing
@world.difficulty = Math . max 0 , @ world . difficulty - 1 # Show the difficulty they won, not the next one.
2016-07-13 13:04:43 -04:00
serializedLevel = @ level . serialize { @ supermodel , @ session , @ opponentSession , @ headless , @ sessionless }
2014-02-28 01:14:52 -05:00
@ world . loadFromLevel serializedLevel , false
2016-06-17 18:15:13 -04:00
console . log ' World has been initialized from level loader. ' if LOG
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: ->
2015-11-29 15:32:04 -05:00
return if @ headless or not me . get ( ' volume ' )
volume = 0.5
if me . level ( ) < 3
volume = 0.25 # Start softly, since they may not be expecting it
2014-09-01 23:53:53 -04:00
# 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 ' ]
2015-11-29 15:32:04 -05:00
AudioPlayer . playInterfaceSound jingles [ Math . floor Math . random ( ) * jingles . length ] , volume
2014-09-01 23:53:53 -04:00
setTimeout f , 500
2014-01-03 13:32:13 -05:00
loadAudio: ->
2015-11-29 15:32:04 -05:00
return if @ headless or not me . get ( ' volume ' )
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: ->
2015-11-29 15:32:04 -05:00
return if @ headless or not me . get ( ' volume ' )
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 ( )