codecombat/app/views/play/SpectateView.coffee

311 lines
11 KiB
CoffeeScript
Raw Normal View History

RootView = require 'views/kinds/RootView'
template = require 'templates/play/spectate'
2014-06-30 22:16:26 -04:00
{me} = require 'lib/auth'
ThangType = require 'models/ThangType'
utils = require 'lib/utils'
World = require 'lib/world/world'
# tools
Surface = require 'lib/surface/Surface'
God = require 'lib/God' # 'lib/Buddha'
GoalManager = require 'lib/world/GoalManager'
ScriptManager = require 'lib/scripts/ScriptManager'
LevelLoader = require 'lib/LevelLoader'
LevelSession = require 'models/LevelSession'
Level = require 'models/Level'
LevelComponent = require 'models/LevelComponent'
2014-03-11 16:12:40 -04:00
Article = require 'models/Article'
Camera = require 'lib/surface/Camera'
2014-03-11 16:12:40 -04:00
AudioPlayer = require 'lib/AudioPlayer'
# subviews
LoadingView = require './level/LevelLoadingView'
TomeView = require './level/tome/TomeView'
ChatView = require './level/LevelChatView'
HUDView = require './level/LevelHUDView'
ControlBarView = require './level/ControlBarView'
PlaybackView = require './level/LevelPlaybackView'
GoalsView = require './level/LevelGoalsView'
GoldView = require './level/LevelGoldView'
VictoryModal = require './level/modal/VictoryModal'
InfiniteLoopModal = require './level/modal/InfiniteLoopModal'
PROFILE_ME = false
module.exports = class SpectateLevelView extends RootView
id: 'spectate-level-view'
template: template
cache: false
isEditorPreview: false
subscriptions:
'level:set-volume': (e) -> createjs.Sound.setVolume(e.volume)
'god:new-world-created': 'onNewWorld'
'god:streaming-world-updated': 'onNewWorld'
'god:infinite-loop': 'onInfiniteLoop'
'level:next-game-pressed': 'onNextGamePressed'
2014-03-14 20:06:08 -04:00
'level:started': 'onLevelStarted'
'level:loading-view-unveiled': 'onLoadingViewUnveiled'
constructor: (options, @levelID) ->
console.profile?() if PROFILE_ME
super options
2014-03-14 20:06:08 -04:00
2014-03-14 15:54:52 -04:00
@sessionOne = @getQueryVariable 'session-one'
@sessionTwo = @getQueryVariable 'session-two'
if options.spectateSessions
@sessionOne = options.spectateSessions.sessionOne
@sessionTwo = options.spectateSessions.sessionTwo
2014-03-16 23:32:24 -04:00
2014-03-14 15:54:52 -04:00
if not @sessionOne or not @sessionTwo
@fetchRandomSessionPair (err, data) =>
if err? then return console.log "There was an error fetching the random session pair: #{data}"
@sessionOne = data[0]._id
@sessionTwo = data[1]._id
@load()
else
@load()
setLevel: (@level, @supermodel) ->
serializedLevel = @level.serialize @supermodel, @session
@god?.setLevel serializedLevel
if @world
@world.loadFromLevel serializedLevel, false
else
@load()
load: ->
@levelLoader = new LevelLoader
supermodel: @supermodel
2014-03-12 20:51:09 -04:00
levelID: @levelID
sessionID: @sessionOne
opponentSessionID: @sessionTwo
spectateMode: true
2014-06-30 22:16:26 -04:00
team: @getQueryVariable('team')
@god = new God maxAngels: 1, spectate: true
getRenderData: ->
c = super()
c.world = @world
c
afterRender: ->
window.onPlayLevelViewLoaded? @ # still a hack
2014-03-14 20:06:08 -04:00
@insertSubView @loadingView = new LoadingView {}
2014-03-11 16:12:40 -04:00
@$el.find('#level-done-button').hide()
super()
$('body').addClass('is-playing')
onLoaded: ->
_.defer => @onLevelLoaderLoaded()
onLevelLoaderLoaded: ->
2014-03-11 16:12:40 -04:00
@grabLevelLoaderData()
2014-03-12 20:51:09 -04:00
#at this point, all requisite data is loaded, and sessions are not denormalized
2014-03-12 10:32:27 -04:00
team = @world.teamForPlayer(0)
2014-03-11 16:12:40 -04:00
@loadOpponentTeam(team)
@god.setLevel @level.serialize @supermodel, @session
@god.setLevelSessionIDs if @otherSession then [@session.id, @otherSession.id] else [@session.id]
@god.setWorldClassMap @world.classMap
2014-03-11 16:12:40 -04:00
@setTeam team
@initSurface()
@initGoalManager()
@initScriptManager()
2014-05-05 18:33:08 -04:00
@insertSubviews()
@initVolume()
2014-03-14 20:06:08 -04:00
@originalSessionState = $.extend(true, {}, @session.get('state'))
@register()
@controlBar.setBus(@bus)
@surface.showLevel()
if @level.get('type', true) isnt 'hero'
if me.id isnt @session.get 'creator'
@surface.createOpponentWizard
id: @session.get('creator')
name: @session.get('creatorName')
team: @session.get('team')
levelSlug: @level.get('slug')
2014-03-12 20:51:09 -04:00
@surface.createOpponentWizard
id: @otherSession.get('creator')
name: @otherSession.get('creatorName')
team: @otherSession.get('team')
levelSlug: @level.get('slug')
2014-03-11 16:12:40 -04:00
grabLevelLoaderData: ->
@session = @levelLoader.session
@world = @levelLoader.world
@level = @levelLoader.level
@otherSession = @levelLoader.opponentSession
@levelLoader.destroy()
@levelLoader = null
loadOpponentTeam: (myTeam) ->
opponentSpells = []
for spellTeam, spells of @session.get('teamSpells') ? @otherSession?.get('teamSpells') ? {}
continue if spellTeam is myTeam or not myTeam
opponentSpells = opponentSpells.concat spells
opponentCode = @otherSession?.get('transpiledCode') or {}
myCode = @session.get('transpiledCode') or {}
2014-03-11 16:12:40 -04:00
for spell in opponentSpells
[thang, spell] = spell.split '/'
c = opponentCode[thang]?[spell]
myCode[thang] ?= {}
if c then myCode[thang][spell] = c else delete myCode[thang][spell]
2014-06-30 22:16:26 -04:00
2014-03-11 16:12:40 -04:00
@session.set('code', myCode)
if @session.get('multiplayer') and @otherSession?
# For now, ladderGame will disallow multiplayer, because session code combining doesn't play nice yet.
@session.set 'multiplayer', false
2014-03-14 20:06:08 -04:00
onLevelStarted: (e) ->
2014-09-21 18:52:49 -04:00
go = =>
@loadingView?.startUnveiling()
@loadingView?.unveil()
_.delay go, 1000
2014-03-14 20:06:08 -04:00
onLoadingViewUnveiled: (e) ->
# Don't remove it; we want its decoration around on large screens.
#@removeSubView @loadingView
#@loadingView = null
Backbone.Mediator.publish 'level:set-playing', playing: true
onSupermodelLoadedOne: =>
@modelsLoaded ?= 0
@modelsLoaded += 1
@updateInitString()
updateInitString: ->
return if @surface
@modelsLoaded ?= 0
canvas = @$el.find('#surface')[0]
ctx = canvas.getContext('2d')
2014-06-30 22:16:26 -04:00
ctx.font='20px Georgia'
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.fillText("Loaded #{@modelsLoaded} thingies",50,50)
2014-05-05 18:33:08 -04:00
insertSubviews: ->
@insertSubView @tome = new TomeView levelID: @levelID, session: @session, thangs: @world.thangs, supermodel: @supermodel, spectateView: true, spectateOpponentCodeLanguage: @otherSession?.get('submittedCodeLanguage'), level: @level
@insertSubView new PlaybackView {}
2014-03-12 10:32:27 -04:00
@insertSubView new GoldView {}
@insertSubView new HUDView {}
worldName = utils.i18n @level.attributes, 'name'
2014-03-12 20:51:09 -04:00
@controlBar = @insertSubView new ControlBarView {worldName: worldName, session: @session, level: @level, supermodel: @supermodel, playableTeams: @world.playableTeams, spectateGame: true}
2014-03-11 16:12:40 -04:00
# callbacks
onInfiniteLoop: (e) ->
return unless e.firstWorld
@openModalView new InfiniteLoopModal()
window.tracker?.trackEvent 'Saw Initial Infinite Loop', level: @world.name, label: @world.name
# initialization
initSurface: ->
surfaceCanvas = $('canvas#surface', @$el)
@surface = new Surface(@world, surfaceCanvas, thangTypes: @supermodel.getModels(ThangType), playJingle: not @isEditorPreview, spectateGame: true, wizards: @level.get('type', true) isnt 'hero')
worldBounds = @world.getBounds()
bounds = [{x:worldBounds.left, y:worldBounds.top}, {x:worldBounds.right, y:worldBounds.bottom}]
@surface.camera.setBounds(bounds)
2014-03-16 23:32:24 -04:00
zoom = =>
@surface.camera.zoomTo({x: (worldBounds.right - worldBounds.left) / 2, y: (worldBounds.top - worldBounds.bottom) / 2}, 0.1, 0)
_.delay zoom, 4000 # call it later for some reason (TODO: figure this out)
initGoalManager: ->
2014-03-11 16:12:40 -04:00
@goalManager = new GoalManager(@world, @level.get('goals'))
@god.setGoalManager @goalManager
initScriptManager: ->
2014-03-13 18:22:35 -04:00
if @world.scripts
nonVictoryPlaybackScripts = _.reject @world.scripts, (script) ->
script.id.indexOf('Set Camera Boundaries') is -1
2014-03-13 18:22:35 -04:00
else
2014-06-30 22:16:26 -04:00
console.log 'World scripts don\'t exist!'
2014-03-13 18:22:35 -04:00
nonVictoryPlaybackScripts = []
@scriptManager = new ScriptManager({scripts: nonVictoryPlaybackScripts, view:@, session: @session})
@scriptManager.loadFromSession()
initVolume: ->
volume = me.get('volume')
volume = 1.0 unless volume?
Backbone.Mediator.publish 'level:set-volume', volume: volume
2014-03-12 20:51:09 -04:00
register: -> return
onSessionWillSave: (e) ->
# Something interesting has happened, so (at a lower frequency), we'll save a screenshot.
2014-06-30 22:16:26 -04:00
console.log 'Session is saving but shouldn\'t save!!!!!!!'
# Throttled
saveScreenshot: (session) =>
return unless screenshot = @surface?.screenshot()
session.save {screenshot: screenshot}, {patch: true}
setTeam: (team) ->
team = team?.team unless _.isString team
team ?= 'humans'
me.team = team
Backbone.Mediator.publish 'level:team-set', team: team
2014-03-11 16:12:40 -04:00
# Dynamic sound loading
onNewWorld: (e) ->
2014-03-11 16:12:40 -04:00
return if @headless
scripts = @world.scripts # Since these worlds don't have scripts, preserve them.
@world = e.world
@world.scripts = scripts
2014-03-11 16:12:40 -04:00
thangTypes = @supermodel.getModels(ThangType)
startFrame = @lastWorldFramesLoaded ? 0
if @world.frames.length is @world.totalFrames # Finished loading
@lastWorldFramesLoaded = 0
else
@lastWorldFramesLoaded = @world.frames.length
for [spriteName, message] in @world.thangDialogueSounds startFrame
2014-03-11 16:12:40 -04:00
continue unless thangType = _.find thangTypes, (m) -> m.get('name') is spriteName
continue unless sound = AudioPlayer.soundForDialogue message, thangType.get('soundTriggers')
AudioPlayer.preloadSoundReference sound
2014-03-12 20:51:09 -04:00
onNextGamePressed: (e) ->
@fetchRandomSessionPair (err, data) =>
if err? then return console.log "There was an error fetching the random session pair: #{data}"
@sessionOne = data[0]._id
@sessionTwo = data[1]._id
url = "/play/spectate/#{@levelID}?session-one=#{@sessionOne}&session-two=#{@sessionTwo}"
Backbone.Mediator.publish 'router:navigate', {
route: url,
viewClass: SpectateLevelView,
viewArgs: [
{
spectateSessions: {sessionOne: @sessionOne, sessionTwo: @sessionTwo}
supermodel: @supermodel
}
2014-06-30 22:16:26 -04:00
@levelID
]
}
history?.pushState? {}, '', url # Backbone won't update the URL if just query parameters change
fetchRandomSessionPair: (cb) ->
2014-06-30 22:16:26 -04:00
console.log 'Fetching random session pair!'
2014-03-14 15:54:52 -04:00
randomSessionPairURL = "/db/level/#{@levelID}/random_session_pair"
$.ajax
url: randomSessionPairURL
2014-06-30 22:16:26 -04:00
type: 'GET'
complete: (jqxhr, textStatus) ->
2014-06-30 22:16:26 -04:00
if textStatus isnt 'success'
cb('error', jqxhr.statusText)
else
cb(null, $.parseJSON(jqxhr.responseText))
2014-03-14 20:06:08 -04:00
2014-03-13 18:22:35 -04:00
destroy: ()->
@levelLoader?.destroy()
@surface?.destroy()
@god?.destroy()
@goalManager?.destroy()
@scriptManager?.destroy()
delete window.world # not sure where this is set, but this is one way to clean it up
console.profileEnd?() if PROFILE_ME
2014-03-13 18:22:35 -04:00
super()