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
2016-07-15 01:43:25 -04:00
# - for each spell (programmableMethod) (which is now just always only 'plan')
2014-01-03 13:32:13 -05:00
# - a Spell, which has
2016-07-15 01:43:25 -04:00
# - a Thang that uses that Spell, with an aether and a castAether
2014-01-03 13:32:13 -05:00
# - a SpellView, which has
# - tons of stuff; the meat
2016-07-15 01:43:25 -04:00
# - a SpellTopBarView, which has some controls
2014-01-03 13:32:13 -05:00
# - a SpellPaletteView, which has
# - for each programmableProperty:
# - a SpellPaletteEntryView
#
2016-07-15 01:43:25 -04:00
# The CastButtonView always shows.
2014-01-03 13:32:13 -05:00
# 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.
# 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.
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 '
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-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:
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
2016-07-14 15:34:22 -04:00
if @ options . level . isType ( ' web-dev ' )
if @fakeProgrammableThang = @ createFakeProgrammableThang ( )
programmableThangs = [ @ fakeProgrammableThang ]
2016-07-15 01:43:25 -04:00
@ createSpells programmableThangs , programmableThangs [ 0 ] ? . world # Do before castButton
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) ->
2016-07-15 01:43:25 -04:00
programmableThangs = _ . filter e . thangs , (t) -> t . isProgrammable and t . programmableMethods and t . inThangList
2014-02-05 23:08:28 -05:00
@ createSpells programmableThangs , e . world
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 ]
pathComponents [ 0 ] = _ . string . slugify pathComponents [ 0 ]
spellKey = pathComponents . join ' / '
@ thangSpells [ thang . id ] . push spellKey
2016-07-15 01:43:25 -04:00
skipProtectAPI = @ getQueryVariable ' skip_protect_api ' , false
spell = @ spells [ spellKey ] = new Spell
hintsState: @ options . hintsState
programmableMethod: method
spellKey: spellKey
pathComponents: pathPrefixComponents . concat ( pathComponents )
session: @ options . session
otherSession: @ options . otherSession
supermodel: @ supermodel
skipProtectAPI: skipProtectAPI
worker: @ worker
language: language
spectateView: @ options . spectateView
spectateOpponentCodeLanguage: @ options . spectateOpponentCodeLanguage
observing: @ options . observing
levelID: @ options . levelID
level: @ options . level
god: @ options . god
2016-07-16 01:14:25 -04:00
courseID: @ options . courseID
2014-05-16 00:53:23 -04:00
2014-01-03 13:32:13 -05:00
for thangID , spellKeys of @ thangSpells
2016-07-14 15:34:22 -04:00
thang = @ fakeProgrammableThang ? world . getThangByID thangID
2014-02-05 18:16:59 -05:00
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) ->
2016-07-14 15:34:22 -04:00
return if @ options . level . isType ( ' web-dev ' )
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
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
onSpriteSelected: (e) ->
2016-07-14 15:34:22 -04:00
return if @ spellView and @ options . level . get ( ' type ' , true ) in [ ' hero ' , ' hero-ladder ' , ' hero-coop ' , ' course ' , ' course-ladder ' , ' game-dev ' , ' web-dev ' ] # Never deselect the hero in the Tome.
2016-07-15 01:43:25 -04:00
spell = @ spellFor e . thang , e . spellName
if spell ? . canRead ( )
@ setSpellView spell , e . thang
2016-07-14 15:34:22 -04:00
setSpellView: (spell, thang) ->
2014-02-02 13:26:42 -05:00
unless spell . view is @ spellView
@spellView = spell . view
2016-07-15 01:43:25 -04:00
@spellTopBarView = spell . topBarView
2014-02-02 13:26:42 -05:00
@ $el . find ( ' # ' + @ spellView . id ) . after ( @ spellView . el ) . remove ( )
2016-07-15 01:43:25 -04:00
@ $el . find ( ' # ' + @ spellTopBarView . id ) . after ( @ spellTopBarView . el ) . remove ( )
2015-02-02 14:50:25 -05:00
@ castButton ? . attachTo @ spellView
2014-10-16 15:08:21 -04:00
@ updateSpellPalette thang , spell
2014-01-31 19:16:59 -05:00
@ spellView ? . setThang thang
2014-04-25 19:57:42 -04:00
updateSpellPalette: (thang, spell) ->
2016-07-14 22:14:18 -04:00
return unless thang and @ spellPaletteView ? . thang isnt thang and ( thang . programmableProperties or thang . apiProperties or thang . programmableHTMLProperties )
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) ->
2016-07-14 15:34:22 -04:00
if @ options . level . isType ( ' web-dev ' )
@ setSpellView @ spells [ ' hero-placeholder/plan ' ] , @ fakeProgrammableThang
return
2016-07-15 01:43:25 -04:00
# This is fired by PlayLevelView
2014-11-17 18:07:10 -05:00
# 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
2016-07-14 15:34:22 -04:00
createFakeProgrammableThang: ->
return null unless hero = _ . find @ options . level . get ( ' thangs ' ) , id: ' Hero Placeholder '
return null unless programmableConfig = _ . find ( hero . components , (component) -> component . config ? . programmableMethods ) . config
2016-07-14 22:14:18 -04:00
usesHTMLConfig = _ . find ( hero . components , (component) -> component . config ? . programmableHTMLProperties ) . config
console . warn " Couldn ' t find usesHTML config; is it presented and not defaulted on the Hero Placeholder? " unless usesHTMLConfig
2016-07-14 15:34:22 -04:00
thang =
id: ' Hero Placeholder '
isProgrammable: true
2016-07-14 22:14:18 -04:00
thang = _ . merge thang , programmableConfig , usesHTMLConfig
2016-07-14 15:34:22 -04:00
thang
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 ( )