This commit is contained in:
Nick Winter 2014-11-17 15:09:14 -08:00
commit d3da5e330a
14 changed files with 273 additions and 142 deletions

View file

@ -35,7 +35,6 @@ module.exports = Bus = class Bus extends CocoClass
Backbone.Mediator.publish 'bus:connected', {bus: @}
disconnect: ->
Firebase.goOffline()
@fireRef?.off()
@fireRef = null
@fireChatRef?.off()

View file

@ -56,7 +56,6 @@ module.exports = class LevelLoader extends CocoClass
onLevelLoaded: ->
@loadSession()
@populateLevel()
Backbone.Mediator.publish 'level:loaded', level: @level, team: @team ? 'humans'
# Session Loading
@ -72,7 +71,7 @@ module.exports = class LevelLoader extends CocoClass
@session = @sessionResource.model
if @opponentSessionID
opponentSession = new LevelSession().setURL "/db/level.session/#{@opponentSessionID}"
@opponentSessionResource = @supermodel.loadModel(opponentSession, 'opponent_session')
@opponentSessionResource = @supermodel.loadModel(opponentSession, 'opponent_session', {cache: false})
@opponentSession = @opponentSessionResource.model
if @session.loaded
@ -90,6 +89,9 @@ module.exports = class LevelLoader extends CocoClass
loadDependenciesForSession: (session) ->
if session is @session
# hero-ladder games require the correct session team in level:loaded
team = @team ? @session.get('team')
Backbone.Mediator.publish 'level:loaded', level: @level, team: team
Backbone.Mediator.publish 'level:session-loaded', level: @level, session: @session
@consolidateFlagHistory() if @opponentSession?.loaded
else if session is @opponentSession

View file

@ -6,8 +6,7 @@ module.exports = class WaitingScreen extends CocoClass
'playback:real-time-playback-waiting': 'onRealTimePlaybackWaiting'
'playback:real-time-playback-started': 'onRealTimePlaybackStarted'
'playback:real-time-playback-ended': 'onRealTimePlaybackEnded'
'real-time-multiplayer:joined-game': 'onJoinedRealTimeMultiplayerGame'
'real-time-multiplayer:left-game': 'onLeftRealTimeMultiplayerGame'
'real-time-multiplayer:player-status': 'onRealTimeMultiplayerPlayerStatus'
constructor: (options) ->
super()
@ -49,7 +48,6 @@ module.exports = class WaitingScreen extends CocoClass
@dimLayer.alpha = 0
createjs.Tween.removeTweens @dimLayer
createjs.Tween.get(@dimLayer).to({alpha: 1}, 500)
@updateText()
@layer.addChild @dimLayer
hide: ->
@ -58,27 +56,10 @@ module.exports = class WaitingScreen extends CocoClass
createjs.Tween.removeTweens @dimLayer
createjs.Tween.get(@dimLayer).to({alpha: 0}, 500).call => @layer.removeChild @dimLayer unless @destroyed
updateText: ->
if @multiplayerSession
players = new RealTimeCollection('multiplayer_level_sessions/' + @multiplayerSession.id + '/players')
players.each (player) =>
if player.id isnt me.id
name = player.get('name')
@text.text = "Waiting for #{name}..."
onRealTimeMultiplayerPlayerStatus: (e) -> @text.text = e.status
onRealTimePlaybackWaiting: (e) ->
@show()
onRealTimePlaybackWaiting: (e) -> @show()
onRealTimePlaybackStarted: (e) ->
@hide()
onRealTimePlaybackStarted: (e) -> @hide()
onRealTimePlaybackEnded: (e) ->
@hide()
onJoinedRealTimeMultiplayerGame: (e) ->
@multiplayerSession = e.session
onLeftRealTimeMultiplayerGame: (e) ->
if @multiplayerSession
@multiplayerSession.off()
@multiplayerSession = null
onRealTimePlaybackEnded: (e) -> @hide()

View file

@ -28,7 +28,10 @@ module.exports = class SuperModel extends Backbone.Model
unfinished
loadModel: (model, name, fetchOptions, value=1) ->
cachedModel = @getModelByURL(model.getURL())
# hero-ladder levels need remote opponent_session for latest session data (e.g. code)
# Can't apply to everything since other features rely on cached models being more recent (E.g. level_session)
# E.g.#2 heroConfig isn't necessarily saved to db in world map inventory modal, so we need to load the cached session on level start
cachedModel = @getModelByURL(model.getURL()) unless fetchOptions?.cache is false and name is 'opponent_session'
if cachedModel
if cachedModel.loaded
res = @addModelResource(cachedModel, name, fetchOptions, 0)

