Merge branch 'master' of https://github.com/codecombat/codecombat
This commit is contained in:
commit
d3da5e330a
14 changed files with 273 additions and 142 deletions
app
lib
models
schemas/subscriptions
templates/game-menu
views
server/levels
|
@ -35,7 +35,6 @@ module.exports = Bus = class Bus extends CocoClass
|
||||||
Backbone.Mediator.publish 'bus:connected', {bus: @}
|
Backbone.Mediator.publish 'bus:connected', {bus: @}
|
||||||
|
|
||||||
disconnect: ->
|
disconnect: ->
|
||||||
Firebase.goOffline()
|
|
||||||
@fireRef?.off()
|
@fireRef?.off()
|
||||||
@fireRef = null
|
@fireRef = null
|
||||||
@fireChatRef?.off()
|
@fireChatRef?.off()
|
||||||
|
|
|
@ -56,7 +56,6 @@ module.exports = class LevelLoader extends CocoClass
|
||||||
onLevelLoaded: ->
|
onLevelLoaded: ->
|
||||||
@loadSession()
|
@loadSession()
|
||||||
@populateLevel()
|
@populateLevel()
|
||||||
Backbone.Mediator.publish 'level:loaded', level: @level, team: @team ? 'humans'
|
|
||||||
|
|
||||||
# Session Loading
|
# Session Loading
|
||||||
|
|
||||||
|
@ -72,7 +71,7 @@ module.exports = class LevelLoader extends CocoClass
|
||||||
@session = @sessionResource.model
|
@session = @sessionResource.model
|
||||||
if @opponentSessionID
|
if @opponentSessionID
|
||||||
opponentSession = new LevelSession().setURL "/db/level.session/#{@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
|
@opponentSession = @opponentSessionResource.model
|
||||||
|
|
||||||
if @session.loaded
|
if @session.loaded
|
||||||
|
@ -90,6 +89,9 @@ module.exports = class LevelLoader extends CocoClass
|
||||||
|
|
||||||
loadDependenciesForSession: (session) ->
|
loadDependenciesForSession: (session) ->
|
||||||
if session is @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
|
Backbone.Mediator.publish 'level:session-loaded', level: @level, session: @session
|
||||||
@consolidateFlagHistory() if @opponentSession?.loaded
|
@consolidateFlagHistory() if @opponentSession?.loaded
|
||||||
else if session is @opponentSession
|
else if session is @opponentSession
|
||||||
|
|
|
@ -6,8 +6,7 @@ module.exports = class WaitingScreen extends CocoClass
|
||||||
'playback:real-time-playback-waiting': 'onRealTimePlaybackWaiting'
|
'playback:real-time-playback-waiting': 'onRealTimePlaybackWaiting'
|
||||||
'playback:real-time-playback-started': 'onRealTimePlaybackStarted'
|
'playback:real-time-playback-started': 'onRealTimePlaybackStarted'
|
||||||
'playback:real-time-playback-ended': 'onRealTimePlaybackEnded'
|
'playback:real-time-playback-ended': 'onRealTimePlaybackEnded'
|
||||||
'real-time-multiplayer:joined-game': 'onJoinedRealTimeMultiplayerGame'
|
'real-time-multiplayer:player-status': 'onRealTimeMultiplayerPlayerStatus'
|
||||||
'real-time-multiplayer:left-game': 'onLeftRealTimeMultiplayerGame'
|
|
||||||
|
|
||||||
constructor: (options) ->
|
constructor: (options) ->
|
||||||
super()
|
super()
|
||||||
|
@ -49,7 +48,6 @@ module.exports = class WaitingScreen extends CocoClass
|
||||||
@dimLayer.alpha = 0
|
@dimLayer.alpha = 0
|
||||||
createjs.Tween.removeTweens @dimLayer
|
createjs.Tween.removeTweens @dimLayer
|
||||||
createjs.Tween.get(@dimLayer).to({alpha: 1}, 500)
|
createjs.Tween.get(@dimLayer).to({alpha: 1}, 500)
|
||||||
@updateText()
|
|
||||||
@layer.addChild @dimLayer
|
@layer.addChild @dimLayer
|
||||||
|
|
||||||
hide: ->
|
hide: ->
|
||||||
|
@ -58,27 +56,10 @@ module.exports = class WaitingScreen extends CocoClass
|
||||||
createjs.Tween.removeTweens @dimLayer
|
createjs.Tween.removeTweens @dimLayer
|
||||||
createjs.Tween.get(@dimLayer).to({alpha: 0}, 500).call => @layer.removeChild @dimLayer unless @destroyed
|
createjs.Tween.get(@dimLayer).to({alpha: 0}, 500).call => @layer.removeChild @dimLayer unless @destroyed
|
||||||
|
|
||||||
updateText: ->
|
onRealTimeMultiplayerPlayerStatus: (e) -> @text.text = e.status
|
||||||
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}..."
|
|
||||||
|
|
||||||
onRealTimePlaybackWaiting: (e) ->
|
onRealTimePlaybackWaiting: (e) -> @show()
|
||||||
@show()
|
|
||||||
|
|
||||||
onRealTimePlaybackStarted: (e) ->
|
onRealTimePlaybackStarted: (e) -> @hide()
|
||||||
@hide()
|
|
||||||
|
|
||||||
onRealTimePlaybackEnded: (e) ->
|
onRealTimePlaybackEnded: (e) -> @hide()
|
||||||
@hide()
|
|
||||||
|
|
||||||
onJoinedRealTimeMultiplayerGame: (e) ->
|
|
||||||
@multiplayerSession = e.session
|
|
||||||
|
|
||||||
onLeftRealTimeMultiplayerGame: (e) ->
|
|
||||||
if @multiplayerSession
|
|
||||||
@multiplayerSession.off()
|
|
||||||
@multiplayerSession = null
|
|
||||||
|
|
|
@ -28,7 +28,10 @@ module.exports = class SuperModel extends Backbone.Model
|
||||||
unfinished
|
unfinished
|
||||||
|
|
||||||
loadModel: (model, name, fetchOptions, value=1) ->
|
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
|
||||||
if cachedModel.loaded
|
if cachedModel.loaded
|
||||||
res = @addModelResource(cachedModel, name, fetchOptions, 0)
|
res = @addModelResource(cachedModel, name, fetchOptions, 0)
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
c = require 'schemas/schemas'
|
c = require 'schemas/schemas'
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
'real-time-multiplayer:created-game': c.object {title: 'Multiplayer created game', required: ['session']},
|
'real-time-multiplayer:created-game': c.object {title: 'Multiplayer created game', required: ['realTimeSessionID']},
|
||||||
session: {type: 'object'}
|
realTimeSessionID: {type: 'string'}
|
||||||
|
|
||||||
'real-time-multiplayer:joined-game': c.object {title: 'Multiplayer joined game', required: ['id', 'session']},
|
'real-time-multiplayer:joined-game': c.object {title: 'Multiplayer joined game', required: ['realTimeSessionID']},
|
||||||
id: {type: 'string'}
|
realTimeSessionID: {type: 'string'}
|
||||||
session: {type: 'object'}
|
|
||||||
|
|
||||||
'real-time-multiplayer:left-game': c.object {title: 'Multiplayer left game', required: ['id']},
|
'real-time-multiplayer:left-game': c.object {title: 'Multiplayer left game'},
|
||||||
id: {type: 'string'}
|
userID: {type: 'string'}
|
||||||
|
|
||||||
'real-time-multiplayer:manual-cast': c.object {title: 'Multiplayer manual cast'}
|
'real-time-multiplayer:manual-cast': c.object {title: 'Multiplayer manual cast'}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ if !ladderGame
|
||||||
if ladderGame
|
if ladderGame
|
||||||
if me.get('anonymous')
|
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.
|
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
|
button#create-game-button Create Game
|
||||||
|
|
||||||
hr
|
hr
|
||||||
|
@ -37,7 +37,7 @@ if ladderGame
|
||||||
span(style="margin:10px")= currentRealTimeSession.id
|
span(style="margin:10px")= currentRealTimeSession.id
|
||||||
button#leave-game-button(data-item=item) Leave Game
|
button#leave-game-button(data-item=item) Leave Game
|
||||||
div
|
div
|
||||||
- var players = realTimeSessionPlayers[currentRealTimeSession.id]
|
- var players = realTimeSessionsPlayers[currentRealTimeSession.id]
|
||||||
if players
|
if players
|
||||||
span(style="margin:10px") Players:
|
span(style="margin:10px") Players:
|
||||||
- for (var i=0; i < players.length; i++) {
|
- for (var i=0; i < players.length; i++) {
|
||||||
|
@ -61,7 +61,7 @@ if ladderGame
|
||||||
- continue
|
- continue
|
||||||
if levelID === realTimeSessions.at(i).get('levelID') && realTimeSessions.at(i).get('state') === 'creating'
|
if levelID === realTimeSessions.at(i).get('levelID') && realTimeSessions.at(i).get('state') === 'creating'
|
||||||
- var id = realTimeSessions.at(i).get('id')
|
- var id = realTimeSessions.at(i).get('id')
|
||||||
- var players = realTimeSessionPlayers[id]
|
- var players = realTimeSessionsPlayers[id]
|
||||||
if players && players.length === 1
|
if players && players.length === 1
|
||||||
- noOpenGames = false
|
- noOpenGames = false
|
||||||
- var creatorName = realTimeSessions.at(i).get('creatorName')
|
- var creatorName = realTimeSessions.at(i).get('creatorName')
|
||||||
|
|
|
@ -26,7 +26,7 @@ module.exports = class MultiplayerView extends CocoView
|
||||||
@level = options.level
|
@level = options.level
|
||||||
@session = options.session
|
@session = options.session
|
||||||
@listenTo @session, 'change:multiplayer', @updateLinkSection
|
@listenTo @session, 'change:multiplayer', @updateLinkSection
|
||||||
@watchRealTimeSessions()
|
@watchRealTimeSessions() if @level?.get('type') in ['hero-ladder']
|
||||||
|
|
||||||
destroy: ->
|
destroy: ->
|
||||||
@realTimeSessions?.off 'add', @onRealTimeSessionAdded
|
@realTimeSessions?.off 'add', @onRealTimeSessionAdded
|
||||||
|
@ -46,15 +46,16 @@ module.exports = class MultiplayerView extends CocoView
|
||||||
c.readyToRank = @session?.readyToRank()
|
c.readyToRank = @session?.readyToRank()
|
||||||
|
|
||||||
# Real-time multiplayer stuff
|
# Real-time multiplayer stuff
|
||||||
c.levelID = @session.get('levelID')
|
if @level?.get('type') in ['hero-ladder']
|
||||||
c.realTimeSessions = @realTimeSessions
|
c.levelID = @session.get('levelID')
|
||||||
c.currentRealTimeSession = @currentRealTimeSession if @currentRealTimeSession
|
c.realTimeSessions = @realTimeSessions
|
||||||
c.realTimeSessionPlayers = @realTimeSessionsPlayers if @realTimeSessionsPlayers
|
c.currentRealTimeSession = @currentRealTimeSession if @currentRealTimeSession
|
||||||
# console.log 'MultiplayerView getRenderData', c.levelID
|
c.realTimeSessionsPlayers = @realTimeSessionsPlayers if @realTimeSessionsPlayers
|
||||||
# console.log 'realTimeSessions', c.realTimeSessions
|
# console.log 'MultiplayerView getRenderData', c.levelID
|
||||||
# console.log c.realTimeSessions.at(c.realTimeSessions.length - 1).get('state') if c.realTimeSessions.length > 0
|
# console.log 'realTimeSessions', c.realTimeSessions
|
||||||
# console.log 'currentRealTimeSession', c.currentRealTimeSession
|
# console.log c.realTimeSessions.at(c.realTimeSessions.length - 1).get('state') if c.realTimeSessions.length > 0
|
||||||
# console.log 'realTimeSessionPlayers', c.realTimeSessionPlayers
|
# console.log 'currentRealTimeSession', c.currentRealTimeSession
|
||||||
|
# console.log 'realTimeSessionPlayers', c.realTimeSessionsPlayers
|
||||||
|
|
||||||
c
|
c
|
||||||
|
|
||||||
|
@ -134,7 +135,9 @@ module.exports = class MultiplayerView extends CocoView
|
||||||
# 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/' + rts.id)
|
||||||
@currentRealTimeSession.on 'change', @onCurrentRealTimeSessionChanged
|
@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) =>
|
onRealTimeSessionAdded: (rts) =>
|
||||||
@watchRealTimeSession rts
|
@watchRealTimeSession rts
|
||||||
|
@ -168,8 +171,13 @@ module.exports = class MultiplayerView extends CocoView
|
||||||
@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/' + @currentRealTimeSession.id + '/players')
|
||||||
players.create id: me.id, state: 'coding', name: @session.get('creatorName'), team: @session.get('team')
|
players.create
|
||||||
Backbone.Mediator.publish 'real-time-multiplayer:created-game', session: @currentRealTimeSession
|
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()
|
@render()
|
||||||
|
|
||||||
onJoinRealTimeGame: (e) ->
|
onJoinRealTimeGame: (e) ->
|
||||||
|
@ -178,17 +186,31 @@ module.exports = class MultiplayerView extends CocoView
|
||||||
@currentRealTimeSession = @realTimeSessions.get(item.id)
|
@currentRealTimeSession = @realTimeSessions.get(item.id)
|
||||||
@currentRealTimeSession.on 'change', @onCurrentRealTimeSessionChanged
|
@currentRealTimeSession.on 'change', @onCurrentRealTimeSessionChanged
|
||||||
if @realTimeSessionsPlayers[item.id]
|
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
|
else
|
||||||
console.error 'MultiplayerView onJoinRealTimeGame did not have a players collection', @currentRealTimeSession
|
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()
|
@render()
|
||||||
|
|
||||||
onLeaveRealTimeGame: (e) ->
|
onLeaveRealTimeGame: (e) ->
|
||||||
if @currentRealTimeSession
|
if @currentRealTimeSession
|
||||||
@currentRealTimeSession.off 'change', @onCurrentRealTimeSessionChanged
|
@currentRealTimeSession.off 'change', @onCurrentRealTimeSessionChanged
|
||||||
@currentRealTimeSession = null
|
@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
|
else
|
||||||
console.error "Tried to leave a game with no currentMultiplayerSession"
|
console.error "Tried to leave a game with no currentMultiplayerSession"
|
||||||
@render()
|
@render()
|
||||||
|
|
|
@ -84,13 +84,14 @@ 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.session.id + '/flagHistory')
|
@realTimeFlags = new RealTimeCollection('multiplayer_level_sessions/' + e.realTimeSessionID + '/flagHistory')
|
||||||
@realTimeFlags.on 'add', @onRealTimeMultiplayerFlagAdded
|
@realTimeFlags.on 'add', @onRealTimeMultiplayerFlagAdded
|
||||||
@realTimeFlags.on 'remove', @onRealTimeMultiplayerFlagRemoved
|
@realTimeFlags.on 'remove', @onRealTimeMultiplayerFlagRemoved
|
||||||
|
|
||||||
onLeftMultiplayerGame: (e) ->
|
onLeftMultiplayerGame: (e) ->
|
||||||
if @realTimeFlags
|
if @realTimeFlags
|
||||||
@realTimeFlags.off()
|
@realTimeFlags.off 'add', @onRealTimeMultiplayerFlagAdded
|
||||||
|
@realTimeFlags.off 'remove', @onRealTimeMultiplayerFlagRemoved
|
||||||
@realTimeFlags = null
|
@realTimeFlags = null
|
||||||
|
|
||||||
onRealTimeMultiplayerFlagAdded: (e) =>
|
onRealTimeMultiplayerFlagAdded: (e) =>
|
||||||
|
|
|
@ -97,6 +97,9 @@ module.exports = class PlayLevelView extends RootView
|
||||||
|
|
||||||
@isEditorPreview = @getQueryVariable 'dev'
|
@isEditorPreview = @getQueryVariable 'dev'
|
||||||
@sessionID = @getQueryVariable 'session'
|
@sessionID = @getQueryVariable 'session'
|
||||||
|
|
||||||
|
@opponentSessionID = @getQueryVariable('opponent')
|
||||||
|
@opponentSessionID ?= @options.opponent
|
||||||
|
|
||||||
$(window).on 'resize', @onWindowResize
|
$(window).on 'resize', @onWindowResize
|
||||||
@saveScreenshot = _.throttle @saveScreenshot, 30000
|
@saveScreenshot = _.throttle @saveScreenshot, 30000
|
||||||
|
@ -131,7 +134,7 @@ module.exports = class PlayLevelView extends RootView
|
||||||
load: ->
|
load: ->
|
||||||
@loadStartTime = new Date()
|
@loadStartTime = new Date()
|
||||||
@god = new God debugWorker: true
|
@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
|
@listenToOnce @levelLoader, 'world-necessities-loaded', @onWorldNecessitiesLoaded
|
||||||
|
|
||||||
trackLevelLoadEnd: ->
|
trackLevelLoadEnd: ->
|
||||||
|
@ -170,7 +173,7 @@ module.exports = class PlayLevelView extends RootView
|
||||||
onWorldNecessitiesLoaded: ->
|
onWorldNecessitiesLoaded: ->
|
||||||
# Called when we have enough to build the world, but not everything is loaded
|
# Called when we have enough to build the world, but not everything is loaded
|
||||||
@grabLevelLoaderData()
|
@grabLevelLoaderData()
|
||||||
team = @getQueryVariable('team') ? @world.teamForPlayer(0)
|
team = @getQueryVariable('team') ? @session.get('team') ? @world.teamForPlayer(0)
|
||||||
@loadOpponentTeam(team)
|
@loadOpponentTeam(team)
|
||||||
@setupGod()
|
@setupGod()
|
||||||
@setTeam team
|
@setTeam team
|
||||||
|
@ -190,6 +193,8 @@ module.exports = class PlayLevelView extends RootView
|
||||||
@level = @levelLoader.level
|
@level = @levelLoader.level
|
||||||
@$el.addClass 'hero' if @level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop']
|
@$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'
|
@$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
|
@otherSession = @levelLoader.opponentSession
|
||||||
@worldLoadFakeResources = [] # first element (0) is 1%, last (100) is 100%
|
@worldLoadFakeResources = [] # first element (0) is 1%, last (100) is 100%
|
||||||
for percent in [1 .. 100]
|
for percent in [1 .. 100]
|
||||||
|
@ -211,7 +216,8 @@ module.exports = class PlayLevelView extends RootView
|
||||||
opponentSpells = opponentSpells.concat spells
|
opponentSpells = opponentSpells.concat spells
|
||||||
if (not @session.get('teamSpells')) and @otherSession?.get('teamSpells')
|
if (not @session.get('teamSpells')) and @otherSession?.get('teamSpells')
|
||||||
@session.set('teamSpells', @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 {}
|
myCode = @session.get('code') or {}
|
||||||
for spell in opponentSpells
|
for spell in opponentSpells
|
||||||
[thang, spell] = spell.split '/'
|
[thang, spell] = spell.split '/'
|
||||||
|
@ -232,6 +238,7 @@ module.exports = class PlayLevelView extends RootView
|
||||||
team = team?.team unless _.isString team
|
team = team?.team unless _.isString team
|
||||||
team ?= 'humans'
|
team ?= 'humans'
|
||||||
me.team = team
|
me.team = team
|
||||||
|
@session.set 'team', team
|
||||||
Backbone.Mediator.publish 'level:team-set', team: team # Needed for scripts
|
Backbone.Mediator.publish 'level:team-set', team: team # Needed for scripts
|
||||||
@team = team
|
@team = team
|
||||||
|
|
||||||
|
@ -248,7 +255,7 @@ module.exports = class PlayLevelView extends RootView
|
||||||
@insertSubView new HUDView {level: @level}
|
@insertSubView new HUDView {level: @level}
|
||||||
@insertSubView new LevelDialogueView {level: @level}
|
@insertSubView new LevelDialogueView {level: @level}
|
||||||
@insertSubView new ChatView levelID: @levelID, sessionID: @session.id, session: @session
|
@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 MultiplayerStatusView levelID: @levelID, session: @session, level: @level
|
||||||
@insertSubView new ProblemAlertView {}
|
@insertSubView new ProblemAlertView {}
|
||||||
worldName = utils.i18n @level.attributes, 'name'
|
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 = new LevelSetupManager({supermodel: @supermodel, levelID: @levelID, parent: @, session: @session})
|
||||||
@setupManager.open()
|
@setupManager.open()
|
||||||
|
|
||||||
@onRealTimeMultiplayerLevelLoaded e.session if e.level.get('type') in ['ladder', 'hero-ladder']
|
@onRealTimeMultiplayerLevelLoaded() if e.level.get('type') in ['hero-ladder']
|
||||||
|
|
||||||
onLoaded: ->
|
onLoaded: ->
|
||||||
_.defer => @onLevelLoaderLoaded()
|
_.defer => @onLevelLoaderLoaded()
|
||||||
|
@ -342,6 +349,9 @@ module.exports = class PlayLevelView extends RootView
|
||||||
@removeSubView @loadingView
|
@removeSubView @loadingView
|
||||||
@loadingView = null
|
@loadingView = null
|
||||||
@playAmbientSound()
|
@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
|
application.tracker?.trackEvent 'Play Level', Action: 'Start Level', levelID: @levelID
|
||||||
|
|
||||||
playAmbientSound: ->
|
playAmbientSound: ->
|
||||||
|
@ -540,7 +550,9 @@ module.exports = class PlayLevelView extends RootView
|
||||||
|
|
||||||
onSubmissionComplete: =>
|
onSubmissionComplete: =>
|
||||||
return if @destroyed
|
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: ->
|
destroy: ->
|
||||||
@levelLoader?.destroy()
|
@levelLoader?.destroy()
|
||||||
|
@ -572,6 +584,8 @@ module.exports = class PlayLevelView extends RootView
|
||||||
# Updates real-time multiplayer player state
|
# Updates real-time multiplayer player state
|
||||||
# Cleans up old sessions (sets state to 'finished')
|
# Cleans up old sessions (sets state to 'finished')
|
||||||
# Real-time multiplayer cast handshake
|
# 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:
|
# It monitors these:
|
||||||
# Real-time multiplayer sessions
|
# Real-time multiplayer sessions
|
||||||
|
@ -585,19 +599,23 @@ module.exports = class PlayLevelView extends RootView
|
||||||
# @realTimeOpponent - Current real-time multiplayer opponent
|
# @realTimeOpponent - Current real-time multiplayer opponent
|
||||||
# @realTimePlayers - Real-time players for current real-time multiplayer game session
|
# @realTimePlayers - Real-time players for current real-time multiplayer game session
|
||||||
# @realTimeSessionCollection - Collection of all real-time multiplayer sessions
|
# @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: 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')
|
return if me.get('anonymous')
|
||||||
players = new RealTimeCollection('multiplayer_players/' + @levelID)
|
unless @options.realTimeMultiplayerSessionID?
|
||||||
players.create
|
players = new RealTimeCollection('multiplayer_players/' + @levelID)
|
||||||
id: me.id
|
players.create
|
||||||
name: me.get('name')
|
id: me.id
|
||||||
state: 'playing'
|
name: me.get('name')
|
||||||
created: new Date().toISOString()
|
state: 'playing'
|
||||||
heartbeat: new Date().toISOString()
|
created: new Date().toISOString()
|
||||||
|
heartbeat: new Date().toISOString()
|
||||||
@realTimePlayerStatus = new RealTimeModel('multiplayer_players/' + @levelID + '/' + me.id)
|
@realTimePlayerStatus = new RealTimeModel('multiplayer_players/' + @levelID + '/' + me.id)
|
||||||
@timerMultiplayerHeartbeatID = setInterval @onRealTimeMultiplayerHeartbeat, 60 * 1000
|
@timerMultiplayerHeartbeatID = setInterval @onRealTimeMultiplayerHeartbeat, 60 * 1000
|
||||||
@cleanupRealTimeSessions()
|
@cleanupRealTimeSessions()
|
||||||
|
@ -608,6 +626,7 @@ module.exports = class PlayLevelView extends RootView
|
||||||
@realTimeSessionCollection.each @cleanupRealTimeSession
|
@realTimeSessionCollection.each @cleanupRealTimeSession
|
||||||
|
|
||||||
cleanupRealTimeSession: (session) =>
|
cleanupRealTimeSession: (session) =>
|
||||||
|
return if @options.realTimeMultiplayerSessionID? and @options.realTimeMultiplayerSessionID is session.id
|
||||||
if session.get('state') isnt 'finished'
|
if session.get('state') isnt 'finished'
|
||||||
players = new RealTimeCollection 'multiplayer_level_sessions/' + session.id + '/players'
|
players = new RealTimeCollection 'multiplayer_level_sessions/' + session.id + '/players'
|
||||||
players.each (player) =>
|
players.each (player) =>
|
||||||
|
@ -618,21 +637,34 @@ module.exports = class PlayLevelView extends RootView
|
||||||
session.set 'state', 'finished'
|
session.set 'state', 'finished'
|
||||||
|
|
||||||
onRealTimeMultiplayerLevelUnloaded: ->
|
onRealTimeMultiplayerLevelUnloaded: ->
|
||||||
clearInterval @timerMultiplayerHeartbeatID if @timerMultiplayerHeartbeatID?
|
# console.log 'PlayLevelView onRealTimeMultiplayerLevelUnloaded'
|
||||||
|
if @timerMultiplayerHeartbeatID?
|
||||||
|
clearInterval @timerMultiplayerHeartbeatID
|
||||||
|
@timerMultiplayerHeartbeatID = null
|
||||||
if @realTimeSessionCollection?
|
if @realTimeSessionCollection?
|
||||||
@realTimeSessionCollection.off 'add', @cleanupRealTimeSession
|
@realTimeSessionCollection.off 'add', @cleanupRealTimeSession
|
||||||
@realTimeSessionCollection = null
|
@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: =>
|
onRealTimeMultiplayerHeartbeat: =>
|
||||||
@realTimePlayerStatus.set 'heartbeat', new Date().toISOString() if @realTimePlayerStatus
|
@realTimePlayerStatus.set 'heartbeat', new Date().toISOString() if @realTimePlayerStatus
|
||||||
|
|
||||||
onRealTimeMultiplayerCreatedGame: (e) ->
|
onRealTimeMultiplayerCreatedGame: (e) ->
|
||||||
# Watch external multiplayer session
|
@joinRealTimeMultiplayerGame e
|
||||||
@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
|
|
||||||
@realTimePlayerGameStatus.set 'state', 'coding'
|
@realTimePlayerGameStatus.set 'state', 'coding'
|
||||||
@realTimePlayerStatus.set 'state', 'available'
|
@realTimePlayerStatus.set '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..'
|
||||||
|
@ -662,33 +694,72 @@ module.exports = class PlayLevelView extends RootView
|
||||||
console.info 'Real-time multiplayer opponent left the game'
|
console.info 'Real-time multiplayer opponent left the game'
|
||||||
opponentID = @realTimeOpponent.id
|
opponentID = @realTimeOpponent.id
|
||||||
@realTimeGameEnded()
|
@realTimeGameEnded()
|
||||||
Backbone.Mediator.publish 'real-time-multiplayer:left-game', id: 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: @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) ->
|
onRealTimeMultiplayerJoinedGame: (e) ->
|
||||||
# console.log 'PlayLevelView onRealTimeMultiplayerJoinedGame', e
|
# console.log 'PlayLevelView onRealTimeMultiplayerJoinedGame', e
|
||||||
if e.id is me.id
|
@joinRealTimeMultiplayerGame e
|
||||||
@realTimeSession = new RealTimeModel 'multiplayer_level_sessions/' + e.session.id
|
@realTimePlayerGameStatus.set 'state', 'coding'
|
||||||
@realTimeSession.set 'state', 'coding'
|
@realTimePlayerStatus.set 'state', 'unavailable'
|
||||||
@realTimeSession.on 'change', @onRealTimeSessionChanged
|
unless @realTimeOpponent?
|
||||||
@realTimePlayers = new RealTimeCollection 'multiplayer_level_sessions/' + e.session.id + '/players'
|
for id, player of @realTimeSession.get('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')
|
|
||||||
if id isnt me.id
|
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
|
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.id? and e.id is me.id
|
if e.userID? and e.userID is me.id
|
||||||
@realTimePlayerGameStatus.set 'state', 'left'
|
@realTimePlayerGameStatus.set 'state', 'left'
|
||||||
@realTimeGameEnded()
|
@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: ->
|
realTimeGameEnded: ->
|
||||||
if @realTimeOpponent?
|
if @realTimeOpponent?
|
||||||
@realTimeOpponent.off 'change', @onRealTimeOpponentChanged
|
@realTimeOpponent.off 'change', @onRealTimeOpponentChanged
|
||||||
|
@ -707,9 +778,21 @@ module.exports = class PlayLevelView extends RootView
|
||||||
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
|
||||||
unless @realTimeSession
|
unless @realTimeSession
|
||||||
console.error 'onRealTimeMultiplayerCast without a multiplayerSession'
|
console.error 'onRealTimeMultiplayerCast without a multiplayerSession'
|
||||||
return
|
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')
|
players = new RealTimeCollection('multiplayer_level_sessions/' + @realTimeSession.id + '/players')
|
||||||
myPlayer = opponentPlayer = null
|
myPlayer = opponentPlayer = null
|
||||||
players.each (player) ->
|
players.each (player) ->
|
||||||
|
@ -719,10 +802,8 @@ module.exports = class PlayLevelView extends RootView
|
||||||
opponentPlayer = player
|
opponentPlayer = player
|
||||||
if myPlayer
|
if myPlayer
|
||||||
console.info 'Submitting my code'
|
console.info 'Submitting my code'
|
||||||
myPlayer.set 'code', @session.get('code')
|
@session.patch()
|
||||||
myPlayer.set 'codeLanguage', @session.get('codeLanguage')
|
|
||||||
myPlayer.set 'state', 'submitted'
|
myPlayer.set 'state', 'submitted'
|
||||||
myPlayer.set 'team', me.team
|
|
||||||
else
|
else
|
||||||
console.error 'Did not find my player in onRealTimeMultiplayerCast'
|
console.error 'Did not find my player in onRealTimeMultiplayerCast'
|
||||||
if opponentPlayer
|
if opponentPlayer
|
||||||
|
@ -738,6 +819,9 @@ module.exports = class PlayLevelView extends RootView
|
||||||
state = opponentPlayer.get('state')
|
state = opponentPlayer.get('state')
|
||||||
if state in ['submitted', 'ready']
|
if state in ['submitted', 'ready']
|
||||||
@realTimeOpponentSubmittedCode opponentPlayer, myPlayer
|
@realTimeOpponentSubmittedCode opponentPlayer, myPlayer
|
||||||
|
opponentPlayer.off 'change'
|
||||||
|
else
|
||||||
|
console.error 'Did not find opponent player in onRealTimeMultiplayerCast'
|
||||||
|
|
||||||
onRealTimeMultiplayerPlaybackEnded: ->
|
onRealTimeMultiplayerPlaybackEnded: ->
|
||||||
if @realTimeSession?
|
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')
|
Backbone.Mediator.publish 'real-time-multiplayer:player-status', status: 'Playing against ' + @realTimeOpponent.get('name')
|
||||||
|
|
||||||
realTimeOpponentSubmittedCode: (opponentPlayer, myPlayer) =>
|
realTimeOpponentSubmittedCode: (opponentPlayer, myPlayer) =>
|
||||||
# Save opponent's code
|
# console.log 'PlayLevelView realTimeOpponentSubmittedCode', @realTimeSession.id, opponentPlayer.get('level_session')
|
||||||
Backbone.Mediator.publish 'real-time-multiplayer:new-opponent-code', {codeLanguage: opponentPlayer.get('codeLanguage'), code: opponentPlayer.get('code'), team: opponentPlayer.get('team')}
|
# Read submissionCount for joined real-time multiplayer session
|
||||||
# I'm ready to rumble
|
if me.id isnt @realTimeSession.get('creator')
|
||||||
myPlayer.set 'state', 'ready'
|
sessionState = @session.get('state') ? {}
|
||||||
if opponentPlayer.get('state') is 'ready'
|
newSubmissionCount = @realTimeSession.get 'submissionCount'
|
||||||
console.info 'All real-time multiplayer players are ready!'
|
if newSubmissionCount?
|
||||||
@realTimeSession.set 'state', 'running'
|
# TODO: This isn't always getting updated where the random seed generation uses it.
|
||||||
Backbone.Mediator.publish 'real-time-multiplayer:player-status', status: 'Battling ' + @realTimeOpponent.get('name')
|
sessionState.submissionCount = parseInt newSubmissionCount
|
||||||
else
|
console.info 'Got multiplayer submissionCount', sessionState.submissionCount
|
||||||
# Wait for opponent to be ready
|
@session.set 'state', sessionState
|
||||||
opponentPlayer.on 'change', (e) =>
|
@session.patch()
|
||||||
if opponentPlayer.get('state') is 'ready'
|
|
||||||
opponentPlayer.off 'change'
|
# Reload this level so the opponent session can easily be wired up
|
||||||
console.info 'All real-time multiplayer players are ready!'
|
Backbone.Mediator.publish 'router:navigate',
|
||||||
@realTimeSession.set 'state', 'running'
|
route: "/play/level/#{@levelID}"
|
||||||
Backbone.Mediator.publish 'real-time-multiplayer:player-status', status: 'Battling ' + @realTimeOpponent.get('name')
|
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'
|
||||||
|
|
|
@ -63,14 +63,8 @@ module.exports = class CastButtonView extends CocoView
|
||||||
Backbone.Mediator.publish 'tome:manual-cast', {}
|
Backbone.Mediator.publish 'tome:manual-cast', {}
|
||||||
|
|
||||||
onCastRealTimeButtonClick: (e) ->
|
onCastRealTimeButtonClick: (e) ->
|
||||||
if @multiplayerSession
|
if @inRealTimeMultiplayerSession
|
||||||
Backbone.Mediator.publish 'real-time-multiplayer:manual-cast', {}
|
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
|
else
|
||||||
Backbone.Mediator.publish 'tome:manual-cast', {realTime: true}
|
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)
|
$(@).toggleClass('selected', parseInt($(@).attr('data-delay')) is delay)
|
||||||
|
|
||||||
onJoinedRealTimeMultiplayerGame: (e) ->
|
onJoinedRealTimeMultiplayerGame: (e) ->
|
||||||
@multiplayerSession = e.session
|
@inRealTimeMultiplayerSession = true
|
||||||
|
|
||||||
onLeftRealTimeMultiplayerGame: (e) ->
|
onLeftRealTimeMultiplayerGame: (e) ->
|
||||||
if @multiplayerSession
|
@inRealTimeMultiplayerSession = false
|
||||||
@multiplayerSession.off 'change'
|
|
||||||
@multiplayerSession = null
|
|
||||||
|
|
||||||
initButtonTextABTest: ->
|
initButtonTextABTest: ->
|
||||||
return if me.isAdmin()
|
return if me.isAdmin()
|
||||||
|
|
|
@ -46,20 +46,18 @@ module.exports = class Spell
|
||||||
@source = @originalSource = p.aiSource
|
@source = @originalSource = p.aiSource
|
||||||
@thangs = {}
|
@thangs = {}
|
||||||
if @canRead() # We can avoid creating these views if we'll never use them.
|
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
|
@view.render() # Get it ready and code loaded in advance
|
||||||
@tabView = new SpellListTabEntryView spell: @, supermodel: @supermodel, codeLanguage: @language, level: options.level
|
@tabView = new SpellListTabEntryView spell: @, supermodel: @supermodel, codeLanguage: @language, level: options.level
|
||||||
@tabView.render()
|
@tabView.render()
|
||||||
@team = @permissions.readwrite[0] ? 'common'
|
@team = @permissions.readwrite[0] ? 'common'
|
||||||
Backbone.Mediator.publish 'tome:spell-created', spell: @
|
Backbone.Mediator.publish 'tome:spell-created', spell: @
|
||||||
Backbone.Mediator.subscribe 'real-time-multiplayer:new-opponent-code', @onNewOpponentCode, @
|
|
||||||
|
|
||||||
destroy: ->
|
destroy: ->
|
||||||
@view?.destroy()
|
@view?.destroy()
|
||||||
@tabView?.destroy()
|
@tabView?.destroy()
|
||||||
@thangs = null
|
@thangs = null
|
||||||
@worker = null
|
@worker = null
|
||||||
Backbone.Mediator.unsubscribe 'real-time-multiplayer:new-opponent-code', @onNewOpponentCode, @
|
|
||||||
|
|
||||||
setLanguage: (@language) ->
|
setLanguage: (@language) ->
|
||||||
#console.log 'setting language to', @language, 'so using original source', @languages[language] ? @languages.javascript
|
#console.log 'setting language to', @language, 'so using original source', @languages[language] ? @languages.javascript
|
||||||
|
@ -187,22 +185,13 @@ module.exports = class Spell
|
||||||
|
|
||||||
shouldUseTranspiledCode: ->
|
shouldUseTranspiledCode: ->
|
||||||
# Determine whether this code has already been transpiled, or whether it's raw source needing transpilation.
|
# 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 @spectateView # Use transpiled code for both teams if we're just spectating.
|
||||||
return true if @isEnemySpell() # Use transpiled for enemy spells.
|
return true if @isEnemySpell() # Use transpiled for enemy spells.
|
||||||
# Players without permissions can't view the raw code.
|
# 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))
|
return true if @session.get('creator') isnt me.id and not (me.isAdmin() or 'employer' in me.get('permissions', true))
|
||||||
false
|
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) ->
|
createProblemContext: (thang) ->
|
||||||
# Create problemContext Aether can use to craft better error messages
|
# 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)
|
# stringReferences: values that should be referred to as a string instead of a variable (e.g. "Brak", not Brak)
|
||||||
|
|
|
@ -237,8 +237,12 @@ module.exports = class TomeView extends CocoView
|
||||||
@cast()
|
@cast()
|
||||||
|
|
||||||
onSelectPrimarySprite: (e) ->
|
onSelectPrimarySprite: (e) ->
|
||||||
# TODO: this may not be correct
|
# This is only fired by PlayLevelView for hero levels currently
|
||||||
Backbone.Mediator.publish 'level:select-sprite', thangID: 'Hero Placeholder'
|
# 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: ->
|
destroy: ->
|
||||||
spell.destroy() for spellKey, spell of @spells
|
spell.destroy() for spellKey, spell of @spells
|
||||||
|
|
|
@ -68,10 +68,6 @@ LevelHandler = class LevelHandler extends Handler
|
||||||
if req.query.team?
|
if req.query.team?
|
||||||
sessionQuery.team = 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) =>
|
Session.findOne(sessionQuery).exec (err, doc) =>
|
||||||
return @sendDatabaseError(res, err) if err
|
return @sendDatabaseError(res, err) if err
|
||||||
return @sendSuccess(res, doc) if doc?
|
return @sendSuccess(res, doc) if doc?
|
||||||
|
|
|
@ -38,7 +38,8 @@ LevelSessionSchema.pre 'save', (next) ->
|
||||||
LevelSessionSchema.statics.privateProperties = ['code', 'submittedCode', 'unsubscribed']
|
LevelSessionSchema.statics.privateProperties = ['code', 'submittedCode', 'unsubscribed']
|
||||||
LevelSessionSchema.statics.editableProperties = ['multiplayer', 'players', 'code', 'codeLanguage', 'completed', 'state',
|
LevelSessionSchema.statics.editableProperties = ['multiplayer', 'players', 'code', 'codeLanguage', 'completed', 'state',
|
||||||
'levelName', 'creatorName', 'levelID', 'screenshot',
|
'levelName', 'creatorName', 'levelID', 'screenshot',
|
||||||
'chat', 'teamSpells', 'submitted', 'submittedCodeLanguage', 'unsubscribed', 'playtime', 'heroConfig']
|
'chat', 'teamSpells', 'submitted', 'submittedCodeLanguage',
|
||||||
|
'unsubscribed', 'playtime', 'heroConfig', 'team']
|
||||||
LevelSessionSchema.statics.jsonSchema = jsonschema
|
LevelSessionSchema.statics.jsonSchema = jsonschema
|
||||||
|
|
||||||
LevelSessionSchema.index {user: 1, changed: -1}, {sparse: true, name: 'last played index'}
|
LevelSessionSchema.index {user: 1, changed: -1}, {sparse: true, name: 'last played index'}
|
||||||
|
|
Reference in a new issue