2014-11-28 20:49:41 -05:00
RootView = require ' views/core/RootView '
2014-01-03 13:32:13 -05:00
template = require ' templates/play/level '
2014-11-28 20:49:41 -05:00
{ me } = require ' core/auth '
2014-01-03 13:32:13 -05:00
ThangType = require ' models/ThangType '
2014-11-28 20:49:41 -05:00
utils = require ' core/utils '
storage = require ' core/storage '
2014-11-22 23:48:04 -05:00
{ createAetherOptions } = require ' lib/aether_utils '
2014-01-03 13:32:13 -05:00
# tools
Surface = require ' lib/surface/Surface '
God = require ' lib/God '
GoalManager = require ' lib/world/GoalManager '
ScriptManager = require ' lib/scripts/ScriptManager '
2014-05-05 20:37:14 -04:00
LevelBus = require ' lib/LevelBus '
2014-01-03 13:32:13 -05:00
LevelLoader = require ' lib/LevelLoader '
LevelSession = require ' models/LevelSession '
Level = require ' models/Level '
LevelComponent = require ' models/LevelComponent '
2014-03-07 18:18:56 -05:00
Article = require ' models/Article '
2014-01-03 13:32:13 -05:00
Camera = require ' lib/surface/Camera '
2014-02-19 14:42:33 -05:00
AudioPlayer = require ' lib/AudioPlayer '
2014-01-03 13:32:13 -05:00
# subviews
2014-07-23 10:02:45 -04:00
LevelLoadingView = require ' ./LevelLoadingView '
2014-11-07 00:43:39 -05:00
ProblemAlertView = require ' ./tome/ProblemAlertView '
2014-07-23 10:02:45 -04:00
TomeView = require ' ./tome/TomeView '
ChatView = require ' ./LevelChatView '
HUDView = require ' ./LevelHUDView '
2014-11-08 14:35:25 -05:00
LevelDialogueView = require ' ./LevelDialogueView '
2014-07-23 10:02:45 -04:00
ControlBarView = require ' ./ControlBarView '
LevelPlaybackView = require ' ./LevelPlaybackView '
GoalsView = require ' ./LevelGoalsView '
2014-08-23 22:00:35 -04:00
LevelFlagsView = require ' ./LevelFlagsView '
2014-07-23 10:02:45 -04:00
GoldView = require ' ./LevelGoldView '
2015-08-20 14:57:47 -04:00
DuelStatsView = require ' ./DuelStatsView '
2014-07-23 10:02:45 -04:00
VictoryModal = require ' ./modal/VictoryModal '
2014-09-30 19:14:47 -04:00
HeroVictoryModal = require ' ./modal/HeroVictoryModal '
2014-07-23 10:02:45 -04:00
InfiniteLoopModal = require ' ./modal/InfiniteLoopModal '
2014-11-06 19:23:23 -05:00
LevelSetupManager = require ' lib/LevelSetupManager '
2014-12-19 21:37:42 -05:00
ContactModal = require ' views/core/ContactModal '
2014-01-03 13:32:13 -05:00
PROFILE_ME = false
2014-07-17 20:16:32 -04:00
module.exports = class PlayLevelView extends RootView
2014-01-03 13:32:13 -05:00
id: ' level-view '
template: template
cache: false
shortcutsEnabled: true
isEditorPreview: false
subscriptions:
2014-09-24 18:06:22 -04:00
' level:set-volume ' : (e) -> createjs . Sound . setVolume ( if e . volume is 1 then 0.6 else e . volume ) # Quieter for now until individual sound FX controls work again.
2014-08-27 15:24:03 -04:00
' level:show-victory ' : ' onShowVictory '
' level:restart ' : ' onRestartLevel '
2014-10-08 13:46:10 -04:00
' level:highlight-dom ' : ' onHighlightDOM '
2014-08-27 15:24:03 -04:00
' level:end-highlight-dom ' : ' onEndHighlight '
' level:focus-dom ' : ' onFocusDom '
' level:disable-controls ' : ' onDisableControls '
' level:enable-controls ' : ' onEnableControls '
2014-08-30 12:19:41 -04:00
' god:world-load-progress-changed ' : ' onWorldLoadProgressChanged '
2014-02-06 17:00:27 -05:00
' god:new-world-created ' : ' onNewWorld '
2014-08-21 19:27:52 -04:00
' god:streaming-world-updated ' : ' onNewWorld '
2014-01-03 13:32:13 -05:00
' god:infinite-loop ' : ' onInfiniteLoop '
2014-08-27 15:24:03 -04:00
' level:reload-from-data ' : ' onLevelReloadFromData '
' level:reload-thang-type ' : ' onLevelReloadThangType '
2014-01-03 13:32:13 -05:00
' level:session-will-save ' : ' onSessionWillSave '
2014-03-14 20:06:08 -04:00
' level:started ' : ' onLevelStarted '
2014-09-21 18:52:49 -04:00
' level:loading-view-unveiling ' : ' onLoadingViewUnveiling '
2014-03-14 20:06:08 -04:00
' level:loading-view-unveiled ' : ' onLoadingViewUnveiled '
2014-09-21 23:19:27 -04:00
' level:session-loaded ' : ' onSessionLoaded '
2014-08-30 00:46:26 -04:00
' playback:real-time-playback-waiting ' : ' onRealTimePlaybackWaiting '
2014-08-23 16:54:52 -04:00
' playback:real-time-playback-started ' : ' onRealTimePlaybackStarted '
' playback:real-time-playback-ended ' : ' onRealTimePlaybackEnded '
2014-10-31 19:33:43 -04:00
' real-time-multiplayer:created-game ' : ' onRealTimeMultiplayerCreatedGame '
' real-time-multiplayer:joined-game ' : ' onRealTimeMultiplayerJoinedGame '
' real-time-multiplayer:left-game ' : ' onRealTimeMultiplayerLeftGame '
2014-08-29 18:10:04 -04:00
' real-time-multiplayer:manual-cast ' : ' onRealTimeMultiplayerCast '
2014-11-18 14:21:29 -05:00
' ipad:memory-warning ' : ' onIPadMemoryWarning '
2014-11-21 19:23:26 -05:00
' store:item-purchased ' : ' onItemPurchased '
2014-01-03 13:32:13 -05:00
events:
' click # level-done-button ' : ' onDonePressed '
2014-09-22 18:34:25 -04:00
' click # stop-real-time-playback-button ' : -> Backbone . Mediator . publish ' playback:stop-real-time-playback ' , { }
2014-08-27 21:43:17 -04:00
' click # fullscreen-editor-background-screen ' : (e) -> Backbone . Mediator . publish ' tome:toggle-maximize ' , { }
2014-12-19 21:37:42 -05:00
' click .contact-link ' : ' onContactClicked '
2014-01-03 13:32:13 -05:00
2014-02-20 18:11:20 -05:00
shortcuts:
' ctrl+s ' : ' onCtrlS '
2014-11-23 18:24:59 -05:00
' esc ' : ' onEscapePressed '
2014-05-19 23:49:17 -04:00
2014-05-19 20:10:41 -04:00
# Initial Setup #############################################################
2014-02-20 18:11:20 -05:00
2014-01-03 13:32:13 -05:00
constructor: (options, @levelID) ->
console . profile ? ( ) if PROFILE_ME
super options
2015-09-24 20:12:18 -04:00
@courseID = options . courseID
@courseInstanceID = options . courseInstanceID
2014-01-27 14:00:36 -05:00
@isEditorPreview = @ getQueryVariable ' dev '
@sessionID = @ getQueryVariable ' session '
2015-01-31 13:04:02 -05:00
@observing = @ getQueryVariable ' observing '
2014-11-18 00:30:44 -05:00
2014-11-17 18:07:10 -05:00
@opponentSessionID = @ getQueryVariable ( ' opponent ' )
@ opponentSessionID ? = @ options . opponent
2014-01-03 13:32:13 -05:00
2014-08-29 20:52:47 -04:00
$ ( window ) . on ' resize ' , @ onWindowResize
2014-01-03 13:32:13 -05:00
@saveScreenshot = _ . throttle @ saveScreenshot , 30000
2014-01-16 14:37:04 -05:00
2015-08-03 18:52:52 -04:00
application . tracker ? . enableInspectletJS ( @ levelID )
2014-01-27 14:00:36 -05:00
if @ isEditorPreview
2014-07-17 18:50:29 -04:00
@supermodel.shouldSaveBackups = (model) -> # Make sure to load possibly changed things from localStorage.
model . constructor . className in [ ' Level ' , ' LevelComponent ' , ' LevelSystem ' , ' ThangType ' ]
f = => @ load ( ) unless @ levelLoader # Wait to see if it's just given to us through setLevel.
2014-01-27 14:00:36 -05:00
setTimeout f , 100
else
@ load ( )
2015-02-27 19:07:41 -05:00
application . tracker ? . trackEvent ' Started Level Load ' , category: ' Play Level ' , level: @ levelID , label: @ levelID unless @ observing
2014-05-19 23:49:17 -04:00
2014-05-01 19:38:27 -04:00
setLevel: (@level, givenSupermodel) ->
@supermodel.models = givenSupermodel . models
@supermodel.collections = givenSupermodel . collections
@supermodel.shouldSaveBackups = givenSupermodel . shouldSaveBackups
2014-10-18 17:51:43 -04:00
serializedLevel = @ level . serialize @ supermodel , @ session , @ otherSession
2014-05-15 17:54:31 -04:00
@ god ? . setLevel serializedLevel
2014-01-21 02:14:34 -05:00
if @ world
@ world . loadFromLevel serializedLevel , false
else
@ load ( )
2014-01-16 14:37:04 -05:00
2014-01-15 21:37:47 -05:00
load: ->
2014-04-13 23:31:23 -04:00
@loadStartTime = new Date ( )
2014-05-10 21:24:50 -04:00
@god = new God debugWorker: true
2015-02-04 14:31:43 -05:00
@levelLoader = new LevelLoader supermodel: @ supermodel , levelID: @ levelID , sessionID: @ sessionID , opponentSessionID: @ opponentSessionID , team: @ getQueryVariable ( ' team ' ) , observing: @ observing
2014-05-19 20:10:41 -04:00
@ listenToOnce @ levelLoader , ' world-necessities-loaded ' , @ onWorldNecessitiesLoaded
2014-10-06 20:46:13 -04:00
trackLevelLoadEnd: ->
return if @ isEditorPreview
@loadEndTime = new Date ( )
loadDuration = @ loadEndTime - @ loadStartTime
console . debug " Level unveiled after #{ ( loadDuration / 1000 ) . toFixed ( 2 ) } s "
2015-01-31 13:04:02 -05:00
unless @ observing
2015-02-27 19:07:41 -05:00
application . tracker ? . trackEvent ' Finished Level Load ' , category: ' Play Level ' , label: @ levelID , level: @ levelID , loadDuration: loadDuration
2015-01-31 13:04:02 -05:00
application . tracker ? . trackTiming loadDuration , ' Level Load Time ' , @ levelID , @ levelID
2014-10-06 20:46:13 -04:00
2014-05-19 20:10:41 -04:00
# CocoView overridden methods ###############################################
2014-05-19 23:49:17 -04:00
2014-01-03 13:32:13 -05:00
getRenderData: ->
c = super ( )
c.world = @ world
c
afterRender: ->
2014-04-17 19:23:35 -04:00
super ( )
2014-01-03 13:32:13 -05:00
window . onPlayLevelViewLoaded ? @ # still a hack
2015-11-10 18:22:09 -05:00
@ insertSubView @loadingView = new LevelLoadingView autoUnveil: @ options . autoUnveil or @ observing , level: @ levelLoader ? . level ? @ level , session: @ levelLoader ? . session ? @ session # May not have @level loaded yet
2014-02-13 12:26:21 -05:00
@ $el . find ( ' # level-done-button ' ) . hide ( )
2014-04-11 19:15:26 -04:00
$ ( ' body ' ) . addClass ( ' is-playing ' )
2014-09-06 22:50:31 -04:00
$ ( ' body ' ) . bind ( ' touchmove ' , false ) if @ isIPadApp ( )
2014-01-03 13:32:13 -05:00
2014-05-19 20:10:41 -04:00
afterInsert: ->
super ( )
2014-03-07 18:18:56 -05:00
2014-05-19 20:10:41 -04:00
# Partially Loaded Setup ####################################################
2014-05-19 23:49:17 -04:00
2014-05-19 20:10:41 -04:00
onWorldNecessitiesLoaded: ->
# Called when we have enough to build the world, but not everything is loaded
@ grabLevelLoaderData ( )
2014-11-17 18:07:10 -05:00
team = @ getQueryVariable ( ' team ' ) ? @ session . get ( ' team ' ) ? @ world . teamForPlayer ( 0 )
2014-05-02 15:32:41 -04:00
@ loadOpponentTeam ( team )
2014-05-19 20:10:41 -04:00
@ setupGod ( )
2014-05-02 15:32:41 -04:00
@ setTeam team
@ initGoalManager ( )
2014-05-19 20:10:41 -04:00
@ insertSubviews ( )
2014-05-02 15:32:41 -04:00
@ initVolume ( )
@ listenTo ( @ session , ' change:multiplayer ' , @ onMultiplayerChanged )
2014-09-30 19:14:47 -04:00
2014-05-02 15:32:41 -04:00
@ register ( )
@ controlBar . setBus ( @ bus )
2014-01-03 13:32:13 -05:00
@ initScriptManager ( )
2014-03-13 12:02:19 -04:00
2014-02-27 19:44:11 -05:00
grabLevelLoaderData: ->
@session = @ levelLoader . session
@world = @ levelLoader . world
@level = @ levelLoader . level
2015-07-24 20:37:42 -04:00
@ $el . addClass ' hero ' if @ level . get ( ' type ' , true ) in [ ' hero ' , ' hero-ladder ' , ' hero-coop ' , ' course ' , ' course-ladder ' ]
2014-11-10 13:51:46 -05:00
@ $el . addClass ' flags ' if _ . any ( @ world . thangs , (t) -> ( t . programmableProperties and ' findFlags ' in t . programmableProperties ) or t . inventory ? . flag ) or @ level . get ( ' slug ' ) is ' sky-span '
2014-11-17 18:07:10 -05:00
# TODO: Update terminology to always be opponentSession or otherSession
# TODO: E.g. if it's always opponent right now, then variable names should be opponentSession until we have coop play
2014-02-27 19:44:11 -05:00
@otherSession = @ levelLoader . opponentSession
2014-08-30 12:19:41 -04:00
@worldLoadFakeResources = [ ] # first element (0) is 1%, last (100) is 100%
for percent in [ 1 . . 100 ]
@ worldLoadFakeResources . push @ supermodel . addSomethingResource " world_simulation_ #{ percent } % " , 1
onWorldLoadProgressChanged: (e) ->
return unless @ worldLoadFakeResources
@ lastWorldLoadPercent ? = 0
worldLoadPercent = Math . floor 100 * e . progress
for percent in [ @ lastWorldLoadPercent + 1 . . worldLoadPercent ] by 1
@ worldLoadFakeResources [ percent - 1 ] . markLoaded ( )
@lastWorldLoadPercent = worldLoadPercent
@worldFakeLoadResources = null if worldLoadPercent is 100 # Done, don't need to watch progress any more.
2014-03-13 12:02:19 -04:00
2014-02-27 19:44:11 -05:00
loadOpponentTeam: (myTeam) ->
opponentSpells = [ ]
for spellTeam , spells of @ session . get ( ' teamSpells ' ) ? @ otherSession ? . get ( ' teamSpells ' ) ? { }
continue if spellTeam is myTeam or not myTeam
opponentSpells = opponentSpells . concat spells
2014-05-16 20:38:33 -04:00
if ( not @ session . get ( ' teamSpells ' ) ) and @ otherSession ? . get ( ' teamSpells ' )
2014-06-30 22:16:26 -04:00
@ session . set ( ' teamSpells ' , @ otherSession . get ( ' teamSpells ' ) )
2014-11-22 23:48:04 -05:00
opponentCode = @ otherSession ? . get ( ' transpiledCode ' ) or { }
2014-02-27 19:44:11 -05:00
myCode = @ session . get ( ' code ' ) or { }
for spell in opponentSpells
[ thang , spell ] = spell . split ' / '
c = opponentCode [ thang ] ? [ spell ]
myCode [ thang ] ? = { }
if c then myCode [ thang ] [ spell ] = c else delete myCode [ thang ] [ spell ]
@ session . set ( ' code ' , myCode )
if @ session . get ( ' multiplayer ' ) and @ otherSession ?
# For now, ladderGame will disallow multiplayer, because session code combining doesn't play nice yet.
@ session . set ' multiplayer ' , false
2014-01-03 13:32:13 -05:00
2014-05-19 20:10:41 -04:00
setupGod: ->
2014-10-18 17:51:43 -04:00
@ god . setLevel @ level . serialize @ supermodel , @ session , @ otherSession
2014-05-19 20:10:41 -04:00
@ god . setLevelSessionIDs if @ otherSession then [ @ session . id , @ otherSession . id ] else [ @ session . id ]
@ god . setWorldClassMap @ world . classMap
setTeam: (team) ->
team = team ? . team unless _ . isString team
team ? = ' humans '
me.team = team
2014-11-17 18:07:10 -05:00
@ session . set ' team ' , team
2014-08-27 15:24:03 -04:00
Backbone . Mediator . publish ' level:team-set ' , team: team # Needed for scripts
2014-05-19 23:50:05 -04:00
@team = team
2014-05-19 20:10:41 -04:00
initGoalManager: ->
2014-05-19 23:50:05 -04:00
@goalManager = new GoalManager ( @ world , @ level . get ( ' goals ' ) , @ team )
2014-05-19 20:10:41 -04:00
@ god . setGoalManager @ goalManager
insertSubviews: ->
2015-01-31 13:04:02 -05:00
@ insertSubView @tome = new TomeView levelID: @ levelID , session: @ session , otherSession: @ otherSession , thangs: @ world . thangs , supermodel: @ supermodel , level: @ level , observing: @ observing
2014-12-28 16:25:20 -05:00
@ insertSubView new LevelPlaybackView session: @ session , level: @ level
2014-05-19 20:10:41 -04:00
@ insertSubView new GoalsView { }
2014-11-23 20:15:59 -05:00
@ insertSubView new LevelFlagsView levelID: @ levelID , world: @ world if @ $el . hasClass ' flags '
2014-05-19 20:10:41 -04:00
@ insertSubView new GoldView { }
2014-10-15 01:38:22 -04:00
@ insertSubView new HUDView { level: @ level }
2015-01-15 14:04:48 -05:00
@ insertSubView new LevelDialogueView { level: @ level , sessionID: @ session . id }
2014-05-19 20:10:41 -04:00
@ insertSubView new ChatView levelID: @ levelID , sessionID: @ session . id , session: @ session
2014-12-15 18:11:27 -05:00
@ insertSubView new ProblemAlertView session: @ session , level: @ level , supermodel: @ supermodel
2015-08-20 14:57:47 -04:00
@ insertSubView new DuelStatsView level: @ level , session: @ session , otherSession: @ otherSession , supermodel: @ supermodel , thangs: @ world . thangs if @ level . get ( ' type ' ) in [ ' hero-ladder ' , ' course-ladder ' ]
2015-09-24 20:12:18 -04:00
@ insertSubView @controlBar = new ControlBarView { worldName: utils . i18n ( @ level . attributes , ' name ' ) , session: @ session , level: @ level , supermodel: @ supermodel , courseID: @ courseID , courseInstanceID: @ courseInstanceID }
2014-10-02 01:02:52 -04:00
#_.delay (=> Backbone.Mediator.publish('level:set-debug', debug: true)), 5000 if @isIPadApp() # if me.displayName() is 'Nick'
2014-05-19 20:10:41 -04:00
initVolume: ->
volume = me . get ( ' volume ' )
volume = 1.0 unless volume ?
2014-08-27 15:24:03 -04:00
Backbone . Mediator . publish ' level:set-volume ' , volume: volume
2014-05-19 20:10:41 -04:00
initScriptManager: ->
2014-11-01 19:59:12 -04:00
@scriptManager = new ScriptManager ( { scripts: @ world . scripts or [ ] , view: @ , session: @ session , levelID: @ level . get ( ' slug ' ) } )
2014-05-19 20:10:41 -04:00
@ scriptManager . loadFromSession ( )
register: ->
@bus = LevelBus . get ( @ levelID , @ session . id )
@ bus . setSession ( @ session )
@ bus . setSpells @ tome . spells
2014-12-12 09:37:44 -05:00
if @ session . get ( ' multiplayer ' ) and not me . isAdmin ( )
@ session . set ' multiplayer ' , false # Temp: multiplayer has bugged out some sessions, so ignoring it.
2014-05-19 20:10:41 -04:00
@ bus . connect ( ) if @ session . get ( ' multiplayer ' )
2014-05-19 23:49:17 -04:00
2014-05-19 20:10:41 -04:00
# Load Completed Setup ######################################################
2014-09-21 23:19:27 -04:00
onSessionLoaded: (e) ->
2014-11-30 16:19:00 -05:00
Backbone . Mediator . publish " ipad:language-chosen " , language: e . session . get ( ' codeLanguage ' ) ? " python "
2014-09-21 23:19:27 -04:00
# Just the level and session have been loaded by the level loader
2015-03-14 12:39:43 -04:00
if e . level . get ( ' slug ' ) is ' zero-sum '
2015-04-03 15:19:00 -04:00
sorcerer = ' 52fd1524c7e6cf99160e7bc9 '
if e . session . get ( ' creator ' ) is ' 532dbc73a622924444b68ed9 ' # Wizard Dude gets his own avatar
sorcerer = ' 53e126a4e06b897606d38bef '
e . session . set ' heroConfig ' , { " thangType " : sorcerer , " inventory " : { " misc-0 " : " 53e2396a53457600003e3f0f " , " programming-book " : " 546e266e9df4a17d0d449be5 " , " minion " : " 54eb5dbc49fa2d5c905ddf56 " , " feet " : " 53e214f153457600003e3eab " , " right-hand " : " 54eab7f52b7506e891ca7202 " , " left-hand " : " 5463758f3839c6e02811d30f " , " wrists " : " 54693797a2b1f53ce79443e9 " , " gloves " : " 5469425ca2b1f53ce7944421 " , " torso " : " 546d4a549df4a17d0d449a97 " , " neck " : " 54693274a2b1f53ce79443c9 " , " eyes " : " 546941fda2b1f53ce794441d " , " head " : " 546d4ca19df4a17d0d449abf " } }
2015-08-29 11:02:20 -04:00
else if e . level . get ( ' slug ' ) is ' ace-of-coders '
goliath = ' 55e1a6e876cb0948c96af9f8 '
2015-08-30 10:28:35 -04:00
e . session . set ' heroConfig ' , { " thangType " : goliath , " inventory " : { " eyes " : " 53eb99f41a100989a40ce46e " , " neck " : " 54693274a2b1f53ce79443c9 " , " wrists " : " 54693797a2b1f53ce79443e9 " , " feet " : " 546d4d8e9df4a17d0d449acd " , " minion " : " 54eb5bf649fa2d5c905ddf4a " , " programming-book " : " 557871261ff17fef5abee3ee " } }
2015-03-14 12:39:43 -04:00
else if e . level . get ( ' type ' , true ) in [ ' hero ' , ' hero-ladder ' , ' hero-coop ' ] and not _ . size e . session . get ( ' heroConfig ' ) ? . inventory ? { }
2014-11-09 19:19:18 -05:00
@ setupManager ? . destroy ( )
2015-08-13 08:58:37 -04:00
@setupManager = new LevelSetupManager ( { supermodel: @ supermodel , level: @ level , levelID: @ levelID , parent: @ , session: @ session } )
2014-11-09 19:19:18 -05:00
@ setupManager . open ( )
2014-11-06 19:23:23 -05:00
2015-07-24 20:37:42 -04:00
@ onRealTimeMultiplayerLevelLoaded e . session if e . level . get ( ' type ' ) in [ ' hero-ladder ' , ' course-ladder ' ]
2014-09-21 23:19:27 -04:00
2014-05-19 20:10:41 -04:00
onLoaded: ->
2014-09-21 23:19:27 -04:00
_ . defer => @ onLevelLoaderLoaded ( )
2014-05-19 23:49:17 -04:00
2014-09-21 23:19:27 -04:00
onLevelLoaderLoaded: ->
2014-05-19 20:10:41 -04:00
# Everything is now loaded
2014-05-19 23:49:17 -04:00
return unless @ levelLoader . progress ( ) is 1 # double check, since closing the guide may trigger this early
2014-05-19 20:10:41 -04:00
2014-08-30 17:30:53 -04:00
# Save latest level played.
2015-01-31 13:04:02 -05:00
if not @ observing and not ( @ levelLoader . level . get ( ' type ' ) in [ ' ladder ' , ' ladder-tutorial ' ] )
2014-05-19 20:10:41 -04:00
me . set ( ' lastLevel ' , @ levelID )
me . save ( )
2015-01-28 20:58:56 -05:00
application . tracker ? . identify ( )
2014-08-30 17:30:53 -04:00
@ saveRecentMatch ( ) if @ otherSession
2014-05-19 20:10:41 -04:00
@ levelLoader . destroy ( )
@levelLoader = null
@ initSurface ( )
2014-08-30 17:30:53 -04:00
saveRecentMatch: ->
allRecentlyPlayedMatches = storage . load ( ' recently-played-matches ' ) ? { }
recentlyPlayedMatches = allRecentlyPlayedMatches [ @ levelID ] ? [ ]
allRecentlyPlayedMatches [ @ levelID ] = recentlyPlayedMatches
recentlyPlayedMatches . unshift yourTeam: me . team , otherSessionID: @ otherSession . id , opponentName: @ otherSession . get ( ' creatorName ' ) unless _ . find recentlyPlayedMatches , otherSessionID: @ otherSession . id
recentlyPlayedMatches . splice ( 8 )
storage . save ' recently-played-matches ' , allRecentlyPlayedMatches
2014-05-19 20:10:41 -04:00
initSurface: ->
2014-09-25 13:47:53 -04:00
webGLSurface = $ ( ' canvas # webgl-surface ' , @ $el )
normalSurface = $ ( ' canvas # normal-surface ' , @ $el )
2015-07-24 20:37:42 -04:00
@surface = new Surface ( @ world , normalSurface , webGLSurface , thangTypes: @ supermodel . getModels ( ThangType ) , playJingle: not @ isEditorPreview , observing: @ observing , playerNames: @ findPlayerNames ( ) )
2014-05-19 20:10:41 -04:00
worldBounds = @ world . getBounds ( )
2014-06-30 22:16:26 -04:00
bounds = [ { x: worldBounds . left , y: worldBounds . top } , { x: worldBounds . right , y: worldBounds . bottom } ]
2014-05-19 20:10:41 -04:00
@ surface . camera . setBounds ( bounds )
2014-06-30 22:16:26 -04:00
@ surface . camera . zoomTo ( { x: 0 , y: 0 } , 0.1 , 0 )
2014-05-19 20:10:41 -04:00
2015-02-12 22:47:57 -05:00
findPlayerNames: ->
2015-09-03 16:32:20 -04:00
return { } unless @ level . get ( ' type ' ) in [ ' ladder ' , ' hero-ladder ' , ' course-ladder ' ]
2015-02-12 22:47:57 -05:00
playerNames = { }
for session in [ @ session , @ otherSession ] when session ? . get ( ' team ' )
playerNames [ session . get ( ' team ' ) ] = session . get ( ' creatorName ' ) or ' Anoner '
playerNames
2014-05-19 20:10:41 -04:00
# Once Surface is Loaded ####################################################
onLevelStarted: ->
2014-07-07 19:44:18 -04:00
return unless @ surface ?
2014-05-19 23:49:17 -04:00
@ loadingView . showReady ( )
2014-10-06 20:46:13 -04:00
@ trackLevelLoadEnd ( )
2014-09-23 14:39:56 -04:00
if window . currentModal and not window . currentModal . destroyed and window . currentModal . constructor isnt VictoryModal
2014-08-27 15:24:03 -04:00
return Backbone . Mediator . subscribeOnce ' modal:closed ' , @ onLevelStarted , @
2014-05-03 12:13:26 -04:00
@ surface . showLevel ( )
2015-11-10 18:22:09 -05:00
Backbone . Mediator . publish ' level:set-time ' , time: 0
2015-11-10 19:09:21 -05:00
if ( @ isEditorPreview or @ observing ) and not @ getQueryVariable ( ' intro ' )
2014-09-21 23:19:27 -04:00
@ loadingView . startUnveiling ( )
2015-11-10 18:22:09 -05:00
@ loadingView . unveil true
2014-03-14 20:06:08 -04:00
2014-09-21 18:52:49 -04:00
onLoadingViewUnveiling: (e) ->
2015-11-10 18:22:09 -05:00
@ selectHero ( )
2014-03-14 20:06:08 -04:00
onLoadingViewUnveiled: (e) ->
2015-11-10 18:22:09 -05:00
Backbone . Mediator . publish ' level:set-playing ' , playing: true
2014-04-28 17:01:33 -04:00
@ loadingView . $el . remove ( )
2014-03-14 20:06:08 -04:00
@ removeSubView @ loadingView
@loadingView = null
2014-09-22 01:10:52 -04:00
@ playAmbientSound ( )
2014-11-17 18:07:10 -05:00
if @ options . realTimeMultiplayerSessionID ?
Backbone . Mediator . publish ' playback:real-time-playback-waiting ' , { }
@ realTimeMultiplayerContinueGame @ options . realTimeMultiplayerSessionID
2015-01-15 14:04:48 -05:00
# TODO: Is it possible to create a Mongoose ObjectId for 'ls', instead of the string returned from get()?
2015-01-31 13:04:02 -05:00
application . tracker ? . trackEvent ' Started Level ' , category : ' Play Level ' , levelID: @ levelID , ls: @ session ? . get ( ' _id ' ) unless @ observing
2015-04-18 19:33:40 -04:00
$ ( window ) . trigger ' resize '
2014-09-22 01:10:52 -04:00
playAmbientSound: ->
2014-11-29 19:46:36 -05:00
return if @ destroyed
2014-09-22 01:10:52 -04:00
return if @ ambientSound
return unless file = { Dungeon: ' ambient-dungeon ' , Grass: ' ambient-grass ' } [ @ level . get ( ' terrain ' ) ]
src = " /file/interface/ #{ file } #{ AudioPlayer . ext } "
unless AudioPlayer . getStatus ( src ) ? . loaded
AudioPlayer . preloadSound src
Backbone . Mediator . subscribeOnce ' audio-player:loaded ' , @ playAmbientSound , @
return
2014-09-22 23:15:51 -04:00
@ambientSound = createjs . Sound . play src , loop : - 1 , volume: 0.1
createjs . Tween . get ( @ ambientSound ) . to ( { volume: 1.0 } , 10000 )
2014-03-13 12:02:19 -04:00
2015-11-10 18:22:09 -05:00
selectHero: ->
Backbone . Mediator . publish ' level:suppress-selection-sounds ' , suppress: true
Backbone . Mediator . publish ' tome:select-primary-sprite ' , { }
Backbone . Mediator . publish ' level:suppress-selection-sounds ' , suppress: false
@ surface . focusOnHero ( )
2014-01-03 13:32:13 -05:00
# callbacks
2014-02-20 18:11:20 -05:00
onCtrlS: (e) ->
e . preventDefault ( )
2014-11-23 18:24:59 -05:00
onEscapePressed: (e) ->
return unless @ $el . hasClass ' real-time '
Backbone . Mediator . publish ' playback:stop-real-time-playback ' , { }
2014-01-03 13:32:13 -05:00
onLevelReloadFromData: (e) ->
isReload = Boolean @ world
2014-08-12 15:56:55 -04:00
@ setLevel e . level , e . supermodel
2014-01-03 13:32:13 -05:00
if isReload
@ scriptManager . setScripts ( e . level . get ( ' scripts ' ) )
2014-08-31 18:08:52 -04:00
Backbone . Mediator . publish ' tome:cast-spell ' , { } # a bit hacky
2014-01-03 13:32:13 -05:00
2014-08-12 15:50:41 -04:00
onLevelReloadThangType: (e) ->
tt = e . thangType
for url , model of @ supermodel . models
if model . id is tt . id
for key , val of tt . attributes
model . attributes [ key ] = val
break
2014-08-31 18:08:52 -04:00
Backbone . Mediator . publish ' tome:cast-spell ' , { }
2014-10-09 22:54:39 -04:00
onWindowResize: (e) => @ endHighlight ( )
2014-01-03 13:32:13 -05:00
2014-02-12 15:41:41 -05:00
onDisableControls: (e) ->
2014-01-03 13:32:13 -05:00
return if e . controls and not ( ' level ' in e . controls )
@shortcutsEnabled = false
@wasFocusedOn = document . activeElement
$ ( ' body ' ) . focus ( )
2014-02-12 15:41:41 -05:00
onEnableControls: (e) ->
2014-01-03 13:32:13 -05:00
return if e . controls ? and not ( ' level ' in e . controls )
@shortcutsEnabled = true
$ ( @ wasFocusedOn ) . focus ( ) if @ wasFocusedOn
@wasFocusedOn = null
2014-02-12 15:41:41 -05:00
onDonePressed: -> @ showVictory ( )
2014-01-03 13:32:13 -05:00
2014-02-12 15:41:41 -05:00
onShowVictory: (e) ->
2015-07-24 20:37:42 -04:00
$ ( ' # level-done-button ' ) . show ( ) unless @ level . get ( ' type ' , true ) in [ ' hero ' , ' hero-ladder ' , ' hero-coop ' , ' course ' , ' course-ladder ' ]
2014-01-03 13:32:13 -05:00
@ showVictory ( ) if e . showModal
2014-04-13 23:31:23 -04:00
return if @ victorySeen
@victorySeen = true
victoryTime = ( new Date ( ) ) - @ loadEndTime
2015-01-31 13:04:02 -05:00
if not @ observing and victoryTime > 10 * 1000 # Don't track it if we're reloading an already-beaten level
2014-11-10 12:36:40 -05:00
application . tracker ? . trackEvent ' Saw Victory ' ,
2014-11-28 15:05:34 -05:00
category: ' Play Level '
2014-11-10 00:33:47 -05:00
level: @ level . get ( ' name ' )
label: @ level . get ( ' name ' )
2015-01-08 19:09:39 -05:00
levelID: @ levelID
2015-01-15 14:04:48 -05:00
ls: @ session ? . get ( ' _id ' )
2015-02-27 19:07:41 -05:00
application . tracker ? . trackTiming victoryTime , ' Level Victory Time ' , @ levelID , @ levelID
2014-01-03 13:32:13 -05:00
showVictory: ->
2015-07-24 11:56:20 -04:00
return if @ level . hasLocalChanges ( ) # Don't award achievements when beating level changed in level editor
2014-11-12 18:28:08 -05:00
@ endHighlight ( )
2015-09-24 20:12:18 -04:00
options = { level: @ level , supermodel: @ supermodel , session: @ session , hasReceivedMemoryWarning: @ hasReceivedMemoryWarning , courseID: @ courseID , courseInstanceID: @ courseInstanceID }
2015-07-24 20:37:42 -04:00
ModalClass = if @ level . get ( ' type ' , true ) in [ ' hero ' , ' hero-ladder ' , ' hero-coop ' , ' course ' , ' course-ladder ' ] then HeroVictoryModal else VictoryModal
2014-10-13 17:18:33 -04:00
victoryModal = new ModalClass ( options )
2014-09-30 19:14:47 -04:00
@ openModalView ( victoryModal )
2014-03-31 16:56:13 -04:00
if me . get ( ' anonymous ' )
2014-12-28 16:25:20 -05:00
window . nextURL = ' /play/ ' + ( @ level . get ( ' campaign ' ) ? ' ' ) # Signup will go here on completion instead of reloading.
2014-01-03 13:32:13 -05:00
onRestartLevel: ->
@ tome . reloadAllCode ( )
2014-08-27 15:24:03 -04:00
Backbone . Mediator . publish ' level:restarted ' , { }
2014-01-03 13:32:13 -05:00
$ ( ' # level-done-button ' , @ $el ) . hide ( )
2015-01-31 13:04:02 -05:00
application . tracker ? . trackEvent ' Confirmed Restart ' , category: ' Play Level ' , level: @ level . get ( ' name ' ) , label: @ level . get ( ' name ' ) unless @ observing
2014-01-03 13:32:13 -05:00
onInfiniteLoop: (e) ->
return unless e . firstWorld
2015-04-25 20:29:02 -04:00
@ openModalView new InfiniteLoopModal nonUserCodeProblem: e . nonUserCodeProblem
2015-01-31 13:04:02 -05:00
application . tracker ? . trackEvent ' Saw Initial Infinite Loop ' , category: ' Play Level ' , level: @ level . get ( ' name ' ) , label: @ level . get ( ' name ' ) unless @ observing
2014-01-03 13:32:13 -05:00
2014-10-08 13:46:10 -04:00
onHighlightDOM: (e) -> @ highlightElement e . selector , delay: e . delay , sides: e . sides , offset: e . offset , rotation: e . rotation
2014-01-03 13:32:13 -05:00
2014-10-09 22:54:39 -04:00
onEndHighlight: -> @ endHighlight ( )
2014-01-03 13:32:13 -05:00
2014-10-08 13:46:10 -04:00
onFocusDom: (e) -> $ ( e . selector ) . focus ( )
2014-01-03 13:32:13 -05:00
2014-02-11 18:38:36 -05:00
onMultiplayerChanged: (e) ->
2014-01-03 13:32:13 -05:00
if @ session . get ( ' multiplayer ' )
@ bus . connect ( )
else
@ bus . removeFirebaseData =>
@ bus . disconnect ( )
onSessionWillSave: (e) ->
# Something interesting has happened, so (at a lower frequency), we'll save a screenshot.
2014-05-12 16:28:46 -04:00
#@saveScreenshot e.session
2014-01-03 13:32:13 -05:00
# Throttled
saveScreenshot: (session) =>
return unless screenshot = @ surface ? . screenshot ( )
2014-10-27 19:09:52 -04:00
session . save { screenshot: screenshot } , { patch: true , type: ' PUT ' }
2014-01-03 13:32:13 -05:00
2014-12-19 21:37:42 -05:00
onContactClicked: (e) ->
@ openModalView contactModal = new ContactModal ( )
screenshot = @ surface . screenshot ( 1 , ' image/png ' , 1.0 , 1 )
body =
b64png: screenshot . replace ' data:image/png;base64, ' , ' '
filename: " screenshot- #{ @ levelID } - #{ _ . string . slugify ( ( new Date ( ) ) . toString ( ) ) } .png "
path: " db/user/ #{ me . id } "
mimetype: ' image/png '
contactModal.screenshotURL = " http://codecombat.com/file/ #{ body . path } / #{ body . filename } "
window . screenshot = screenshot
window . screenshotURL = contactModal . screenshotURL
$ . ajax ' /file ' , type: ' POST ' , data: body , success: (e) ->
contactModal . updateScreenshot ? ( )
2014-02-19 14:42:33 -05:00
# Dynamic sound loading
2014-05-14 13:35:16 -04:00
onNewWorld: (e) ->
2014-02-19 14:42:33 -05:00
return if @ headless
2014-05-16 20:38:33 -04:00
scripts = @ world . scripts # Since these worlds don't have scripts, preserve them.
2014-05-14 13:35:16 -04:00
@world = e . world
2014-05-16 20:38:33 -04:00
@world.scripts = scripts
2014-02-19 14:42:33 -05:00
thangTypes = @ supermodel . getModels ( ThangType )
2014-08-25 00:39:34 -04:00
startFrame = @ lastWorldFramesLoaded ? 0
2014-09-22 17:05:13 -04:00
finishedLoading = @ world . frames . length is @ world . totalFrames
if finishedLoading
2014-08-25 00:39:34 -04:00
@lastWorldFramesLoaded = 0
2014-09-22 17:05:13 -04:00
if @ waitingForSubmissionComplete
2014-10-02 20:49:14 -04:00
_ . defer @ onSubmissionComplete # Give it a frame to make sure we have the latest goals
2014-09-22 17:05:13 -04:00
@waitingForSubmissionComplete = false
2014-08-25 00:39:34 -04:00
else
@lastWorldFramesLoaded = @ world . frames . length
for [ spriteName , message ] in @ world . thangDialogueSounds startFrame
2014-02-19 14:42:33 -05:00
continue unless thangType = _ . find thangTypes , (m) -> m . get ( ' name ' ) is spriteName
continue unless sound = AudioPlayer . soundForDialogue message , thangType . get ( ' soundTriggers ' )
AudioPlayer . preloadSoundReference sound
2014-08-23 16:54:52 -04:00
# Real-time playback
2014-08-30 00:46:26 -04:00
onRealTimePlaybackWaiting: (e) ->
@ $el . addClass ( ' real-time ' ) . focus ( )
@ onWindowResize ( )
2014-08-23 16:54:52 -04:00
onRealTimePlaybackStarted: (e) ->
2014-08-23 20:26:56 -04:00
@ $el . addClass ( ' real-time ' ) . focus ( )
2014-08-23 16:54:52 -04:00
@ onWindowResize ( )
onRealTimePlaybackEnded: (e) ->
2014-09-23 13:56:52 -04:00
return unless @ $el . hasClass ' real-time '
2014-08-23 16:54:52 -04:00
@ $el . removeClass ' real-time '
@ onWindowResize ( )
2014-09-22 17:05:13 -04:00
if @ world . frames . length is @ world . totalFrames
_ . delay @ onSubmissionComplete , 750 # Wait for transition to end.
else
@waitingForSubmissionComplete = true
2014-08-29 18:26:39 -04:00
@ onRealTimeMultiplayerPlaybackEnded ( )
2014-08-23 16:54:52 -04:00
2014-09-22 17:05:13 -04:00
onSubmissionComplete: =>
return if @ destroyed
2015-07-24 11:56:20 -04:00
return if @ level . hasLocalChanges ( ) # Don't award achievements when beating level changed in level editor
2014-11-17 18:07:10 -05:00
# TODO: Show a victory dialog specific to hero-ladder level
if @ goalManager . checkOverallStatus ( ) is ' success ' and not @ options . realTimeMultiplayerSessionID ?
2015-01-22 15:26:37 -05:00
showModalFn = -> Backbone . Mediator . publish ' level:show-victory ' , showModal: true
2015-01-31 00:36:36 -05:00
@ session . recordScores @ world . scores , @ level
2015-01-22 15:26:37 -05:00
if @ level . get ' replayable '
@ session . increaseDifficulty showModalFn
else
showModalFn ( )
2014-09-22 17:05:13 -04:00
2014-01-03 13:32:13 -05:00
destroy: ->
2014-02-06 17:00:27 -05:00
@ levelLoader ? . destroy ( )
2014-01-03 13:32:13 -05:00
@ surface ? . destroy ( )
@ god ? . destroy ( )
@ goalManager ? . destroy ( )
@ scriptManager ? . destroy ( )
2014-11-09 19:19:18 -05:00
@ setupManager ? . destroy ( )
2014-09-22 23:15:51 -04:00
if ambientSound = @ ambientSound
# Doesn't seem to work; stops immediately.
createjs . Tween . get ( ambientSound ) . to ( { volume: 0.0 } , 1500 ) . call -> ambientSound . stop ( )
2014-08-29 20:52:47 -04:00
$ ( window ) . off ' resize ' , @ onWindowResize
2014-02-11 17:24:06 -05:00
delete window . world # not sure where this is set, but this is one way to clean it up
2014-01-03 13:32:13 -05:00
@ bus ? . destroy ( )
#@instance.save() unless @instance.loading
2014-12-28 16:25:20 -05:00
delete window . nextURL
2014-01-03 13:32:13 -05:00
console . profileEnd ? ( ) if PROFILE_ME
2014-10-31 19:33:43 -04:00
@ onRealTimeMultiplayerLevelUnloaded ( )
2015-08-03 18:52:52 -04:00
application . tracker ? . disableInspectletJS ( )
2014-02-14 13:57:47 -05:00
super ( )
Real-time multiplayer initial commit
Simple matchmaking, synchronous multiplayer PVP, flags!
Rough matchmaking is under the game menu multiplayer tab, for ladder
games only. After creating a 2-person game there, you can exit that
modal and real-time cast to play against each other.
If you’re the first person to cast, you’ll sit at the real-time level
playback view waiting until the other player casts. When they do, you
both should start the real-time playback (and start placing flags like
crazy people).
If in a multiplayer session, the real-time simulation runs the players’
code against each other. Your multiplayer opponent’s name should be up
near the level name.
Multiplayer sessions are stored completely in Firebase for now, and
removed if both players leave the game. There’s plenty of bugs,
synchronization issues, and minimal polish to add before we push it to
master.
2014-08-29 02:34:07 -04:00
2014-11-22 23:48:04 -05:00
onIPadMemoryWarning: (e) ->
@hasReceivedMemoryWarning = true
onItemPurchased: (e) ->
heroConfig = @ session . get ( ' heroConfig ' ) ? { }
inventory = heroConfig . inventory ? { }
slot = e . item . getAllowedSlots ( ) [ 0 ]
if slot and not inventory [ slot ]
# Open up the inventory modal so they can equip the new item
@ setupManager ? . destroy ( )
2015-08-13 08:58:37 -04:00
@setupManager = new LevelSetupManager ( { supermodel: @ supermodel , level: @ level , levelID: @ levelID , parent: @ , session: @ session , hadEverChosenHero: true } )
2014-11-22 23:48:04 -05:00
@ setupManager . open ( )
# Start Real-time Multiplayer ######################################################
2014-10-31 19:33:43 -04:00
#
# This view acts as a hub for the real-time multiplayer session for the current level.
#
# It performs these actions:
# Player heartbeat
# Publishes player status
# Updates real-time multiplayer session state
# Updates real-time multiplayer player state
# Cleans up old sessions (sets state to 'finished')
# Real-time multiplayer cast handshake
2014-11-17 18:07:10 -05:00
# Swap teams on game joined, if necessary
# Reload PlayLevelView on real-time submit, automatically continue game and real-time playback
2014-10-31 19:33:43 -04:00
#
# It monitors these:
# Real-time multiplayer sessions
# Current real-time multiplayer session
# Internal multiplayer create/joined/left events
#
2014-11-27 12:44:08 -05:00
# Real-time state variables.
2014-11-23 20:15:59 -05:00
# Each Ref is Firebase reference, and may have a matching Data suffixed variable with the latest data received.
# @realTimePlayerRef - User's real-time multiplayer player for this level
# @realTimePlayerGameRef - User's current real-time multiplayer player game session
# @realTimeSessionRef - Current real-time multiplayer game session
# @realTimeOpponentRef - Current real-time multiplayer opponent
# @realTimePlayersRef - Real-time players for current real-time multiplayer game session
2014-11-17 18:07:10 -05:00
# @options.realTimeMultiplayerSessionID - Need to continue an existing real-time multiplayer session
2014-10-31 19:33:43 -04:00
#
# TODO: Move this code to it's own file, or possibly the LevelBus
2014-11-17 18:07:10 -05:00
# TODO: Save settings somewhere reasonable
2014-11-23 20:15:59 -05:00
multiplayerFireHost: ' https://codecombat.firebaseio.com/test/db/ '
2014-10-31 19:33:43 -04:00
2014-11-23 20:15:59 -05:00
onRealTimeMultiplayerLevelLoaded: (session) ->
# console.log 'PlayLevelView onRealTimeMultiplayerLevelLoaded'
return if @ realTimePlayerRef ?
2014-10-31 19:33:43 -04:00
return if me . get ( ' anonymous ' )
2014-11-23 20:15:59 -05:00
@realTimePlayerRef = new Firebase " #{ @ multiplayerFireHost } multiplayer_players/ #{ @ levelID } / #{ me . id } "
2014-11-17 18:07:10 -05:00
unless @ options . realTimeMultiplayerSessionID ?
2014-11-23 20:15:59 -05:00
# TODO: Wait for name instead of using 'Anon', or try and update it later?
name = me . get ( ' name ' ) ? session . get ( ' creatorName ' ) ? ' Anon '
@ realTimePlayerRef . set
id: me . id # TODO: is this redundant info necessary?
name: name
2014-11-17 18:07:10 -05:00
state: ' playing '
created: new Date ( ) . toISOString ( )
heartbeat: new Date ( ) . toISOString ( )
2014-10-31 19:33:43 -04:00
@timerMultiplayerHeartbeatID = setInterval @ onRealTimeMultiplayerHeartbeat , 60 * 1000
@ cleanupRealTimeSessions ( )
cleanupRealTimeSessions: ->
2014-11-23 20:15:59 -05:00
# console.log 'PlayLevelView cleanupRealTimeSessions'
# TODO: Reduce this call, possibly by username and dates
realTimeSessionCollection = new Firebase " #{ @ multiplayerFireHost } multiplayer_level_sessions/ #{ @ levelID } "
realTimeSessionCollection . once ' value ' , (collectionSnapshot) =>
for multiplayerSessionID , multiplayerSession of collectionSnapshot . val ( )
continue if @ options . realTimeMultiplayerSessionID ? and @ options . realTimeMultiplayerSessionID is multiplayerSessionID
continue unless multiplayerSession . state isnt ' finished '
player = realTimeSessionCollection . child " #{ multiplayerSession . id } /players/ #{ me . id } "
player . once ' value ' , (playerSnapshot) =>
if playerSnapshot . val ( )
console . info ' Cleaning up previous real-time multiplayer session ' , multiplayerSessionID
player . update ' state ' : ' left '
multiplayerSessionRef = realTimeSessionCollection . child " #{ multiplayerSessionID } "
multiplayerSessionRef . update ' state ' : ' finished '
2014-10-31 19:33:43 -04:00
onRealTimeMultiplayerLevelUnloaded: ->
2014-11-17 18:07:10 -05:00
# console.log 'PlayLevelView onRealTimeMultiplayerLevelUnloaded'
if @ timerMultiplayerHeartbeatID ?
clearInterval @ timerMultiplayerHeartbeatID
@timerMultiplayerHeartbeatID = null
2014-10-31 19:33:43 -04:00
2014-11-17 18:07:10 -05:00
# TODO: similar to game ending cleanup
2014-11-23 20:15:59 -05:00
if @ realTimeOpponentRef ?
@ realTimeOpponentRef . off ' value ' , @ onRealTimeOpponentChanged
@realTimeOpponentRef = null
if @ realTimePlayersRef ?
@ realTimePlayersRef . off ' child_added ' , @ onRealTimePlayerAdded
@realTimePlayersRef = null
if @ realTimeSessionRef ?
@ realTimeSessionRef . off ' value ' , @ onRealTimeSessionChanged
@realTimeSessionRef = null
if @ realTimePlayerGameRef ?
@realTimePlayerGameRef = null
if @ realTimePlayerRef ?
@realTimePlayerRef = null
2014-11-17 18:07:10 -05:00
2014-10-31 19:33:43 -04:00
onRealTimeMultiplayerHeartbeat: =>
2014-11-23 20:15:59 -05:00
# console.log 'PlayLevelView onRealTimeMultiplayerHeartbeat', @realTimePlayerRef
@ realTimePlayerRef . update ' heartbeat ' : new Date ( ) . toISOString ( ) if @ realTimePlayerRef ?
2014-10-31 19:33:43 -04:00
onRealTimeMultiplayerCreatedGame: (e) ->
2014-11-23 20:15:59 -05:00
# console.log 'PlayLevelView onRealTimeMultiplayerCreatedGame'
2014-11-17 18:07:10 -05:00
@ joinRealTimeMultiplayerGame e
2014-11-23 20:15:59 -05:00
@ realTimePlayerGameRef . update ' state ' : ' coding '
@ realTimePlayerRef . update ' state ' : ' available '
2014-10-31 19:33:43 -04:00
Backbone . Mediator . publish ' real-time-multiplayer:player-status ' , status: ' Waiting for opponent.. '
2014-11-23 20:15:59 -05:00
onRealTimeSessionChanged: (snapshot) =>
# console.log 'PlayLevelView onRealTimeSessionChanged', snapshot.val()
@realTimeSessionData = snapshot . val ( )
if @ realTimeSessionData ? . state is ' finished '
2014-10-31 19:33:43 -04:00
@ realTimeGameEnded ( )
Backbone . Mediator . publish ' real-time-multiplayer:left-game ' , { }
2014-11-23 20:15:59 -05:00
onRealTimePlayerAdded: (snapshot) =>
# console.log 'PlayLevelView onRealTimePlayerAdded', snapshot.val()
2014-10-31 19:33:43 -04:00
# Assume game is full, game on
2014-11-23 20:15:59 -05:00
data = snapshot . val ( )
if data ? and data . id isnt me . id
@realTimeOpponentData = data
2014-11-24 02:49:03 -05:00
# console.log 'PlayLevelView onRealTimePlayerAdded opponent', @realTimeOpponentData, @realTimePlayersData
2014-11-23 20:15:59 -05:00
@ realTimePlayersData [ @ realTimeOpponentData . id ] = @ realTimeOpponentData
if @ realTimeSessionData ? . state is ' creating '
@ realTimeSessionRef . update ' state ' : ' coding '
@ realTimePlayerRef . update ' state ' : ' unavailable '
@realTimeOpponentRef = @ realTimeSessionRef . child " players/ #{ @ realTimeOpponentData . id } "
@ realTimeOpponentRef . on ' value ' , @ onRealTimeOpponentChanged
Backbone . Mediator . publish ' real-time-multiplayer:player-status ' , status: " Playing against #{ @ realTimeOpponentData . name } "
onRealTimeOpponentChanged: (snapshot) =>
# console.log 'PlayLevelView onRealTimeOpponentChanged', snapshot.val()
@realTimeOpponentData = snapshot . val ( )
switch @ realTimeOpponentData ? . state
2014-10-31 19:33:43 -04:00
when ' left '
console . info ' Real-time multiplayer opponent left the game '
2014-11-23 20:15:59 -05:00
opponentID = @ realTimeOpponentData . id
2014-10-31 19:33:43 -04:00
@ realTimeGameEnded ( )
2014-11-17 18:07:10 -05:00
Backbone . Mediator . publish ' real-time-multiplayer:left-game ' , userID: opponentID
2014-10-31 19:33:43 -04:00
when ' submitted '
# TODO: What should this message say?
2014-11-23 20:15:59 -05:00
Backbone . Mediator . publish ' real-time-multiplayer:player-status ' , status: " #{ @ realTimeOpponentData . name } waiting for your code "
2014-10-31 19:33:43 -04:00
2014-11-17 18:07:10 -05:00
joinRealTimeMultiplayerGame: (e) ->
2014-11-23 20:15:59 -05:00
# console.log 'PlayLevelView joinRealTimeMultiplayerGame', e
unless @ realTimeSessionRef ?
2014-11-17 18:07:10 -05:00
@ session . set ( ' submittedCodeLanguage ' , @ session . get ( ' codeLanguage ' ) )
@ session . save ( )
2014-11-23 20:15:59 -05:00
@realTimeSessionRef = new Firebase " #{ @ multiplayerFireHost } multiplayer_level_sessions/ #{ @ levelID } / #{ e . realTimeSessionID } "
@realTimePlayersRef = @ realTimeSessionRef . child ' players '
2014-11-27 12:44:08 -05:00
2014-11-23 20:15:59 -05:00
# Look for opponent
@ realTimeSessionRef . once ' value ' , (multiplayerSessionSnapshot) =>
if @realTimeSessionData = multiplayerSessionSnapshot . val ( )
@ realTimePlayersRef . once ' value ' , (playsSnapshot) =>
if @realTimePlayersData = playsSnapshot . val ( )
for id , player of @ realTimePlayersData
if id isnt me . id
@realTimeOpponentRef = @ realTimeSessionRef . child " players/ #{ id } "
@ realTimeOpponentRef . once ' value ' , (opponentSnapshot) =>
if @realTimeOpponentData = opponentSnapshot . val ( )
@ updateTeam ( )
else
console . error ' Could not lookup multiplayer opponent data. '
@ realTimeOpponentRef . on ' value ' , @ onRealTimeOpponentChanged
Backbone . Mediator . publish ' real-time-multiplayer:player-status ' , status: ' Playing against ' + player . name
else
console . error ' Could not lookup multiplayer session players data. '
# TODO: need child_removed too?
@ realTimePlayersRef . on ' child_added ' , @ onRealTimePlayerAdded
else
console . error ' Could not lookup multiplayer session data. '
@ realTimeSessionRef . on ' value ' , @ onRealTimeSessionChanged
2014-11-27 12:44:08 -05:00
@realTimePlayerGameRef = @ realTimeSessionRef . child " players/ #{ me . id } "
2014-11-23 20:15:59 -05:00
2014-11-17 18:07:10 -05:00
# TODO: Follow up in MultiplayerView to see if double joins can be avoided
# else
2014-11-23 20:15:59 -05:00
# console.error 'Joining real-time multiplayer game with an existing @realTimeSessionRef.'
2014-11-17 18:07:10 -05:00
onRealTimeMultiplayerJoinedGame: (e) ->
2014-11-24 02:49:03 -05:00
# console.log 'PlayLevelView onRealTimeMultiplayerJoinedGame', e
2014-11-17 18:07:10 -05:00
@ joinRealTimeMultiplayerGame e
2014-11-23 20:15:59 -05:00
@ realTimePlayerGameRef . update ' state ' : ' coding '
@ realTimePlayerRef . update ' state ' : ' unavailable '
2014-10-31 19:33:43 -04:00
onRealTimeMultiplayerLeftGame: (e) ->
# console.log 'PlayLevelView onRealTimeMultiplayerLeftGame', e
2014-11-17 18:07:10 -05:00
if e . userID ? and e . userID is me . id
2014-11-23 20:15:59 -05:00
@ realTimePlayerGameRef . update ' state ' : ' left '
2014-10-31 19:33:43 -04:00
@ realTimeGameEnded ( )
2014-11-17 18:07:10 -05:00
realTimeMultiplayerContinueGame: (realTimeSessionID) ->
# console.log 'PlayLevelView realTimeMultiplayerContinueGame', realTimeSessionID, me.id
Backbone . Mediator . publish ' real-time-multiplayer:joined-game ' , realTimeSessionID: realTimeSessionID
console . info ' Setting my game status to ready '
2014-11-23 20:15:59 -05:00
@ realTimePlayerGameRef . update ' state ' : ' ready '
2014-11-17 18:07:10 -05:00
2014-11-23 20:15:59 -05:00
if @ realTimeOpponentData . state is ' ready '
2014-11-17 18:07:10 -05:00
@ realTimeOpponentIsReady ( )
else
console . info ' Waiting for opponent to be ready '
2014-11-23 20:15:59 -05:00
@ realTimeOpponentRef . on ' value ' , @ realTimeOpponentMaybeReady
2014-11-17 18:07:10 -05:00
2014-11-23 20:15:59 -05:00
realTimeOpponentMaybeReady: (snapshot) =>
2014-11-17 18:07:10 -05:00
# console.log 'PlayLevelView realTimeOpponentMaybeReady'
2014-11-23 20:15:59 -05:00
if @realTimeOpponentData = snapshot . val ( )
if @ realTimeOpponentData . state is ' ready '
@ realTimeOpponentRef . off ' value ' , @ realTimeOpponentMaybeReady
@ realTimeOpponentIsReady ( )
2014-11-18 00:30:44 -05:00
2014-11-17 18:07:10 -05:00
realTimeOpponentIsReady: =>
console . info ' All real-time multiplayer players are ready! '
2014-11-23 20:15:59 -05:00
@ realTimeSessionRef . update ' state ' : ' running '
Backbone . Mediator . publish ' real-time-multiplayer:player-status ' , status: ' Battling ' + @ realTimeOpponentData . name
2014-11-17 18:07:10 -05:00
Backbone . Mediator . publish ' tome:manual-cast ' , { realTime: true }
2014-10-31 19:33:43 -04:00
realTimeGameEnded: ->
2014-11-23 20:15:59 -05:00
if @ realTimeOpponentRef ?
@ realTimeOpponentRef . off ' value ' , @ onRealTimeOpponentChanged
@realTimeOpponentRef = null
if @ realTimePlayersRef ?
@ realTimePlayersRef . off ' child_added ' , @ onRealTimePlayerAdded
@realTimePlayersRef = null
if @ realTimeSessionRef ?
@ realTimeSessionRef . off ' value ' , @ onRealTimeSessionChanged
@ realTimeSessionRef . update ' state ' : ' finished '
@realTimeSessionRef = null
if @ realTimePlayerGameRef ?
@realTimePlayerGameRef = null
if @ realTimePlayerRef ?
@ realTimePlayerRef . update ' state ' : ' playing '
2014-10-31 19:33:43 -04:00
Backbone . Mediator . publish ' real-time-multiplayer:player-status ' , status: ' '
Real-time multiplayer initial commit
Simple matchmaking, synchronous multiplayer PVP, flags!
Rough matchmaking is under the game menu multiplayer tab, for ladder
games only. After creating a 2-person game there, you can exit that
modal and real-time cast to play against each other.
If you’re the first person to cast, you’ll sit at the real-time level
playback view waiting until the other player casts. When they do, you
both should start the real-time playback (and start placing flags like
crazy people).
If in a multiplayer session, the real-time simulation runs the players’
code against each other. Your multiplayer opponent’s name should be up
near the level name.
Multiplayer sessions are stored completely in Firebase for now, and
removed if both players leave the game. There’s plenty of bugs,
synchronization issues, and minimal polish to add before we push it to
master.
2014-08-29 02:34:07 -04:00
onRealTimeMultiplayerCast: (e) ->
2014-11-23 20:15:59 -05:00
# console.log 'PlayLevelView onRealTimeMultiplayerCast', @realTimeSessionData, @realTimePlayersData
unless @ realTimeSessionRef ?
console . error ' Real-time multiplayer cast without multiplayer session. '
return
2014-11-27 12:44:08 -05:00
unless @ realTimeSessionData ?
2014-11-23 20:15:59 -05:00
console . error ' Real-time multiplayer cast without multiplayer data. '
return
unless @ realTimePlayersData ?
console . error ' Real-time multiplayer cast without multiplayer players data. '
Real-time multiplayer initial commit
Simple matchmaking, synchronous multiplayer PVP, flags!
Rough matchmaking is under the game menu multiplayer tab, for ladder
games only. After creating a 2-person game there, you can exit that
modal and real-time cast to play against each other.
If you’re the first person to cast, you’ll sit at the real-time level
playback view waiting until the other player casts. When they do, you
both should start the real-time playback (and start placing flags like
crazy people).
If in a multiplayer session, the real-time simulation runs the players’
code against each other. Your multiplayer opponent’s name should be up
near the level name.
Multiplayer sessions are stored completely in Firebase for now, and
removed if both players leave the game. There’s plenty of bugs,
synchronization issues, and minimal polish to add before we push it to
master.
2014-08-29 02:34:07 -04:00
return
2014-11-17 18:07:10 -05:00
# Set submissionCount for created real-time multiplayer session
2014-11-23 20:15:59 -05:00
if me . id is @ realTimeSessionData . creator
2014-11-17 18:07:10 -05:00
sessionState = @ session . get ( ' state ' )
if sessionState ?
2014-11-24 02:49:03 -05:00
submissionCount = sessionState . submissionCount ? 0
2014-11-17 18:07:10 -05:00
console . info ' Setting multiplayer submissionCount to ' , submissionCount
2014-11-23 20:15:59 -05:00
@ realTimeSessionRef . update ' submissionCount ' : submissionCount
2014-11-17 18:07:10 -05:00
else
console . error ' Failed to read sessionState in onRealTimeMultiplayerCast '
2014-11-23 20:15:59 -05:00
console . info ' Submitting my code '
# Transpiling code copied from scripts/transpile.coffee
# TODO: Should this live somewhere else?
transpiledCode = { }
for thang , spells of @ session . get ( ' code ' )
transpiledCode [ thang ] = { }
for spellID , spell of spells
spellName = thang + ' / ' + spellID
continue if @ session . get ( ' teamSpells ' ) and not ( spellName in @ session . get ( ' teamSpells ' ) [ @ session . get ( ' team ' ) ] )
# console.log "PlayLevelView Transpiling spell #{spellName}"
aetherOptions = createAetherOptions functionName: spellID , codeLanguage: @ session . get ( ' submittedCodeLanguage ' ) , includeFlow: true
aether = new Aether aetherOptions
transpiledCode [ thang ] [ spellID ] = aether . transpile spell
# console.log "PlayLevelView transpiled code", transpiledCode
@ session . set ' transpiledCode ' , transpiledCode
2014-11-24 02:49:03 -05:00
permissions = @ session . get ' permissions ' ? [ ]
unless _ . find ( permissions , (p) -> p . target is ' public ' and p . access is ' read ' )
permissions . push target : ' public ' , access : ' read '
@ session . set ' permissions ' , permissions
2014-11-23 20:15:59 -05:00
@ session . patch ( )
@ realTimePlayerGameRef . update ' state ' : ' submitted '
2014-11-24 02:49:03 -05:00
2014-11-23 20:15:59 -05:00
console . info ' Other player is ' , @ realTimeOpponentData . state
if @ realTimeOpponentData . state in [ ' submitted ' , ' ready ' ]
@ realTimeOpponentSubmittedCode @ realTimeOpponentData , @ realTimePlayerGameData
Real-time multiplayer initial commit
Simple matchmaking, synchronous multiplayer PVP, flags!
Rough matchmaking is under the game menu multiplayer tab, for ladder
games only. After creating a 2-person game there, you can exit that
modal and real-time cast to play against each other.
If you’re the first person to cast, you’ll sit at the real-time level
playback view waiting until the other player casts. When they do, you
both should start the real-time playback (and start placing flags like
crazy people).
If in a multiplayer session, the real-time simulation runs the players’
code against each other. Your multiplayer opponent’s name should be up
near the level name.
Multiplayer sessions are stored completely in Firebase for now, and
removed if both players leave the game. There’s plenty of bugs,
synchronization issues, and minimal polish to add before we push it to
master.
2014-08-29 02:34:07 -04:00
else
2014-11-23 20:15:59 -05:00
# Wait for opponent to submit their code
Backbone . Mediator . publish ' real-time-multiplayer:player-status ' , status: " Waiting for code from #{ @ realTimeOpponentData . name } "
@ realTimeOpponentRef . on ' value ' , @ realTimeOpponentMaybeSubmitted
realTimeOpponentMaybeSubmitted: (snapshot) =>
if @realTimeOpponentData = snapshot . val ( )
if @ realTimeOpponentData . state in [ ' submitted ' , ' ready ' ]
@ realTimeOpponentRef . off ' value ' , @ realTimeOpponentMaybeSubmitted
@ realTimeOpponentSubmittedCode @ realTimeOpponentData , @ realTimePlayerGameData
2014-10-31 19:33:43 -04:00
onRealTimeMultiplayerPlaybackEnded: ->
2014-11-23 20:15:59 -05:00
# console.log 'PlayLevelView onRealTimeMultiplayerPlaybackEnded'
if @ realTimeSessionRef ?
@ realTimeSessionRef . update ' state ' : ' coding '
@ realTimePlayerGameRef . update ' state ' : ' coding '
if @ realTimeOpponentData ?
Backbone . Mediator . publish ' real-time-multiplayer:player-status ' , status: " Playing against #{ @ realTimeOpponentData . name } "
Real-time multiplayer initial commit
Simple matchmaking, synchronous multiplayer PVP, flags!
Rough matchmaking is under the game menu multiplayer tab, for ladder
games only. After creating a 2-person game there, you can exit that
modal and real-time cast to play against each other.
If you’re the first person to cast, you’ll sit at the real-time level
playback view waiting until the other player casts. When they do, you
both should start the real-time playback (and start placing flags like
crazy people).
If in a multiplayer session, the real-time simulation runs the players’
code against each other. Your multiplayer opponent’s name should be up
near the level name.
Multiplayer sessions are stored completely in Firebase for now, and
removed if both players leave the game. There’s plenty of bugs,
synchronization issues, and minimal polish to add before we push it to
master.
2014-08-29 02:34:07 -04:00
2014-10-31 19:33:43 -04:00
realTimeOpponentSubmittedCode: (opponentPlayer, myPlayer) =>
2014-11-23 20:15:59 -05:00
# console.log 'PlayLevelView realTimeOpponentSubmittedCode', @realTimeSessionData.id, opponentPlayer.level_session
2014-11-17 18:07:10 -05:00
# Read submissionCount for joined real-time multiplayer session
2014-11-23 20:15:59 -05:00
if me . id isnt @ realTimeSessionData . creator
2014-11-17 18:07:10 -05:00
sessionState = @ session . get ( ' state ' ) ? { }
2014-11-23 20:15:59 -05:00
newSubmissionCount = @ realTimeSessionData . submissionCount
2014-11-17 18:07:10 -05:00
if newSubmissionCount ?
# TODO: This isn't always getting updated where the random seed generation uses it.
sessionState.submissionCount = parseInt newSubmissionCount
console . info ' Got multiplayer submissionCount ' , sessionState . submissionCount
2014-11-18 00:30:44 -05:00
@ session . set ' state ' , sessionState
2014-11-17 18:07:10 -05:00
@ session . patch ( )
# Reload this level so the opponent session can easily be wired up
Backbone . Mediator . publish ' router:navigate ' ,
route: " /play/level/ #{ @ levelID } "
viewClass: PlayLevelView
2014-11-23 20:15:59 -05:00
viewArgs: [ { supermodel: @ supermodel , autoUnveil: true , realTimeMultiplayerSessionID: @ realTimeSessionData . id , opponent: opponentPlayer . level_session , team: @ team } , @ levelID ]
2014-11-17 18:07:10 -05:00
updateTeam: ->
# If not creator, and same team as creator, then switch teams
# TODO: Assumes there are only 'humans' and 'ogres'
2014-11-23 20:15:59 -05:00
unless @ realTimeOpponentData ?
console . error ' Tried to switch teams without real-time multiplayer opponent data. '
2014-11-17 18:07:10 -05:00
return
2014-11-23 20:15:59 -05:00
unless @ realTimeSessionData ?
console . error ' Tried to switch teams without real-time multiplayer session data. '
2014-11-17 18:07:10 -05:00
return
2014-11-23 20:15:59 -05:00
return if me . id is @ realTimeSessionData . creator
2014-11-17 18:07:10 -05:00
2014-11-23 20:15:59 -05:00
oldTeam = @ realTimeOpponentData . team
2014-11-18 00:30:44 -05:00
return unless oldTeam is @ session . get ( ' team ' )
2014-11-17 18:07:10 -05:00
# Need to switch to other team
newTeam = if oldTeam is ' humans ' then ' ogres ' else ' humans '
console . info " Switching from team #{ oldTeam } to #{ newTeam } "
# Move code from old team to new team
# Assumes teamSpells has matching spells for each team
# TODO: Similar to code in loadOpponentTeam, consolidate?
code = @ session . get ' code '
teamSpells = @ session . get ' teamSpells '
for oldSpellKey in teamSpells [ oldTeam ]
[ oldThang , oldSpell ] = oldSpellKey . split ' / '
oldCode = code [ oldThang ] ? [ oldSpell ]
continue unless oldCode ?
# Move oldCode to new team under same spell
for newSpellKey in teamSpells [ newTeam ]
[ newThang , newSpell ] = newSpellKey . split ' / '
if newSpell is oldSpell
# Found spell location under new team
2014-11-24 02:49:03 -05:00
# console.log "Swapping spell=#{oldSpell} from #{oldThang} to #{newThang}"
2014-11-17 18:07:10 -05:00
if code [ newThang ] ? [ oldSpell ] ?
# Option 1: have a new spell to swap
code [ oldThang ] [ oldSpell ] = code [ newThang ] [ oldSpell ]
else
# Option 2: no new spell to swap
delete code [ oldThang ] [ oldSpell ]
code [ newThang ] = { } unless code [ newThang ] ?
code [ newThang ] [ oldSpell ] = oldCode
break
@ setTeam newTeam # Sets @session 'team'
sessionState = @ session . get ( ' state ' )
if sessionState ?
# TODO: Don't hard code thangID
sessionState.selected = if newTeam is ' humans ' then ' Hero Placeholder ' else ' Hero Placeholder 1 '
2014-11-18 00:30:44 -05:00
@ session . set ' state ' , sessionState
2014-11-17 18:07:10 -05:00
@ session . set ' code ' , code
@ session . patch ( )
if sessionState ?
# TODO: Don't hardcode spellName
Backbone . Mediator . publish ' level:select-sprite ' , thangID: sessionState . selected , spellName: ' plan '
2014-11-18 14:21:29 -05:00
2014-11-22 23:48:04 -05:00
# End Real-time Multiplayer ######################################################