View file

@ -1,15 +1,14 @@
c = require 'schemas/schemas'
module.exports =
'real-time-multiplayer:created-game': c.object {title: 'Multiplayer created game', required: ['session']},
session: {type: 'object'}
'real-time-multiplayer:created-game': c.object {title: 'Multiplayer created game', required: ['realTimeSessionID']},
realTimeSessionID: {type: 'string'}
'real-time-multiplayer:joined-game': c.object {title: 'Multiplayer joined game', required: ['id', 'session']},
id: {type: 'string'}
session: {type: 'object'}
'real-time-multiplayer:joined-game': c.object {title: 'Multiplayer joined game', required: ['realTimeSessionID']},
realTimeSessionID: {type: 'string'}
'real-time-multiplayer:left-game': c.object {title: 'Multiplayer left game', required: ['id']},
id: {type: 'string'}
'real-time-multiplayer:left-game': c.object {title: 'Multiplayer left game'},
userID: {type: 'string'}
'real-time-multiplayer:manual-cast': c.object {title: 'Multiplayer manual cast'}

View file

@ -22,7 +22,7 @@ if !ladderGame
if ladderGame
if me.get('anonymous')
p(data-i18n="multiplayer.multiplayer_sign_in_leaderboard") Sign in or create an account and get your solution on the leaderboard.
else if readyToRank
else if realTimeSessions && realTimeSessionsPlayers
button#create-game-button Create Game
hr
@ -37,7 +37,7 @@ if ladderGame
span(style="margin:10px")= currentRealTimeSession.id
button#leave-game-button(data-item=item) Leave Game
div
- var players = realTimeSessionPlayers[currentRealTimeSession.id]
- var players = realTimeSessionsPlayers[currentRealTimeSession.id]
if players
span(style="margin:10px") Players:
- for (var i=0; i < players.length; i++) {
@ -61,7 +61,7 @@ if ladderGame
- continue
if levelID === realTimeSessions.at(i).get('levelID') && realTimeSessions.at(i).get('state') === 'creating'
- var id = realTimeSessions.at(i).get('id')
- var players = realTimeSessionPlayers[id]
- var players = realTimeSessionsPlayers[id]
if players && players.length === 1
- noOpenGames = false
- var creatorName = realTimeSessions.at(i).get('creatorName')

View file

@ -26,7 +26,7 @@ module.exports = class MultiplayerView extends CocoView
@level = options.level
@session = options.session
@listenTo @session, 'change:multiplayer', @updateLinkSection
@watchRealTimeSessions()
@watchRealTimeSessions() if @level?.get('type') in ['hero-ladder']
destroy: ->
@realTimeSessions?.off 'add', @onRealTimeSessionAdded
@ -46,15 +46,16 @@ module.exports = class MultiplayerView extends CocoView
c.readyToRank = @session?.readyToRank()
# Real-time multiplayer stuff
c.levelID = @session.get('levelID')
c.realTimeSessions = @realTimeSessions
c.currentRealTimeSession = @currentRealTimeSession if @currentRealTimeSession
c.realTimeSessionPlayers = @realTimeSessionsPlayers if @realTimeSessionsPlayers
# console.log 'MultiplayerView getRenderData', c.levelID
# console.log 'realTimeSessions', c.realTimeSessions
# console.log c.realTimeSessions.at(c.realTimeSessions.length - 1).get('state') if c.realTimeSessions.length > 0
# console.log 'currentRealTimeSession', c.currentRealTimeSession
# console.log 'realTimeSessionPlayers', c.realTimeSessionPlayers
if @level?.get('type') in ['hero-ladder']
c.levelID = @session.get('levelID')
c.realTimeSessions = @realTimeSessions
c.currentRealTimeSession = @currentRealTimeSession if @currentRealTimeSession
c.realTimeSessionsPlayers = @realTimeSessionsPlayers if @realTimeSessionsPlayers
# console.log 'MultiplayerView getRenderData', c.levelID
# console.log 'realTimeSessions', c.realTimeSessions
# console.log c.realTimeSessions.at(c.realTimeSessions.length - 1).get('state') if c.realTimeSessions.length > 0
# console.log 'currentRealTimeSession', c.currentRealTimeSession
# console.log 'realTimeSessionPlayers', c.realTimeSessionsPlayers
c
@ -134,7 +135,9 @@ module.exports = class MultiplayerView extends CocoView
# console.log 'MultiplayerView found current real-time session', rts
@currentRealTimeSession = new RealTimeModel('multiplayer_level_sessions/' + rts.id)
@currentRealTimeSession.on 'change', @onCurrentRealTimeSessionChanged
Backbone.Mediator.publish 'real-time-multiplayer:joined-game', id: me.id, session: @currentRealTimeSession
# TODO: Is this necessary? Shouldn't everyone already know we joined a game at this point?
Backbone.Mediator.publish 'real-time-multiplayer:joined-game', realTimeSessionID: @currentRealTimeSession.id
onRealTimeSessionAdded: (rts) =>
@watchRealTimeSession rts
@ -168,8 +171,13 @@ module.exports = class MultiplayerView extends CocoView
@currentRealTimeSession.on 'change', @onCurrentRealTimeSessionChanged
# TODO: s.id === @currentRealTimeSession.id ?
players = new RealTimeCollection('multiplayer_level_sessions/' + @currentRealTimeSession.id + '/players')
players.create id: me.id, state: 'coding', name: @session.get('creatorName'), team: @session.get('team')
Backbone.Mediator.publish 'real-time-multiplayer:created-game', session: @currentRealTimeSession
players.create
id: me.id
state: 'coding'
name: @session.get('creatorName')
team: @session.get('team')
level_session: @session.id
Backbone.Mediator.publish 'real-time-multiplayer:created-game', realTimeSessionID: @currentRealTimeSession.id
@render()
onJoinRealTimeGame: (e) ->
@ -178,17 +186,31 @@ module.exports = class MultiplayerView extends CocoView
@currentRealTimeSession = @realTimeSessions.get(item.id)
@currentRealTimeSession.on 'change', @onCurrentRealTimeSessionChanged
if @realTimeSessionsPlayers[item.id]
@realTimeSessionsPlayers[item.id].create id: me.id, state: 'coding', name: @session.get('creatorName'), team: @session.get('team')
# TODO: SpellView updateTeam() should take care of this team swap update in the real-time multiplayer session
creatorID = @currentRealTimeSession.get('creator')
creator = @realTimeSessionsPlayers[item.id].get(creatorID)
creatorTeam = creator.get('team')
myTeam = @session.get('team')
if myTeam is creatorTeam
myTeam = if creatorTeam is 'humans' then 'ogres' else 'humans'
@realTimeSessionsPlayers[item.id].create
id: me.id
state: 'coding'
name: me.get('name')
team: myTeam
level_session: @session.id
else
console.error 'MultiplayerView onJoinRealTimeGame did not have a players collection', @currentRealTimeSession
Backbone.Mediator.publish 'real-time-multiplayer:joined-game', id: me.id, session: @currentRealTimeSession
Backbone.Mediator.publish 'real-time-multiplayer:joined-game', realTimeSessionID: @currentRealTimeSession.id
@render()
onLeaveRealTimeGame: (e) ->
if @currentRealTimeSession
@currentRealTimeSession.off 'change', @onCurrentRealTimeSessionChanged
@currentRealTimeSession = null
Backbone.Mediator.publish 'real-time-multiplayer:left-game', id: me.id
Backbone.Mediator.publish 'real-time-multiplayer:left-game', userID: me.id
else
console.error "Tried to leave a game with no currentMultiplayerSession"
@render()

View file

@ -84,13 +84,14 @@ module.exports = class LevelFlagsView extends CocoView
@world = @options.world = event.world
onJoinedMultiplayerGame: (e) ->
@realTimeFlags = new RealTimeCollection('multiplayer_level_sessions/' + e.session.id + '/flagHistory')
@realTimeFlags = new RealTimeCollection('multiplayer_level_sessions/' + e.realTimeSessionID + '/flagHistory')
@realTimeFlags.on 'add', @onRealTimeMultiplayerFlagAdded
@realTimeFlags.on 'remove', @onRealTimeMultiplayerFlagRemoved
onLeftMultiplayerGame: (e) ->
if @realTimeFlags
@realTimeFlags.off()
@realTimeFlags.off 'add', @onRealTimeMultiplayerFlagAdded
@realTimeFlags.off 'remove', @onRealTimeMultiplayerFlagRemoved
@realTimeFlags = null
onRealTimeMultiplayerFlagAdded: (e) =>

View file

@ -97,6 +97,9 @@ module.exports = class PlayLevelView extends RootView
@isEditorPreview = @getQueryVariable 'dev'
@sessionID = @getQueryVariable 'session'
@opponentSessionID = @getQueryVariable('opponent')
@opponentSessionID ?= @options.opponent
$(window).on 'resize', @onWindowResize
@saveScreenshot = _.throttle @saveScreenshot, 30000
@ -131,7 +134,7 @@ module.exports = class PlayLevelView extends RootView
load: ->
@loadStartTime = new Date()
@god = new God debugWorker: true
@levelLoader = new LevelLoader supermodel: @supermodel, levelID: @levelID, sessionID: @sessionID, opponentSessionID: @getQueryVariable('opponent'), team: @getQueryVariable('team')
@levelLoader = new LevelLoader supermodel: @supermodel, levelID: @levelID, sessionID: @sessionID, opponentSessionID: @opponentSessionID, team: @getQueryVariable('team')
@listenToOnce @levelLoader, 'world-necessities-loaded', @onWorldNecessitiesLoaded
trackLevelLoadEnd: ->
@ -170,7 +173,7 @@ module.exports = class PlayLevelView extends RootView
onWorldNecessitiesLoaded: ->
# Called when we have enough to build the world, but not everything is loaded
@grabLevelLoaderData()
team = @getQueryVariable('team') ? @world.teamForPlayer(0)
team = @getQueryVariable('team') ? @session.get('team') ? @world.teamForPlayer(0)
@loadOpponentTeam(team)
@setupGod()
@setTeam team
@ -190,6 +193,8 @@ module.exports = class PlayLevelView extends RootView
@level = @levelLoader.level
@$el.addClass 'hero' if @level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop']
@$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'
# 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
@otherSession = @levelLoader.opponentSession
@worldLoadFakeResources = [] # first element (0) is 1%, last (100) is 100%
for percent in [1 .. 100]
@ -211,7 +216,8 @@ module.exports = class PlayLevelView extends RootView
opponentSpells = opponentSpells.concat spells
if (not @session.get('teamSpells')) and @otherSession?.get('teamSpells')
@session.set('teamSpells', @otherSession.get('teamSpells'))
opponentCode = @otherSession?.get('transpiledCode') or {}
# hero-ladder levels use code instead of transpiledCode
opponentCode = @otherSession?.get('transpiledCode') or @otherSession?.get('code') or {}
myCode = @session.get('code') or {}
for spell in opponentSpells
[thang, spell] = spell.split '/'
@ -232,6 +238,7 @@ module.exports = class PlayLevelView extends RootView
team = team?.team unless _.isString team
team ?= 'humans'
me.team = team
@session.set 'team', team
Backbone.Mediator.publish 'level:team-set', team: team # Needed for scripts
@team = team
@ -248,7 +255,7 @@ module.exports = class PlayLevelView extends RootView
@insertSubView new HUDView {level: @level}
@insertSubView new LevelDialogueView {level: @level}
@insertSubView new ChatView levelID: @levelID, sessionID: @session.id, session: @session
if @level.get('type') in ['ladder', 'hero-ladder']
if @level.get('type') in ['hero-ladder']
@insertSubView new MultiplayerStatusView levelID: @levelID, session: @session, level: @level
@insertSubView new ProblemAlertView {}
worldName = utils.i18n @level.attributes, 'name'
@ -283,7 +290,7 @@ module.exports = class PlayLevelView extends RootView
@setupManager = new LevelSetupManager({supermodel: @supermodel, levelID: @levelID, parent: @, session: @session})
@setupManager.open()
@onRealTimeMultiplayerLevelLoaded e.session if e.level.get('type') in ['ladder', 'hero-ladder']
@onRealTimeMultiplayerLevelLoaded() if e.level.get('type') in ['hero-ladder']
onLoaded: ->
_.defer => @onLevelLoaderLoaded()
@ -342,6 +349,9 @@ module.exports = class PlayLevelView extends RootView
@removeSubView @loadingView
@loadingView = null
@playAmbientSound()
if @options.realTimeMultiplayerSessionID?
Backbone.Mediator.publish 'playback:real-time-playback-waiting', {}
@realTimeMultiplayerContinueGame @options.realTimeMultiplayerSessionID
application.tracker?.trackEvent 'Play Level', Action: 'Start Level', levelID: @levelID
playAmbientSound: ->
@ -540,7 +550,9 @@ module.exports = class PlayLevelView extends RootView
onSubmissionComplete: =>
return if @destroyed
Backbone.Mediator.publish 'level:show-victory', showModal: true if @goalManager.checkOverallStatus() is 'success'
# TODO: Show a victory dialog specific to hero-ladder level
if @goalManager.checkOverallStatus() is 'success' and not @options.realTimeMultiplayerSessionID?
Backbone.Mediator.publish 'level:show-victory', showModal: true
destroy: ->
@levelLoader?.destroy()
@ -572,6 +584,8 @@ module.exports = class PlayLevelView extends RootView
# Updates real-time multiplayer player state
# Cleans up old sessions (sets state to 'finished')
# Real-time multiplayer cast handshake
# Swap teams on game joined, if necessary
# Reload PlayLevelView on real-time submit, automatically continue game and real-time playback
#
# It monitors these:
# Real-time multiplayer sessions
@ -585,19 +599,23 @@ module.exports = class PlayLevelView extends RootView
# @realTimeOpponent - Current real-time multiplayer opponent
# @realTimePlayers - Real-time players for current real-time multiplayer game session
# @realTimeSessionCollection - Collection of all real-time multiplayer sessions
# @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: 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).
onRealTimeMultiplayerLevelLoaded: (session) ->
onRealTimeMultiplayerLevelLoaded: ->
return if @realTimePlayerStatus?
return if me.get('anonymous')
players = new RealTimeCollection('multiplayer_players/' + @levelID)
players.create
id: me.id
name: me.get('name')
state: 'playing'
created: new Date().toISOString()
heartbeat: new Date().toISOString()
unless @options.realTimeMultiplayerSessionID?
players = new RealTimeCollection('multiplayer_players/' + @levelID)
players.create
id: me.id
name: me.get('name')
state: 'playing'
created: new Date().toISOString()
heartbeat: new Date().toISOString()
@realTimePlayerStatus = new RealTimeModel('multiplayer_players/' + @levelID + '/' + me.id)
@timerMultiplayerHeartbeatID = setInterval @onRealTimeMultiplayerHeartbeat, 60 * 1000
@cleanupRealTimeSessions()
@ -608,6 +626,7 @@ module.exports = class PlayLevelView extends RootView
@realTimeSessionCollection.each @cleanupRealTimeSession
cleanupRealTimeSession: (session) =>
return if @options.realTimeMultiplayerSessionID? and @options.realTimeMultiplayerSessionID is session.id
if session.get('state') isnt 'finished'
players = new RealTimeCollection 'multiplayer_level_sessions/' + session.id + '/players'
players.each (player) =>
@ -618,21 +637,34 @@ module.exports = class PlayLevelView extends RootView
session.set 'state', 'finished'
onRealTimeMultiplayerLevelUnloaded: ->
clearInterval @timerMultiplayerHeartbeatID if @timerMultiplayerHeartbeatID?
# console.log 'PlayLevelView onRealTimeMultiplayerLevelUnloaded'
if @timerMultiplayerHeartbeatID?
clearInterval @timerMultiplayerHeartbeatID
@timerMultiplayerHeartbeatID = null
if @realTimeSessionCollection?
@realTimeSessionCollection.off 'add', @cleanupRealTimeSession
@realTimeSessionCollection = null
# TODO: similar to game ending cleanup
if @realTimeOpponent?
@realTimeOpponent.off 'change', @onRealTimeOpponentChanged
@realTimeOpponent = null
if @realTimePlayers?
@realTimePlayers.off 'add', @onRealTimePlayerAdded
@realTimePlayers = null
if @realTimeSession?
@realTimeSession.off 'change', @onRealTimeSessionChanged
@realTimeSession = null
if @realTimePlayerGameStatus?
@realTimePlayerGameStatus = null
if @realTimePlayerStatus?
@realTimePlayerStatus = null
onRealTimeMultiplayerHeartbeat: =>
@realTimePlayerStatus.set 'heartbeat', new Date().toISOString() if @realTimePlayerStatus
onRealTimeMultiplayerCreatedGame: (e) ->
# Watch external multiplayer session
@realTimeSession = new RealTimeModel 'multiplayer_level_sessions/' + e.session.id
@realTimeSession.on 'change', @onRealTimeSessionChanged
@realTimePlayers = new RealTimeCollection 'multiplayer_level_sessions/' + e.session.id + '/players'
@realTimePlayers.on 'add', @onRealTimePlayerAdded
@realTimePlayerGameStatus = new RealTimeModel 'multiplayer_level_sessions/' + e.session.id + '/players/' + me.id
@joinRealTimeMultiplayerGame e
@realTimePlayerGameStatus.set 'state', 'coding'
@realTimePlayerStatus.set 'state', 'available'
Backbone.Mediator.publish 'real-time-multiplayer:player-status', status: 'Waiting for opponent..'
@ -662,33 +694,72 @@ module.exports = class PlayLevelView extends RootView
console.info 'Real-time multiplayer opponent left the game'
opponentID = @realTimeOpponent.id
@realTimeGameEnded()
Backbone.Mediator.publish 'real-time-multiplayer:left-game', id: opponentID
Backbone.Mediator.publish 'real-time-multiplayer:left-game', userID: opponentID
when 'submitted'
# 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: @realTimeOpponent.get('name') + ' waiting for your code'
joinRealTimeMultiplayerGame: (e) ->
unless @realTimeSession?
# TODO: Necessary for real-time multiplayer sessions?
@session.set('submittedCodeLanguage', @session.get('codeLanguage'))
@session.save()
@realTimeSession = new RealTimeModel 'multiplayer_level_sessions/' + e.realTimeSessionID
@realTimeSession.on 'change', @onRealTimeSessionChanged
@realTimePlayers = new RealTimeCollection 'multiplayer_level_sessions/' + e.realTimeSessionID + '/players'
@realTimePlayers.on 'add', @onRealTimePlayerAdded
@realTimePlayerGameStatus = new RealTimeModel 'multiplayer_level_sessions/' + e.realTimeSessionID + '/players/' + me.id
# TODO: Follow up in MultiplayerView to see if double joins can be avoided
# else
# console.error 'Joining real-time multiplayer game with an existing @realTimeSession.'
onRealTimeMultiplayerJoinedGame: (e) ->
# console.log 'PlayLevelView onRealTimeMultiplayerJoinedGame', e
if e.id is me.id
@realTimeSession = new RealTimeModel 'multiplayer_level_sessions/' + e.session.id
@realTimeSession.set 'state', 'coding'
@realTimeSession.on 'change', @onRealTimeSessionChanged
@realTimePlayers = new RealTimeCollection 'multiplayer_level_sessions/' + e.session.id + '/players'
@realTimePlayers.on 'add', @onRealTimePlayerAdded
@realTimePlayerGameStatus = new RealTimeModel 'multiplayer_level_sessions/' + e.session.id + '/players/' + me.id
@realTimePlayerGameStatus.set 'state', 'coding'
@realTimePlayerStatus.set 'state', 'unavailable'
for id, player of e.session.get('players')
@joinRealTimeMultiplayerGame e
@realTimePlayerGameStatus.set 'state', 'coding'
@realTimePlayerStatus.set 'state', 'unavailable'
unless @realTimeOpponent?
for id, player of @realTimeSession.get('players')
if id isnt me.id
@realTimeOpponent = new RealTimeModel 'multiplayer_level_sessions/' + e.session.id + '/players/' + 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) ->
# console.log 'PlayLevelView onRealTimeMultiplayerLeftGame', e
if e.id? and e.id is me.id
if e.userID? and e.userID is me.id
@realTimePlayerGameStatus.set 'state', 'left'
@realTimeGameEnded()
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'
@realTimePlayerGameStatus.set 'state', 'ready'
if @realTimeOpponent.get('state') is 'ready'
@realTimeOpponentIsReady()
else
console.info 'Waiting for opponent to be ready'
@realTimeOpponent.on 'change', @realTimeOpponentMaybeReady
realTimeOpponentMaybeReady: =>
# console.log 'PlayLevelView realTimeOpponentMaybeReady'
if @realTimeOpponent.get('state') is 'ready'
@realTimeOpponent.off 'change', @realTimeOpponentMaybeReady
@realTimeOpponentIsReady()
realTimeOpponentIsReady: =>
console.info 'All real-time multiplayer players are ready!'
@realTimeSession.set 'state', 'running'
Backbone.Mediator.publish 'real-time-multiplayer:player-status', status: 'Battling ' + @realTimeOpponent.get('name')
Backbone.Mediator.publish 'tome:manual-cast', {realTime: true}
realTimeGameEnded: ->
if @realTimeOpponent?
@realTimeOpponent.off 'change', @onRealTimeOpponentChanged
@ -707,9 +778,21 @@ module.exports = class PlayLevelView extends RootView
Backbone.Mediator.publish 'real-time-multiplayer:player-status', status: ''
onRealTimeMultiplayerCast: (e) ->
# console.log 'PlayLevelView onRealTimeMultiplayerCast', e
unless @realTimeSession
console.error 'onRealTimeMultiplayerCast without a multiplayerSession'
return
# Set submissionCount for created real-time multiplayer session
if me.id is @realTimeSession.get('creator')
sessionState = @session.get('state')
if sessionState?
submissionCount = sessionState.submissionCount
console.info 'Setting multiplayer submissionCount to', submissionCount
@realTimeSession.set 'submissionCount', submissionCount
else
console.error 'Failed to read sessionState in onRealTimeMultiplayerCast'
players = new RealTimeCollection('multiplayer_level_sessions/' + @realTimeSession.id + '/players')
myPlayer = opponentPlayer = null
players.each (player) ->
@ -719,10 +802,8 @@ module.exports = class PlayLevelView extends RootView
opponentPlayer = player
if myPlayer
console.info 'Submitting my code'
myPlayer.set 'code', @session.get('code')
myPlayer.set 'codeLanguage', @session.get('codeLanguage')
@session.patch()
myPlayer.set 'state', 'submitted'
myPlayer.set 'team', me.team
else
console.error 'Did not find my player in onRealTimeMultiplayerCast'
if opponentPlayer
@ -738,6 +819,9 @@ module.exports = class PlayLevelView extends RootView
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: ->
if @realTimeSession?
@ -747,19 +831,77 @@ module.exports = class PlayLevelView extends RootView
Backbone.Mediator.publish 'real-time-multiplayer:player-status', status: 'Playing against ' + @realTimeOpponent.get('name')
realTimeOpponentSubmittedCode: (opponentPlayer, myPlayer) =>
# Save opponent's code
Backbone.Mediator.publish 'real-time-multiplayer:new-opponent-code', {codeLanguage: opponentPlayer.get('codeLanguage'), code: opponentPlayer.get('code'), team: opponentPlayer.get('team')}
# I'm ready to rumble
myPlayer.set 'state', 'ready'
if opponentPlayer.get('state') is 'ready'
console.info 'All real-time multiplayer players are ready!'
@realTimeSession.set 'state', 'running'
Backbone.Mediator.publish 'real-time-multiplayer:player-status', status: 'Battling ' + @realTimeOpponent.get('name')
else
# Wait for opponent to be ready
opponentPlayer.on 'change', (e) =>
if opponentPlayer.get('state') is 'ready'
opponentPlayer.off 'change'
console.info 'All real-time multiplayer players are ready!'
@realTimeSession.set 'state', 'running'
Backbone.Mediator.publish 'real-time-multiplayer:player-status', status: 'Battling ' + @realTimeOpponent.get('name')
# console.log 'PlayLevelView realTimeOpponentSubmittedCode', @realTimeSession.id, opponentPlayer.get('level_session')
# Read submissionCount for joined real-time multiplayer session
if me.id isnt @realTimeSession.get('creator')
sessionState = @session.get('state') ? {}
newSubmissionCount = @realTimeSession.get 'submissionCount'
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
@session.set 'state', sessionState
@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
viewArgs: [{supermodel: @supermodel, autoUnveil: true, realTimeMultiplayerSessionID: @realTimeSession.id, opponent: opponentPlayer.get('level_session'), team: @team}, @levelID]
updateTeam: ->
# If not creator, and same team as creator, then switch teams
# TODO: Assumes there are only 'humans' and 'ogres'
unless @realTimeOpponent?
console.error 'Tried to switch teams without a real-time opponent.'
return
unless @realTimeSession?
console.error 'Tried to switch teams without a real-time session.'
return
return if me.id is @realTimeSession.get('creator')
oldTeam = @realTimeOpponent.get('team')
return unless oldTeam is @session.get('team')
# 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
console.log "Swapping spell=#{oldSpell} from #{oldThang} to #{newThang}"
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'
@session.set 'state', sessionState
@session.set 'code', code
@session.patch()
if sessionState?
# TODO: Don't hardcode spellName
Backbone.Mediator.publish 'level:select-sprite', thangID: sessionState.selected, spellName: 'plan'

