Stub WebSurface showing for web-dev levels

This commit is contained in:
Nick Winter 2016-07-14 12:34:22 -07:00
parent c5c831c211
commit 16b10612b6
16 changed files with 102 additions and 40 deletions

View file

@ -19,6 +19,7 @@ var languagesImported = {};
var ensureLanguageImported = function(language) {
if (languagesImported[language]) return;
if (language === 'html') return;
importScripts("/javascripts/app/vendor/aether-" + language + ".js");
languagesImported[language] = true;
};

View file

@ -80,7 +80,7 @@ var myImportScripts = importScripts;
var languagesImported = {};
var ensureLanguageImported = function(language) {
if (languagesImported[language]) return;
if (language === 'javascript') return; // Only has JSHint, but we don't need to lint here.
if (language === 'javascript' || language === 'html') return; // Only has JSHint, but we don't need to lint here.
myImportScripts("/javascripts/app/vendor/aether-" + language + ".js");
languagesImported[language] = true;
};

View file

@ -2,6 +2,7 @@ CocoModel = require 'models/CocoModel'
CocoCollection = require 'collections/CocoCollection'
{me} = require('core/auth')
locale = require 'locale/locale'
utils = require 'core/utils'
initializeFilePicker = ->
require('core/services/filepicker')() unless window.application.isIPadApp
@ -234,21 +235,14 @@ class ImageFileTreema extends TreemaNode.nodeMap.string
@refreshDisplay()
codeLanguages =
javascript: 'ace/mode/javascript'
coffeescript: 'ace/mode/coffee'
python: 'ace/mode/python'
lua: 'ace/mode/lua'
java: 'ace/mode/java'
class CodeLanguagesObjectTreema extends TreemaNode.nodeMap.object
childPropertiesAvailable: ->
(key for key in _.keys(codeLanguages) when not @data[key]? and not (key is 'javascript' and @workingSchema.skipJavaScript))
(key for key in _.keys(utils.aceEditModes) when not @data[key]? and not (key is 'javascript' and @workingSchema.skipJavaScript))
class CodeLanguageTreema extends TreemaNode.nodeMap.string
buildValueForEditing: (valEl, data) ->
super(valEl, data)
valEl.find('input').autocomplete(source: _.keys(codeLanguages), minLength: 0, delay: 0, autoFocus: true)
valEl.find('input').autocomplete(source: _.keys(utils.aceEditModes), minLength: 0, delay: 0, autoFocus: true)
valEl
class CodeTreema extends TreemaNode.nodeMap.ace
@ -256,8 +250,8 @@ class CodeTreema extends TreemaNode.nodeMap.ace
super(arguments...)
@workingSchema.aceTabSize = 4
# TODO: Find a less hacky solution for this
@workingSchema.aceMode = mode if mode = codeLanguages[@keyForParent]
@workingSchema.aceMode = mode if mode = codeLanguages[@parent?.data?.language]
@workingSchema.aceMode = mode if mode = utils.aceEditModes[@keyForParent]
@workingSchema.aceMode = mode if mode = utils.aceEditModes[@parent?.data?.language]
class CoffeeTreema extends CodeTreema
constructor: ->

View file

@ -259,7 +259,7 @@ startsWithVowel = (s) -> s[0] in 'aeiouAEIOU'
module.exports.filterMarkdownCodeLanguages = (text, language) ->
return '' unless text
currentLanguage = language or me.get('aceConfig')?.language or 'python'
excludedLanguages = _.without ['javascript', 'python', 'coffeescript', 'clojure', 'lua', 'java', 'io'], currentLanguage
excludedLanguages = _.without ['javascript', 'python', 'coffeescript', 'clojure', 'lua', 'java', 'io', 'html'], currentLanguage
# Exclude language-specific code blocks like ```python (... code ...)``` for each non-target language.
codeBlockExclusionRegex = new RegExp "```(#{excludedLanguages.join('|')})\n[^`]+```\n?", 'gm'
# Exclude language-specific images like ![python - image description](image url) for each non-target language.
@ -290,12 +290,12 @@ module.exports.filterMarkdownCodeLanguages = (text, language) ->
return text
module.exports.aceEditModes = aceEditModes =
'javascript': 'ace/mode/javascript'
'coffeescript': 'ace/mode/coffee'
'python': 'ace/mode/python'
'java': 'ace/mode/java'
'lua': 'ace/mode/lua'
'java': 'ace/mode/java'
javascript: 'ace/mode/javascript'
coffeescript: 'ace/mode/coffee'
python: 'ace/mode/python'
lua: 'ace/mode/lua'
java: 'ace/mode/java'
html: 'ace/mode/html'
module.exports.initializeACE = (el, codeLanguage) ->
contents = $(el).text().trim()

