2014-11-28 20:49:41 -05:00
CocoView = require ' views/core/CocoView '
2014-01-03 13:32:13 -05:00
template = require ' templates/play/level/control_bar '
2014-11-28 20:49:41 -05:00
{ me } = require ' core/auth '
2014-01-03 13:32:13 -05:00
2014-11-29 15:46:04 -05:00
GameMenuModal = require ' views/play/menu/GameMenuModal '
2014-11-18 12:01:04 -05:00
RealTimeModel = require ' models/RealTimeModel '
Real-time multiplayer initial commit
Simple matchmaking, synchronous multiplayer PVP, flags!
Rough matchmaking is under the game menu multiplayer tab, for ladder
games only. After creating a 2-person game there, you can exit that
modal and real-time cast to play against each other.
If you’re the first person to cast, you’ll sit at the real-time level
playback view waiting until the other player casts. When they do, you
both should start the real-time playback (and start placing flags like
crazy people).
If in a multiplayer session, the real-time simulation runs the players’
code against each other. Your multiplayer opponent’s name should be up
near the level name.
Multiplayer sessions are stored completely in Firebase for now, and
removed if both players leave the game. There’s plenty of bugs,
synchronization issues, and minimal polish to add before we push it to
master.
2014-08-29 02:34:07 -04:00
RealTimeCollection = require ' collections/RealTimeCollection '
2014-11-07 11:54:22 -05:00
LevelSetupManager = require ' lib/LevelSetupManager '
2014-11-29 15:46:04 -05:00
GameMenuModal = require ' views/play/menu/GameMenuModal '
2014-01-03 13:32:13 -05:00
2014-07-17 20:20:11 -04:00
module.exports = class ControlBarView extends CocoView
2014-06-30 22:16:26 -04:00
id: ' control-bar-view '
2014-01-03 13:32:13 -05:00
template: template
subscriptions:
' bus:player-states-changed ' : ' onPlayerStatesChanged '
2014-11-08 11:58:36 -05:00
' level:disable-controls ' : ' onDisableControls '
' level:enable-controls ' : ' onEnableControls '
2014-11-18 14:21:29 -05:00
' ipad:memory-warning ' : ' onIPadMemoryWarning '
2014-01-03 13:32:13 -05:00
events:
2014-08-27 15:24:03 -04:00
' click # next-game-button ' : -> Backbone . Mediator . publish ' level:next-game-pressed ' , { }
2014-08-26 01:05:24 -04:00
' click # game-menu-button ' : ' showGameMenuModal '
' click ' : -> Backbone . Mediator . publish ' tome:focus-editor ' , { }
2014-11-20 14:11:27 -05:00
' click .levels-link-area ' : ' onClickHome '
2014-09-25 03:02:53 -04:00
' click .home a ' : ' onClickHome '
2014-11-18 12:01:04 -05:00
' click .multiplayer-area ' : ' onClickMultiplayer '
2014-12-08 01:44:20 -05:00
' click # control-bar-sign-up-button ' : ' onClickSignupButton '
2014-09-25 03:02:53 -04:00
2014-01-03 13:32:13 -05:00
constructor: (options) ->
2015-11-12 12:57:34 -05:00
@courseID = options . courseID
@courseInstanceID = options . courseInstanceID
2015-09-24 20:12:18 -04:00
2014-01-03 13:32:13 -05:00
@worldName = options . worldName
@session = options . session
@level = options . level
2016-06-08 09:24:59 -04:00
@levelSlug = @ level . get ( ' slug ' )
@levelID = @ levelSlug or @ level . id
2014-03-12 20:51:09 -04:00
@spectateGame = options . spectateGame ? false
2015-01-31 13:04:02 -05:00
@observing = options . session . get ( ' creator ' ) isnt me . id
2014-01-03 13:32:13 -05:00
super options
2015-07-24 20:37:42 -04:00
if @ level . get ( ' type ' ) in [ ' hero-ladder ' , ' course-ladder ' ] and me . isAdmin ( )
2014-12-09 17:30:47 -05:00
@isMultiplayerLevel = true
2014-11-18 12:01:04 -05:00
@multiplayerStatusManager = new MultiplayerStatusManager @ levelID , @ onMultiplayerStateChanged
2015-01-16 15:50:10 -05:00
if @ level . get ' replayable '
@ listenTo @ session , ' change-difficulty ' , @ onSessionDifficultyChanged
2014-01-03 13:32:13 -05:00
setBus: (@bus) ->
onPlayerStatesChanged: (e) ->
2014-02-12 15:41:41 -05:00
# TODO: this doesn't fire any more. Replacement?
2014-01-03 13:32:13 -05:00
return unless @ bus is e . bus
numPlayers = _ . keys ( e . players ) . length
return if numPlayers is @ numPlayers
@numPlayers = numPlayers
text = ' Multiplayer '
text += " ( #{ numPlayers } ) " if numPlayers > 1
$ ( ' # multiplayer-button ' , @ $el ) . text ( text )
2014-11-18 12:01:04 -05:00
onMultiplayerStateChanged: => @ render ? ( )
2014-02-26 21:58:25 -05:00
getRenderData: (c={}) ->
super c
c.worldName = @ worldName
2016-06-08 19:57:00 -04:00
c.campaignIndex = @ level . get ( ' campaignIndex ' ) + 1 if @ level . get ( ' type ' ) is ' course ' and @ level . get ( ' campaignIndex ' ) ? # TODO: support 'game-dev' levels in courses
2014-02-26 21:58:25 -05:00
c.multiplayerEnabled = @ session . get ( ' multiplayer ' )
2015-07-24 20:37:42 -04:00
c.ladderGame = @ level . get ( ' type ' ) in [ ' ladder ' , ' hero-ladder ' , ' course-ladder ' ]
2014-11-18 12:01:04 -05:00
if c.isMultiplayerLevel = @ isMultiplayerLevel
c.multiplayerStatus = @ multiplayerStatusManager ? . status
2015-01-16 15:50:10 -05:00
if @ level . get ' replayable '
c.levelDifficulty = @ session . get ( ' state ' ) ? . difficulty ? 0
2015-02-04 14:31:43 -05:00
if @ observing
c.levelDifficulty = Math . max 0 , c . levelDifficulty - 1 # Show the difficulty they won, not the next one.
2015-01-16 15:50:10 -05:00
c.difficultyTitle = " #{ $ . i18n . t ' play.level_difficulty ' } #{ c . levelDifficulty } "
@lastDifficulty = c . levelDifficulty
2014-03-12 20:51:09 -04:00
c.spectateGame = @ spectateGame
2015-01-31 13:04:02 -05:00
c.observing = @ observing
2014-11-18 14:21:29 -05:00
@homeViewArgs = [ { supermodel: if @ hasReceivedMemoryWarning then null else @ supermodel } ]
2016-04-15 13:58:52 -04:00
if me . isSessionless ( )
2016-04-08 15:59:10 -04:00
@homeLink = " /teachers/courses "
@homeViewClass = " views/courses/TeacherCoursesView "
else if @ level . get ( ' type ' , true ) in [ ' ladder ' , ' ladder-tutorial ' , ' hero-ladder ' , ' course-ladder ' ]
2015-09-02 17:54:44 -04:00
levelID = @ level . get ( ' slug ' ) ? . replace ( /\-tutorial$/ , ' ' ) or @ level . id
2015-07-24 20:37:42 -04:00
@homeLink = ' /play/ladder/ ' + levelID
2014-11-29 19:46:36 -05:00
@homeViewClass = ' views/ladder/LadderView '
2014-09-25 03:02:53 -04:00
@ homeViewArgs . push levelID
2015-09-04 19:21:35 -04:00
if leagueID = @ getQueryVariable ' league '
leagueType = if @ level . get ( ' type ' ) is ' course-ladder ' then ' course ' else ' clan '
@ homeViewArgs . push leagueType
@ homeViewArgs . push leagueID
@ homeLink += " / #{ leagueType } / #{ leagueID } "
2016-02-17 14:33:50 -05:00
else if @ level . get ( ' type ' , true ) in [ ' hero ' , ' hero-coop ' ] or window . serverConfig . picoCTF
2015-07-24 20:37:42 -04:00
@homeLink = ' /play '
2014-12-28 16:25:20 -05:00
@homeViewClass = ' views/play/CampaignView '
campaign = @ level . get ' campaign '
2015-02-05 18:05:22 -05:00
@ homeLink += ' / ' + campaign
@ homeViewArgs . push campaign
2015-09-04 19:21:35 -04:00
else if @ level . get ( ' type ' , true ) in [ ' course ' ]
2015-09-24 20:12:18 -04:00
@homeLink = ' /courses '
@homeViewClass = ' views/courses/CoursesView '
if @ courseID
@ homeLink += " / #{ @ courseID } "
@ homeViewArgs . push @ courseID
@homeViewClass = ' views/courses/CourseDetailsView '
if @ courseInstanceID
2015-09-24 20:52:00 -04:00
@ homeLink += " / #{ @ courseInstanceID } "
2015-09-24 20:12:18 -04:00
@ homeViewArgs . push @ courseInstanceID
2016-06-08 19:57:00 -04:00
#else if @level.get('type', true) is 'game-dev' # TODO
2014-05-05 18:33:08 -04:00
else
2015-07-24 20:37:42 -04:00
@homeLink = ' / '
2014-11-29 19:46:36 -05:00
@homeViewClass = ' views/HomeView '
2015-09-02 17:54:44 -04:00
c.editorLink = " /editor/level/ #{ @ level . get ( ' slug ' ) or @ level . id } "
2014-11-11 19:36:44 -05:00
c.homeLink = @ homeLink
2014-02-26 21:58:25 -05:00
c
2014-01-03 13:32:13 -05:00
2015-08-13 14:09:26 -04:00
showGameMenuModal: (e, tab=null) ->
2015-08-13 08:58:37 -04:00
gameMenuModal = new GameMenuModal level: @ level , session: @ session , supermodel: @ supermodel , showTab: tab
2014-11-07 11:54:22 -05:00
@ openModalView gameMenuModal
@ listenToOnce gameMenuModal , ' change-hero ' , ->
2014-11-09 19:19:18 -05:00
@ setupManager ? . destroy ( )
2015-11-12 14:00:54 -05:00
@setupManager = new LevelSetupManager ( { supermodel: @ supermodel , level: @ level , levelID: @ levelID , parent: @ , session: @ session , courseID: @ courseID , courseInstanceID: @ courseInstanceID } )
2014-11-09 19:19:18 -05:00
@ setupManager . open ( )
Real-time multiplayer initial commit
Simple matchmaking, synchronous multiplayer PVP, flags!
Rough matchmaking is under the game menu multiplayer tab, for ladder
games only. After creating a 2-person game there, you can exit that
modal and real-time cast to play against each other.
If you’re the first person to cast, you’ll sit at the real-time level
playback view waiting until the other player casts. When they do, you
both should start the real-time playback (and start placing flags like
crazy people).
If in a multiplayer session, the real-time simulation runs the players’
code against each other. Your multiplayer opponent’s name should be up
near the level name.
Multiplayer sessions are stored completely in Firebase for now, and
removed if both players leave the game. There’s plenty of bugs,
synchronization issues, and minimal polish to add before we push it to
master.
2014-08-29 02:34:07 -04:00
2014-09-25 03:02:53 -04:00
onClickHome: (e) ->
2016-06-08 09:24:59 -04:00
if @ level . get ( ' type ' , true ) in [ ' course ' ]
category = if me . isTeacher ( ) then ' Teachers ' else ' Students '
window . tracker ? . trackEvent ' Play Level Back To Levels ' , category: category , levelSlug: @ levelSlug , [ ' Mixpanel ' ]
2014-09-25 03:02:53 -04:00
e . preventDefault ( )
e . stopImmediatePropagation ( )
Backbone . Mediator . publish ' router:navigate ' , route: @ homeLink , viewClass: @ homeViewClass , viewArgs: @ homeViewArgs
2014-11-08 11:58:36 -05:00
2014-11-18 12:01:04 -05:00
onClickMultiplayer: (e) ->
2015-08-13 14:09:26 -04:00
@ showGameMenuModal e , ' multiplayer '
2014-11-18 12:01:04 -05:00
2015-08-13 14:09:26 -04:00
onClickSignupButton: (e) ->
2014-12-08 01:44:20 -05:00
window . tracker ? . trackEvent ' Started Signup ' , category: ' Play Level ' , label: ' Control Bar ' , level: @ levelID
2014-11-08 11:58:36 -05:00
onDisableControls: (e) -> @ toggleControls e , false
onEnableControls: (e) -> @ toggleControls e , true
toggleControls: (e, enabled) ->
return if e . controls and not ( ' level ' in e . controls )
return if enabled is @ controlsEnabled
@controlsEnabled = enabled
@ $el . toggleClass ' controls-disabled ' , not enabled
2014-11-09 19:19:18 -05:00
2014-11-18 14:21:29 -05:00
onIPadMemoryWarning: (e) ->
@hasReceivedMemoryWarning = true
2015-01-16 15:50:10 -05:00
onSessionDifficultyChanged: ->
return if @ session . get ( ' state ' ) ? . difficulty is @ lastDifficulty
@ render ( )
2014-11-09 19:19:18 -05:00
destroy: ->
@ setupManager ? . destroy ( )
2014-11-18 12:01:04 -05:00
@ multiplayerStatusManager ? . destroy ( )
2014-11-09 19:19:18 -05:00
super ( )
2014-11-18 12:01:04 -05:00
# MultiplayerStatusManager ######################################################
#
# Manages the multiplayer status, and calls @statusChangedCallback when it changes.
#
# It monitors these:
# Real-time multiplayer players
# Internal multiplayer status
#
# Real-time state variables:
# @playersCollection - Real-time multiplayer players
#
# TODO: Not currently using player counts. Should remove if we keep simple design.
#
class MultiplayerStatusManager
constructor: (@levelID, @statusChangedCallback) ->
@status = ' '
# @players = {}
# @playersCollection = new RealTimeCollection('multiplayer_players/' + @levelID)
# @playersCollection.on 'add', @onPlayerAdded
# @playersCollection.each (player) => @onPlayerAdded player
Backbone . Mediator . subscribe ' real-time-multiplayer:player-status ' , @ onMultiplayerPlayerStatus
destroy: ->
Backbone . Mediator . unsubscribe ' real-time-multiplayer:player-status ' , @ onMultiplayerPlayerStatus
# @playersCollection?.off 'add', @onPlayerAdded
# player.off 'change', @onPlayerChanged for id, player of @players
2014-11-20 14:11:27 -05:00
onMultiplayerPlayerStatus: (e) =>
2014-11-18 12:01:04 -05:00
@status = e . status
@ statusChangedCallback ( )
# onPlayerAdded: (player) =>
# unless player.id is me.id
# @players[player.id] = new RealTimeModel('multiplayer_players/' + @levelID + '/' + player.id)
# @players[player.id].on 'change', @onPlayerChanged
# @countPlayers player
2014-11-20 14:11:27 -05:00
#
2014-11-18 12:01:04 -05:00
# onPlayerChanged: (player) =>
# @countPlayers player
2014-11-20 14:11:27 -05:00
#
2014-11-18 12:01:04 -05:00
# countPlayers: (changedPlayer) =>
# # TODO: save this stale hearbeat threshold setting somewhere
# staleHeartbeat = new Date()
# staleHeartbeat.setMinutes staleHeartbeat.getMinutes() - 3
# @playerCount = 0
# @playersCollectionAvailable = 0
# @playersCollectionUnavailable = 0
# @playersCollection.each (player) =>
# # Assume changedPlayer is fresher than entry in @playersCollection collection
# player = changedPlayer if changedPlayer? and player.id is changedPlayer.id
# unless staleHeartbeat >= new Date(player.get('heartbeat'))
# @playerCount++
# @playersCollectionAvailable++ if player.get('state') is 'available'
# @playersCollectionUnavailable++ if player.get('state') is 'unavailable'
# @statusChangedCallback()