View file

@ -63,14 +63,8 @@ module.exports = class CastButtonView extends CocoView
Backbone.Mediator.publish 'tome:manual-cast', {}
onCastRealTimeButtonClick: (e) ->
if @multiplayerSession
if @inRealTimeMultiplayerSession
Backbone.Mediator.publish 'real-time-multiplayer:manual-cast', {}
# Wait for multiplayer session to be up and running
@multiplayerSession.on 'change', (e) =>
if @multiplayerSession.get('state') is 'running'
# Real-time multiplayer session is ready to go, so resume normal cast
@multiplayerSession.off 'change'
Backbone.Mediator.publish 'tome:manual-cast', {realTime: true}
else
Backbone.Mediator.publish 'tome:manual-cast', {realTime: true}
@ -149,12 +143,10 @@ module.exports = class CastButtonView extends CocoView
$(@).toggleClass('selected', parseInt($(@).attr('data-delay')) is delay)
onJoinedRealTimeMultiplayerGame: (e) ->
@multiplayerSession = e.session
@inRealTimeMultiplayerSession = true
onLeftRealTimeMultiplayerGame: (e) ->
if @multiplayerSession
@multiplayerSession.off 'change'
@multiplayerSession = null
@inRealTimeMultiplayerSession = false
initButtonTextABTest: ->
return if me.isAdmin()

