2014-01-03 10:32:13 -08:00
Level = require ' models/Level '
CocoClass = require ' lib/CocoClass '
AudioPlayer = require ' lib/AudioPlayer '
LevelSession = require ' models/LevelSession '
ThangType = require ' models/ThangType '
app = require ' application '
2014-01-14 13:16:30 -08:00
World = require ' lib/world/world '
2014-01-03 10:32:13 -08: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-06 23:45:33 -08:00
2014-01-03 10:32:13 -08:00
spriteSheetsBuilt: 0
spriteSheetsToBuild: 0
2014-01-06 23:45:33 -08:00
2014-02-15 15:44:45 -08:00
constructor: (options) ->
2014-01-03 10:32:13 -08:00
super ( )
2014-02-15 15:44:45 -08:00
@supermodel = options . supermodel
@levelID = options . levelID
@sessionID = options . sessionID
@opponentSessionID = options . opponentSessionID
@team = options . team
@headless = options . headless
2014-01-03 10:32:13 -08:00
@ loadSession ( )
@ loadLevelModels ( )
@ loadAudio ( )
@ playJingle ( )
2014-02-12 12:41:41 -08:00
_ . defer @ update # Lets everything else resolve first
2014-01-06 23:45:33 -08:00
2014-01-03 10:32:13 -08:00
playJingle: ->
2014-02-15 15:44:45 -08:00
return if @ headless
2014-02-26 20:23:13 -08: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 " ]
AudioPlayer . playInterfaceSound jingles [ Math . floor Math . random ( ) * jingles . length ]
setTimeout f , 500
2014-01-03 10:32:13 -08:00
# Session Loading
2014-01-06 23:45:33 -08:00
2014-01-03 10:32:13 -08:00
loadSession: ->
2014-02-13 15:51:41 -08:00
if @ sessionID
url = " /db/level_session/ #{ @ sessionID } "
else
url = " /db/level/ #{ @ levelID } /session "
url += " ?team= #{ @ team } " if @ team
2014-02-15 15:44:45 -08:00
2014-01-03 10:32:13 -08:00
@session = new LevelSession ( )
@session.url = -> url
2014-02-14 15:35:54 -08:00
# Unless you specify cache:false, sometimes the browser will use a cached session
# and players will 'lose' code
@ session . fetch ( { cache : false } )
2014-02-11 15:47:59 -08:00
@ session . once ' sync ' , @ onSessionLoaded , @
2014-02-15 15:44:45 -08:00
2014-02-13 16:42:35 -08:00
if @ opponentSessionID
@opponentSession = new LevelSession ( )
@opponentSession.url = " /db/level_session/ #{ @ opponentSessionID } "
@ opponentSession . fetch ( )
@ opponentSession . once ' sync ' , @ onSessionLoaded , @
2014-02-15 15:44:45 -08:00
2014-02-13 16:42:35 -08:00
sessionsLoaded: ->
@ session . loaded and ( ( not @ opponentSession ) or @ opponentSession . loaded )
2014-01-06 23:45:33 -08:00
2014-02-11 15:47:59 -08:00
onSessionLoaded: ->
2014-03-06 15:52:51 -08:00
return if @ destroyed
2014-01-03 10:32:13 -08:00
# TODO: maybe have all non versioned models do this? Or make it work to PUT/PATCH to relative urls
2014-02-13 16:42:35 -08:00
if @ session . loaded
@session.url = -> ' /db/level.session/ ' + @ id
@ update ( ) if @ sessionsLoaded ( )
2014-01-03 10:32:13 -08:00
# Supermodel (Level) Loading
loadLevelModels: ->
2014-02-11 15:47:59 -08:00
@ supermodel . on ' loaded-one ' , @ onSupermodelLoadedOne , @
@ supermodel . once ' error ' , @ onSupermodelError , @
2014-01-03 10:32:13 -08:00
@level = @ supermodel . getModel ( Level , @ levelID ) or new Level _id: @ levelID
2014-02-11 15:38:36 -08:00
levelID = @ levelID
2014-02-15 15:44:45 -08:00
headless = @ headless
2014-01-06 23:45:33 -08:00
2014-02-11 15:38:36 -08:00
@supermodel.shouldPopulate = (model) ->
2014-01-03 10:32:13 -08:00
# if left unchecked, the supermodel would load this level
# and every level next on the chain. This limits the population
handles = [ model . id , model . get ' slug ' ]
2014-02-11 15:38:36 -08:00
return model . constructor . className isnt " Level " or levelID in handles
2014-01-06 23:45:33 -08:00
2014-02-15 17:29:54 -08:00
@supermodel.shouldLoadProjection = (model) ->
return true if headless and model . constructor . className is ' ThangType '
false
2014-02-15 15:44:45 -08:00
2014-01-03 10:32:13 -08:00
@ supermodel . populateModel @ level
2014-01-06 23:45:33 -08:00
2014-02-11 15:47:59 -08:00
onSupermodelError: ->
2014-01-06 23:45:33 -08:00
2014-02-11 15:47:59 -08:00
onSupermodelLoadedOne: (e) ->
2014-02-27 22:14:52 -08:00
@ buildSpriteSheetsForThangType e . model if not @ headless and e . model instanceof ThangType
2014-02-16 18:29:24 -08:00
@ update ( )
2014-01-03 10:32:13 -08:00
2014-01-06 23:45:33 -08:00
# Things to do when either the Session or Supermodel load
2014-01-03 10:32:13 -08:00
2014-02-12 12:41:41 -08:00
update: =>
2014-01-03 10:32:13 -08:00
@ notifyProgress ( )
return if @ updateCompleted
2014-02-17 08:15:53 -08:00
return unless @ supermodel ? . finished ( ) and @ sessionsLoaded ( )
2014-01-03 10:32:13 -08:00
@ denormalizeSession ( )
@ loadLevelSounds ( )
app . tracker . updatePlayState ( @ level , @ session )
@updateCompleted = true
2014-01-06 23:45:33 -08:00
2014-01-03 10:32:13 -08:00
denormalizeSession: ->
2014-02-26 17:06:21 -08:00
return if @ sessionDenormalized
2014-01-03 10:32:13 -08: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 17:06:21 -08: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 10:32:13 -08:00
@sessionDenormalized = true
2014-01-16 11:37:04 -08:00
2014-02-27 22:14:52 -08:00
# Building sprite sheets
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.
@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 } } }
@ teamConfigs
buildSpriteSheetsForThangType: (thangType) ->
@ grabThangTypeTeams ( ) unless @ thangTypeTeams
for team in @ thangTypeTeams [ thangType . get ( ' original ' ) ] ? [ null ]
spriteOptions = { resolutionFactor: 4 , async: true }
2014-02-17 17:38:49 -08:00
if thangType . get ( ' kind ' ) is ' Floor '
2014-02-27 22:14:52 -08:00
spriteOptions.resolutionFactor = 2
if team and color = @ teamConfigs [ team ] ? . color
spriteOptions.colorConfig = team: color
@ buildSpriteSheet thangType , spriteOptions
2014-01-14 13:16:30 -08:00
buildSpriteSheet: (thangType, options) ->
if thangType . get ( ' name ' ) is ' Wizard '
options.colorConfig = me . get ( ' wizard ' ) ? . colorConfig or { }
building = thangType . buildSpriteSheet options
return unless building
2014-02-27 22:14:52 -08:00
#console.log 'Building:', thangType.get('name'), options
2014-02-27 20:54:29 -08:00
t0 = new Date ( )
2014-01-14 13:16:30 -08:00
@ spriteSheetsToBuild += 1
2014-02-12 12:41:41 -08:00
thangType . once ' build-complete ' , =>
2014-03-06 15:52:51 -08:00
return if @ destroyed
2014-01-14 13:16:30 -08:00
@ spriteSheetsBuilt += 1
@ notifyProgress ( )
2014-02-27 20:54:29 -08:00
console . log " Built " , thangType . get ( ' name ' ) , ' after ' , ( ( new Date ( ) ) - t0 ) , ' ms '
2014-01-16 11:37:04 -08:00
2014-02-27 22:14:52 -08:00
# World init
initWorld: ->
return if @ initialized
@initialized = true
@world = new World @ level . get ( ' name ' )
serializedLevel = @ level . serialize ( @ supermodel )
@ world . loadFromLevel serializedLevel , false
2014-01-03 10:32:13 -08:00
# Initial Sound Loading
loadAudio: ->
2014-02-15 15:44:45 -08:00
return if @ headless
2014-01-03 10:32:13 -08:00
AudioPlayer . preloadInterfaceSounds [ " victory " ]
2014-01-06 23:45:33 -08:00
2014-01-03 10:32:13 -08:00
loadLevelSounds: ->
2014-02-15 15:44:45 -08:00
return if @ headless
2014-01-03 10:32:13 -08: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-06 23:45:33 -08:00
2014-01-03 10:32:13 -08:00
# everything else sound wise is loaded as needed as worlds are generated
2014-01-06 23:45:33 -08:00
2014-01-03 10:32:13 -08:00
allDone: ->
2014-02-13 16:42:35 -08:00
@ supermodel . finished ( ) and @ sessionsLoaded ( ) and @ spriteSheetsBuilt is @ spriteSheetsToBuild
2014-01-06 23:45:33 -08:00
2014-01-03 10:32:13 -08:00
progress: ->
return 0 unless @ level . loaded
overallProgress = 0
supermodelProgress = @ supermodel . progress ( )
overallProgress += supermodelProgress * 0.7
2014-02-13 16:42:35 -08:00
overallProgress += 0.1 if @ sessionsLoaded ( )
2014-02-15 15:44:45 -08:00
if @ headless
spriteMapProgress = 0.2
else
spriteMapProgress = if supermodelProgress is 1 then 0.2 else 0
spriteMapProgress *= @ spriteSheetsBuilt / @ spriteSheetsToBuild if @ spriteSheetsToBuild
2014-01-03 10:32:13 -08:00
overallProgress += spriteMapProgress
return overallProgress
notifyProgress: ->
Backbone . Mediator . publish ' level-loader:progress-changed ' , progress: @ progress ( )
2014-01-14 13:16:30 -08:00
@ initWorld ( ) if @ allDone ( )
2014-03-07 15:18:56 -08:00
@ trigger ' progress '
2014-01-14 13:16:30 -08:00
@ trigger ' loaded-all ' if @ progress ( ) is 1
2014-01-03 10:32:13 -08:00
destroy: ->
@ supermodel . off ' loaded-one ' , @ onSupermodelLoadedOne
2014-02-15 17:29:54 -08:00
@world = null # don't hold onto garbage
2014-02-15 15:44:45 -08:00
@update = null
super ( )