2014-07-17 20:16:32 -04:00
RootView = require ' views/kinds/RootView '
2014-01-03 13:32:13 -05:00
template = require ' templates/play/level '
2014-06-30 22:16:26 -04:00
{ me } = require ' lib/auth '
2014-01-03 13:32:13 -05:00
ThangType = require ' models/ThangType '
2014-03-13 12:02:19 -04:00
utils = require ' lib/utils '
2014-08-30 17:30:53 -04:00
storage = require ' lib/storage '
2014-01-03 13:32:13 -05:00
# tools
Surface = require ' lib/surface/Surface '
God = require ' lib/God '
GoalManager = require ' lib/world/GoalManager '
ScriptManager = require ' lib/scripts/ScriptManager '
2014-05-05 20:37:14 -04:00
LevelBus = require ' lib/LevelBus '
2014-01-03 13:32:13 -05:00
LevelLoader = require ' lib/LevelLoader '
LevelSession = require ' models/LevelSession '
Level = require ' models/Level '
LevelComponent = require ' models/LevelComponent '
2014-03-07 18:18:56 -05:00
Article = require ' models/Article '
2014-01-03 13:32:13 -05:00
Camera = require ' lib/surface/Camera '
2014-02-19 14:42:33 -05:00
AudioPlayer = require ' lib/AudioPlayer '
2014-08-30 00:46:26 -04: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-01-03 13:32:13 -05:00
# subviews
2014-07-23 10:02:45 -04:00
LevelLoadingView = require ' ./LevelLoadingView '
TomeView = require ' ./tome/TomeView '
ChatView = require ' ./LevelChatView '
HUDView = require ' ./LevelHUDView '
ControlBarView = require ' ./ControlBarView '
LevelPlaybackView = require ' ./LevelPlaybackView '
GoalsView = require ' ./LevelGoalsView '
2014-08-23 22:00:35 -04:00
LevelFlagsView = require ' ./LevelFlagsView '
2014-07-23 10:02:45 -04:00
GoldView = require ' ./LevelGoldView '
VictoryModal = require ' ./modal/VictoryModal '
InfiniteLoopModal = require ' ./modal/InfiniteLoopModal '
2014-01-03 13:32:13 -05:00
PROFILE_ME = false
2014-07-17 20:16:32 -04:00
module.exports = class PlayLevelView extends RootView
2014-01-03 13:32:13 -05:00
id: ' level-view '
template: template
cache: false
shortcutsEnabled: true
isEditorPreview: false
subscriptions:
2014-08-27 15:24:03 -04:00
' level:set-volume ' : (e) -> createjs . Sound . setVolume ( e . volume )
' level:show-victory ' : ' onShowVictory '
' level:restart ' : ' onRestartLevel '
' level:highlight-dom ' : ' onHighlightDom '
' level:end-highlight-dom ' : ' onEndHighlight '
' level:focus-dom ' : ' onFocusDom '
' level:disable-controls ' : ' onDisableControls '
' level:enable-controls ' : ' onEnableControls '
2014-08-30 12:19:41 -04:00
' god:world-load-progress-changed ' : ' onWorldLoadProgressChanged '
2014-02-06 17:00:27 -05:00
' god:new-world-created ' : ' onNewWorld '
2014-08-21 19:27:52 -04:00
' god:streaming-world-updated ' : ' onNewWorld '
2014-01-03 13:32:13 -05:00
' god:infinite-loop ' : ' onInfiniteLoop '
2014-08-27 15:24:03 -04:00
' level:reload-from-data ' : ' onLevelReloadFromData '
' level:reload-thang-type ' : ' onLevelReloadThangType '
' level:play-next-level ' : ' onPlayNextLevel '
' level:edit-wizard-settings ' : ' showWizardSettingsModal '
2014-01-03 13:32:13 -05:00
' surface:world-set-up ' : ' onSurfaceSetUpNewWorld '
' level:session-will-save ' : ' onSessionWillSave '
2014-03-14 20:06:08 -04:00
' level:started ' : ' onLevelStarted '
' level:loading-view-unveiled ' : ' onLoadingViewUnveiled '
2014-08-30 00:46:26 -04:00
' playback:real-time-playback-waiting ' : ' onRealTimePlaybackWaiting '
2014-08-23 16:54:52 -04:00
' playback:real-time-playback-started ' : ' onRealTimePlaybackStarted '
' playback:real-time-playback-ended ' : ' onRealTimePlaybackEnded '
2014-08-29 18:10:04 -04:00
' real-time-multiplayer:joined-game ' : ' onJoinedRealTimeMultiplayerGame '
' real-time-multiplayer:left-game ' : ' onLeftRealTimeMultiplayerGame '
' real-time-multiplayer:manual-cast ' : ' onRealTimeMultiplayerCast '
2014-09-20 18:18:21 -04:00
' level:hero-config-changed ' : ' onHeroConfigChanged '
2014-01-03 13:32:13 -05:00
events:
' click # level-done-button ' : ' onDonePressed '
2014-08-27 21:43:17 -04:00
' click # fullscreen-editor-background-screen ' : (e) -> Backbone . Mediator . publish ' tome:toggle-maximize ' , { }
2014-01-03 13:32:13 -05:00
2014-02-20 18:11:20 -05:00
shortcuts:
' ctrl+s ' : ' onCtrlS '
2014-05-19 23:49:17 -04:00
2014-05-19 20:10:41 -04:00
# Initial Setup #############################################################
2014-02-20 18:11:20 -05:00
2014-01-03 13:32:13 -05:00
constructor: (options, @levelID) ->
console . profile ? ( ) if PROFILE_ME
super options
2014-06-30 22:16:26 -04:00
if not me . get ( ' hourOfCode ' ) and @ getQueryVariable ' hour_of_code '
2014-05-19 20:10:41 -04:00
@ setUpHourOfCode ( )
2014-01-03 13:32:13 -05:00
2014-01-27 14:00:36 -05:00
@isEditorPreview = @ getQueryVariable ' dev '
@sessionID = @ getQueryVariable ' session '
2014-01-03 13:32:13 -05:00
2014-08-29 20:52:47 -04:00
$ ( window ) . on ' resize ' , @ onWindowResize
2014-01-03 13:32:13 -05:00
@saveScreenshot = _ . throttle @ saveScreenshot , 30000
2014-01-16 14:37:04 -05:00
2014-01-27 14:00:36 -05:00
if @ isEditorPreview
2014-07-17 18:50:29 -04:00
@supermodel.shouldSaveBackups = (model) -> # Make sure to load possibly changed things from localStorage.
model . constructor . className in [ ' Level ' , ' LevelComponent ' , ' LevelSystem ' , ' ThangType ' ]
f = => @ load ( ) unless @ levelLoader # Wait to see if it's just given to us through setLevel.
2014-01-27 14:00:36 -05:00
setTimeout f , 100
else
@ load ( )
2014-06-21 12:47:48 -04:00
application . tracker ? . trackEvent ' Started Level Load ' , level: @ levelID , label: @ levelID , [ ' Google Analytics ' ]
2014-05-19 23:49:17 -04:00
2014-05-19 20:10:41 -04:00
setUpHourOfCode: ->
me . set ' hourOfCode ' , true
2014-06-11 17:17:31 -04:00
me . patch ( )
2014-06-30 22:16:26 -04:00
$ ( ' body ' ) . append ( $ ( ' <img src= " http://code.org/api/hour/begin_codecombat.png " style= " visibility: hidden; " > ' ) )
2014-05-19 20:10:41 -04:00
application . tracker ? . trackEvent ' Hour of Code Begin ' , { }
2014-02-12 15:41:41 -05:00
2014-05-01 19:38:27 -04:00
setLevel: (@level, givenSupermodel) ->
@supermodel.models = givenSupermodel . models
@supermodel.collections = givenSupermodel . collections
@supermodel.shouldSaveBackups = givenSupermodel . shouldSaveBackups
2014-08-14 19:34:55 -04:00
serializedLevel = @ level . serialize @ supermodel , @ session
2014-05-15 17:54:31 -04:00
@ god ? . setLevel serializedLevel
2014-01-21 02:14:34 -05:00
if @ world
@ world . loadFromLevel serializedLevel , false
else
@ load ( )
2014-01-16 14:37:04 -05:00
2014-01-15 21:37:47 -05:00
load: ->
2014-04-13 23:31:23 -04:00
@loadStartTime = new Date ( )
2014-05-10 21:24:50 -04:00
@god = new God debugWorker: true
2014-06-30 22:16:26 -04:00
@levelLoader = new LevelLoader supermodel: @ supermodel , levelID: @ levelID , sessionID: @ sessionID , opponentSessionID: @ getQueryVariable ( ' opponent ' ) , team: @ getQueryVariable ( ' team ' )
2014-05-19 20:10:41 -04:00
@ listenToOnce @ levelLoader , ' world-necessities-loaded ' , @ onWorldNecessitiesLoaded
# CocoView overridden methods ###############################################
2014-05-19 23:49:17 -04:00
2014-05-19 20:10:41 -04:00
updateProgress: (progress) ->
super ( progress )
return if @ seenDocs
2014-09-06 22:50:31 -04:00
return if @ isIPadApp ( )
2014-05-19 20:10:41 -04:00
return unless @ levelLoader . session . loaded and @ levelLoader . level . loaded
return unless showFrequency = @ levelLoader . level . get ( ' showsGuide ' )
session = @ levelLoader . session
diff = new Date ( ) . getTime ( ) - new Date ( session . get ( ' created ' ) ) . getTime ( )
return if showFrequency is ' first-time ' and diff > ( 5 * 60 * 1000 )
articles = @ levelLoader . supermodel . getModels Article
for article in articles
return unless article . loaded
@ showGuide ( )
showGuide: ->
@seenDocs = true
2014-07-29 13:45:01 -04:00
LevelGuideModal = require ' ./modal/LevelGuideModal '
2014-05-20 01:25:40 -04:00
options =
docs: @ levelLoader . level . get ( ' documentation ' )
supermodel: @ supermodel
firstOnly: true
2014-07-29 13:45:01 -04:00
@ openModalView ( new LevelGuideModal ( options ) , true )
2014-08-27 15:24:03 -04:00
onGuideOpened = (e) ->
2014-07-13 19:07:15 -04:00
@guideOpenTime = new Date ( )
2014-08-27 15:24:03 -04:00
onGuideClosed = (e) ->
2014-07-13 19:07:15 -04:00
application . tracker ? . trackTiming new Date ( ) - @ guideOpenTime , ' Intro Guide Time ' , @ levelID , @ levelID , 100
@ onLevelStarted ( )
2014-08-27 15:24:03 -04:00
Backbone . Mediator . subscribeOnce ' modal:opened ' , onGuideOpened , @
Backbone . Mediator . subscribeOnce ' modal:closed ' , onGuideClosed , @
2014-05-19 20:10:41 -04:00
return true
2014-01-03 13:32:13 -05:00
getRenderData: ->
c = super ( )
c.world = @ world
2014-08-23 18:51:59 -04:00
if me . get ( ' hourOfCode ' ) and me . get ( ' preferredLanguage ' , true ) is ' en-US '
2014-02-23 14:48:34 -05:00
# Show the Hour of Code footer explanation until it's been more than a day
elapsed = ( new Date ( ) - new Date ( me . get ( ' dateCreated ' ) ) )
c.explainHourOfCode = elapsed < 86400 * 1000
2014-01-03 13:32:13 -05:00
c
afterRender: ->
2014-04-17 19:23:35 -04:00
super ( )
2014-01-03 13:32:13 -05:00
window . onPlayLevelViewLoaded ? @ # still a hack
2014-07-23 10:02:45 -04:00
@ insertSubView @loadingView = new LevelLoadingView { }
2014-02-13 12:26:21 -05:00
@ $el . find ( ' # level-done-button ' ) . hide ( )
2014-04-11 19:15:26 -04:00
$ ( ' body ' ) . addClass ( ' is-playing ' )
2014-09-06 22:50:31 -04:00
$ ( ' body ' ) . bind ( ' touchmove ' , false ) if @ isIPadApp ( )
2014-01-03 13:32:13 -05:00
2014-05-19 20:10:41 -04:00
afterInsert: ->
super ( )
2014-09-06 22:50:31 -04:00
@ showWizardSettingsModal ( ) if not me . get ( ' name ' ) and not @ isIPadApp ( )
2014-03-07 18:18:56 -05:00
2014-05-19 20:10:41 -04:00
# Partially Loaded Setup ####################################################
2014-05-19 23:49:17 -04:00
2014-05-19 20:10:41 -04:00
onWorldNecessitiesLoaded: ->
# Called when we have enough to build the world, but not everything is loaded
@ grabLevelLoaderData ( )
2014-06-30 22:16:26 -04:00
team = @ getQueryVariable ( ' team ' ) ? @ world . teamForPlayer ( 0 )
2014-05-02 15:32:41 -04:00
@ loadOpponentTeam ( team )
2014-05-19 20:10:41 -04:00
@ setupGod ( )
2014-05-02 15:32:41 -04:00
@ setTeam team
@ initGoalManager ( )
2014-05-19 20:10:41 -04:00
@ insertSubviews ( )
2014-05-02 15:32:41 -04:00
@ initVolume ( )
@ listenTo ( @ session , ' change:multiplayer ' , @ onMultiplayerChanged )
@originalSessionState = $ . extend ( true , { } , @ session . get ( ' state ' ) )
@ register ( )
@ controlBar . setBus ( @ bus )
2014-01-03 13:32:13 -05:00
@ initScriptManager ( )
2014-03-13 12:02:19 -04:00
2014-02-27 19:44:11 -05:00
grabLevelLoaderData: ->
@session = @ levelLoader . session
@world = @ levelLoader . world
@level = @ levelLoader . level
@otherSession = @ levelLoader . opponentSession
2014-08-30 12:19:41 -04:00
@worldLoadFakeResources = [ ] # first element (0) is 1%, last (100) is 100%
for percent in [ 1 . . 100 ]
@ worldLoadFakeResources . push @ supermodel . addSomethingResource " world_simulation_ #{ percent } % " , 1
onWorldLoadProgressChanged: (e) ->
return unless @ worldLoadFakeResources
@ lastWorldLoadPercent ? = 0
worldLoadPercent = Math . floor 100 * e . progress
for percent in [ @ lastWorldLoadPercent + 1 . . worldLoadPercent ] by 1
@ worldLoadFakeResources [ percent - 1 ] . markLoaded ( )
@lastWorldLoadPercent = worldLoadPercent
@worldFakeLoadResources = null if worldLoadPercent is 100 # Done, don't need to watch progress any more.
2014-03-13 12:02:19 -04:00
2014-02-27 19:44:11 -05:00
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
2014-05-16 20:38:33 -04:00
if ( not @ session . get ( ' teamSpells ' ) ) and @ otherSession ? . get ( ' teamSpells ' )
2014-06-30 22:16:26 -04:00
@ session . set ( ' teamSpells ' , @ otherSession . get ( ' teamSpells ' ) )
2014-05-15 18:18:15 -04:00
opponentCode = @ otherSession ? . get ( ' transpiledCode ' ) or { }
2014-02-27 19:44:11 -05:00
myCode = @ session . get ( ' code ' ) or { }
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 ]
@ 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-01-03 13:32:13 -05:00
2014-05-19 20:10:41 -04:00
setupGod: ->
2014-08-14 19:34:55 -04:00
@ god . setLevel @ level . serialize @ supermodel , @ session
2014-05-19 20:10:41 -04:00
@ god . setLevelSessionIDs if @ otherSession then [ @ session . id , @ otherSession . id ] else [ @ session . id ]
@ god . setWorldClassMap @ world . classMap
setTeam: (team) ->
team = team ? . team unless _ . isString team
team ? = ' humans '
me.team = team
2014-08-27 15:24:03 -04:00
Backbone . Mediator . publish ' level:team-set ' , team: team # Needed for scripts
2014-05-19 23:50:05 -04:00
@team = team
2014-05-19 20:10:41 -04:00
initGoalManager: ->
2014-05-19 23:50:05 -04:00
@goalManager = new GoalManager ( @ world , @ level . get ( ' goals ' ) , @ team )
2014-05-19 20:10:41 -04:00
@ god . setGoalManager @ goalManager
insertSubviews: ->
2014-06-20 20:19:18 -04:00
@ insertSubView @tome = new TomeView levelID: @ levelID , session: @ session , otherSession: @ otherSession , thangs: @ world . thangs , supermodel: @ supermodel
2014-07-23 10:02:45 -04:00
@ insertSubView new LevelPlaybackView session: @ session
2014-05-19 20:10:41 -04:00
@ insertSubView new GoalsView { }
2014-08-23 22:00:35 -04:00
@ insertSubView new LevelFlagsView world: @ world
2014-05-19 20:10:41 -04:00
@ insertSubView new GoldView { }
@ insertSubView new HUDView { }
@ insertSubView new ChatView levelID: @ levelID , sessionID: @ session . id , session: @ session
worldName = utils . i18n @ level . attributes , ' name '
@controlBar = @ insertSubView new ControlBarView { worldName: worldName , session: @ session , level: @ level , supermodel: @ supermodel , playableTeams: @ world . playableTeams }
2014-09-06 22:50:31 -04:00
Backbone . Mediator . publish ( ' level:set-debug ' , debug: true ) if @ isIPadApp ( ) # if me.displayName() is 'Nick'
2014-05-19 20:10:41 -04:00
initVolume: ->
volume = me . get ( ' volume ' )
volume = 1.0 unless volume ?
2014-08-27 15:24:03 -04:00
Backbone . Mediator . publish ' level:set-volume ' , volume: volume
2014-05-19 20:10:41 -04:00
initScriptManager: ->
2014-06-30 22:16:26 -04:00
@scriptManager = new ScriptManager ( { scripts: @ world . scripts or [ ] , view: @ , session: @ session } )
2014-05-19 20:10:41 -04:00
@ scriptManager . loadFromSession ( )
register: ->
@bus = LevelBus . get ( @ levelID , @ session . id )
@ bus . setSession ( @ session )
@ bus . setSpells @ tome . spells
@ bus . connect ( ) if @ session . get ( ' multiplayer ' )
2014-05-19 23:49:17 -04:00
2014-05-19 20:10:41 -04:00
# Load Completed Setup ######################################################
onLoaded: ->
_ . defer => @ onLevelLoaded ( )
2014-05-19 23:49:17 -04:00
2014-05-19 20:10:41 -04:00
onLevelLoaded: ->
# Everything is now loaded
2014-05-19 23:49:17 -04:00
return unless @ levelLoader . progress ( ) is 1 # double check, since closing the guide may trigger this early
2014-05-19 20:10:41 -04:00
2014-08-30 17:30:53 -04:00
# Save latest level played.
2014-05-19 20:10:41 -04:00
if not ( @ levelLoader . level . get ( ' type ' ) in [ ' ladder ' , ' ladder-tutorial ' ] )
me . set ( ' lastLevel ' , @ levelID )
me . save ( )
2014-08-30 17:30:53 -04:00
@ saveRecentMatch ( ) if @ otherSession
2014-05-19 20:10:41 -04:00
@ levelLoader . destroy ( )
@levelLoader = null
@ initSurface ( )
2014-08-30 17:30:53 -04:00
saveRecentMatch: ->
allRecentlyPlayedMatches = storage . load ( ' recently-played-matches ' ) ? { }
recentlyPlayedMatches = allRecentlyPlayedMatches [ @ levelID ] ? [ ]
allRecentlyPlayedMatches [ @ levelID ] = recentlyPlayedMatches
recentlyPlayedMatches . unshift yourTeam: me . team , otherSessionID: @ otherSession . id , opponentName: @ otherSession . get ( ' creatorName ' ) unless _ . find recentlyPlayedMatches , otherSessionID: @ otherSession . id
recentlyPlayedMatches . splice ( 8 )
storage . save ' recently-played-matches ' , allRecentlyPlayedMatches
2014-05-19 20:10:41 -04:00
initSurface: ->
surfaceCanvas = $ ( ' canvas # surface ' , @ $el )
@surface = new Surface ( @ world , surfaceCanvas , thangTypes: @ supermodel . getModels ( ThangType ) , playJingle: not @ isEditorPreview )
worldBounds = @ world . getBounds ( )
2014-06-30 22:16:26 -04:00
bounds = [ { x: worldBounds . left , y: worldBounds . top } , { x: worldBounds . right , y: worldBounds . bottom } ]
2014-05-19 20:10:41 -04:00
@ surface . camera . setBounds ( bounds )
2014-06-30 22:16:26 -04:00
@ surface . camera . zoomTo ( { x: 0 , y: 0 } , 0.1 , 0 )
2014-05-19 20:10:41 -04:00
# Once Surface is Loaded ####################################################
onLevelStarted: ->
2014-07-07 19:44:18 -04:00
return unless @ surface ?
2014-05-19 23:49:17 -04:00
@ loadingView . showReady ( )
if window . currentModal and not window . currentModal . destroyed
2014-08-27 15:24:03 -04:00
return Backbone . Mediator . subscribeOnce ' modal:closed ' , @ onLevelStarted , @
2014-05-03 12:13:26 -04:00
@ surface . showLevel ( )
if @ otherSession
# TODO: colorize name and cloud by team, colorize wizard by user's color config
2014-07-13 23:19:51 -04:00
@ surface . createOpponentWizard id: @ otherSession . get ( ' creator ' ) , name: @ otherSession . get ( ' creatorName ' ) , team: @ otherSession . get ( ' team ' ) , levelSlug: @ level . get ( ' slug ' ) , codeLanguage: @ otherSession . get ( ' submittedCodeLanguage ' )
2014-03-14 20:06:08 -04:00
@ loadingView ? . unveil ( )
onLoadingViewUnveiled: (e) ->
2014-04-28 17:01:33 -04:00
@ loadingView . $el . remove ( )
2014-03-14 20:06:08 -04:00
@ removeSubView @ loadingView
@loadingView = null
2014-04-13 23:31:23 -04:00
unless @ isEditorPreview
@loadEndTime = new Date ( )
loadDuration = @ loadEndTime - @ loadStartTime
2014-05-20 13:46:52 -04:00
console . debug " Level unveiled after #{ ( loadDuration / 1000 ) . toFixed ( 2 ) } s "
2014-06-21 12:47:48 -04:00
application . tracker ? . trackEvent ' Finished Level Load ' , level: @ levelID , label: @ levelID , loadDuration: loadDuration , [ ' Google Analytics ' ]
2014-04-13 23:31:23 -04:00
application . tracker ? . trackTiming loadDuration , ' Level Load Time ' , @ levelID , @ levelID
2014-03-13 12:02:19 -04:00
2014-05-19 20:10:41 -04:00
onSurfaceSetUpNewWorld: ->
return if @ alreadyLoadedState
@alreadyLoadedState = true
state = @ originalSessionState
if state . frame and @ level . get ( ' type ' ) isnt ' ladder ' # https://github.com/codecombat/codecombat/issues/714
2014-08-27 15:24:03 -04:00
Backbone . Mediator . publish ' level:set-time ' , time: 0 , frameOffset: state . frame
2014-05-19 20:10:41 -04:00
if state . selected
# TODO: Should also restore selected spell here by saving spellName
2014-08-27 15:24:03 -04:00
Backbone . Mediator . publish ' level:select-sprite ' , thangID: state . selected , spellName: null
2014-09-06 22:50:31 -04:00
else if @ isIPadApp ( )
Backbone . Mediator . publish ' tome:select-primary-sprite ' , { }
2014-05-19 20:10:41 -04:00
if state . playing ?
2014-08-27 15:24:03 -04:00
Backbone . Mediator . publish ' level:set-playing ' , playing: state . playing
2014-01-03 13:32:13 -05:00
# callbacks
2014-02-20 18:11:20 -05:00
onCtrlS: (e) ->
e . preventDefault ( )
2014-01-03 13:32:13 -05:00
onLevelReloadFromData: (e) ->
isReload = Boolean @ world
2014-08-12 15:56:55 -04:00
@ setLevel e . level , e . supermodel
2014-01-03 13:32:13 -05:00
if isReload
@ scriptManager . setScripts ( e . level . get ( ' scripts ' ) )
2014-08-31 18:08:52 -04:00
Backbone . Mediator . publish ' tome:cast-spell ' , { } # a bit hacky
2014-01-03 13:32:13 -05:00
2014-08-12 15:50:41 -04:00
onLevelReloadThangType: (e) ->
tt = e . thangType
for url , model of @ supermodel . models
if model . id is tt . id
for key , val of tt . attributes
model . attributes [ key ] = val
break
2014-08-31 18:08:52 -04:00
Backbone . Mediator . publish ' tome:cast-spell ' , { }
2014-09-20 18:18:21 -04:00
onHeroConfigChanged: (e) ->
2014-08-31 18:08:52 -04:00
# Doesn't work because the new inventory ThangTypes may not be loaded.
#@setLevel @level, @supermodel
#Backbone.Mediator.publish 'tome:cast-spell', {}
# We'll just make a new PlayLevelView instead
Backbone . Mediator . publish ' router:navigate ' , {
route: window . location . pathname ,
viewClass: PlayLevelView ,
viewArgs: [ { supermodel: @ supermodel } , @ levelID ] }
2014-08-12 15:50:41 -04:00
2014-01-03 13:32:13 -05:00
onWindowResize: (s...) ->
$ ( ' # pointer ' ) . css ( ' opacity ' , 0.0 )
2014-09-18 21:25:33 -04:00
clearInterval ( @ pointerInterval )
2014-01-03 13:32:13 -05:00
2014-02-12 15:41:41 -05:00
onDisableControls: (e) ->
2014-01-03 13:32:13 -05:00
return if e . controls and not ( ' level ' in e . controls )
@shortcutsEnabled = false
@wasFocusedOn = document . activeElement
$ ( ' body ' ) . focus ( )
2014-02-12 15:41:41 -05:00
onEnableControls: (e) ->
2014-01-03 13:32:13 -05:00
return if e . controls ? and not ( ' level ' in e . controls )
@shortcutsEnabled = true
$ ( @ wasFocusedOn ) . focus ( ) if @ wasFocusedOn
@wasFocusedOn = null
2014-02-12 15:41:41 -05:00
onDonePressed: -> @ showVictory ( )
2014-01-03 13:32:13 -05:00
2014-02-12 15:41:41 -05:00
onShowVictory: (e) ->
2014-01-03 13:32:13 -05:00
$ ( ' # level-done-button ' ) . show ( )
@ showVictory ( ) if e . showModal
setTimeout ( @ preloadNextLevel , 3000 )
2014-04-13 23:31:23 -04:00
return if @ victorySeen
@victorySeen = true
victoryTime = ( new Date ( ) ) - @ loadEndTime
if victoryTime > 10 * 1000 # Don't track it if we're reloading an already-beaten level
2014-05-16 20:38:33 -04:00
application . tracker ? . trackEvent ' Saw Victory ' , level: @ level . get ( ' name ' ) , label: @ level . get ( ' name ' )
2014-04-13 23:31:23 -04:00
application . tracker ? . trackTiming victoryTime , ' Level Victory Time ' , @ levelID , @ levelID , 100
2014-01-03 13:32:13 -05:00
showVictory: ->
2014-03-31 16:56:13 -04:00
options = { level: @ level , supermodel: @ supermodel , session: @ session }
2014-01-03 13:32:13 -05:00
docs = new VictoryModal ( options )
@ openModalView ( docs )
2014-03-31 16:56:13 -04:00
if me . get ( ' anonymous ' )
2014-04-29 18:25:59 -04:00
window . nextLevelURL = @ getNextLevelURL ( ) # Signup will go here on completion instead of reloading.
2014-01-03 13:32:13 -05:00
onRestartLevel: ->
@ tome . reloadAllCode ( )
2014-08-27 15:24:03 -04:00
Backbone . Mediator . publish ' level:restarted ' , { }
2014-01-03 13:32:13 -05:00
$ ( ' # level-done-button ' , @ $el ) . hide ( )
2014-05-16 20:38:33 -04:00
application . tracker ? . trackEvent ' Confirmed Restart ' , level: @ level . get ( ' name ' ) , label: @ level . get ( ' name ' )
2014-01-03 13:32:13 -05:00
onInfiniteLoop: (e) ->
return unless e . firstWorld
@ openModalView new InfiniteLoopModal ( )
2014-05-16 20:38:33 -04:00
application . tracker ? . trackEvent ' Saw Initial Infinite Loop ' , level: @ level . get ( ' name ' ) , label: @ level . get ( ' name ' )
2014-01-03 13:32:13 -05:00
2014-02-12 15:41:41 -05:00
onPlayNextLevel: ->
2014-03-31 16:56:13 -04:00
nextLevelID = @ getNextLevelID ( )
nextLevelURL = @ getNextLevelURL ( )
2014-01-03 13:32:13 -05:00
Backbone . Mediator . publish ' router:navigate ' , {
2014-03-31 16:56:13 -04:00
route: nextLevelURL ,
2014-01-03 13:32:13 -05:00
viewClass: PlayLevelView ,
2014-06-30 22:16:26 -04:00
viewArgs: [ { supermodel: @ supermodel } , nextLevelID ] }
2014-01-03 13:32:13 -05:00
getNextLevel: ->
2014-04-29 18:25:59 -04:00
return null unless nextLevelOriginal = @ level . get ( ' nextLevel ' ) ? . original
2014-01-03 13:32:13 -05:00
levels = @ supermodel . getModels ( Level )
return l for l in levels when l . get ( ' original ' ) is nextLevelOriginal
2014-03-31 16:56:13 -04:00
getNextLevelID: ->
2014-04-29 18:25:59 -04:00
return null unless nextLevel = @ getNextLevel ( )
2014-03-31 16:56:13 -04:00
nextLevelID = nextLevel . get ( ' slug ' ) or nextLevel . id
2014-04-29 18:25:59 -04:00
getNextLevelURL: ->
return null unless @ getNextLevelID ( )
" /play/level/ #{ @ getNextLevelID ( ) } "
2014-03-31 16:56:13 -04:00
2014-02-12 15:41:41 -05:00
onHighlightDom: (e) ->
2014-01-03 13:32:13 -05:00
if e . delay
delay = e . delay
delete e . delay
@pointerInterval = _ . delay ( ( => @ onHighlightDom e ) , delay )
return
@ addPointer ( )
selector = e . selector + ' :visible '
dom = $ ( selector )
return if parseFloat ( dom . css ( ' opacity ' ) ) is 0.0
offset = dom . offset ( )
return if not offset
target_left = offset . left + dom . outerWidth ( ) * 0.5
target_top = offset . top + dom . outerHeight ( ) * 0.5
if e . sides
if ' left ' in e . sides then target_left = offset . left
if ' right ' in e . sides then target_left = offset . left + dom . outerWidth ( )
if ' top ' in e . sides then target_top = offset . top
if ' bottom ' in e . sides then target_top = offset . top + dom . outerHeight ( )
else
# aim to hit the side if the target is entirely on one side of the screen
2014-08-27 15:24:03 -04:00
if offset . left > @ $el . outerWidth ( ) * 0.5
2014-01-03 13:32:13 -05:00
target_left = offset . left
2014-08-27 15:24:03 -04:00
else if offset . left + dom . outerWidth ( ) < @ $el . outerWidth ( ) * 0.5
2014-01-03 13:32:13 -05:00
target_left = offset . left + dom . outerWidth ( )
# aim to hit the bottom or top if the target is entirely on the top or bottom of the screen
2014-08-27 15:24:03 -04:00
if offset . top > @ $el . outerWidth ( ) * 0.5
2014-01-03 13:32:13 -05:00
target_top = offset . top
2014-08-27 15:24:03 -04:00
else if offset . top + dom . outerHeight ( ) < @ $el . outerHeight ( ) * 0.5
2014-01-03 13:32:13 -05:00
target_top = offset . top + dom . outerHeight ( )
if e . offset
target_left += e . offset . x
target_top += e . offset . y
@pointerRadialDistance = - 47 # - Math.sqrt(Math.pow(dom.outerHeight()*0.5, 2), Math.pow(dom.outerWidth()*0.5))
2014-08-27 15:24:03 -04:00
@pointerRotation = e . rotation ? Math . atan2 ( @ $el . outerWidth ( ) * 0.5 - target_left , target_top - @ $el . outerHeight ( ) * 0.5 )
2014-01-03 13:32:13 -05:00
pointer = $ ( ' # pointer ' )
pointer
. css ( ' opacity ' , 1.0 )
. css ( ' transition ' , ' none ' )
. css ( ' transform ' , " rotate( #{ @ pointerRotation } rad) translate(-3px, #{ @ pointerRadialDistance } px) " )
. css ( ' top ' , target_top - 50 )
. css ( ' left ' , target_left - 50 )
2014-04-10 16:59:08 -04:00
setTimeout ( ()=>
2014-04-25 17:30:06 -04:00
return if @ destroyed
2014-01-03 13:32:13 -05:00
@ animatePointer ( )
clearInterval ( @ pointerInterval )
@pointerInterval = setInterval ( @ animatePointer , 1200 )
2014-04-10 16:59:08 -04:00
, 1 )
2014-01-03 13:32:13 -05:00
2014-04-28 17:01:33 -04:00
animatePointer: =>
2014-01-03 13:32:13 -05:00
pointer = $ ( ' # pointer ' )
pointer . css ( ' transition ' , ' all 0.6s ease-out ' )
pointer . css ( ' transform ' , " rotate( #{ @ pointerRotation } rad) translate(-3px, #{ @ pointerRadialDistance - 50 } px) " )
2014-09-18 21:25:33 -04:00
#Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'dom_highlight', volume: 0.75 # Never mind, this is currently so annoying
2014-01-03 13:32:13 -05:00
setTimeout ( ( =>
pointer . css ( ' transform ' , " rotate( #{ @ pointerRotation } rad) translate(-3px, #{ @ pointerRadialDistance } px) " ) . css ( ' transition ' , ' all 0.4s ease-in ' ) ) , 800 )
2014-02-12 15:41:41 -05:00
onFocusDom: (e) -> $ ( e . selector ) . focus ( )
2014-01-03 13:32:13 -05:00
2014-02-12 15:41:41 -05:00
onEndHighlight: ->
2014-01-03 13:32:13 -05:00
$ ( ' # pointer ' ) . css ( ' opacity ' , 0.0 )
clearInterval ( @ pointerInterval )
2014-02-11 18:38:36 -05:00
onMultiplayerChanged: (e) ->
2014-01-03 13:32:13 -05:00
if @ session . get ( ' multiplayer ' )
@ bus . connect ( )
else
@ bus . removeFirebaseData =>
@ bus . disconnect ( )
addPointer: ->
p = $ ( ' # pointer ' )
return if p . length
@ $el . append ( $ ( ' <img src= " /images/level/pointer.png " id= " pointer " > ' ) )
preloadNextLevel: =>
# TODO: Loading models in the middle of gameplay causes stuttering. Most of the improvement in loading time is simply from passing the supermodel from this level to the next, but if we can find a way to populate the level early without it being noticeable, that would be even better.
# return if @destroyed
# return if @preloaded
# nextLevel = @getNextLevel()
# @supermodel.populateModel nextLevel
# @preloaded = true
onSessionWillSave: (e) ->
# Something interesting has happened, so (at a lower frequency), we'll save a screenshot.
2014-05-12 16:28:46 -04:00
#@saveScreenshot e.session
2014-01-03 13:32:13 -05:00
# Throttled
saveScreenshot: (session) =>
return unless screenshot = @ surface ? . screenshot ( )
session . save { screenshot: screenshot } , { patch: true }
2014-02-19 14:42:33 -05:00
# Dynamic sound loading
2014-05-14 13:35:16 -04:00
onNewWorld: (e) ->
2014-02-19 14:42:33 -05:00
return if @ headless
2014-05-16 20:38:33 -04:00
scripts = @ world . scripts # Since these worlds don't have scripts, preserve them.
2014-05-14 13:35:16 -04:00
@world = e . world
2014-05-16 20:38:33 -04:00
@world.scripts = scripts
2014-02-19 14:42:33 -05:00
thangTypes = @ supermodel . getModels ( ThangType )
2014-08-25 00:39:34 -04:00
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-02-19 14:42:33 -05: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-08-23 16:54:52 -04:00
# Real-time playback
2014-08-30 00:46:26 -04:00
onRealTimePlaybackWaiting: (e) ->
@ $el . addClass ( ' real-time ' ) . focus ( )
@ onWindowResize ( )
2014-08-23 16:54:52 -04:00
onRealTimePlaybackStarted: (e) ->
2014-08-23 20:26:56 -04:00
@ $el . addClass ( ' real-time ' ) . focus ( )
2014-08-23 16:54:52 -04:00
@ onWindowResize ( )
onRealTimePlaybackEnded: (e) ->
@ $el . removeClass ' real-time '
@ onWindowResize ( )
2014-08-29 18:26:39 -04:00
@ onRealTimeMultiplayerPlaybackEnded ( )
2014-08-23 16:54:52 -04:00
2014-01-03 13:32:13 -05:00
destroy: ->
2014-02-06 17:00:27 -05:00
@ levelLoader ? . destroy ( )
2014-01-03 13:32:13 -05:00
@ surface ? . destroy ( )
@ god ? . destroy ( )
@ goalManager ? . destroy ( )
@ scriptManager ? . destroy ( )
2014-08-29 20:52:47 -04:00
$ ( window ) . off ' resize ' , @ onWindowResize
2014-02-11 17:24:06 -05:00
delete window . world # not sure where this is set, but this is one way to clean it up
2014-01-03 13:32:13 -05:00
clearInterval ( @ pointerInterval )
@ bus ? . destroy ( )
#@instance.save() unless @instance.loading
2014-03-31 16:56:13 -04:00
delete window . nextLevelURL
2014-01-03 13:32:13 -05:00
console . profileEnd ? ( ) if PROFILE_ME
2014-02-14 13:57:47 -05:00
super ( )
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
# Real-time Multiplayer ######################################################
2014-08-29 18:26:39 -04:00
onRealTimeMultiplayerPlaybackEnded: ->
if @ multiplayerSession
2014-08-30 00:46:26 -04:00
@ multiplayerSession . set ' state ' , ' coding '
2014-08-29 18:26:39 -04:00
players = new RealTimeCollection ( ' multiplayer_level_sessions/ ' + @ multiplayerSession . id + ' /players ' )
players . each (player) -> player . set ' state ' , ' coding ' if player . id is me . id
2014-08-29 18:10:04 -04:00
onJoinedRealTimeMultiplayerGame: (e) ->
2014-08-30 00:46:26 -04:00
@multiplayerSession = new RealTimeModel ( ' multiplayer_level_sessions/ ' + e . session . id )
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-08-29 18:10:04 -04:00
onLeftRealTimeMultiplayerGame: (e) ->
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
if @ multiplayerSession
@ multiplayerSession . off ( )
@multiplayerSession = null
onRealTimeMultiplayerCast: (e) ->
unless @ multiplayerSession
console . error ' onRealTimeMultiplayerCast without a multiplayerSession '
return
players = new RealTimeCollection ( ' multiplayer_level_sessions/ ' + @ multiplayerSession . id + ' /players ' )
myPlayer = opponentPlayer = null
2014-08-29 18:26:39 -04:00
players . each (player) ->
if player . id is me . id
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
myPlayer = player
else
opponentPlayer = player
if myPlayer
console . info ' Submitting my code '
myPlayer . set ' code ' , @ session . get ( ' code ' )
myPlayer . set ' codeLanguage ' , @ session . get ( ' codeLanguage ' )
myPlayer . set ' state ' , ' submitted '
2014-09-04 23:10:04 -04:00
myPlayer . set ' team ' , me . team
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
else
console . error ' Did not find my player in onRealTimeMultiplayerCast '
if opponentPlayer
# TODO: Shouldn't need nested opponentPlayer change listeners here
state = opponentPlayer . get ( ' state ' )
console . info ' Other player is ' , state
if state in [ ' submitted ' , ' ready ' ]
@ onOpponentSubmitted ( opponentPlayer , myPlayer )
else
# Wait for opponent to submit their code
opponentPlayer . on ' change ' , (e) =>
state = opponentPlayer . get ( ' state ' )
if state in [ ' submitted ' , ' ready ' ]
@ onOpponentSubmitted ( opponentPlayer , myPlayer )
onOpponentSubmitted: (opponentPlayer, myPlayer) =>
# Save opponent's code
2014-09-04 23:10:04 -04:00
Backbone . Mediator . publish ' real-time-multiplayer:new-opponent-code ' , { codeLanguage: opponentPlayer . get ( ' codeLanguage ' ) , code: opponentPlayer . get ( ' code ' ) , team: opponentPlayer . get ( ' team ' ) }
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
# I'm ready to rumble
myPlayer . set ' state ' , ' ready '
if opponentPlayer . get ( ' state ' ) is ' ready '
console . info ' All real-time multiplayer players are ready! '
@ multiplayerSession . set ' state ' , ' running '
else
# Wait for opponent to be ready
opponentPlayer . on ' change ' , (e) =>
if opponentPlayer . get ( ' state ' ) is ' ready '
opponentPlayer . off ( )
console . info ' All real-time multiplayer players are ready! '
@ multiplayerSession . set ' state ' , ' running '