mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-03-13 22:49:51 -04:00
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
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'}
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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) =>
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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'}
|
||||
|
|
Loading…
Reference in a new issue