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
|
2015-09-02 17:54:44 -04:00
|
|
|
@levelID = @level.get('slug') 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
|
2015-11-24 11:56:28 -05:00
|
|
|
c.campaignIndex = @level.get('campaignIndex') if @level.get('type') is 'course'
|
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}]
|
2015-07-24 20:37:42 -04:00
|
|
|
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}"
|
2014-10-18 17:51:43 -04:00
|
|
|
else if @level.get('type', true) in ['hero', 'hero-coop']
|
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
|
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) ->
|
|
|
|
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()
|