View file

@ -46,20 +46,18 @@ module.exports = class Spell
@source = @originalSource = p.aiSource
@thangs = {}
if @canRead() # We can avoid creating these views if we'll never use them.
@view = new SpellView {spell: @, level: options.level, session: @session, worker: @worker}
@view = new SpellView {spell: @, level: options.level, session: @session, otherSession: @otherSession, worker: @worker}
@view.render() # Get it ready and code loaded in advance
@tabView = new SpellListTabEntryView spell: @, supermodel: @supermodel, codeLanguage: @language, level: options.level
@tabView.render()
@team = @permissions.readwrite[0] ? 'common'
Backbone.Mediator.publish 'tome:spell-created', spell: @
Backbone.Mediator.subscribe 'real-time-multiplayer:new-opponent-code', @onNewOpponentCode, @
destroy: ->
@view?.destroy()
@tabView?.destroy()
@thangs = null
@worker = null
Backbone.Mediator.unsubscribe 'real-time-multiplayer:new-opponent-code', @onNewOpponentCode, @
setLanguage: (@language) ->
#console.log 'setting language to', @language, 'so using original source', @languages[language] ? @languages.javascript
@ -187,22 +185,13 @@ module.exports = class Spell
shouldUseTranspiledCode: ->
# Determine whether this code has already been transpiled, or whether it's raw source needing transpilation.
return false if @levelType is 'hero-ladder'
return true if @spectateView # Use transpiled code for both teams if we're just spectating.
return true if @isEnemySpell() # Use transpiled for enemy spells.
# Players without permissions can't view the raw code.
return true if @session.get('creator') isnt me.id and not (me.isAdmin() or 'employer' in me.get('permissions', true))
false
onNewOpponentCode: (e) ->
return unless @spellKey and @canWrite e.team
if e.codeLanguage and e.code
[thangSlug, methodSlug] = @spellKey.split '/'
if opponentCode = e.code[thangSlug]?[methodSlug]
@source = opponentCode
@updateLanguageAether e.codeLanguage
else
console.error 'Spell onNewOpponentCode did not receive code', e
createProblemContext: (thang) ->
# Create problemContext Aether can use to craft better error messages
# stringReferences: values that should be referred to as a string instead of a variable (e.g. "Brak", not Brak)

