Fix sync PVP opponent syncing issue

Ripped out backfire from PlayLevelView so we have more control over
data synchronization.  These changes should wait properly for the
opponent data to load before blazing ahead, after a PlayLevelView
reload.

Fixes #1767
This commit is contained in:
Matt Lott 2014-11-23 17:15:59 -08:00
parent 0c8b5ec9a8
commit c203ff15a2
3 changed files with 202 additions and 186 deletions

View file

@ -24,6 +24,7 @@ module.exports = class MultiplayerView extends CocoView
constructor: (options) -> constructor: (options) ->
super(options) super(options)
@level = options.level @level = options.level
@levelID = @level?.get 'slug'
@session = options.session @session = options.session
@listenTo @session, 'change:multiplayer', @updateLinkSection @listenTo @session, 'change:multiplayer', @updateLinkSection
@watchRealTimeSessions() if @level?.get('type') in ['hero-ladder'] @watchRealTimeSessions() if @level?.get('type') in ['hero-ladder']
@ -39,7 +40,7 @@ module.exports = class MultiplayerView extends CocoView
c.joinLink = "#{document.location.href.replace(/\?.*/, '').replace('#', '')}?session=#{@session.id}" c.joinLink = "#{document.location.href.replace(/\?.*/, '').replace('#', '')}?session=#{@session.id}"
c.multiplayer = @session.get 'multiplayer' c.multiplayer = @session.get 'multiplayer'
c.team = @session.get 'team' c.team = @session.get 'team'
c.levelSlug = @level?.get 'slug' c.levelSlug = @levelID
# For now, ladderGame will disallow multiplayer, because session code combining doesn't play nice yet. # For now, ladderGame will disallow multiplayer, because session code combining doesn't play nice yet.
if @level?.get('type') in ['ladder', 'hero-ladder'] if @level?.get('type') in ['ladder', 'hero-ladder']
c.ladderGame = true c.ladderGame = true
@ -71,7 +72,7 @@ module.exports = class MultiplayerView extends CocoView
e.target.select() e.target.select()
onGameSubmitted: (e) -> onGameSubmitted: (e) ->
ladderURL = "/play/ladder/#{@level.get('slug')}#my-matches" ladderURL = "/play/ladder/#{@levelID}#my-matches"
Backbone.Mediator.publish 'router:navigate', route: ladderURL Backbone.Mediator.publish 'router:navigate', route: ladderURL
updateLinkSection: -> updateLinkSection: ->
@ -104,13 +105,14 @@ module.exports = class MultiplayerView extends CocoView
# @realTimeSessionsPlayers - Collection of player lists for active real-time multiplayer sessions # @realTimeSessionsPlayers - Collection of player lists for active real-time multiplayer sessions
# @realTimeSessions - Active real-time multiplayer sessions # @realTimeSessions - Active real-time multiplayer sessions
# @currentRealTimeSession - Our current real-time multiplayer session # @currentRealTimeSession - Our current real-time multiplayer session
#
# TODO: Ditch backfire and just use Firebase directly. Easier to debug, richer APIs (E.g. presence stuff).
watchRealTimeSessions: -> watchRealTimeSessions: ->
# Setup monitoring of real-time multiplayer level sessions # Setup monitoring of real-time multiplayer level sessions
@realTimeSessionsPlayers = {} @realTimeSessionsPlayers = {}
# TODO: only request sessions for this level, !team, etc. # TODO: only request sessions for this level, !team, etc.
# TODO: move this to multiplayer_level_sessions/#{levelID}/ @realTimeSessions = new RealTimeCollection("multiplayer_level_sessions/#{@levelID}")
@realTimeSessions = new RealTimeCollection('multiplayer_level_sessions/')
@realTimeSessions.on 'add', @onRealTimeSessionAdded @realTimeSessions.on 'add', @onRealTimeSessionAdded
@realTimeSessions.each (rts) => @watchRealTimeSession rts @realTimeSessions.each (rts) => @watchRealTimeSession rts
@ -120,9 +122,9 @@ module.exports = class MultiplayerView extends CocoView
# console.log 'MultiplayerView watchRealTimeSession', rts # console.log 'MultiplayerView watchRealTimeSession', rts
# Setup monitoring of players for given session # Setup monitoring of players for given session
# TODO: verify we need this # TODO: verify we need this
realTimeSession = new RealTimeModel('multiplayer_level_sessions/' + rts.id) realTimeSession = new RealTimeModel("multiplayer_level_sessions/#{@levelID}/#{rts.id}")
realTimeSession.on 'change', @onRealTimeSessionChanged realTimeSession.on 'change', @onRealTimeSessionChanged
@realTimeSessionsPlayers[rts.id] = new RealTimeCollection('multiplayer_level_sessions/' + rts.id + '/players') @realTimeSessionsPlayers[rts.id] = new RealTimeCollection("multiplayer_level_sessions/#{@levelID}/#{rts.id}/players")
@realTimeSessionsPlayers[rts.id].on 'add', @onRealTimePlayerAdded @realTimeSessionsPlayers[rts.id].on 'add', @onRealTimePlayerAdded
@findCurrentRealTimeSession rts @findCurrentRealTimeSession rts
@ -133,7 +135,7 @@ module.exports = class MultiplayerView extends CocoView
@realTimeSessionsPlayers[rts.id].each (player) => @realTimeSessionsPlayers[rts.id].each (player) =>
if player.id is me.id and player.get('state') isnt 'left' if player.id is me.id and player.get('state') isnt 'left'
# console.log 'MultiplayerView found current real-time session', rts # console.log 'MultiplayerView found current real-time session', rts
@currentRealTimeSession = new RealTimeModel('multiplayer_level_sessions/' + rts.id) @currentRealTimeSession = new RealTimeModel("multiplayer_level_sessions/#{@levelID}/#{rts.id}")
@currentRealTimeSession.on 'change', @onCurrentRealTimeSessionChanged @currentRealTimeSession.on 'change', @onCurrentRealTimeSessionChanged
# TODO: Is this necessary? Shouldn't everyone already know we joined a game at this point? # TODO: Is this necessary? Shouldn't everyone already know we joined a game at this point?
@ -170,7 +172,7 @@ module.exports = class MultiplayerView extends CocoView
@currentRealTimeSession = @realTimeSessions.get(s.id) @currentRealTimeSession = @realTimeSessions.get(s.id)
@currentRealTimeSession.on 'change', @onCurrentRealTimeSessionChanged @currentRealTimeSession.on 'change', @onCurrentRealTimeSessionChanged
# TODO: s.id === @currentRealTimeSession.id ? # TODO: s.id === @currentRealTimeSession.id ?
players = new RealTimeCollection('multiplayer_level_sessions/' + @currentRealTimeSession.id + '/players') players = new RealTimeCollection("multiplayer_level_sessions/#{@levelID}/#{@currentRealTimeSession.id}/players")
players.create players.create
id: me.id id: me.id
state: 'coding' state: 'coding'

