2014-01-03 13:32:13 -05:00
CocoClass = require ' lib/CocoClass '
2014-06-05 19:55:49 -04:00
CocoView = require ' views/kinds/CocoView '
2014-01-03 13:32:13 -05:00
{ scriptMatchesEventPrereqs } = require ' ./../world/script_event_prereqs '
allScriptModules = [ ]
allScriptModules . push ( require ' ./SpriteScriptModule ' )
allScriptModules . push ( require ' ./DOMScriptModule ' )
allScriptModules . push ( require ' ./SurfaceScriptModule ' )
allScriptModules . push ( require ' ./PlaybackScriptModule ' )
allScriptModules . push ( require ' ./SoundScriptModule ' )
DEFAULT_BOT_MOVE_DURATION = 500
DEFAULT_SCRUB_DURATION = 1000
module.exports = ScriptManager = class ScriptManager extends CocoClass
scriptInProgress: false
currentNoteGroup: null
currentTimeouts: [ ]
worldLoading: true
ignoreEvents: false
quiet: false
triggered: [ ]
ended: [ ]
noteGroupQueue: [ ]
originalScripts: [ ] # use these later when you want to revert to an original state
subscriptions:
2014-08-27 15:24:03 -04:00
' script:end-current-script ' : ' onEndNoteGroup '
2014-09-21 18:52:49 -04:00
' level:loading-view-unveiling ' : -> @ setWorldLoading ( false )
2014-01-03 13:32:13 -05:00
' level:restarted ' : ' onLevelRestarted '
' level:shift-space-pressed ' : ' onEndNoteGroup '
' level:escape-pressed ' : ' onEndAll '
shortcuts:
2014-08-27 15:24:03 -04:00
' ⇧+space, space, enter ' : -> Backbone . Mediator . publish ' level:shift-space-pressed ' , { }
' escape ' : -> Backbone . Mediator . publish ' level:escape-pressed ' , { }
2014-01-03 13:32:13 -05:00
# SETUP / TEARDOWN
constructor: (options) ->
super ( options )
@originalScripts = options . scripts
@session = options . session
2014-10-29 16:45:04 -04:00
@debugScripts = application . isIPadApp or CocoView . getQueryVariable ' dev '
2014-01-03 13:32:13 -05:00
@ initProperties ( )
@ addScriptSubscriptions ( )
2014-05-20 18:31:49 -04:00
@ beginTicking ( )
2014-01-03 13:32:13 -05:00
setScripts: (@originalScripts) ->
@quiet = true
@ initProperties ( )
@ loadFromSession ( )
@quiet = false
2014-05-21 17:50:27 -04:00
@ addScriptSubscriptions ( )
2014-01-03 13:32:13 -05:00
@ run ( )
initProperties: ->
@ endAll ( { force : true } ) if @ scriptInProgress
@triggered = [ ]
@ended = [ ]
@noteGroupQueue = [ ]
2014-03-18 16:47:51 -04:00
@scripts = $ . extend ( true , [ ] , @ originalScripts )
2014-01-03 13:32:13 -05:00
addScriptSubscriptions: ->
idNum = 0
makeCallback = (channel) => (event) => @ onNote ( channel , event )
for script in @ scripts
script.id = ( idNum ++ ) . toString ( ) unless script . id
callback = makeCallback ( script . channel ) # curry in the channel argument
@ addNewSubscription ( script . channel , callback )
2014-06-30 22:16:26 -04:00
2014-05-20 18:31:49 -04:00
beginTicking: ->
@tickInterval = setInterval @ tick , 5000
2014-06-30 22:16:26 -04:00
2014-05-20 18:31:49 -04:00
tick: =>
scriptStates = { }
now = new Date ( )
for script in @ scripts
scriptStates [ script . id ] =
timeSinceLastEnded: ( if script . lastEnded then now - script . lastEnded else 0 ) / 1000
timeSinceLastTriggered: ( if script . lastTriggered then now - script . lastTriggered else 0 ) / 1000
2014-06-30 22:16:26 -04:00
2014-05-20 18:31:49 -04:00
stateEvent =
scriptRunning: @ currentNoteGroup ? . scriptID or ' '
noteGroupRunning: @ currentNoteGroup ? . name or ' '
scriptStates: scriptStates
timeSinceLastScriptEnded: ( if @ lastScriptEnded then now - @ lastScriptEnded else 0 ) / 1000
2014-08-27 19:31:39 -04:00
Backbone . Mediator . publish ' script:tick ' , stateEvent # Used to trigger level scripts.
2014-01-03 13:32:13 -05:00
loadFromSession: ->
# load the queue with note groups to skip through
@ addEndedScriptsFromSession ( )
@ addPartiallyEndedScriptFromSession ( )
2014-08-27 15:24:03 -04:00
for noteGroup in @ noteGroupQueue
@ processNoteGroup ( noteGroup )
2014-01-03 13:32:13 -05:00
addPartiallyEndedScriptFromSession: ->
scripts = @ session . get ( ' state ' ) . scripts
return unless scripts ? . currentScript
script = _ . find @ scripts , { id: scripts . currentScript }
return unless script
@ triggered . push ( script . id )
noteChain = @ processScript ( script )
2014-05-21 17:50:27 -04:00
return unless noteChain
2014-01-03 13:32:13 -05:00
if scripts . currentScriptOffset
noteGroup.skipMe = true for noteGroup in noteChain [ . . scripts . currentScriptOffset - 1 ]
@ addNoteChain ( noteChain , false )
addEndedScriptsFromSession: ->
scripts = @ session . get ( ' state ' ) . scripts
return unless scripts
endedObj = scripts [ ' ended ' ] or { }
sortedPairs = _ . sortBy ( _ . pairs ( endedObj ) , (pair) -> pair [ 1 ] )
scriptsToSkip = ( p [ 0 ] for p in sortedPairs )
for scriptID in scriptsToSkip
script = _ . find @ scripts , { id: scriptID }
unless script
2014-06-30 22:16:26 -04:00
console . warn ' Couldn \' t find script for ' , scriptID , ' from scripts ' , @ scripts , ' when restoring session scripts. '
2014-01-03 13:32:13 -05:00
continue
2014-02-28 14:28:23 -05:00
continue if script . repeats # repeating scripts are not 'rerun'
2014-01-03 13:32:13 -05:00
@ triggered . push ( scriptID )
@ ended . push ( scriptID )
noteChain = @ processScript ( script )
2014-05-21 17:50:27 -04:00
return unless noteChain
2014-01-03 13:32:13 -05:00
noteGroup.skipMe = true for noteGroup in noteChain
@ addNoteChain ( noteChain , false )
setWorldLoading: (@worldLoading) ->
@ run ( ) unless @ worldLoading
destroy: ->
@ onEndAll ( )
2014-05-20 18:31:49 -04:00
clearInterval @ tickInterval
2014-02-14 13:57:47 -05:00
super ( )
2014-01-03 13:32:13 -05:00
# TRIGGERERING NOTES
onNote: (channel, event) ->
return if @ ignoreEvents
for script in @ scripts
alreadyTriggered = script . id in @ triggered
continue unless script . channel is channel
continue if alreadyTriggered and not script . repeats
2014-02-28 14:28:23 -05:00
continue if script . lastTriggered ? and script . repeats is ' session '
2014-01-03 13:32:13 -05:00
continue if script . lastTriggered ? and new Date ( ) . getTime ( ) - script . lastTriggered < 1
2014-02-25 13:50:40 -05:00
continue if script . neverRun
if script . notAfter
for scriptID in script . notAfter
if scriptID in @ triggered
script.neverRun = true
break
continue if script . neverRun
2014-01-03 13:32:13 -05:00
continue unless @ scriptPrereqsSatisfied ( script )
continue unless scriptMatchesEventPrereqs ( script , event )
# everything passed!
2014-05-21 17:50:27 -04:00
console . debug " SCRIPT: Running script ' #{ script . id } ' " if @ debugScripts
2014-01-03 13:32:13 -05:00
script.lastTriggered = new Date ( ) . getTime ( )
@ triggered . push ( script . id ) unless alreadyTriggered
noteChain = @ processScript ( script )
2014-05-21 17:50:27 -04:00
if not noteChain then return @ trackScriptCompletions ( script . id )
2014-01-03 13:32:13 -05:00
@ addNoteChain ( noteChain )
@ run ( )
scriptPrereqsSatisfied: (script) ->
_ . every ( script . scriptPrereqs or [ ] , (prereq) => prereq in @ triggered )
processScript: (script) ->
noteChain = script . noteChain
2014-05-21 17:50:27 -04:00
return null unless noteChain ? . length
2014-01-03 13:32:13 -05:00
noteGroup.scriptID = script . id for noteGroup in noteChain
2014-05-21 17:50:27 -04:00
lastNoteGroup = noteChain [ noteChain . length - 1 ]
lastNoteGroup.isLast = true
2014-01-03 13:32:13 -05:00
return noteChain
addNoteChain: (noteChain, clearYields=true) ->
@ processNoteGroup ( noteGroup ) for noteGroup in noteChain
noteGroup.index = i for noteGroup , i in noteChain
if clearYields
noteGroup.skipMe = true for noteGroup in @ noteGroupQueue when noteGroup . script . yields
@ noteGroupQueue . push noteGroup for noteGroup in noteChain
@ endYieldingNote ( )
processNoteGroup: (noteGroup) ->
return if noteGroup . modules ?
if noteGroup . playback ? . scrub ?
noteGroup . playback . scrub . duration ? = DEFAULT_SCRUB_DURATION
noteGroup . sprites ? = [ ]
for sprite in noteGroup . sprites
if sprite . move ?
sprite . move . duration ? = DEFAULT_BOT_MOVE_DURATION
2014-10-29 16:45:04 -04:00
sprite . id ? = ' Hero Placeholder '
2014-01-03 13:32:13 -05:00
noteGroup . script ? = { }
noteGroup . script . yields ? = true
noteGroup . script . skippable ? = true
2014-06-05 19:55:49 -04:00
noteGroup.modules = ( new Module ( noteGroup ) for Module in allScriptModules when Module . neededFor ( noteGroup ) )
2014-01-03 13:32:13 -05:00
endYieldingNote: ->
if @ scriptInProgress and @ currentNoteGroup ? . script . yields
@ endNoteGroup ( )
return true
# STARTING NOTES
2014-02-11 17:24:06 -05:00
run: ->
2014-01-03 13:32:13 -05:00
# catch all for analyzing the current state and doing whatever needs to happen next
return if @ scriptInProgress
@ skipAhead ( )
return unless @ noteGroupQueue . length
nextNoteGroup = @ noteGroupQueue [ 0 ]
return if @ worldLoading and nextNoteGroup . skipMe
return if @ worldLoading and not nextNoteGroup . script ? . beforeLoad
@noteGroupQueue = @ noteGroupQueue [ 1 . . ]
@currentNoteGroup = nextNoteGroup
@ notifyScriptStateChanged ( )
@scriptInProgress = true
@currentTimeouts = [ ]
2014-05-21 17:50:27 -04:00
console . debug " SCRIPT: Starting note group ' #{ nextNoteGroup . name } ' " if @ debugScripts
2014-01-03 13:32:13 -05:00
for module in nextNoteGroup . modules
@ processNote ( note , nextNoteGroup ) for note in module . startNotes ( )
if nextNoteGroup . script . duration
2014-04-25 18:00:46 -04:00
f = => @ onNoteGroupTimeout ? nextNoteGroup
2014-01-03 13:32:13 -05:00
setTimeout ( f , nextNoteGroup . script . duration )
2014-08-27 15:24:03 -04:00
Backbone . Mediator . publish ' script:note-group-started ' , { }
2014-01-03 13:32:13 -05:00
skipAhead: ->
return if @ worldLoading
return unless @ noteGroupQueue [ 0 ] ? . skipMe
@ignoreEvents = true
for noteGroup , i in @ noteGroupQueue
break unless noteGroup . skipMe
2014-05-21 17:50:27 -04:00
console . debug " SCRIPT: Skipping note group ' #{ noteGroup . name } ' " if @ debugScripts
2014-01-03 13:32:13 -05:00
@ processNoteGroup ( noteGroup )
for module in noteGroup . modules
notes = module . skipNotes ( )
@ processNote ( note , noteGroup ) for note in notes
2014-05-21 17:50:27 -04:00
@ trackScriptCompletionsFromNoteGroup ( noteGroup )
2014-01-03 13:32:13 -05:00
@noteGroupQueue = @ noteGroupQueue [ i . . ]
@ignoreEvents = false
processNote: (note, noteGroup) ->
note . event ? = { }
if note . delay
f = => @ sendDelayedNote noteGroup , note
@ currentTimeouts . push setTimeout ( f , note . delay )
else
@ publishNote ( note )
sendDelayedNote: (noteGroup, note) ->
# some events should only happen after the bot has moved into position
return unless noteGroup is @ currentNoteGroup
@ publishNote ( note )
publishNote: (note) ->
2014-09-22 17:05:13 -04:00
Backbone . Mediator . publish ' playback:real-time-playback-ended ' , { } unless @ session . get ( ' heroConfig ' ) # Only old levels need this, to stop interfering with old victory coolcams.
2014-08-27 15:24:03 -04:00
Backbone . Mediator . publish note . channel , note . event ? { }
2014-01-03 13:32:13 -05:00
# ENDING NOTES
onLevelRestarted: ->
@quiet = true
@ endAll ( { force : true } )
@ initProperties ( )
@ resetThings ( )
2014-08-27 15:24:03 -04:00
Backbone . Mediator . publish ' script:reset ' , { }
2014-01-03 13:32:13 -05:00
@quiet = false
@ run ( )
onEndNoteGroup: (e) ->
# press enter
return unless @ currentNoteGroup ? . script . skippable
@ endNoteGroup ( )
@ run ( )
endNoteGroup: ->
return if @ ending # kill infinite loops right here
@ending = true
return unless @ currentNoteGroup ?
2014-05-21 17:50:27 -04:00
console . debug " SCRIPT: Ending note group ' #{ @ currentNoteGroup . name } ' " if @ debugScripts
2014-01-03 13:32:13 -05:00
clearTimeout ( timeout ) for timeout in @ currentTimeouts
for module in @ currentNoteGroup . modules
@ processNote ( note , @ currentNoteGroup ) for note in module . endNotes ( )
2014-08-28 12:27:42 -04:00
Backbone . Mediator . publish ' script:note-group-ended ' , { } unless @ quiet
2014-01-03 13:32:13 -05:00
@scriptInProgress = false
2014-05-21 17:50:27 -04:00
@ trackScriptCompletionsFromNoteGroup ( @ currentNoteGroup )
2014-01-03 13:32:13 -05:00
@currentNoteGroup = null
unless @ noteGroupQueue . length
@ notifyScriptStateChanged ( )
@ resetThings ( )
@ending = false
2014-08-27 15:24:03 -04:00
onEndAll: (e) ->
# Escape was pressed.
2014-01-03 13:32:13 -05:00
@ endAll ( )
endAll: (options) ->
options ? = { }
if @ scriptInProgress
return if ( not @ currentNoteGroup . script . skippable ) and ( not options . force )
@ endNoteGroup ( )
for noteGroup , i in @ noteGroupQueue
if ( ( noteGroup . script ? . skippable ) is false ) and not options . force
@noteGroupQueue = @ noteGroupQueue [ i . . ]
@ run ( )
2014-07-13 16:11:45 -04:00
@ notifyScriptStateChanged ( )
2014-01-03 13:32:13 -05:00
return
@ processNoteGroup ( noteGroup )
for module in noteGroup . modules
notes = module . skipNotes ( )
@ processNote ( note , noteGroup ) for note in notes unless @ quiet
2014-05-21 17:50:27 -04:00
@ trackScriptCompletionsFromNoteGroup ( noteGroup ) unless @ quiet
2014-01-03 13:32:13 -05:00
@noteGroupQueue = [ ]
@ resetThings ( )
2014-07-13 16:11:45 -04:00
@ notifyScriptStateChanged ( )
2014-01-03 13:32:13 -05:00
onNoteGroupTimeout: (noteGroup) ->
return unless noteGroup is @ currentNoteGroup
@ endNoteGroup ( )
@ run ( )
resetThings: ->
2014-08-27 15:24:03 -04:00
Backbone . Mediator . publish ' level:enable-controls ' , { }
Backbone . Mediator . publish ' level:set-letterbox ' , { on : false }
2014-01-03 13:32:13 -05:00
2014-05-21 17:50:27 -04:00
trackScriptCompletionsFromNoteGroup: (noteGroup) ->
2014-01-03 13:32:13 -05:00
return unless noteGroup . isLast
2014-05-21 17:50:27 -04:00
@ trackScriptCompletions ( noteGroup . scriptID )
trackScriptCompletions: (scriptID) ->
return if @ quiet
@ ended . push ( scriptID ) unless scriptID in @ ended
2014-05-20 18:31:49 -04:00
for script in @ scripts
2014-05-21 17:50:27 -04:00
if script . id is scriptID
2014-05-20 18:31:49 -04:00
script.lastEnded = new Date ( )
@lastScriptEnded = new Date ( )
2014-05-21 17:50:27 -04:00
Backbone . Mediator . publish ' script:ended ' , { scriptID: scriptID }
2014-01-03 13:32:13 -05:00
notifyScriptStateChanged: ->
return if @ quiet
event =
currentScript: @ currentNoteGroup ? . scriptID or null
currentScriptOffset: @ currentNoteGroup ? . index or 0
Backbone . Mediator . publish ' script:state-changed ' , event