View file

@ -74,6 +74,8 @@ module.exports = class LevelLoader extends CocoClass
onLevelLoaded: ->
if not @sessionless and @level.isType('hero', 'hero-ladder', 'hero-coop', 'course')
@sessionDependenciesRegistered = {}
if @level.isType('web-dev')
@headless = true
if (@courseID and not @level.isType('course', 'course-ladder')) or window.serverConfig.picoCTF
# Because we now use original hero levels for both hero and course levels, we fake being a course level in this context.
originalGet = @level.get
@ -481,6 +483,7 @@ module.exports = class LevelLoader extends CocoClass
initWorld: ->
return if @initialized
@initialized = true
return if @level.isType('web-dev')
@world = new World()
@world.levelSessionIDs = if @opponentSessionID then [@sessionID, @opponentSessionID] else [@sessionID]
@world.submissionCount = @session?.get('state')?.submissionCount ? 0

View file

@ -61,7 +61,7 @@ _.extend CampaignSchema.properties, {
i18n: { type: 'object', format: 'hidden' }
requiresSubscription: { type: 'boolean' }
replayable: { type: 'boolean' }
type: {'enum': ['ladder', 'ladder-tutorial', 'hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder', 'game-dev']}
type: {'enum': ['ladder', 'ladder-tutorial', 'hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder', 'game-dev', 'web-dev']}
slug: { type: 'string', format: 'hidden' }
original: { type: 'string', format: 'hidden' }
adventurer: { type: 'boolean' }

View file

@ -313,7 +313,7 @@ _.extend LevelSchema.properties,
icon: {type: 'string', format: 'image-file', title: 'Icon'}
banner: {type: 'string', format: 'image-file', title: 'Banner'}
goals: c.array {title: 'Goals', description: 'An array of goals which are visible to the player and can trigger scripts.'}, GoalSchema
type: c.shortString(title: 'Type', description: 'What kind of level this is.', 'enum': ['campaign', 'ladder', 'ladder-tutorial', 'hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder', 'game-dev'])
type: c.shortString(title: 'Type', description: 'What kind of level this is.', 'enum': ['campaign', 'ladder', 'ladder-tutorial', 'hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder', 'game-dev', 'web-dev'])
terrain: c.terrainString
showsGuide: c.shortString(title: 'Shows Guide', description: 'If the guide is shown at the beginning of the level.', 'enum': ['first-time', 'always'])
requiresSubscription: {title: 'Requires Subscription', description: 'Whether this level is available to subscribers only.', type: 'boolean'}

View file

@ -272,6 +272,26 @@ $level-resize-transition-time: 0.5s
right: 45%
z-index: 1000000
&.web-dev
position: absolute
top: 0
bottom: 0
left: 0
right: 0
#playback-view, #thang-hud, #level-dialogue-view, #play-footer, #level-footer-background, #level-footer-shadow
display: none
.game-container, .level-content, #game-area, #canvas-wrapper
height: 100%
#canvas-wrapper canvas
display: none
#web-surface
width: 100%
height: 100%
html.fullscreen-editor
#level-view
#fullscreen-editor-background-screen

View file

@ -24,6 +24,8 @@ if view.showAds()
#canvas-wrapper
canvas(width=924, height=589)#webgl-surface
canvas(width=924, height=589)#normal-surface
#web-surface
#ascii-surface
#canvas-left-gradient.gradient
#canvas-top-gradient.gradient

View file

@ -169,7 +169,7 @@ module.exports = class LevelLoadingView extends CocoView
@playSound 'loading-view-unveil', 0.5
@$el.find('.left-wing').css left: '-100%', backgroundPosition: 'right -400px top 0'
@$el.find('.right-wing').css right: '-100%', backgroundPosition: 'left -400px top 0'
$('#level-footer-background').detach().appendTo('#page-container').slideDown(duration)
$('#level-footer-background').detach().appendTo('#page-container').slideDown(duration) unless @level.isType('web-dev')
unveilIntro: =>
return if @destroyed or not @intro or @unveiled

View file

@ -132,7 +132,7 @@ module.exports = class PlayLevelView extends RootView
load: ->
@loadStartTime = new Date()
@god = new God({@gameUIState})
@god = new God({@gameUIState}) # TODO: don't make one of these in web-dev mode
levelLoaderOptions = supermodel: @supermodel, levelID: @levelID, sessionID: @sessionID, opponentSessionID: @opponentSessionID, team: @getQueryVariable('team'), observing: @observing, courseID: @courseID
if me.isSessionless()
levelLoaderOptions.fakeSessionConfig = {}
@ -196,9 +196,12 @@ module.exports = class PlayLevelView extends RootView
grabLevelLoaderData: ->
@session = @levelLoader.session
@world = @levelLoader.world
@level = @levelLoader.level
@$el.addClass 'hero' if @level.isType('hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder', 'game-dev', 'web-dev') # TODO: figure out what this does and comment it
if @level.isType('web-dev')
@$el.addClass 'web-dev' # Hide some of the elements we won't be using
return
@world = @levelLoader.world
@$el.addClass 'hero' if @level.isType('hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder', 'game-dev') # TODO: figure out what this does and comment it
@$el.addClass 'flags' if _.any(@world.thangs, (t) -> (t.programmableProperties and 'findFlags' in t.programmableProperties) or t.inventory?.flag) or @level.get('slug') is 'sky-span'
# TODO: Update terminology to always be opponentSession or otherSession
# TODO: E.g. if it's always opponent right now, then variable names should be opponentSession until we have coop play
@ -234,6 +237,7 @@ module.exports = class PlayLevelView extends RootView
@session.set('code', myCode)
setupGod: ->
return if @level.isType('web-dev')
@god.setLevel @level.serialize {@supermodel, @session, @otherSession, headless: false, sessionless: false}
@god.setLevelSessionIDs if @otherSession then [@session.id, @otherSession.id] else [@session.id]
@god.setWorldClassMap @world.classMap
@ -252,12 +256,12 @@ module.exports = class PlayLevelView extends RootView
insertSubviews: ->
@hintsState = new HintsState({ hidden: true }, { @session, @level })
@insertSubView @tome = new TomeView { @levelID, @session, @otherSession, thangs: @world.thangs, @supermodel, @level, @observing, @courseID, @courseInstanceID, @god, @hintsState }
@insertSubView new LevelPlaybackView session: @session, level: @level
@insertSubView @tome = new TomeView { @levelID, @session, @otherSession, thangs: @world?.thangs ? [], @supermodel, @level, @observing, @courseID, @courseInstanceID, @god, @hintsState }
@insertSubView new LevelPlaybackView session: @session, level: @level unless @level.isType('web-dev')
@insertSubView new GoalsView {level: @level}
@insertSubView new LevelFlagsView levelID: @levelID, world: @world if @$el.hasClass 'flags'
@insertSubView new GoldView {} unless @level.get('slug') in ['wakka-maul']
@insertSubView new HUDView {level: @level}
@insertSubView new GoldView {} unless @level.get('slug') in ['wakka-maul'] unless @level.isType('web-dev')
@insertSubView new HUDView {level: @level} unless @level.isType('web-dev')
@insertSubView new LevelDialogueView {level: @level, sessionID: @session.id}
@insertSubView new ChatView levelID: @levelID, sessionID: @session.id, session: @session
@insertSubView new ProblemAlertView session: @session, level: @level, supermodel: @supermodel
@ -272,6 +276,7 @@ module.exports = class PlayLevelView extends RootView
Backbone.Mediator.publish 'level:set-volume', volume: volume
initScriptManager: ->
return if @level.isType('web-dev')
@scriptManager = new ScriptManager({scripts: @world.scripts or [], view: @, session: @session, levelID: @level.get('slug')})
@scriptManager.loadFromSession()
@ -318,7 +323,10 @@ module.exports = class PlayLevelView extends RootView
@saveRecentMatch() if @otherSession
@levelLoader.destroy()
@levelLoader = null
@initSurface()
if @level.isType('web-dev')
@initWebSurface()
else
@initSurface()
saveRecentMatch: ->
allRecentlyPlayedMatches = storage.load('recently-played-matches') ? {}
@ -355,12 +363,13 @@ module.exports = class PlayLevelView extends RootView
# Once Surface is Loaded ####################################################
onLevelStarted: ->
return unless @surface?
return unless @surface? or @webSurface?
@loadingView.showReady()
@trackLevelLoadEnd()
if window.currentModal and not window.currentModal.destroyed and window.currentModal.constructor isnt VictoryModal
return Backbone.Mediator.subscribeOnce 'modal:closed', @onLevelStarted, @
@surface.showLevel()
@surface?.showLevel()
@webSurface?.showLevel()
Backbone.Mediator.publish 'level:set-time', time: 0
if (@isEditorPreview or @observing) and not @getQueryVariable('intro')
@loadingView.startUnveiling()
@ -406,7 +415,7 @@ module.exports = class PlayLevelView extends RootView
Backbone.Mediator.publish 'level:suppress-selection-sounds', suppress: true
Backbone.Mediator.publish 'tome:select-primary-sprite', {}
Backbone.Mediator.publish 'level:suppress-selection-sounds', suppress: false
@surface.focusOnHero()
@surface?.focusOnHero()
perhapsStartSimulating: ->
return unless @shouldSimulate()
@ -662,3 +671,11 @@ module.exports = class PlayLevelView extends RootView
@setupManager?.destroy()
@setupManager = new LevelSetupManager({supermodel: @supermodel, level: @level, levelID: @levelID, parent: @, session: @session, hadEverChosenHero: true})
@setupManager.open()
# web-dev levels
initWebSurface: ->
@webSurface = showLevel: =>
# TODO: make a real WebSurface class
@$('#web-surface').css('background-color', 'red')
Backbone.Mediator.publish 'level:started', {}

View file

@ -42,6 +42,7 @@ module.exports = class DocFormatter
@fillOutDoc()
fillOutDoc: ->
# TODO: figure out how to do html docs for web-dev levels
if _.isString @doc
@doc = name: @doc, type: typeof @options.thang[@doc]
if @options.isSnippet

View file

@ -65,6 +65,7 @@ module.exports = class Spell
@worker = null
setLanguage: (@language) ->
@language = 'html' if @level.isType('web-dev')
#console.log 'setting language to', @language, 'so using original source', @languages[language] ? @languages.javascript
@originalSource = @languages[@language] ? @languages.javascript
@originalSource = @addPicoCTFProblem() if window.serverConfig.picoCTF
@ -126,6 +127,8 @@ module.exports = class Spell
else
source = @getSource()
[pure, problems] = [null, null]
if @language is 'html'
[pure, problems] = [source, []] # TODO: problems? Actually do something when transpiling
for thangID, spellThang of @thangs
unless pure
pure = spellThang.aether.transpile source

View file

@ -55,6 +55,7 @@ module.exports = class SpellListTabEntryView extends SpellListEntryView
@buildDocs() unless @docsBuilt
buildAvatar: ->
return unless @thang.world
avatar = new ThangAvatarView thang: @thang, includeName: false, supermodel: @supermodel
if @avatar
@avatar.$el.replaceWith avatar.$el

View file

@ -226,7 +226,7 @@ module.exports = class SpellView extends CocoView
disableSpaces = @options.level.get('disableSpaces') or false
aceConfig = me.get('aceConfig') ? {}
disableSpaces = false if aceConfig.keyBindings and aceConfig.keyBindings isnt 'default' # Not in vim/emacs mode
disableSpaces = false if @spell.language in ['lua', 'java', 'coffeescript'] # Don't disable for more advanced/experimental languages
disableSpaces = false if @spell.language in ['lua', 'java', 'coffeescript', 'html'] # Don't disable for more advanced/experimental languages
if not disableSpaces or (_.isNumber(disableSpaces) and disableSpaces < me.level())
return @ace.execCommand 'insertstring', ' '
line = @aceDoc.getLine @ace.getCursorPosition().row
@ -470,6 +470,7 @@ module.exports = class SpellView extends CocoView
# TODO: Turn on more autocompletion based on level sophistication
# TODO: E.g. using the language default snippets yields a bunch of crazy non-beginner suggestions
# TODO: Options logic shouldn't exist both here and in updateAutocomplete()
return if @spell.language is 'html'
popupFontSizePx = @options.level.get('autocompleteFontSizePx') ? 16
@zatanna = new Zatanna @ace,
basic: false
@ -864,7 +865,9 @@ module.exports = class SpellView extends CocoView
beginningOfLine = not currentLine.substr(0, cursorPosition.column).trim().length # uncommenting code, for example
incompleteThis = /^(s|se|sel|self|t|th|thi|this)$/.test currentLine.trim()
#console.log "finished=#{valid and (endOfLine or beginningOfLine) and not incompleteThis}", valid, endOfLine, beginningOfLine, incompleteThis, cursorPosition, currentLine.length, aether, new Date() - 0, currentLine
if valid and (endOfLine or beginningOfLine) and not incompleteThis
if @options.level.isType('web-dev') and valid
console.log 'Update it!'
else if valid and (endOfLine or beginningOfLine) and not incompleteThis
@preload()
singleLineCommentRegex: ->
@ -976,8 +979,6 @@ module.exports = class SpellView extends CocoView
@ace.insert "{x=#{e.x}, y=#{e.y}}"
else
@ace.insert "{x: #{e.x}, y: #{e.y}}"
@highlightCurrentLine()
onStatementIndexUpdated: (e) ->
@ -1246,7 +1247,7 @@ module.exports = class SpellView extends CocoView
@debugView?.destroy()
@translationView?.destroy()
@toolbarView?.destroy()
@zatanna.addSnippets [], @editorLang if @editorLang?
@zatanna?.addSnippets [], @editorLang if @editorLang?
$(window).off 'resize', @onWindowResize
window.clearTimeout @saveSpadeTimeout
@saveSpadeTimeout = null

View file

@ -59,6 +59,9 @@ module.exports = class TomeView extends CocoView
super()
@worker = @createWorker()
programmableThangs = _.filter @options.thangs, (t) -> t.isProgrammable and t.programmableMethods
if @options.level.isType('web-dev')
if @fakeProgrammableThang = @createFakeProgrammableThang()
programmableThangs = [@fakeProgrammableThang]
@createSpells programmableThangs, programmableThangs[0]?.world # Do before spellList, thangList, and castButton
unless @options.level.isType('hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder', 'game-dev', 'web-dev')
@spellList = @insertSubView new SpellListView spells: @spells, supermodel: @supermodel, level: @options.level
@ -140,7 +143,7 @@ module.exports = class TomeView extends CocoView
god: @options.god
for thangID, spellKeys of @thangSpells
thang = world.getThangByID thangID
thang = @fakeProgrammableThang ? world.getThangByID thangID
if thang
@spells[spellKey].addThang thang for spellKey in spellKeys
else
@ -161,6 +164,7 @@ module.exports = class TomeView extends CocoView
@cast e?.preload, e?.realTime
cast: (preload=false, realTime=false) ->
return if @options.level.isType('web-dev')
sessionState = @options.session.get('state') ? {}
if realTime
sessionState.submissionCount = (sessionState.submissionCount ? 0) + 1
@ -194,7 +198,7 @@ module.exports = class TomeView extends CocoView
@castButton?.$el.hide()
onSpriteSelected: (e) ->
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.
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.
thang = e.thang
spellName = e.spellName
@spellList?.$el.hide()
@ -204,6 +208,9 @@ module.exports = class TomeView extends CocoView
@clearSpellView()
@updateSpellPalette thang, spell if spell
return
@setSpellView spell, thang
setSpellView: (spell, thang) ->
unless spell.view is @spellView
@clearSpellView()
@spellView = spell.view
@ -246,6 +253,9 @@ module.exports = class TomeView extends CocoView
@cast()
onSelectPrimarySprite: (e) ->
if @options.level.isType('web-dev')
@setSpellView @spells['hero-placeholder/plan'], @fakeProgrammableThang
return
# 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'
@ -253,6 +263,15 @@ module.exports = class TomeView extends CocoView
else
Backbone.Mediator.publish 'level:select-sprite', thangID: 'Hero Placeholder'
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
thang =
id: 'Hero Placeholder'
isProgrammable: true
thang = _.merge thang, programmableConfig
thang
destroy: ->
spell.destroy() for spellKey, spell of @spells
@worker?.terminate()