2014-01-03 13:32:13 -05:00
# There's one TomeView per Level. It has:
# - a CastButtonView, which has
# - a cast button
2015-04-18 18:52:24 -04:00
# - a submit/done button
2014-01-03 13:32:13 -05:00
# - for each spell (programmableMethod):
# - a Spell, which has
# - a list of Thangs that share that Spell, with one aether per Thang per Spell
# - a SpellView, which has
# - tons of stuff; the meat
# - a SpellListView, which has
# - for each spell:
# - a SpellListEntryView, which has
# - icons for each Thang
# - the spell name
# - a reload button
# - documentation for that method (in a popover)
# - a SpellPaletteView, which has
# - for each programmableProperty:
# - a SpellPaletteEntryView
#
# The CastButtonView and SpellListView always show.
# The SpellPaletteView shows the entries for the currently selected Programmable Thang.
# The SpellView shows the code and runtime state for the currently selected Spell and, specifically, Thang.
# The SpellView obscures most of the SpellListView when present. We might mess with this.
# You can switch a SpellView to showing the runtime state of another Thang sharing that Spell.
# SpellPaletteViews are destroyed and recreated whenever you switch Thangs.
# The SpellListView shows spells to which your team has read or readwrite access.
# It doubles as a Thang selector, since it's there when nothing is selected.
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/tome/tome '
2014-11-28 20:49:41 -05:00
{ me } = require ' core/auth '
2014-07-23 10:02:45 -04:00
Spell = require ' ./Spell '
SpellListView = require ' ./SpellListView '
SpellPaletteView = require ' ./SpellPaletteView '
CastButtonView = require ' ./CastButtonView '
2014-01-03 13:32:13 -05:00
2014-07-17 20:20:11 -04:00
module.exports = class TomeView extends CocoView
2014-01-03 13:32:13 -05:00
id: ' tome-view '
template: template
controlsEnabled: true
cache: false
subscriptions:
2014-06-30 22:16:26 -04:00
' tome:spell-loaded ' : ' onSpellLoaded '
' tome:cast-spell ' : ' onCastSpell '
2014-01-03 13:32:13 -05:00
' tome:toggle-spell-list ' : ' onToggleSpellList '
2014-03-16 21:14:04 -04:00
' tome:change-language ' : ' updateLanguageForAllSpells '
2014-01-03 13:32:13 -05:00
' surface:sprite-selected ' : ' onSpriteSelected '
2014-02-05 18:16:59 -05:00
' god:new-world-created ' : ' onNewWorld '
2014-04-14 17:49:38 -04:00
' tome:comment-my-code ' : ' onCommentMyCode '
2014-09-22 01:10:52 -04:00
' tome:select-primary-sprite ' : ' onSelectPrimarySprite '
2014-01-03 13:32:13 -05:00
events:
' click # spell-view ' : ' onSpellViewClick '
2014-02-16 18:30:00 -05:00
' click ' : ' onClick '
2014-01-03 13:32:13 -05:00
afterRender: ->
super ( )
2014-02-17 20:38:49 -05:00
@worker = @ createWorker ( )
2015-02-28 00:37:29 -05:00
programmableThangs = _ . filter @ options . thangs , (t) -> t . isProgrammable and t . programmableMethods
2014-08-19 11:09:15 -04:00
@ createSpells programmableThangs , programmableThangs [ 0 ] ? . world # Do before spellList, thangList, and castButton
2016-07-14 12:38:45 -04:00
unless @ options . level . isType ( ' hero ' , ' hero-ladder ' , ' hero-coop ' , ' course ' , ' course-ladder ' , ' game-dev ' , ' web-dev ' )
2015-03-10 17:37:00 -04:00
@spellList = @ insertSubView new SpellListView spells: @ spells , supermodel: @ supermodel , level: @ options . level
2015-11-29 15:30:19 -05:00
@castButton = @ insertSubView new CastButtonView spells: @ spells , level: @ options . level , session: @ options . session , god: @ options . god
2014-08-19 11:09:15 -04:00
@teamSpellMap = @ generateTeamSpellMap ( @ spells )
unless programmableThangs . length
2014-01-16 16:18:38 -05:00
@ cast ( )
2014-08-19 11:09:15 -04:00
warning = ' Warning: There are no Programmable Thangs in this level, which makes it unplayable. '
noty text: warning , layout: ' topCenter ' , type: ' warning ' , killer: false , timeout: 15000 , dismissQueue: true , maxVisible: 3
console . warn warning
2014-02-06 17:00:27 -05:00
delete @ options . thangs
2014-04-23 19:11:47 -04:00
2014-02-05 18:16:59 -05:00
onNewWorld: (e) ->
2014-04-24 17:23:15 -04:00
thangs = _ . filter e . world . thangs , ' inThangList '
2015-02-28 00:37:29 -05:00
programmableThangs = _ . filter thangs , (t) -> t . isProgrammable and t . programmableMethods
2014-02-05 23:08:28 -05:00
@ createSpells programmableThangs , e . world
2015-03-10 17:37:00 -04:00
@ spellList ? . adjustSpells @ spells
2014-01-29 13:14:12 -05:00
2014-04-14 17:49:38 -04:00
onCommentMyCode: (e) ->
for spellKey , spell of @ spells when spell . canWrite ( )
2014-06-30 22:16:26 -04:00
console . log ' Commenting out ' , spellKey
2015-02-04 11:18:01 -05:00
commentedSource = spell . view . commentOutMyCode ( ) + ' Commented out to stop infinite loop. \n ' + spell . getSource ( )
2014-04-14 17:49:38 -04:00
spell . view . updateACEText commentedSource
spell . view . recompile false
@ cast ( )
2014-02-17 20:38:49 -05:00
createWorker: ->
2014-09-29 02:24:18 -04:00
return null unless Worker ?
2014-11-09 20:35:50 -05:00
return null if window . application . isIPadApp # Save memory!
2014-06-30 22:16:26 -04:00
return new Worker ( ' /javascripts/workers/aether_worker.js ' )
2014-02-17 20:38:49 -05:00
2014-02-10 19:18:39 -05:00
generateTeamSpellMap: (spellObject) ->
teamSpellMap = { }
for spellName , spell of spellObject
teamName = spell . team
teamSpellMap [ teamName ] ? = [ ]
spellNameElements = spellName . split ' / '
thangName = spellNameElements [ 0 ]
spellName = spellNameElements [ 1 ]
teamSpellMap [ teamName ] . push thangName if thangName not in teamSpellMap [ teamName ]
return teamSpellMap
2014-02-05 18:16:59 -05:00
createSpells: (programmableThangs, world) ->
2014-09-25 16:17:41 -04:00
language = @ options . session . get ( ' codeLanguage ' ) ? me . get ( ' aceConfig ' ) ? . language ? ' python '
2014-01-03 13:32:13 -05:00
pathPrefixComponents = [ ' play ' , ' level ' , @ options . levelID , @ options . session . id , ' code ' ]
2014-01-29 13:14:12 -05:00
@ spells ? = { }
@ thangSpells ? = { }
2014-01-03 13:32:13 -05:00
for thang in programmableThangs
2014-01-29 13:14:12 -05:00
continue if @ thangSpells [ thang . id ] ?
2014-01-03 13:32:13 -05:00
@ thangSpells [ thang . id ] = [ ]
for methodName , method of thang . programmableMethods
pathComponents = [ thang . id , methodName ]
if method . cloneOf
pathComponents [ 0 ] = method . cloneOf # referencing another Thang's method
pathComponents [ 0 ] = _ . string . slugify pathComponents [ 0 ]
spellKey = pathComponents . join ' / '
@ thangSpells [ thang . id ] . push spellKey
unless method . cloneOf
2014-08-20 16:26:42 -04:00
skipProtectAPI = @ getQueryVariable ' skip_protect_api ' , ( @ options . levelID in [ ' gridmancer ' , ' minimax-tic-tac-toe ' ] )
2014-05-16 00:53:23 -04:00
spell = @ spells [ spellKey ] = new Spell
2016-05-26 20:46:49 -04:00
hintsState: @ options . hintsState
2014-05-15 18:18:15 -04:00
programmableMethod: method
spellKey: spellKey
pathComponents: pathPrefixComponents . concat ( pathComponents )
session: @ options . session
2014-06-20 20:19:18 -04:00
otherSession: @ options . otherSession
2014-05-15 18:18:15 -04:00
supermodel: @ supermodel
skipProtectAPI: skipProtectAPI
worker: @ worker
language: language
spectateView: @ options . spectateView
2014-09-18 11:12:46 -04:00
spectateOpponentCodeLanguage: @ options . spectateOpponentCodeLanguage
2015-01-31 13:04:02 -05:00
observing: @ options . observing
2014-08-14 14:55:43 -04:00
levelID: @ options . levelID
2014-09-23 14:39:56 -04:00
level: @ options . level
2015-11-29 15:30:19 -05:00
god: @ options . god
2014-05-16 00:53:23 -04:00
2014-01-03 13:32:13 -05:00
for thangID , spellKeys of @ thangSpells
2014-02-05 18:16:59 -05:00
thang = world . getThangByID thangID
if thang
@ spells [ spellKey ] . addThang thang for spellKey in spellKeys
else
delete @ thangSpells [ thangID ]
2014-02-06 17:00:27 -05:00
spell . removeThangID thangID for spell in @ spells
2014-08-25 00:52:33 -04:00
for spellKey , spell of @ spells when not spell . canRead ( ) # Make sure these get transpiled (they have no views).
spell . transpile ( )
spell.loaded = true
2014-01-03 13:32:13 -05:00
null
onSpellLoaded: (e) ->
for spellID , spell of @ spells
return unless spell . loaded
@ cast ( )
onCastSpell: (e) ->
# A single spell is cast.
2014-08-23 00:35:08 -04:00
@ cast e ? . preload , e ? . realTime
2014-01-03 13:32:13 -05:00
2014-08-23 00:35:08 -04:00
cast: (preload=false, realTime=false) ->
2014-10-18 20:32:01 -04:00
sessionState = @ options . session . get ( ' state ' ) ? { }
if realTime
sessionState.submissionCount = ( sessionState . submissionCount ? 0 ) + 1
2014-11-17 11:00:44 -05:00
sessionState.flagHistory = _ . filter sessionState . flagHistory ? [ ] , (event) => event . team isnt ( @ options . session . get ( ' team ' ) ? ' humans ' )
2015-01-05 13:44:17 -05:00
sessionState.lastUnsuccessfulSubmissionTime = new Date ( ) if @ options . level . get ' replayable '
2014-10-18 20:32:01 -04:00
@ options . session . set ' state ' , sessionState
2015-02-04 14:31:43 -05:00
difficulty = sessionState . difficulty ? 0
if @ options . observing
difficulty = Math . max 0 , difficulty - 1 # Show the difficulty they won, not the next one.
2016-04-07 22:06:57 -04:00
Backbone . Mediator . publish ' tome:cast-spells ' , spells: @ spells , preload: preload , realTime: realTime , submissionCount: sessionState . submissionCount ? 0 , flagHistory: sessionState . flagHistory ? [ ] , difficulty: difficulty , god: @ options . god , fixedSeed: @ options . fixedSeed
2014-01-03 13:32:13 -05:00
onToggleSpellList: (e) ->
2015-03-10 17:37:00 -04:00
@ spellList ? . rerenderEntries ( )
@ spellList ? . $el . toggle ( )
2014-01-03 13:32:13 -05:00
onSpellViewClick: (e) ->
2014-12-06 14:33:57 -05:00
@ spellList ? . $el . hide ( )
2014-01-03 13:32:13 -05:00
2014-02-16 18:30:00 -05:00
onClick: (e) ->
2014-08-27 15:24:03 -04:00
Backbone . Mediator . publish ' tome:focus-editor ' , { } unless $ ( e . target ) . parents ( ' .popover ' ) . length
2014-02-16 18:30:00 -05:00
2014-01-03 13:32:13 -05:00
clearSpellView: ->
@ spellView ? . dismiss ( )
@ spellView ? . $el . after ( ' <div id= " ' + @ spellView . id + ' " ></div> ' ) . detach ( )
@spellView = null
@ spellTabView ? . $el . after ( ' <div id= " ' + @ spellTabView . id + ' " ></div> ' ) . detach ( )
@spellTabView = null
@ removeSubView @ spellPaletteView if @ spellPaletteView
2014-02-02 13:26:42 -05:00
@spellPaletteView = null
2014-10-16 15:08:21 -04:00
@ $el . find ( ' # spell-palette-view ' ) . hide ( )
2014-01-21 12:03:04 -05:00
@ castButton ? . $el . hide ( )
2014-01-03 13:32:13 -05:00
onSpriteSelected: (e) ->
2016-06-25 11:38:59 -04:00
return if @ spellView and @ options . level . get ( ' type ' , true ) in [ ' hero ' , ' hero-ladder ' , ' hero-coop ' , ' course ' , ' course-ladder ' , ' game-dev ' ] # Never deselect the hero in the Tome.
2014-01-03 13:32:13 -05:00
thang = e . thang
spellName = e . spellName
2014-01-16 17:29:57 -05:00
@ spellList ? . $el . hide ( )
2014-04-25 19:57:42 -04:00
return @ clearSpellView ( ) unless thang
spell = @ spellFor thang , spellName
unless spell ? . canRead ( )
@ clearSpellView ( )
2016-07-08 16:27:42 -04:00
@ updateSpellPalette thang , spell if spell
2014-04-25 19:57:42 -04:00
return
2014-02-02 13:26:42 -05:00
unless spell . view is @ spellView
@ clearSpellView ( )
@spellView = spell . view
@spellTabView = spell . tabView
@ $el . find ( ' # ' + @ spellView . id ) . after ( @ spellView . el ) . remove ( )
@ $el . find ( ' # ' + @ spellTabView . id ) . after ( @ spellTabView . el ) . remove ( )
2015-02-02 14:50:25 -05:00
@ castButton ? . attachTo @ spellView
2014-02-02 13:26:42 -05:00
Backbone . Mediator . publish ' tome:spell-shown ' , thang: thang , spell: spell
2014-10-16 15:08:21 -04:00
@ updateSpellPalette thang , spell
2015-03-10 17:37:00 -04:00
@ spellList ? . setThangAndSpell thang , spell
2014-01-31 19:16:59 -05:00
@ spellView ? . setThang thang
@ spellTabView ? . setThang thang
2014-04-25 19:57:42 -04:00
updateSpellPalette: (thang, spell) ->
return unless thang and @ spellPaletteView ? . thang isnt thang and thang . programmableProperties or thang . apiProperties
2016-05-13 13:37:36 -04:00
useHero = /hero/ . test ( spell . getSource ( ) ) or not /(self[\.\:]|this\.|\@)/ . test ( spell . getSource ( ) )
2016-05-26 20:46:49 -04:00
@spellPaletteView = @ insertSubView new SpellPaletteView { thang , @ supermodel , programmable: spell ? . canRead ( ) , language: spell ? . language ? @ options . session . get ( ' codeLanguage ' ) , session: @ options . session , level: @ options . level , courseID: @ options . courseID , courseInstanceID: @ options . courseInstanceID , useHero }
2014-11-07 19:04:35 -05:00
@ spellPaletteView . toggleControls { } , spell . view . controlsEnabled if spell ? . view # TODO: know when palette should have been disabled but didn't exist
2014-04-25 19:57:42 -04:00
spellFor: (thang, spellName) ->
return null unless thang ? . isProgrammable
2014-08-24 19:48:59 -04:00
return unless @ thangSpells [ thang . id ] # Probably in streaming mode, where we don't update until it's done.
2014-04-25 19:57:42 -04:00
selectedThangSpells = ( @ spells [ spellKey ] for spellKey in @ thangSpells [ thang . id ] )
if spellName
spell = _ . find selectedThangSpells , { name: spellName }
2014-09-22 01:10:52 -04:00
else
2014-11-18 10:42:49 -05:00
spell = _ . find selectedThangSpells , (spell) -> spell . canWrite ( )
spell ? = _ . find selectedThangSpells , (spell) -> spell . canRead ( )
2014-04-25 19:57:42 -04:00
spell
2014-01-03 13:32:13 -05:00
reloadAllCode: ->
2014-08-28 12:41:47 -04:00
spell . view . reloadCode false for spellKey , spell of @ spells when spell . view and ( spell . team is me . team or ( spell . team in [ ' common ' , ' neutral ' , null ] ) )
2014-10-18 20:32:01 -04:00
@ cast false , false
2014-01-03 13:32:13 -05:00
2014-05-15 00:54:36 -04:00
updateLanguageForAllSpells: (e) ->
spell . updateLanguageAether e . language for spellKey , spell of @ spells when spell . canWrite ( )
2014-11-10 12:36:40 -05:00
if e . reload
@ reloadAllCode ( )
else
@ cast ( )
2014-03-16 21:14:04 -04:00
2014-09-22 01:10:52 -04:00
onSelectPrimarySprite: (e) ->
2014-11-17 18:07:10 -05:00
# This is only fired by PlayLevelView for hero levels currently
# TODO: Don't hard code these hero names
if @ options . session . get ( ' team ' ) is ' ogres '
Backbone . Mediator . publish ' level:select-sprite ' , thangID: ' Hero Placeholder 1 '
else
Backbone . Mediator . publish ' level:select-sprite ' , thangID: ' Hero Placeholder '
2014-09-22 01:10:52 -04:00
2014-01-03 13:32:13 -05:00
destroy: ->
2014-02-14 13:57:47 -05:00
spell . destroy ( ) for spellKey , spell of @ spells
2014-04-22 11:54:35 -04:00
@ worker ? . terminate ( )
2014-01-03 13:32:13 -05:00
super ( )