View file

@ -237,8 +237,12 @@ module.exports = class TomeView extends CocoView
@cast()
onSelectPrimarySprite: (e) ->
# TODO: this may not be correct
Backbone.Mediator.publish 'level:select-sprite', thangID: 'Hero Placeholder'
# This is only fired by PlayLevelView for hero levels currently
# TODO: Don't hard code these hero names
if @options.session.get('team') is 'ogres'
Backbone.Mediator.publish 'level:select-sprite', thangID: 'Hero Placeholder 1'
else
Backbone.Mediator.publish 'level:select-sprite', thangID: 'Hero Placeholder'
destroy: ->
spell.destroy() for spellKey, spell of @spells

View file

@ -68,10 +68,6 @@ LevelHandler = class LevelHandler extends Handler
if req.query.team?
sessionQuery.team = req.query.team
# TODO: generalize this for levels based on their teams
else if level.get('type') in ['ladder', 'hero-ladder']
sessionQuery.team = 'humans'
Session.findOne(sessionQuery).exec (err, doc) =>
return @sendDatabaseError(res, err) if err
return @sendSuccess(res, doc) if doc?

View file

@ -38,7 +38,8 @@ LevelSessionSchema.pre 'save', (next) ->
LevelSessionSchema.statics.privateProperties = ['code', 'submittedCode', 'unsubscribed']
LevelSessionSchema.statics.editableProperties = ['multiplayer', 'players', 'code', 'codeLanguage', 'completed', 'state',
'levelName', 'creatorName', 'levelID', 'screenshot',
'chat', 'teamSpells', 'submitted', 'submittedCodeLanguage', 'unsubscribed', 'playtime', 'heroConfig']
'chat', 'teamSpells', 'submitted', 'submittedCodeLanguage',
'unsubscribed', 'playtime', 'heroConfig', 'team']
LevelSessionSchema.statics.jsonSchema = jsonschema
LevelSessionSchema.index {user: 1, changed: -1}, {sparse: true, name: 'last played index'}