View file

@ -33,6 +33,7 @@ module.exports = class LevelFlagsView extends CocoView
constructor: (options) -> constructor: (options) ->
super options super options
@levelID = options.levelID
@world = options.world @world = options.world
onRealTimePlaybackStarted: (e) -> onRealTimePlaybackStarted: (e) ->
@ -84,7 +85,7 @@ module.exports = class LevelFlagsView extends CocoView
@world = @options.world = event.world @world = @options.world = event.world
onJoinedMultiplayerGame: (e) -> onJoinedMultiplayerGame: (e) ->
@realTimeFlags = new RealTimeCollection('multiplayer_level_sessions/' + e.realTimeSessionID + '/flagHistory') @realTimeFlags = new RealTimeCollection("multiplayer_level_sessions/#{@levelID}/#{e.realTimeSessionID}/flagHistory")
@realTimeFlags.on 'add', @onRealTimeMultiplayerFlagAdded @realTimeFlags.on 'add', @onRealTimeMultiplayerFlagAdded
@realTimeFlags.on 'remove', @onRealTimeMultiplayerFlagRemoved @realTimeFlags.on 'remove', @onRealTimeMultiplayerFlagRemoved

View file

@ -19,8 +19,6 @@ LevelComponent = require 'models/LevelComponent'
Article = require 'models/Article' Article = require 'models/Article'
Camera = require 'lib/surface/Camera' Camera = require 'lib/surface/Camera'
AudioPlayer = require 'lib/AudioPlayer' AudioPlayer = require 'lib/AudioPlayer'
RealTimeModel = require 'models/RealTimeModel'
RealTimeCollection = require 'collections/RealTimeCollection'
# subviews # subviews
LevelLoadingView = require './LevelLoadingView' LevelLoadingView = require './LevelLoadingView'
@ -252,7 +250,7 @@ module.exports = class PlayLevelView extends RootView
@insertSubView @tome = new TomeView levelID: @levelID, session: @session, otherSession: @otherSession, thangs: @world.thangs, supermodel: @supermodel, level: @level @insertSubView @tome = new TomeView levelID: @levelID, session: @session, otherSession: @otherSession, thangs: @world.thangs, supermodel: @supermodel, level: @level
@insertSubView new LevelPlaybackView session: @session, levelID: @levelID, level: @level @insertSubView new LevelPlaybackView session: @session, levelID: @levelID, level: @level
@insertSubView new GoalsView {} @insertSubView new GoalsView {}
@insertSubView new LevelFlagsView world: @world if @$el.hasClass 'flags' @insertSubView new LevelFlagsView levelID: @levelID, world: @world if @$el.hasClass 'flags'
@insertSubView new GoldView {} @insertSubView new GoldView {}
@insertSubView new HUDView {level: @level} @insertSubView new HUDView {level: @level}
@insertSubView new LevelDialogueView {level: @level} @insertSubView new LevelDialogueView {level: @level}
@ -290,7 +288,7 @@ module.exports = class PlayLevelView extends RootView
@setupManager = new LevelSetupManager({supermodel: @supermodel, levelID: @levelID, parent: @, session: @session}) @setupManager = new LevelSetupManager({supermodel: @supermodel, levelID: @levelID, parent: @, session: @session})
@setupManager.open() @setupManager.open()
@onRealTimeMultiplayerLevelLoaded() if e.level.get('type') in ['hero-ladder'] @onRealTimeMultiplayerLevelLoaded e.session if e.level.get('type') in ['hero-ladder']
onLoaded: -> onLoaded: ->
_.defer => @onLevelLoaderLoaded() _.defer => @onLevelLoaderLoaded()
@ -608,147 +606,167 @@ module.exports = class PlayLevelView extends RootView
# Current real-time multiplayer session # Current real-time multiplayer session
# Internal multiplayer create/joined/left events # Internal multiplayer create/joined/left events
# #
# Real-time state variables: # Real-time state variables.
# @realTimePlayerStatus - User's real-time multiplayer state for this level # Each Ref is Firebase reference, and may have a matching Data suffixed variable with the latest data received.
# @realTimePlayerGameStatus - User's state for current real-time multiplayer game session # @realTimePlayerRef - User's real-time multiplayer player for this level
# @realTimeSession - Current real-time multiplayer game session # @realTimePlayerGameRef - User's current real-time multiplayer player game session
# @realTimeOpponent - Current real-time multiplayer opponent # @realTimeSessionRef - Current real-time multiplayer game session
# @realTimePlayers - Real-time players for current real-time multiplayer game session # @realTimeOpponentRef - Current real-time multiplayer opponent
# @realTimeSessionCollection - Collection of all real-time multiplayer sessions # @realTimePlayersRef - Real-time players for current real-time multiplayer game session
# @options.realTimeMultiplayerSessionID - Need to continue an existing real-time multiplayer session # @options.realTimeMultiplayerSessionID - Need to continue an existing real-time multiplayer session
# #
# TODO: Move this code to it's own file, or possibly the LevelBus # TODO: Move this code to it's own file, or possibly the LevelBus
# TODO: Save settings somewhere reasonable # TODO: Save settings somewhere reasonable
# TODO: Ditch backfire and just use Firebase directly. Easier to debug, richer APIs (E.g. presence stuff). multiplayerFireHost: 'https://codecombat.firebaseio.com/test/db/'
onRealTimeMultiplayerLevelLoaded: -> onRealTimeMultiplayerLevelLoaded: (session) ->
return if @realTimePlayerStatus? # console.log 'PlayLevelView onRealTimeMultiplayerLevelLoaded'
return if @realTimePlayerRef?
return if me.get('anonymous') return if me.get('anonymous')
@realTimePlayerRef = new Firebase "#{@multiplayerFireHost}multiplayer_players/#{@levelID}/#{me.id}"
unless @options.realTimeMultiplayerSessionID? unless @options.realTimeMultiplayerSessionID?
players = new RealTimeCollection('multiplayer_players/' + @levelID) # TODO: Wait for name instead of using 'Anon', or try and update it later?
players.create name = me.get('name') ? session.get('creatorName') ? 'Anon'
id: me.id @realTimePlayerRef.set
name: me.get('name') id: me.id # TODO: is this redundant info necessary?
name: name
state: 'playing' state: 'playing'
created: new Date().toISOString() created: new Date().toISOString()
heartbeat: new Date().toISOString() heartbeat: new Date().toISOString()
@realTimePlayerStatus = new RealTimeModel('multiplayer_players/' + @levelID + '/' + me.id)
@timerMultiplayerHeartbeatID = setInterval @onRealTimeMultiplayerHeartbeat, 60 * 1000 @timerMultiplayerHeartbeatID = setInterval @onRealTimeMultiplayerHeartbeat, 60 * 1000
@cleanupRealTimeSessions() @cleanupRealTimeSessions()
cleanupRealTimeSessions: -> cleanupRealTimeSessions: ->
@realTimeSessionCollection = new RealTimeCollection 'multiplayer_level_sessions' # console.log 'PlayLevelView cleanupRealTimeSessions'
@realTimeSessionCollection.on 'add', @cleanupRealTimeSession # TODO: Reduce this call, possibly by username and dates
@realTimeSessionCollection.each @cleanupRealTimeSession realTimeSessionCollection = new Firebase "#{@multiplayerFireHost}multiplayer_level_sessions/#{@levelID}"
realTimeSessionCollection.once 'value', (collectionSnapshot) =>
cleanupRealTimeSession: (session) => for multiplayerSessionID, multiplayerSession of collectionSnapshot.val()
return if @options.realTimeMultiplayerSessionID? and @options.realTimeMultiplayerSessionID is session.id continue if @options.realTimeMultiplayerSessionID? and @options.realTimeMultiplayerSessionID is multiplayerSessionID
if session.get('state') isnt 'finished' continue unless multiplayerSession.state isnt 'finished'
players = new RealTimeCollection 'multiplayer_level_sessions/' + session.id + '/players' player = realTimeSessionCollection.child "#{multiplayerSession.id}/players/#{me.id}"
players.each (player) => player.once 'value', (playerSnapshot) =>
if player.id is me.id if playerSnapshot.val()
p = new RealTimeModel 'multiplayer_level_sessions/' + session.id + '/players/' + me.id console.info 'Cleaning up previous real-time multiplayer session', multiplayerSessionID
console.info 'Cleaning up previous real-time multiplayer session', session.id player.update 'state': 'left'
p.set 'state', 'left' multiplayerSessionRef = realTimeSessionCollection.child "#{multiplayerSessionID}"
session.set 'state', 'finished' multiplayerSessionRef.update 'state': 'finished'
onRealTimeMultiplayerLevelUnloaded: -> onRealTimeMultiplayerLevelUnloaded: ->
# console.log 'PlayLevelView onRealTimeMultiplayerLevelUnloaded' # console.log 'PlayLevelView onRealTimeMultiplayerLevelUnloaded'
if @timerMultiplayerHeartbeatID? if @timerMultiplayerHeartbeatID?
clearInterval @timerMultiplayerHeartbeatID clearInterval @timerMultiplayerHeartbeatID
@timerMultiplayerHeartbeatID = null @timerMultiplayerHeartbeatID = null
if @realTimeSessionCollection?
@realTimeSessionCollection.off 'add', @cleanupRealTimeSession
@realTimeSessionCollection = null
# TODO: similar to game ending cleanup # TODO: similar to game ending cleanup
if @realTimeOpponent? if @realTimeOpponentRef?
@realTimeOpponent.off 'change', @onRealTimeOpponentChanged @realTimeOpponentRef.off 'value', @onRealTimeOpponentChanged
@realTimeOpponent = null @realTimeOpponentRef = null
if @realTimePlayers? if @realTimePlayersRef?
@realTimePlayers.off 'add', @onRealTimePlayerAdded @realTimePlayersRef.off 'child_added', @onRealTimePlayerAdded
@realTimePlayers = null @realTimePlayersRef = null
if @realTimeSession? if @realTimeSessionRef?
@realTimeSession.off 'change', @onRealTimeSessionChanged @realTimeSessionRef.off 'value', @onRealTimeSessionChanged
@realTimeSession = null @realTimeSessionRef = null
if @realTimePlayerGameStatus? if @realTimePlayerGameRef?
@realTimePlayerGameStatus = null @realTimePlayerGameRef = null
if @realTimePlayerStatus? if @realTimePlayerRef?
@realTimePlayerStatus = null @realTimePlayerRef = null
onRealTimeMultiplayerHeartbeat: => onRealTimeMultiplayerHeartbeat: =>
@realTimePlayerStatus.set 'heartbeat', new Date().toISOString() if @realTimePlayerStatus # console.log 'PlayLevelView onRealTimeMultiplayerHeartbeat', @realTimePlayerRef
@realTimePlayerRef.update 'heartbeat': new Date().toISOString() if @realTimePlayerRef?
onRealTimeMultiplayerCreatedGame: (e) -> onRealTimeMultiplayerCreatedGame: (e) ->
# console.log 'PlayLevelView onRealTimeMultiplayerCreatedGame'
@joinRealTimeMultiplayerGame e @joinRealTimeMultiplayerGame e
@realTimePlayerGameStatus.set 'state', 'coding' @realTimePlayerGameRef.update 'state': 'coding'
@realTimePlayerStatus.set 'state', 'available' @realTimePlayerRef.update 'state': 'available'
Backbone.Mediator.publish 'real-time-multiplayer:player-status', status: 'Waiting for opponent..' Backbone.Mediator.publish 'real-time-multiplayer:player-status', status: 'Waiting for opponent..'
onRealTimeSessionChanged: (e) => onRealTimeSessionChanged: (snapshot) =>
# console.log 'PlayLevelView onRealTimeSessionChanged', e # console.log 'PlayLevelView onRealTimeSessionChanged', snapshot.val()
if e.get('state') is 'finished' @realTimeSessionData = snapshot.val()
if @realTimeSessionData?.state is 'finished'
@realTimeGameEnded() @realTimeGameEnded()
Backbone.Mediator.publish 'real-time-multiplayer:left-game', {} Backbone.Mediator.publish 'real-time-multiplayer:left-game', {}
onRealTimePlayerAdded: (e) => onRealTimePlayerAdded: (snapshot) =>
# console.log 'PlayLevelView onRealTimePlayerAdded', e # console.log 'PlayLevelView onRealTimePlayerAdded', snapshot.val()
# Assume game is full, game on # Assume game is full, game on
if @realTimeSession.get('state') is 'creating' data = snapshot.val()
@realTimeSession.set 'state', 'coding' if data? and data.id isnt me.id
@realTimePlayerStatus.set 'state', 'unavailable' @realTimeOpponentData = data
@realTimeOpponent = new RealTimeModel('multiplayer_level_sessions/' + @realTimeSession.id + '/players/' + e.id) console.log 'PlayLevelView onRealTimePlayerAdded opponent', @realTimeOpponentData, @realTimePlayersData
@realTimeOpponent.on 'change', @onRealTimeOpponentChanged @realTimePlayersData[@realTimeOpponentData.id] = @realTimeOpponentData
Backbone.Mediator.publish 'real-time-multiplayer:player-status', status: 'Playing against ' + e.get('name') if @realTimeSessionData?.state is 'creating'
else @realTimeSessionRef.update 'state': 'coding'
console.error 'PlayLevelView onRealTimePlayerAdded session in unexpected state', @realTimeSession.get('state') @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: (e) => onRealTimeOpponentChanged: (snapshot) =>
# console.log 'PlayLevelView onRealTimeOpponentChanged', e # console.log 'PlayLevelView onRealTimeOpponentChanged', snapshot.val()
switch @realTimeOpponent.get('state') @realTimeOpponentData = snapshot.val()
switch @realTimeOpponentData?.state
when 'left' when 'left'
console.info 'Real-time multiplayer opponent left the game' console.info 'Real-time multiplayer opponent left the game'
opponentID = @realTimeOpponent.id opponentID = @realTimeOpponentData.id
@realTimeGameEnded() @realTimeGameEnded()
Backbone.Mediator.publish 'real-time-multiplayer:left-game', userID: opponentID Backbone.Mediator.publish 'real-time-multiplayer:left-game', userID: opponentID
when 'submitted' when 'submitted'
# TODO: What should this message say? # TODO: What should this message say?
Backbone.Mediator.publish 'real-time-multiplayer:player-status', status: @realTimeOpponent.get('name') + ' waiting for your code' Backbone.Mediator.publish 'real-time-multiplayer:player-status', status: "#{@realTimeOpponentData.name} waiting for your code"
joinRealTimeMultiplayerGame: (e) -> joinRealTimeMultiplayerGame: (e) ->
unless @realTimeSession? # console.log 'PlayLevelView joinRealTimeMultiplayerGame', e
# TODO: Necessary for real-time multiplayer sessions? unless @realTimeSessionRef?
@session.set('submittedCodeLanguage', @session.get('codeLanguage')) @session.set('submittedCodeLanguage', @session.get('codeLanguage'))
@session.save() @session.save()
@realTimeSession = new RealTimeModel 'multiplayer_level_sessions/' + e.realTimeSessionID @realTimeSessionRef = new Firebase "#{@multiplayerFireHost}multiplayer_level_sessions/#{@levelID}/#{e.realTimeSessionID}"
@realTimeSession.on 'change', @onRealTimeSessionChanged @realTimePlayersRef = @realTimeSessionRef.child 'players'
@realTimePlayers = new RealTimeCollection 'multiplayer_level_sessions/' + e.realTimeSessionID + '/players'
@realTimePlayers.on 'add', @onRealTimePlayerAdded # Look for opponent
@realTimePlayerGameStatus = new RealTimeModel 'multiplayer_level_sessions/' + e.realTimeSessionID + '/players/' + me.id @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
@realTimePlayerGameRef = @realTimeSessionRef.child "players/#{me.id}"
# TODO: Follow up in MultiplayerView to see if double joins can be avoided # TODO: Follow up in MultiplayerView to see if double joins can be avoided
# else # else
# console.error 'Joining real-time multiplayer game with an existing @realTimeSession.' # console.error 'Joining real-time multiplayer game with an existing @realTimeSessionRef.'
onRealTimeMultiplayerJoinedGame: (e) -> onRealTimeMultiplayerJoinedGame: (e) ->
# console.log 'PlayLevelView onRealTimeMultiplayerJoinedGame', e console.log 'PlayLevelView onRealTimeMultiplayerJoinedGame', e
@joinRealTimeMultiplayerGame e @joinRealTimeMultiplayerGame e
@realTimePlayerGameStatus.set 'state', 'coding' @realTimePlayerGameRef.update 'state': 'coding'
@realTimePlayerStatus.set 'state', 'unavailable' @realTimePlayerRef.update 'state': 'unavailable'
unless @realTimeOpponent?
for id, player of @realTimeSession.get('players')
if id isnt me.id
@realTimeOpponent = new RealTimeModel 'multiplayer_level_sessions/' + e.realTimeSessionID + '/players/' + id
@realTimeOpponent.on 'change', @onRealTimeOpponentChanged
Backbone.Mediator.publish 'real-time-multiplayer:player-status', status: 'Playing against ' + player.name
unless @realTimeOpponent?
console.error 'Did not find an oppoonent in onRealTimeMultiplayerJoinedGame.'
@updateTeam()
onRealTimeMultiplayerLeftGame: (e) -> onRealTimeMultiplayerLeftGame: (e) ->
# console.log 'PlayLevelView onRealTimeMultiplayerLeftGame', e # console.log 'PlayLevelView onRealTimeMultiplayerLeftGame', e
if e.userID? and e.userID is me.id if e.userID? and e.userID is me.id
@realTimePlayerGameStatus.set 'state', 'left' @realTimePlayerGameRef.update 'state': 'left'
@realTimeGameEnded() @realTimeGameEnded()
realTimeMultiplayerContinueGame: (realTimeSessionID) -> realTimeMultiplayerContinueGame: (realTimeSessionID) ->
@ -756,117 +774,112 @@ module.exports = class PlayLevelView extends RootView
Backbone.Mediator.publish 'real-time-multiplayer:joined-game', realTimeSessionID: realTimeSessionID Backbone.Mediator.publish 'real-time-multiplayer:joined-game', realTimeSessionID: realTimeSessionID
console.info 'Setting my game status to ready' console.info 'Setting my game status to ready'
@realTimePlayerGameStatus.set 'state', 'ready' @realTimePlayerGameRef.update 'state': 'ready'
if @realTimeOpponent.get('state') is 'ready' if @realTimeOpponentData.state is 'ready'
@realTimeOpponentIsReady() @realTimeOpponentIsReady()
else else
console.info 'Waiting for opponent to be ready' console.info 'Waiting for opponent to be ready'
@realTimeOpponent.on 'change', @realTimeOpponentMaybeReady @realTimeOpponentRef.on 'value', @realTimeOpponentMaybeReady
realTimeOpponentMaybeReady: => realTimeOpponentMaybeReady: (snapshot) =>
# console.log 'PlayLevelView realTimeOpponentMaybeReady' # console.log 'PlayLevelView realTimeOpponentMaybeReady'
if @realTimeOpponent.get('state') is 'ready' if @realTimeOpponentData = snapshot.val()
@realTimeOpponent.off 'change', @realTimeOpponentMaybeReady if @realTimeOpponentData.state is 'ready'
@realTimeOpponentIsReady() @realTimeOpponentRef.off 'value', @realTimeOpponentMaybeReady
@realTimeOpponentIsReady()
realTimeOpponentIsReady: => realTimeOpponentIsReady: =>
console.info 'All real-time multiplayer players are ready!' console.info 'All real-time multiplayer players are ready!'
@realTimeSession.set 'state', 'running' @realTimeSessionRef.update 'state': 'running'
Backbone.Mediator.publish 'real-time-multiplayer:player-status', status: 'Battling ' + @realTimeOpponent.get('name') Backbone.Mediator.publish 'real-time-multiplayer:player-status', status: 'Battling ' + @realTimeOpponentData.name
Backbone.Mediator.publish 'tome:manual-cast', {realTime: true} Backbone.Mediator.publish 'tome:manual-cast', {realTime: true}
realTimeGameEnded: -> realTimeGameEnded: ->
if @realTimeOpponent? if @realTimeOpponentRef?
@realTimeOpponent.off 'change', @onRealTimeOpponentChanged @realTimeOpponentRef.off 'value', @onRealTimeOpponentChanged
@realTimeOpponent = null @realTimeOpponentRef = null
if @realTimePlayers? if @realTimePlayersRef?
@realTimePlayers.off 'add', @onRealTimePlayerAdded @realTimePlayersRef.off 'child_added', @onRealTimePlayerAdded
@realTimePlayers = null @realTimePlayersRef = null
if @realTimeSession? if @realTimeSessionRef?
@realTimeSession.off 'change', @onRealTimeSessionChanged @realTimeSessionRef.off 'value', @onRealTimeSessionChanged
@realTimeSession.set 'state', 'finished' @realTimeSessionRef.update 'state': 'finished'
@realTimeSession = null @realTimeSessionRef = null
if @realTimePlayerGameStatus? if @realTimePlayerGameRef?
@realTimePlayerGameStatus = null @realTimePlayerGameRef = null
if @realTimePlayerStatus? if @realTimePlayerRef?
@realTimePlayerStatus.set 'state', 'playing' @realTimePlayerRef.update 'state': 'playing'
Backbone.Mediator.publish 'real-time-multiplayer:player-status', status: '' Backbone.Mediator.publish 'real-time-multiplayer:player-status', status: ''
onRealTimeMultiplayerCast: (e) -> onRealTimeMultiplayerCast: (e) ->
# console.log 'PlayLevelView onRealTimeMultiplayerCast', e # console.log 'PlayLevelView onRealTimeMultiplayerCast', @realTimeSessionData, @realTimePlayersData
unless @realTimeSession unless @realTimeSessionRef?
console.error 'onRealTimeMultiplayerCast without a multiplayerSession' console.error 'Real-time multiplayer cast without multiplayer session.'
return
unless @realTimeSessionData?
console.error 'Real-time multiplayer cast without multiplayer data.'
return
unless @realTimePlayersData?
console.error 'Real-time multiplayer cast without multiplayer players data.'
return return
# Set submissionCount for created real-time multiplayer session # Set submissionCount for created real-time multiplayer session
if me.id is @realTimeSession.get('creator') if me.id is @realTimeSessionData.creator
sessionState = @session.get('state') sessionState = @session.get('state')
if sessionState? if sessionState?
submissionCount = sessionState.submissionCount submissionCount = sessionState.submissionCount
console.info 'Setting multiplayer submissionCount to', submissionCount console.info 'Setting multiplayer submissionCount to', submissionCount
@realTimeSession.set 'submissionCount', submissionCount @realTimeSessionRef.update 'submissionCount': submissionCount
else else
console.error 'Failed to read sessionState in onRealTimeMultiplayerCast' console.error 'Failed to read sessionState in onRealTimeMultiplayerCast'
players = new RealTimeCollection('multiplayer_level_sessions/' + @realTimeSession.id + '/players') console.info 'Submitting my code'
myPlayer = opponentPlayer = null # Transpiling code copied from scripts/transpile.coffee
players.each (player) -> # TODO: Should this live somewhere else?
if player.id is me.id transpiledCode = {}
myPlayer = player for thang, spells of @session.get('code')
else transpiledCode[thang] = {}
opponentPlayer = player for spellID, spell of spells
if myPlayer spellName = thang + '/' + spellID
console.info 'Submitting my code' continue if @session.get('teamSpells') and not (spellName in @session.get('teamSpells')[@session.get('team')])
# Transpile code # console.log "PlayLevelView Transpiling spell #{spellName}"
# Copied from scripts/transpile.coffee aetherOptions = createAetherOptions functionName: spellID, codeLanguage: @session.get('submittedCodeLanguage'), includeFlow: true
# TODO: Should this live somewhere else? aether = new Aether aetherOptions
transpiledCode = {} transpiledCode[thang][spellID] = aether.transpile spell
for thang, spells of @session.get('code') # console.log "PlayLevelView transpiled code", transpiledCode
transpiledCode[thang] = {} @session.set 'transpiledCode', transpiledCode
for spellID, spell of spells @session.patch()
spellName = thang + '/' + spellID @realTimePlayerGameRef.update 'state': 'submitted'
continue if @session.get('teamSpells') and not (spellName in @session.get('teamSpells')[@session.get('team')])
# console.log "PlayLevelView Transpiling spell #{spellName}" console.info 'Other player is', @realTimeOpponentData.state
aetherOptions = createAetherOptions functionName: spellID, codeLanguage: @session.get('submittedCodeLanguage') if @realTimeOpponentData.state in ['submitted', 'ready']
aether = new Aether aetherOptions @realTimeOpponentSubmittedCode @realTimeOpponentData, @realTimePlayerGameData
transpiledCode[thang][spellID] = aether.transpile spell
# console.log "PlayLevelView transpiled code", transpiledCode
@session.set 'transpiledCode', transpiledCode
@session.patch()
myPlayer.set 'state', 'submitted'
else else
console.error 'Did not find my player in onRealTimeMultiplayerCast' # Wait for opponent to submit their code
if opponentPlayer Backbone.Mediator.publish 'real-time-multiplayer:player-status', status: "Waiting for code from #{@realTimeOpponentData.name}"
# TODO: Shouldn't need nested opponentPlayer change listeners here @realTimeOpponentRef.on 'value', @realTimeOpponentMaybeSubmitted
state = opponentPlayer.get('state')
console.info 'Other player is', state realTimeOpponentMaybeSubmitted: (snapshot) =>
if state in ['submitted', 'ready'] if @realTimeOpponentData = snapshot.val()
@realTimeOpponentSubmittedCode opponentPlayer, myPlayer if @realTimeOpponentData.state in ['submitted', 'ready']
else @realTimeOpponentRef.off 'value', @realTimeOpponentMaybeSubmitted
# Wait for opponent to submit their code @realTimeOpponentSubmittedCode @realTimeOpponentData, @realTimePlayerGameData
Backbone.Mediator.publish 'real-time-multiplayer:player-status', status: 'Waiting for code from ' + @realTimeOpponent.get('name')
opponentPlayer.on 'change', (e) =>
state = opponentPlayer.get('state')
if state in ['submitted', 'ready']
@realTimeOpponentSubmittedCode opponentPlayer, myPlayer
opponentPlayer.off 'change'
else
console.error 'Did not find opponent player in onRealTimeMultiplayerCast'
onRealTimeMultiplayerPlaybackEnded: -> onRealTimeMultiplayerPlaybackEnded: ->
if @realTimeSession? # console.log 'PlayLevelView onRealTimeMultiplayerPlaybackEnded'
@realTimeSession.set 'state', 'coding' if @realTimeSessionRef?
@realTimePlayers.each (player) -> player.set 'state', 'coding' if player.id is me.id @realTimeSessionRef.update 'state': 'coding'
if @realTimeOpponent? @realTimePlayerGameRef.update 'state': 'coding'
Backbone.Mediator.publish 'real-time-multiplayer:player-status', status: 'Playing against ' + @realTimeOpponent.get('name') if @realTimeOpponentData?
Backbone.Mediator.publish 'real-time-multiplayer:player-status', status: "Playing against #{@realTimeOpponentData.name}"
realTimeOpponentSubmittedCode: (opponentPlayer, myPlayer) => realTimeOpponentSubmittedCode: (opponentPlayer, myPlayer) =>
# console.log 'PlayLevelView realTimeOpponentSubmittedCode', @realTimeSession.id, opponentPlayer.get('level_session') # console.log 'PlayLevelView realTimeOpponentSubmittedCode', @realTimeSessionData.id, opponentPlayer.level_session
# Read submissionCount for joined real-time multiplayer session # Read submissionCount for joined real-time multiplayer session
if me.id isnt @realTimeSession.get('creator') if me.id isnt @realTimeSessionData.creator
sessionState = @session.get('state') ? {} sessionState = @session.get('state') ? {}
newSubmissionCount = @realTimeSession.get 'submissionCount' newSubmissionCount = @realTimeSessionData.submissionCount
if newSubmissionCount? if newSubmissionCount?
# TODO: This isn't always getting updated where the random seed generation uses it. # TODO: This isn't always getting updated where the random seed generation uses it.
sessionState.submissionCount = parseInt newSubmissionCount sessionState.submissionCount = parseInt newSubmissionCount
@ -878,21 +891,21 @@ module.exports = class PlayLevelView extends RootView
Backbone.Mediator.publish 'router:navigate', Backbone.Mediator.publish 'router:navigate',
route: "/play/level/#{@levelID}" route: "/play/level/#{@levelID}"
viewClass: PlayLevelView viewClass: PlayLevelView
viewArgs: [{supermodel: @supermodel, autoUnveil: true, realTimeMultiplayerSessionID: @realTimeSession.id, opponent: opponentPlayer.get('level_session'), team: @team}, @levelID] viewArgs: [{supermodel: @supermodel, autoUnveil: true, realTimeMultiplayerSessionID: @realTimeSessionData.id, opponent: opponentPlayer.level_session, team: @team}, @levelID]
updateTeam: -> updateTeam: ->
# If not creator, and same team as creator, then switch teams # If not creator, and same team as creator, then switch teams
# TODO: Assumes there are only 'humans' and 'ogres' # TODO: Assumes there are only 'humans' and 'ogres'
unless @realTimeOpponent? unless @realTimeOpponentData?
console.error 'Tried to switch teams without a real-time opponent.' console.error 'Tried to switch teams without real-time multiplayer opponent data.'
return return
unless @realTimeSession? unless @realTimeSessionData?
console.error 'Tried to switch teams without a real-time session.' console.error 'Tried to switch teams without real-time multiplayer session data.'
return return
return if me.id is @realTimeSession.get('creator') return if me.id is @realTimeSessionData.creator
oldTeam = @realTimeOpponent.get('team') oldTeam = @realTimeOpponentData.team
return unless oldTeam is @session.get('team') return unless oldTeam is @session.get('team')
# Need to switch to other team # Need to switch to other team