mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-27 09:35:39 -05:00
Game dev levels (#3810)
* Tweak API doc behavior and styling * Instead of moving to the left during active dialogues, just move to the top * Allow pointer events * Adjust close button * Re-enable pinning API docs for game-dev and web-dev levels * Make sidebar in PlayGameDevLevelView stretch, better layout columns * Set up content of PlayGameDevLevelView sidebar to scroll * Add rest of PlayGameDevLevelView sidebar content, rework what loading looks like * Finish PlayGameDevLevelView * Add share area below * Cover the brown background, paint it gray * Tweak PlayGameDevLevelView * Have progress bar show everything * Fix Surface resize handling * Fix PlayGameDevLevelView resizing incorrectly when playing * Add GameDevVictoryModal to PlayGameDevLevelView * Don't show missing-doctype annotation in Ace * Hook up GameDevVictoryModal copy button * Fix onChangeAnnotation runtime error * Fix onLevelLoaded runtime error * Have CourseVictoryModal link to /courses when course is done * Trim, update CourseDetailsView * Remove last vestiges of teacherMode * Remove giant navigation buttons at top * Quick switch to flat style * Add analytics for game-dev * Update Analytics events for gamedev * Prefix event names with context * Send to Mixpanel * Include more properties * Mostly set up indefinite play and autocast for game-dev levels * Set up cast buttons and shortcut for game-dev * Add rudimentary instructions when students play game-dev levels * Couple tweaks * fix a bit of code that expects frames to always stick around * have PlayGameDevLevelView render a couple frames on load * API Docs use 'game' instead of 'hero' * Move tags to head without combining * Add HTML comment-start string Fixes missing entry point arrows * Fix some whitespace
This commit is contained in:
parent
b7f916116d
commit
d77625bc77
40 changed files with 533 additions and 255 deletions
|
@ -3,7 +3,11 @@
|
|||
window.addEventListener('message', receiveMessage, false);
|
||||
|
||||
var concreteDom;
|
||||
var concreteStyles;
|
||||
var concreteScripts;
|
||||
var virtualDom;
|
||||
var virtualStyles;
|
||||
var virtualScripts;
|
||||
var goalStates;
|
||||
|
||||
var allowedOrigins = [
|
||||
|
@ -54,11 +58,31 @@ function create({ dom, styles, scripts }) {
|
|||
concreteDom = deku.dom.create(dom);
|
||||
concreteStyles = deku.dom.create(styles);
|
||||
concreteScripts = deku.dom.create(scripts);
|
||||
// TODO: target the actual HTML tag and combine our initial structure for styles/scripts/tags with theirs
|
||||
// TODO: :after elements don't seem to work? (:before do)
|
||||
$('body').first().empty().append(concreteDom);
|
||||
$('#player-styles').first().empty().append(concreteStyles);
|
||||
$('#player-scripts').first().empty().append(concreteScripts);
|
||||
replaceNodes('[for="player-styles"]', unwrapConcreteNodes(concreteStyles));
|
||||
replaceNodes('[for="player-scripts"]', unwrapConcreteNodes(concreteScripts));
|
||||
}
|
||||
|
||||
function unwrapConcreteNodes(wrappedNodes) {
|
||||
return wrappedNodes.children;
|
||||
}
|
||||
|
||||
function replaceNodes(selector, newNodes){
|
||||
$newNodes = $(newNodes).clone()
|
||||
$(selector + ':not(:first)').remove();
|
||||
|
||||
firstNode = $(selector).first();
|
||||
$newNodes.attr('for', firstNode.attr('for'));
|
||||
|
||||
newFirstNode = $newNodes[0];
|
||||
try {
|
||||
firstNode.replaceWith(newFirstNode); // Removes newFirstNode from its array (!!)
|
||||
} catch (e) {
|
||||
console.log('Failed to update some nodes:', e);
|
||||
}
|
||||
|
||||
$(newFirstNode).after($newNodes);
|
||||
}
|
||||
|
||||
function update({ dom, styles, scripts }) {
|
||||
|
@ -68,11 +92,13 @@ function update({ dom, styles, scripts }) {
|
|||
var domChanges = deku.diff.diffNode(virtualDom, dom);
|
||||
domChanges.reduce(deku.dom.update(dispatch, context), concreteDom); // Rerender
|
||||
|
||||
var scriptChanges = deku.diff.diffNode(virtualScripts, scripts);
|
||||
scriptChanges.reduce(deku.dom.update(dispatch, context), concreteScripts); // Rerender
|
||||
// var scriptChanges = deku.diff.diffNode(virtualScripts, scripts);
|
||||
// scriptChanges.reduce(deku.dom.update(dispatch, context), concreteScripts); // Rerender
|
||||
// replaceNodes('[for="player-scripts"]', unwrapConcreteNodes(concreteScripts));
|
||||
|
||||
var styleChanges = deku.diff.diffNode(virtualStyles, styles);
|
||||
styleChanges.reduce(deku.dom.update(dispatch, context), concreteStyles); // Rerender
|
||||
replaceNodes('[for="player-styles"]', unwrapConcreteNodes(concreteStyles));
|
||||
|
||||
virtualDom = dom;
|
||||
virtualStyles = styles;
|
||||
|
|
|
@ -389,6 +389,8 @@ self.runWorld = function runWorld(args) {
|
|||
self.world.preloading = args.preload;
|
||||
self.world.headless = args.headless;
|
||||
self.world.realTime = args.realTime;
|
||||
self.world.indefiniteLength = args.indefiniteLength;
|
||||
self.world.justBegin = args.justBegin;
|
||||
self.goalManager = new GoalManager(self.world);
|
||||
self.goalManager.setGoals(args.goals);
|
||||
self.goalManager.setCode(args.userCodeMap);
|
||||
|
@ -434,6 +436,9 @@ self.onWorldLoaded = function onWorldLoaded() {
|
|||
var diff = t1 - self.t0;
|
||||
var goalStates = self.goalManager.getGoalStates();
|
||||
var totalFrames = self.world.totalFrames;
|
||||
if(self.world.indefiniteLength) {
|
||||
totalFrames = self.world.frames.length;
|
||||
}
|
||||
if(self.world.ended) {
|
||||
var overallStatus = self.goalManager.checkOverallStatus();
|
||||
var lastFrameHash = self.world.frames[totalFrames - 2].hash
|
||||
|
|
|
@ -35,9 +35,13 @@
|
|||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
|
||||
|
||||
<!-- Extracted player/level styles and scripts -->
|
||||
<style id="player-styles">
|
||||
<style for="level-styles">
|
||||
</style>
|
||||
<script id="player-scripts">
|
||||
<script for="level-scripts">
|
||||
</script>
|
||||
<style for="player-styles">
|
||||
</style>
|
||||
<script for="player-scripts">
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
|
5
app/core/urls.coffee
Normal file
5
app/core/urls.coffee
Normal file
|
@ -0,0 +1,5 @@
|
|||
module.exports =
|
||||
playDevLevel: ({level, session, course}) ->
|
||||
shareURL = "#{window.location.origin}/play/#{level.get('type')}-level/#{level.get('slug')}/#{session.id}"
|
||||
shareURL += "?course=#{course.id}" if course
|
||||
return shareURL
|
|
@ -111,7 +111,9 @@ module.exports = class Angel extends CocoClass
|
|||
when 'user-code-problem'
|
||||
@publishGodEvent 'user-code-problem', problem: event.data.problem
|
||||
when 'world-load-progress-changed'
|
||||
@publishGodEvent 'world-load-progress-changed', progress: event.data.progress
|
||||
progress = event.data.progress
|
||||
progress = Math.min(progress, 0.9) if @work.indefiniteLength
|
||||
@publishGodEvent 'world-load-progress-changed', { progress }
|
||||
unless event.data.progress is 1 or @work.preload or @work.headless or @work.synchronous or @deserializationQueue.length or (@shared.firstWorld and not @shared.spectate)
|
||||
@worker.postMessage func: 'serializeFramesSoFar' # Stream it!
|
||||
|
||||
|
@ -138,6 +140,7 @@ module.exports = class Angel extends CocoClass
|
|||
return if @aborting
|
||||
# Toggle BOX2D_ENABLED during deserialization so that if we have box2d in the namespace, the Collides Components still don't try to create bodies for deserialized Thangs upon attachment.
|
||||
window.BOX2D_ENABLED = false
|
||||
streamingWorld?.indefiniteLength = @work.indefiniteLength
|
||||
@streamingWorld = World.deserialize serialized, @shared.worldClassMap, @shared.lastSerializedWorldFrames, @finishBeholdingWorld(goalStates), startFrame, endFrame, @work.level, streamingWorld
|
||||
window.BOX2D_ENABLED = true
|
||||
@shared.lastSerializedWorldFrames = serialized.frames
|
||||
|
@ -145,7 +148,10 @@ module.exports = class Angel extends CocoClass
|
|||
finishBeholdingWorld: (goalStates) -> (world) =>
|
||||
return if @aborting or @destroyed
|
||||
finished = world.frames.length is world.totalFrames
|
||||
firstChangedFrame = world.findFirstChangedFrame @shared.world
|
||||
if @work?.indefiniteLength and world.victory?
|
||||
finished = true
|
||||
world.totalFrames = world.frames.length
|
||||
firstChangedFrame = if @work.indefiniteLength then 0 else world.findFirstChangedFrame @shared.world
|
||||
eventType = if finished then 'new-world-created' else 'streaming-world-updated'
|
||||
if finished
|
||||
@shared.world = world
|
||||
|
|
|
@ -20,6 +20,7 @@ module.exports = class God extends CocoClass
|
|||
options ?= {}
|
||||
@retrieveValueFromFrame = _.throttle @retrieveValueFromFrame, 1000
|
||||
@gameUIState ?= options.gameUIState or new GameUIState()
|
||||
@indefiniteLength = options.indefiniteLength or false
|
||||
super()
|
||||
|
||||
# Angels are all given access to this.
|
||||
|
@ -71,9 +72,9 @@ module.exports = class God extends CocoClass
|
|||
@lastFixedSeed = e.fixedSeed
|
||||
@lastFlagHistory = (flag for flag in e.flagHistory when flag.source isnt 'code')
|
||||
@lastDifficulty = e.difficulty
|
||||
@createWorld e.spells, e.preload, e.realTime
|
||||
@createWorld e.spells, e.preload, e.realTime, e.justBegin
|
||||
|
||||
createWorld: (spells, preload, realTime) ->
|
||||
createWorld: (spells, preload, realTime, justBegin) ->
|
||||
console.log "#{@nick}: Let there be light upon #{@level.name}! (preload: #{preload})"
|
||||
userCodeMap = @getUserCodeMap spells
|
||||
|
||||
|
@ -107,6 +108,8 @@ module.exports = class God extends CocoClass
|
|||
preload
|
||||
synchronous: not Worker? # Profiling world simulation is easier on main thread, or we are IE9.
|
||||
realTime
|
||||
justBegin
|
||||
indefiniteLength: @indefiniteLength and realTime
|
||||
}
|
||||
@angelsShare.workQueue.push work
|
||||
angel.workIfIdle() for angel in @angelsShare.angels
|
||||
|
|
|
@ -92,9 +92,9 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
})
|
||||
@realTimeInputEvents = @gameUIState.get('realTimeInputEvents')
|
||||
@listenTo(@gameUIState, 'sprite:mouse-down', @onSpriteMouseDown)
|
||||
@onResize = _.debounce @onResize, resizeDelay
|
||||
@initEasel()
|
||||
@initAudio()
|
||||
@onResize = _.debounce @onResize, resizeDelay
|
||||
$(window).on 'resize', @onResize
|
||||
if @world.ended
|
||||
_.defer => @setWorld @world
|
||||
|
@ -131,8 +131,9 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
@handleEvents
|
||||
})
|
||||
@countdownScreen = new CountdownScreen camera: @camera, layer: @screenLayer, showsCountdown: @world.showsCountdown
|
||||
@playbackOverScreen = new PlaybackOverScreen camera: @camera, layer: @screenLayer, playerNames: @options.playerNames
|
||||
@normalStage.addChildAt @playbackOverScreen.dimLayer, 0 # Put this below the other layers, actually, so we can more easily read text on the screen.
|
||||
unless @options.levelType is 'game-dev'
|
||||
@playbackOverScreen = new PlaybackOverScreen camera: @camera, layer: @screenLayer, playerNames: @options.playerNames
|
||||
@normalStage.addChildAt @playbackOverScreen.dimLayer, 0 # Put this below the other layers, actually, so we can more easily read text on the screen.
|
||||
@initCoordinates()
|
||||
@webGLStage.enableMouseOver(10)
|
||||
@webGLStage.addEventListener 'stagemousemove', @onMouseMove
|
||||
|
@ -227,6 +228,7 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
|
||||
restoreWorldState: ->
|
||||
frame = @world.getFrame(@getCurrentFrame())
|
||||
return unless frame
|
||||
frame.restoreState()
|
||||
current = Math.max(0, Math.min(@currentFrame, @world.frames.length - 1))
|
||||
if current - Math.floor(current) > 0.01 and Math.ceil(current) < @world.frames.length - 1
|
||||
|
@ -325,12 +327,12 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
@surfacePauseTimeout = _.delay performToggle, 2000
|
||||
@lankBoss.stop()
|
||||
@trailmaster?.stop()
|
||||
@playbackOverScreen.show()
|
||||
@playbackOverScreen?.show()
|
||||
else
|
||||
performToggle()
|
||||
@lankBoss.play()
|
||||
@trailmaster?.play()
|
||||
@playbackOverScreen.hide()
|
||||
@playbackOverScreen?.hide()
|
||||
|
||||
|
||||
|
||||
|
@ -348,7 +350,7 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
world: @world
|
||||
)
|
||||
|
||||
if @lastFrame < @world.frames.length and @currentFrame >= @world.totalFrames - 1
|
||||
if (not @world.indefiniteLength) and @lastFrame < @world.frames.length and @currentFrame >= @world.totalFrames - 1
|
||||
@ended = true
|
||||
@setPaused true
|
||||
Backbone.Mediator.publish 'surface:playback-ended', {}
|
||||
|
@ -551,6 +553,9 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
if application.isIPadApp
|
||||
newWidth = 1024
|
||||
newHeight = newWidth / aspectRatio
|
||||
else if @options.resizeStrategy is 'wrapper-size'
|
||||
newWidth = $('#canvas-wrapper').width()
|
||||
newHeight = newWidth / aspectRatio
|
||||
else if @realTime or @options.spectateGame
|
||||
pageHeight = $('#page-container').height() - $('#control-bar-view').outerHeight() - $('#playback-view').outerHeight()
|
||||
newWidth = Math.min pageWidth, pageHeight * aspectRatio
|
||||
|
@ -576,6 +581,7 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
return if newWidth is oldWidth and newHeight is oldHeight and not @options.spectateGame
|
||||
return if newWidth < 200 or newHeight < 200
|
||||
@normalCanvas.add(@webGLCanvas).attr width: newWidth, height: newHeight
|
||||
@trigger 'resize', { width: newWidth, height: newHeight }
|
||||
|
||||
# Cannot do this to the webGLStage because it does not use scaleX/Y.
|
||||
# Instead the LayerAdapter scales webGL-enabled layers.
|
||||
|
|
|
@ -28,6 +28,7 @@ module.exports = class World
|
|||
debugging: false # Whether we are just rerunning to debug a world we've already cast
|
||||
headless: false # Whether we are just simulating for goal states instead of all serialized results
|
||||
framesSerializedSoFar: 0
|
||||
framesClearedSoFar: 0
|
||||
apiProperties: ['age', 'dt']
|
||||
realTimeBufferMax: REAL_TIME_BUFFER_MAX / 1000
|
||||
constructor: (@userCodeMap, classMap) ->
|
||||
|
@ -96,6 +97,7 @@ module.exports = class World
|
|||
|
||||
loadFrames: (loadedCallback, errorCallback, loadProgressCallback, preloadedCallback, skipDeferredLoading, loadUntilFrame) ->
|
||||
return if @aborted
|
||||
@totalFrames = 2 if @justBegin
|
||||
console.log 'Warning: loadFrames called on empty World (no thangs).' unless @thangs.length
|
||||
continueLaterFn = =>
|
||||
@loadFrames(loadedCallback, errorCallback, loadProgressCallback, preloadedCallback, skipDeferredLoading, loadUntilFrame) unless @destroyed
|
||||
|
@ -116,7 +118,13 @@ module.exports = class World
|
|||
@lastRealTimeUpdate ?= 0
|
||||
frameToLoadUntil = if loadUntilFrame then loadUntilFrame + 1 else @totalFrames # Might stop early if debugging.
|
||||
i = @frames.length
|
||||
while i < frameToLoadUntil and i < @totalFrames
|
||||
while true
|
||||
if @indefiniteLength
|
||||
break if not @realTime # realtime has been stopped
|
||||
break if @victory? # game won or lost # TODO: give a couple seconds of buffer after victory is set instead of ending instantly
|
||||
else
|
||||
break if i >= frameToLoadUntil
|
||||
break if i >= @totalFrames
|
||||
return unless @shouldContinueLoading t1, loadProgressCallback, skipDeferredLoading, continueLaterFn
|
||||
@adjustFlowSettings loadUntilFrame if @debugging
|
||||
try
|
||||
|
@ -379,6 +387,11 @@ module.exports = class World
|
|||
@freeMemoryBeforeFinalSerialization() if @ended
|
||||
startFrame = @framesSerializedSoFar
|
||||
endFrame = @frames.length
|
||||
if @indefiniteLength
|
||||
toClear = Math.max(@framesSerializedSoFar-10, 0)
|
||||
for i in _.range(@framesClearedSoFar, toClear)
|
||||
@frames[i] = null
|
||||
@framesClearedSoFar = @framesSerializedSoFar
|
||||
#console.log "... world serializing frames from", startFrame, "to", endFrame, "of", @totalFrames
|
||||
[transferableObjects, nontransferableObjects] = [0, 0]
|
||||
serializedFlagHistory = (_.omit(_.clone(flag), 'processed') for flag in @flagHistory)
|
||||
|
@ -525,6 +538,14 @@ module.exports = class World
|
|||
perf.framesCPUTime = 0
|
||||
w.frames = [] unless streamingWorld
|
||||
clearTimeout @deserializationTimeout if @deserializationTimeout
|
||||
|
||||
if w.indefiniteLength
|
||||
clearTo = Math.max(w.frames.length - 100, 0)
|
||||
if clearTo > w.framesClearedSoFar
|
||||
for i in _.range(w.framesClearedSoFar, clearTo)
|
||||
w.frames[i] = null
|
||||
w.framesClearedSoFar = clearTo
|
||||
|
||||
@deserializationTimeout = _.delay @deserializeSomeFrames, 1, o, w, finishedWorldCallback, perf, startFrame, endFrame
|
||||
w # Return in-progress deserializing world
|
||||
|
||||
|
@ -588,6 +609,7 @@ module.exports = class World
|
|||
lastPos = x: null, y: null
|
||||
for frameIndex in [lastFrameIndex .. 0] by -1
|
||||
frame = @frames[frameIndex]
|
||||
continue unless frame # may have been evicted for game dev levels
|
||||
if pos = frame.thangStateMap[thangID]?.getStateForProp 'pos'
|
||||
pos = camera.worldToSurface {x: pos.x, y: pos.y} if camera # without z
|
||||
if not lastPos.x? or (Math.abs(lastPos.x - pos.x) + Math.abs(lastPos.y - pos.y)) > 1
|
||||
|
|
|
@ -10,7 +10,7 @@ module.exports = class WorldFrame
|
|||
getNextFrame: ->
|
||||
# Optimized. Must be called while thangs are current at this frame.
|
||||
nextTime = @time + @world.dt
|
||||
return null if nextTime > @world.lifespan
|
||||
return null if nextTime > @world.lifespan and not @world.indefiniteLength
|
||||
@hash = @world.rand.seed
|
||||
@hash += system.update() for system in @world.systems
|
||||
nextFrame = new WorldFrame(@world, nextTime)
|
||||
|
|
|
@ -6,6 +6,7 @@ module.exports =
|
|||
thang: {type: 'object'}
|
||||
preload: {type: 'boolean'}
|
||||
realTime: {type: 'boolean'}
|
||||
justBegin: {type: 'boolean'}
|
||||
|
||||
'tome:cast-spells': c.object {title: 'Cast Spells', description: 'Published when spells are cast', required: ['spells', 'preload', 'realTime', 'submissionCount', 'flagHistory', 'difficulty', 'god']},
|
||||
spells: {type: 'object'}
|
||||
|
@ -16,6 +17,7 @@ module.exports =
|
|||
flagHistory: {type: 'array'}
|
||||
difficulty: {type: 'integer'}
|
||||
god: {type: 'object'}
|
||||
justBegin: {type: 'boolean'}
|
||||
|
||||
'tome:manual-cast': c.object {title: 'Manually Cast Spells', description: 'Published when you wish to manually recast all spells', required: []},
|
||||
realTime: {type: 'boolean'}
|
||||
|
|
8
app/styles/play/level/modal/game-dev-victory-modal.sass
Normal file
8
app/styles/play/level/modal/game-dev-victory-modal.sass
Normal file
|
@ -0,0 +1,8 @@
|
|||
#game-dev-victory-modal
|
||||
.share-row
|
||||
margin: 20px 0
|
||||
|
||||
#copy-url-input
|
||||
width: 50%
|
||||
margin: 0 10px
|
||||
display: inline-block
|
|
@ -1,9 +1,19 @@
|
|||
#play-game-dev-level-view
|
||||
.container-fluid
|
||||
overflow: hidden
|
||||
background: #333
|
||||
padding: 15px
|
||||
height: 100vh
|
||||
|
||||
#game-row
|
||||
display: flex
|
||||
|
||||
#canvas-wrapper
|
||||
width: 100%
|
||||
position: relative
|
||||
overflow: hidden
|
||||
z-index: 0
|
||||
border-radius: 5px
|
||||
|
||||
#webgl-surface
|
||||
background-color: #333
|
||||
|
@ -18,5 +28,34 @@
|
|||
display: block
|
||||
z-index: 2
|
||||
|
||||
#play-btn
|
||||
text-transform: uppercase
|
||||
#info-col
|
||||
.panel
|
||||
height: 100%
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
.panel-body
|
||||
flex-grow: 1
|
||||
overflow: scroll
|
||||
|
||||
.panel-footer
|
||||
min-height: 70px
|
||||
|
||||
#play-btn
|
||||
text-transform: uppercase
|
||||
|
||||
#share-panel-body
|
||||
display: flex
|
||||
align-items: center
|
||||
|
||||
#share-text-div, #copy-url-div
|
||||
flex-grow: 1
|
||||
|
||||
#share-text-div
|
||||
margin-right: 20px
|
||||
|
||||
#copy-url-input
|
||||
width: 50%
|
||||
|
||||
#copy-url-div
|
||||
margin-left: 20px
|
||||
|
|
|
@ -158,6 +158,10 @@
|
|||
.ace_gutter-cell.ace_error
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTM5jWRgMAAAAVdEVYdENyZWF0aW9uIFRpbWUAMi8xNy8wOCCcqlgAAAQRdEVYdFhNTDpjb20uYWRvYmUueG1wADw/eHBhY2tldCBiZWdpbj0iICAgIiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+Cjx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDQuMS1jMDM0IDQ2LjI3Mjk3NiwgU2F0IEphbiAyNyAyMDA3IDIyOjExOjQxICAgICAgICAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp4YXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iPgogICAgICAgICA8eGFwOkNyZWF0b3JUb29sPkFkb2JlIEZpcmV3b3JrcyBDUzM8L3hhcDpDcmVhdG9yVG9vbD4KICAgICAgICAgPHhhcDpDcmVhdGVEYXRlPjIwMDgtMDItMTdUMDI6MzY6NDVaPC94YXA6Q3JlYXRlRGF0ZT4KICAgICAgICAgPHhhcDpNb2RpZnlEYXRlPjIwMDgtMDMtMjRUMTk6MDA6NDJaPC94YXA6TW9kaWZ5RGF0ZT4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyI+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDUdUmQAAAD5SURBVDiNpZMxagMxEEWfgiCXcB3IbXwD7zbaM0nNyjdIl1O4Dk7pbsslEFbEZFKsJsiJrGDy4YM0M//zRyAoINAJyB8cS43RwwIdMFrvaeE8DADxXqQ3Jstn6GaQ5L3M0GQxsyaZoJtA3r2XCS6o+FkvZkdOIG/eywl+UVHrqcYm4BNIjb1rPdXYBTivj3gVtZ5q/p8gAfPhcLOBamzKcW41UI1dgA/qez4bU6muUE0zwVYEgKeKkWruEnTHENg4R8pFZblCyY1zHEMgQTQAe9gB8cE5XkO4GhugmIk76L+z+Wzy6FzT4CWLXf5MF8upSdMB4gC9Xr4AiezTJHGxdq0AAAAASUVORK5CYII=)
|
||||
|
||||
// NOTE! This hides all info annotations because removing specific annotations with listeners means they flicker. This hides that flicker. see: SpellView.onChangeAnnotation
|
||||
.ace_gutter-cell.ace_info
|
||||
background-image: none
|
||||
|
||||
.ace_gutter-cell.entry-point:not(.next-entry-point):after
|
||||
opacity: 0.5
|
||||
|
||||
|
|
|
@ -46,23 +46,22 @@
|
|||
color: rgb(243, 169, 49)
|
||||
|
||||
|
||||
body:not(.dialogue-view-active)
|
||||
body.dialogue-view-active
|
||||
.spell-palette-popover.popover
|
||||
right: 45%
|
||||
min-width: 500px
|
||||
margin-top: -17%
|
||||
top: 50px !important
|
||||
|
||||
.spell-palette-popover.popover
|
||||
// Only those popovers which are our direct children (spell documentation)
|
||||
left: auto !important
|
||||
right: 45%
|
||||
max-width: 600px
|
||||
min-width: 500px
|
||||
padding: 0
|
||||
border-style: solid
|
||||
border-image: url(/images/level/popover_border_background.png) 16 12 fill round
|
||||
border-width: 16px 12px
|
||||
@include box-shadow(0 0 0 #000)
|
||||
// Prevent flickering in weird scenarios where popover goes over its own property
|
||||
pointer-events: none
|
||||
|
||||
// Jiggle animation
|
||||
// TODO: consolidate with problem_alert.sass jiggle
|
||||
|
@ -96,8 +95,8 @@ body:not(.dialogue-view-active)
|
|||
|
||||
.close
|
||||
position: absolute
|
||||
top: 5%
|
||||
right: 5%
|
||||
top: -7px
|
||||
right: 2px
|
||||
font-size: 28px
|
||||
font-weight: bold
|
||||
@include opacity(0.6)
|
||||
|
|
|
@ -91,6 +91,9 @@ body[lang='ru'], body[lang='uk'], body[lang='bg'], body[lang^='mk'], body[lang='
|
|||
font-size: 40px
|
||||
opacity: 0.5
|
||||
|
||||
hr
|
||||
border-top: 1px solid gray
|
||||
|
||||
// Navbar
|
||||
|
||||
#main-nav.navbar
|
||||
|
|
|
@ -1,135 +1,87 @@
|
|||
extends /templates/base
|
||||
extends /templates/base-flat
|
||||
|
||||
block content
|
||||
|
||||
if me.isTeacher()
|
||||
.alert.alert-danger.text-center
|
||||
// DNT: Temporary
|
||||
h3 ATTENTION TEACHERS:
|
||||
p We are transitioning to a new classroom management system; this page will soon be student-only.
|
||||
a(href="/teachers/classes") Go to teachers area.
|
||||
.container.m-t-3
|
||||
p
|
||||
a(href="/courses", data-i18n="courses.back_courses")
|
||||
|
||||
if view.teacherMode
|
||||
a(href="/teachers/classes", data-i18n="courses.back_classrooms")
|
||||
else
|
||||
a(href="/courses", data-i18n="courses.back_courses")
|
||||
br
|
||||
br
|
||||
p
|
||||
strong
|
||||
if view.courseInstance.get('name')
|
||||
span= view.courseInstance.get('name')
|
||||
else if view.classroom.get('name')
|
||||
span= view.classroom.get('name')
|
||||
else
|
||||
span(data-i18n='courses.unnamed_class')
|
||||
|
||||
p
|
||||
// TODO: format this text all good and stuff
|
||||
strong
|
||||
if view.courseInstance.get('name')
|
||||
span= view.courseInstance.get('name')
|
||||
else if view.classroom.get('name')
|
||||
span= view.classroom.get('name')
|
||||
else
|
||||
span(data-i18n='courses.unnamed_class')
|
||||
if !view.owner.isNew() && view.getOwnerName() && view.courseInstance.get('name') != 'Single Player'
|
||||
span.spl -
|
||||
span.spl(data-i18n='courses.teacher')
|
||||
span.spr :
|
||||
span
|
||||
strong= view.getOwnerName()
|
||||
|
||||
if !view.owner.isNew() && view.getOwnerName() && view.courseInstance.get('name') != 'Single Player'
|
||||
span.spl -
|
||||
span.spl(data-i18n='courses.teacher')
|
||||
span.spr :
|
||||
//a(href="/user/#{view.owner.id}") // Don't link to profiles until we improve them
|
||||
span
|
||||
strong= view.getOwnerName()
|
||||
h1
|
||||
| #{view.course.get('name')}
|
||||
if view.courseComplete
|
||||
span.spl -
|
||||
span.spl(data-i18n='courses.complete')
|
||||
span !
|
||||
|
||||
h1
|
||||
| #{view.course.get('name')}
|
||||
if view.courseComplete
|
||||
span.spl -
|
||||
span.spl(data-i18n='courses.complete')
|
||||
span !
|
||||
p
|
||||
if view.courseInstance.get('description')
|
||||
each line in view.courseInstance.get('description').split('\n')
|
||||
div= line
|
||||
|
||||
p
|
||||
if view.courseInstance.get('description')
|
||||
each line in view.courseInstance.get('description').split('\n')
|
||||
div= line
|
||||
|
||||
if view.courseComplete && !view.teacherMode
|
||||
.jumbotron
|
||||
.row
|
||||
.col-md-6
|
||||
if view.arenaLevel
|
||||
a.btn.btn-lg.btn-success.btn-play-level(data-level-slug=view.arenaLevel.get('slug'), data-level-id=view.arenaLevel.get('original'))
|
||||
h1
|
||||
span(data-i18n='courses.arena')
|
||||
span.spr :
|
||||
span= view.arenaLevel.get('name')
|
||||
p= view.arenaLevel.get('description').replace(/!\[.*?\)/, '')
|
||||
else
|
||||
a.btn.btn-lg.btn-success.disabled
|
||||
h1(data-i18n='courses.arena_soon_title')
|
||||
p
|
||||
span.spr(data-i18n='courses.arena_soon_description')
|
||||
span= view.course.get('name')
|
||||
span .
|
||||
.col-md-6
|
||||
if view.nextCourseInstance && _.contains(view.nextCourseInstance.get('members'), me.id)
|
||||
a.btn.btn-lg.btn-success(href="/courses/#{view.nextCourse.id}/#{view.nextCourseInstance.id}")
|
||||
h1= view.nextCourse.get('name')
|
||||
p= view.nextCourse.get('description')
|
||||
else if view.nextCourse
|
||||
a.btn.btn-lg.btn-success.disabled
|
||||
h1= view.nextCourse.get('name')
|
||||
p.text-uppercase
|
||||
em(data-i18n='courses.not_enrolled1')
|
||||
p(data-i18n='courses.not_enrolled2')
|
||||
else
|
||||
a.btn.btn-lg.btn-success(disabled=!view.nextCourse ? "disabled" : "")
|
||||
h1(data-i18n='courses.next_course')
|
||||
p.text-uppercase
|
||||
em(data-i18n='courses.coming_soon1')
|
||||
p(data-i18n='courses.coming_soon2')
|
||||
|
||||
.available-courses-title(data-i18n='courses.available_levels')
|
||||
table.table.table-striped.table-condensed
|
||||
thead
|
||||
tr
|
||||
th
|
||||
th(data-i18n="clans.status")
|
||||
th(data-i18n="common.type")
|
||||
th(data-i18n="resources.level")
|
||||
th(data-i18n="courses.concepts")
|
||||
tbody
|
||||
- var previousLevelCompleted = true;
|
||||
- var lastLevelCompleted = view.getLastLevelCompleted();
|
||||
- var passedLastCompletedLevel = !lastLevelCompleted;
|
||||
- var levelCount = 0;
|
||||
each level in view.levels.models
|
||||
- var levelStatus = null;
|
||||
- var levelNumber = view.classroom.getLevelNumber(level.get('original'), ++levelCount);
|
||||
if view.userLevelStateMap[me.id]
|
||||
- levelStatus = view.userLevelStateMap[me.id][level.get('original')]
|
||||
.available-courses-title(data-i18n='courses.available_levels')
|
||||
table.table.table-striped.table-condensed
|
||||
thead
|
||||
tr
|
||||
td
|
||||
if previousLevelCompleted || view.teacherMode || !passedLastCompletedLevel || levelStatus
|
||||
- var i18nTag = level.isType('course-ladder') ? 'play.compete' : 'home.play';
|
||||
button.btn.btn-success.btn-play-level(data-level-slug=level.get('slug'), data-i18n=i18nTag, data-level-id=level.get('original'))
|
||||
if level.get('shareable')
|
||||
- var levelOriginal = level.get('original');
|
||||
- var session = view.levelSessions.find(function(session) { return session.get('level').original === levelOriginal });
|
||||
if session
|
||||
- var url = '/play/' + level.get('type') + '-level/' + level.get('slug') + '/' + session.id + '?course=' + view.courseID;
|
||||
a.btn.btn-warning.btn-view-project-level(href=url)
|
||||
if level.isType('game-dev')
|
||||
span(data-i18n='sharing.game')
|
||||
else
|
||||
span(data-i18n='sharing.webpage')
|
||||
td
|
||||
if view.userLevelStateMap[me.id]
|
||||
div= view.userLevelStateMap[me.id][level.get('original')]
|
||||
td #{level.get('practice') ? 'practice' : 'required'}
|
||||
td #{levelNumber}. #{i18n(level.attributes, 'name').replace('Course: ', '')}
|
||||
td
|
||||
if view.levelConceptMap[level.get('original')]
|
||||
each concept in view.course.get('concepts')
|
||||
if view.levelConceptMap[level.get('original')][concept]
|
||||
span.spr.concept(data-i18n="concepts." + concept)
|
||||
if level.get('original') === lastLevelCompleted
|
||||
- passedLastCompletedLevel = true
|
||||
if !level.get('practice')
|
||||
if view.userLevelStateMap[me.id]
|
||||
- previousLevelCompleted = view.userLevelStateMap[me.id][level.get('original')] === 'complete'
|
||||
else
|
||||
- previousLevelCompleted = false
|
||||
th
|
||||
th(data-i18n="clans.status")
|
||||
th(data-i18n="common.type")
|
||||
th(data-i18n="resources.level")
|
||||
th(data-i18n="courses.concepts")
|
||||
tbody
|
||||
- var previousLevelCompleted = true;
|
||||
- var lastLevelCompleted = view.getLastLevelCompleted();
|
||||
- var passedLastCompletedLevel = !lastLevelCompleted;
|
||||
- var levelCount = 0;
|
||||
each level in view.levels.models
|
||||
- var levelStatus = null;
|
||||
- var levelNumber = view.classroom.getLevelNumber(level.get('original'), ++levelCount);
|
||||
if view.userLevelStateMap[me.id]
|
||||
- levelStatus = view.userLevelStateMap[me.id][level.get('original')]
|
||||
tr
|
||||
td
|
||||
if previousLevelCompleted || !passedLastCompletedLevel || levelStatus
|
||||
- var i18nTag = level.isType('course-ladder') ? 'play.compete' : 'home.play';
|
||||
button.btn.btn-forest.btn-play-level(data-level-slug=level.get('slug'), data-i18n=i18nTag, data-level-id=level.get('original'))
|
||||
if level.get('shareable')
|
||||
- var levelOriginal = level.get('original');
|
||||
- var session = view.levelSessions.find(function(session) { return session.get('level').original === levelOriginal });
|
||||
if session
|
||||
- var url = '/play/' + level.get('type') + '-level/' + level.get('slug') + '/' + session.id + '?course=' + view.courseID;
|
||||
a.btn.btn-gold.btn-view-project-level(href=url)
|
||||
if level.isType('game-dev')
|
||||
span(data-i18n='sharing.game')
|
||||
else
|
||||
span(data-i18n='sharing.webpage')
|
||||
td
|
||||
if view.userLevelStateMap[me.id]
|
||||
div= view.userLevelStateMap[me.id][level.get('original')]
|
||||
td #{level.get('practice') ? 'practice' : 'required'}
|
||||
td #{levelNumber}. #{i18n(level.attributes, 'name').replace('Course: ', '')}
|
||||
td
|
||||
if view.levelConceptMap[level.get('original')]
|
||||
each concept in view.course.get('concepts')
|
||||
if view.levelConceptMap[level.get('original')][concept]
|
||||
span.spr.concept(data-i18n="concepts." + concept)
|
||||
if level.get('original') === lastLevelCompleted
|
||||
- passedLastCompletedLevel = true
|
||||
if !level.get('practice')
|
||||
if view.userLevelStateMap[me.id]
|
||||
- previousLevelCompleted = view.userLevelStateMap[me.id][level.get('original')] === 'complete'
|
||||
else
|
||||
- previousLevelCompleted = false
|
||||
|
|
|
@ -10,15 +10,16 @@ button.btn.btn-xs.btn-inverse.picoctf-hide#volume-button(title="Adjust volume")
|
|||
button.btn.btn-xs.btn-inverse.picoctf-hide#music-button(title="Toggle Music")
|
||||
span ♫
|
||||
|
||||
.scrubber
|
||||
.scrubber-inner
|
||||
.progress.secret#timeProgress
|
||||
.progress-bar
|
||||
.scrubber-handle
|
||||
.popover.fade.top.in#timePopover
|
||||
.arrow
|
||||
h3.popover-title
|
||||
.popover-content
|
||||
if !view.options.level.isType('game-dev')
|
||||
.scrubber
|
||||
.scrubber-inner
|
||||
.progress.secret#timeProgress
|
||||
.progress-bar
|
||||
.scrubber-handle
|
||||
.popover.fade.top.in#timePopover
|
||||
.arrow
|
||||
h3.popover-title
|
||||
.popover-content
|
||||
|
||||
.btn-group.dropup#playback-settings
|
||||
button.btn.btn-xs.btn-inverse.toggle-fullscreen(title="Toggle fullscreen")
|
17
app/templates/play/level/modal/game-dev-victory-modal.jade
Normal file
17
app/templates/play/level/modal/game-dev-victory-modal.jade
Normal file
|
@ -0,0 +1,17 @@
|
|||
extends /templates/core/modal-base-flat
|
||||
|
||||
block modal-header-content
|
||||
h3.text-center You beat the game!
|
||||
|
||||
block modal-body-content
|
||||
.text-center Share this level so your friends and family can play it:
|
||||
|
||||
.share-row.text-center
|
||||
input#copy-url-input.text-h4.semibold.form-control.input-lg(value=view.shareURL)
|
||||
button#copy-url-btn.btn.btn-lg.btn-navy-alt
|
||||
span(data-i18n='sharing.copy_url')
|
||||
|
||||
block modal-footer-content
|
||||
.text-center
|
||||
button#replay-game-btn.btn.btn-navy.btn-lg(data-dismiss="modal") Replay Game
|
||||
a#play-more-codecombat-btn.btn.btn-navy.btn-lg(href="/") Play More CodeCombat
|
|
@ -1,38 +1,58 @@
|
|||
.container-fluid
|
||||
.row
|
||||
- var ready = !(view.state.get('errorMessage') || view.state.get('loading'))
|
||||
|
||||
.container-fluid.style-flat
|
||||
#game-row.row
|
||||
.col-xs-9
|
||||
#canvas-wrapper
|
||||
canvas(width=924, height=589)#webgl-surface
|
||||
canvas(width=924, height=589)#normal-surface
|
||||
|
||||
.col-xs-3#info-col.style-flat
|
||||
if view.state.get('errorMessage')
|
||||
.alert.alert-danger= view.state.get('errorMessage')
|
||||
#info-col.col-xs-3
|
||||
.panel.panel-default
|
||||
.panel-body.text-center
|
||||
if view.state.get('errorMessage')
|
||||
.alert.alert-danger= view.state.get('errorMessage')
|
||||
|
||||
else if view.state.get('loading')
|
||||
h1.m-y-1(data-i18n="common.loading")
|
||||
.progress
|
||||
.progress-bar(style="width: #{view.state.get('progress')}")
|
||||
if view.level.id && view.session.id
|
||||
h3.m-y-1= view.level.get('name')
|
||||
h4 Created by #{view.session.get('creatorName')}
|
||||
hr
|
||||
|
||||
else
|
||||
h1.m-y-1 Info
|
||||
ul
|
||||
li
|
||||
b
|
||||
span(data-i18n="play_level.level")
|
||||
span= ': '
|
||||
| #{view.level.get('name')}
|
||||
if view.state.get('loading')
|
||||
h1.m-y-1(data-i18n="common.loading")
|
||||
.progress
|
||||
.progress-bar(style="width: #{view.state.get('progress')}")
|
||||
|
||||
li
|
||||
b
|
||||
span(data-i18n="game_dev.creator")
|
||||
span= ': '
|
||||
| #{view.session.get('creatorName')}
|
||||
if ready
|
||||
h3 Goals
|
||||
for goalName in view.state.get('goalNames')
|
||||
p= goalName
|
||||
|
||||
- var playing = view.state.get('playing')
|
||||
.m-y-3
|
||||
if playing
|
||||
button#play-btn.btn.btn-lg.btn-burgandy(data-i18n="play_level.restart")
|
||||
else
|
||||
button#play-btn.btn.btn-lg.btn-navy(data-i18n="common.play")
|
||||
hr
|
||||
|
||||
h3 How to play:
|
||||
p Use the mouse to control the hero!
|
||||
p Click anywhere on the map to move to that location.
|
||||
p Click on the ogres to attack them.
|
||||
|
||||
if ready
|
||||
.panel-footer
|
||||
- var playing = view.state.get('playing')
|
||||
if playing
|
||||
button#play-btn.btn.btn-lg.btn-burgandy.btn-block Restart Level
|
||||
else
|
||||
button#play-btn.btn.btn-lg.btn-forest.btn-block Play Level
|
||||
|
||||
#share-row.m-t-3
|
||||
if ready
|
||||
.panel.panel-default
|
||||
#share-panel-body.panel-body
|
||||
div#share-text-div.text-right
|
||||
b(data-i18n='sharing.share_game')
|
||||
input#copy-url-input.text-h4.semibold.form-control.input-lg(value=view.state.get('shareURL'))
|
||||
div#copy-url-div
|
||||
button#copy-url-btn.btn.btn-lg.btn-navy-alt
|
||||
span(data-i18n='sharing.copy_url')
|
||||
|
||||
.panel-body
|
||||
a#play-more-codecombat-btn.btn.btn-lg.btn-navy-alt.pull-right(href="/") Play More CodeCombat
|
||||
|
|
25
app/templates/play/level/tome/cast-button-view.jade
Normal file
25
app/templates/play/level/tome/cast-button-view.jade
Normal file
|
@ -0,0 +1,25 @@
|
|||
if view.options.level.isType('game-dev')
|
||||
button.btn.btn-lg.btn-illustrated.btn-success.game-dev-play-btn
|
||||
span(data-i18n="common.play")
|
||||
button.btn.btn-lg.btn-illustrated.btn-success.done-button.secret
|
||||
span(data-i18n="play_level.done")
|
||||
|
||||
else
|
||||
|
||||
button.btn.btn-lg.btn-illustrated.cast-button(title=view.castVerbose())
|
||||
span(data-i18n="play_level.tome_run_button_ran") Ran
|
||||
|
||||
if !view.observing
|
||||
if view.mirror
|
||||
.ladder-submission-view
|
||||
else
|
||||
button.btn.btn-lg.btn-illustrated.submit-button(title=view.castRealTimeVerbose())
|
||||
span(data-i18n="play_level.tome_submit_button") Submit
|
||||
span.spl.secret.submit-again-time
|
||||
|
||||
button.btn.btn-lg.btn-illustrated.btn-success.done-button.secret
|
||||
span(data-i18n="play_level.done") Done
|
||||
|
||||
if view.autoSubmitsToLadder
|
||||
.hidden
|
||||
.ladder-submission-view
|
|
@ -1,17 +0,0 @@
|
|||
button.btn.btn-lg.btn-illustrated.cast-button(title=view.castVerbose())
|
||||
span(data-i18n="play_level.tome_run_button_ran") Ran
|
||||
|
||||
if !view.observing
|
||||
if view.mirror
|
||||
.ladder-submission-view
|
||||
else
|
||||
button.btn.btn-lg.btn-illustrated.submit-button(title=view.castRealTimeVerbose())
|
||||
span(data-i18n="play_level.tome_submit_button") Submit
|
||||
span.spl.secret.submit-again-time
|
||||
|
||||
button.btn.btn-lg.btn-illustrated.btn-success.done-button.secret
|
||||
span(data-i18n="play_level.done") Done
|
||||
|
||||
if view.autoSubmitsToLadder
|
||||
.hidden
|
||||
.ladder-submission-view
|
|
@ -49,7 +49,20 @@ if view.showAds()
|
|||
|
||||
#level-dialogue-view
|
||||
|
||||
button.btn.btn-lg.btn-warning.banner.header-font#stop-real-time-playback-button(title="Stop real-time playback", data-i18n="play_level.skip") Skip
|
||||
|
||||
button.btn.btn-lg.btn-warning.banner.header-font#stop-real-time-playback-button(title="Stop real-time playback")
|
||||
if view.level && view.level.isType('game-dev')
|
||||
| Back to coding
|
||||
else
|
||||
span(data-i18n="play_level.skip")
|
||||
|
||||
#how-to-play-game-dev-panel.panel.panel-default.hide
|
||||
.panel-heading
|
||||
h3.panel-title How to play:
|
||||
.panel-body
|
||||
p Use the mouse to control the hero!
|
||||
p Click anywhere on the map to move to that location.
|
||||
p Click on the ogres to attack them.
|
||||
|
||||
.hints-view.hide
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ storage = require 'core/storage'
|
|||
module.exports = class CourseDetailsView extends RootView
|
||||
id: 'course-details-view'
|
||||
template: template
|
||||
teacherMode: false
|
||||
memberSort: 'nameAsc'
|
||||
|
||||
events:
|
||||
|
@ -24,7 +23,6 @@ module.exports = class CourseDetailsView extends RootView
|
|||
|
||||
constructor: (options, @courseID, @courseInstanceID) ->
|
||||
super options
|
||||
@ownedClassrooms = new Classrooms()
|
||||
@courses = new Courses()
|
||||
@course = new Course()
|
||||
@levelSessions = new LevelSessions()
|
||||
|
@ -34,7 +32,6 @@ module.exports = class CourseDetailsView extends RootView
|
|||
@levels = new Levels()
|
||||
@courseInstances = new CourseInstances()
|
||||
|
||||
@supermodel.trackRequest @ownedClassrooms.fetchMine({data: {project: '_id'}})
|
||||
@supermodel.trackRequest(@courses.fetch().then(=>
|
||||
@course = @courses.get(@courseID)
|
||||
))
|
||||
|
@ -42,8 +39,6 @@ module.exports = class CourseDetailsView extends RootView
|
|||
|
||||
@supermodel.trackRequest(@courseInstance.fetch().then(=>
|
||||
return if @destroyed
|
||||
@teacherMode = @courseInstance.get('ownerID') is me.id
|
||||
|
||||
@owner = new User({_id: @courseInstance.get('ownerID')})
|
||||
@supermodel.trackRequest(@owner.fetch())
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
CocoView = require 'views/core/CocoView'
|
||||
template = require 'templates/play/level/control_bar'
|
||||
template = require 'templates/play/level/control-bar-view'
|
||||
{me} = require 'core/auth'
|
||||
|
||||
Campaign = require 'models/Campaign'
|
||||
|
|
|
@ -103,6 +103,7 @@ module.exports = class LevelGoalsView extends CocoView
|
|||
@updatePlacement()
|
||||
|
||||
onSurfacePlaybackEnded: ->
|
||||
return if @level.isType('game-dev')
|
||||
@playbackEnded = true
|
||||
@updateHeight()
|
||||
@$el.addClass 'brighter'
|
||||
|
@ -140,7 +141,7 @@ module.exports = class LevelGoalsView extends CocoView
|
|||
|
||||
playToggleSound: (sound) =>
|
||||
return if @destroyed
|
||||
@playSound sound
|
||||
@playSound sound unless @options.level.isType('game-dev')
|
||||
@soundTimeout = null
|
||||
|
||||
onSetLetterbox: (e) ->
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
CocoView = require 'views/core/CocoView'
|
||||
template = require 'templates/play/level/playback'
|
||||
template = require 'templates/play/level/level-playback-view'
|
||||
{me} = require 'core/auth'
|
||||
|
||||
module.exports = class LevelPlaybackView extends CocoView
|
||||
|
@ -50,7 +50,7 @@ module.exports = class LevelPlaybackView extends CocoView
|
|||
afterRender: ->
|
||||
super()
|
||||
@$progressScrubber = $('.scrubber .progress', @$el)
|
||||
@hookUpScrubber()
|
||||
@hookUpScrubber() unless @options.level.isType('game-dev')
|
||||
@updateMusicButton()
|
||||
$(window).on('resize', @onWindowResize)
|
||||
ua = navigator.userAgent.toLowerCase()
|
||||
|
@ -154,7 +154,7 @@ module.exports = class LevelPlaybackView extends CocoView
|
|||
ended = button.hasClass 'ended'
|
||||
changed = button.hasClass('playing') isnt @playing
|
||||
button.toggleClass('playing', @playing and not ended).toggleClass('paused', not @playing and not ended)
|
||||
@playSound (if @playing then 'playback-play' else 'playback-pause')
|
||||
@playSound (if @playing then 'playback-play' else 'playback-pause') unless @options.level.isType('game-dev')
|
||||
return # don't stripe the bar
|
||||
bar = @$el.find '.scrubber .progress'
|
||||
bar.toggleClass('progress-striped', @playing and not ended).toggleClass('active', @playing and not ended)
|
||||
|
|
|
@ -10,6 +10,10 @@ ThangType = require 'models/ThangType'
|
|||
Level = require 'models/Level'
|
||||
LevelSession = require 'models/LevelSession'
|
||||
State = require 'models/State'
|
||||
utils = require 'core/utils'
|
||||
urls = require 'core/urls'
|
||||
Course = require 'models/Course'
|
||||
GameDevVictoryModal = require './modal/GameDevVictoryModal'
|
||||
|
||||
TEAM = 'humans'
|
||||
|
||||
|
@ -17,8 +21,13 @@ module.exports = class PlayGameDevLevelView extends RootView
|
|||
id: 'play-game-dev-level-view'
|
||||
template: require 'templates/play/level/play-game-dev-level-view'
|
||||
|
||||
subscriptions:
|
||||
'god:new-world-created': 'onNewWorld'
|
||||
|
||||
events:
|
||||
'click #play-btn': 'onClickPlayButton'
|
||||
'click #copy-url-btn': 'onClickCopyURLButton'
|
||||
'click #play-more-codecombat-btn': 'onClickPlayMoreCodeCombatButton'
|
||||
|
||||
initialize: (@options, @levelID, @sessionID) ->
|
||||
@state = new State({
|
||||
|
@ -32,9 +41,10 @@ module.exports = class PlayGameDevLevelView extends RootView
|
|||
@session = new LevelSession()
|
||||
@gameUIState = new GameUIState()
|
||||
@courseID = @getQueryVariable 'course'
|
||||
@god = new God({ @gameUIState })
|
||||
@god = new God({ @gameUIState, indefiniteLength: true })
|
||||
@levelLoader = new LevelLoader({ @supermodel, @levelID, @sessionID, observing: true, team: TEAM, @courseID })
|
||||
@listenTo @state, 'change', _.debounce(-> @renderSelectors('#info-col'))
|
||||
@supermodel.setMaxProgress 1 # Hack, why are we setting this to 0.2 in LevelLoader?
|
||||
@listenTo @state, 'change', _.debounce @renderAllButCanvas
|
||||
|
||||
@levelLoader.loadWorldNecessities()
|
||||
|
||||
|
@ -50,6 +60,7 @@ module.exports = class PlayGameDevLevelView extends RootView
|
|||
@scriptManager = new ScriptManager({
|
||||
scripts: @world.scripts or [], view: @, @session, levelID: @level.get('slug')})
|
||||
@scriptManager.loadFromSession() # Should we? TODO: Figure out how scripts work for game dev levels
|
||||
@renderAllButCanvas()
|
||||
@supermodel.finishLoading()
|
||||
|
||||
.then (supermodel) =>
|
||||
|
@ -61,7 +72,9 @@ module.exports = class PlayGameDevLevelView extends RootView
|
|||
thangTypes: @supermodel.getModels(ThangType)
|
||||
levelType: @level.get('type', true)
|
||||
@gameUIState
|
||||
resizeStrategy: 'wrapper-size'
|
||||
})
|
||||
@listenTo @surface, 'resize', @onSurfaceResize
|
||||
worldBounds = @world.getBounds()
|
||||
bounds = [{x: worldBounds.left, y: worldBounds.top}, {x: worldBounds.right, y: worldBounds.bottom}]
|
||||
@surface.camera.setBounds(bounds)
|
||||
|
@ -70,18 +83,61 @@ module.exports = class PlayGameDevLevelView extends RootView
|
|||
@scriptManager.initializeCamera()
|
||||
@renderSelectors '#info-col'
|
||||
@spells = @session.generateSpellsObject level: @level
|
||||
@state.set('loading', false)
|
||||
goalNames = (utils.i18n(goal, 'name') for goal in @goalManager.goals)
|
||||
|
||||
.catch ({message}) =>
|
||||
console.error message
|
||||
@state.set('errorMessage', message)
|
||||
course = if @courseID then new Course({_id: @courseID}) else null
|
||||
shareURL = urls.playDevLevel({@level, @session, course})
|
||||
|
||||
@state.set({
|
||||
loading: false
|
||||
goalNames
|
||||
shareURL
|
||||
})
|
||||
@eventProperties = {
|
||||
category: 'Play GameDev Level'
|
||||
@courseID
|
||||
sessionID: @session.id
|
||||
levelID: @level.id
|
||||
levelSlug: @level.get('slug')
|
||||
}
|
||||
window.tracker?.trackEvent 'Play GameDev Level - Load', @eventProperties, ['Mixpanel']
|
||||
@god.createWorld(@spells, false, false, true)
|
||||
|
||||
.catch (e) =>
|
||||
throw e if e.stack
|
||||
@state.set('errorMessage', e.message)
|
||||
|
||||
onClickPlayButton: ->
|
||||
@god.createWorld(@spells, false, true)
|
||||
Backbone.Mediator.publish('playback:real-time-playback-started', {})
|
||||
Backbone.Mediator.publish('level:set-playing', {playing: true})
|
||||
action = if @state.get('playing') then 'Play GameDev Level - Restart Level' else 'Play GameDev Level - Start Level'
|
||||
window.tracker?.trackEvent(action, @eventProperties, ['Mixpanel'])
|
||||
@state.set('playing', true)
|
||||
|
||||
onClickCopyURLButton: ->
|
||||
@$('#copy-url-input').val(@state.get('shareURL')).select()
|
||||
@tryCopy()
|
||||
window.tracker?.trackEvent('Play GameDev Level - Copy URL', @eventProperties, ['Mixpanel'])
|
||||
|
||||
onClickPlayMoreCodeCombatButton: ->
|
||||
window.tracker?.trackEvent('Play GameDev Level - Click Play More CodeCombat', @eventProperties, ['Mixpanel'])
|
||||
|
||||
onSurfaceResize: ({height}) ->
|
||||
@state.set('surfaceHeight', height)
|
||||
|
||||
renderAllButCanvas: ->
|
||||
@renderSelectors('#info-col', '#share-row')
|
||||
height = @state.get('surfaceHeight')
|
||||
if height
|
||||
@$el.find('#info-col').css('height', @state.get('surfaceHeight'))
|
||||
|
||||
onNewWorld: (e) ->
|
||||
if @goalManager.checkOverallStatus() is 'success'
|
||||
modal = new GameDevVictoryModal({ shareURL: @state.get('shareURL'), @eventProperties })
|
||||
@openModalView(modal)
|
||||
modal.once 'replay', @onClickPlayButton, @
|
||||
|
||||
destroy: ->
|
||||
@levelLoader?.destroy()
|
||||
@surface?.destroy()
|
||||
|
|
|
@ -142,7 +142,12 @@ module.exports = class PlayLevelView extends RootView
|
|||
@listenTo @levelLoader, 'world-necessity-load-failed', @onWorldNecessityLoadFailed
|
||||
|
||||
onLevelLoaded: (e) ->
|
||||
@god = new God({@gameUIState}) unless e.level.isType('web-dev')
|
||||
return if @destroyed
|
||||
unless e.level.isType('web-dev')
|
||||
@god = new God({
|
||||
@gameUIState
|
||||
indefiniteLength: e.level.isType('game-dev')
|
||||
})
|
||||
@setupGod() if @waitingToSetUpGod
|
||||
|
||||
trackLevelLoadEnd: ->
|
||||
|
@ -206,6 +211,7 @@ module.exports = class PlayLevelView extends RootView
|
|||
@$el.addClass 'web-dev' # Hide some of the elements we won't be using
|
||||
return
|
||||
@world = @levelLoader.world
|
||||
@$el.addClass 'game-dev' if @level.isType('game-dev')
|
||||
@$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
|
||||
|
@ -214,6 +220,7 @@ module.exports = class PlayLevelView extends RootView
|
|||
@worldLoadFakeResources = [] # first element (0) is 1%, last (99) is 100%
|
||||
for percent in [1 .. 100]
|
||||
@worldLoadFakeResources.push @supermodel.addSomethingResource 1
|
||||
@renderSelectors '#stop-real-time-playback-button'
|
||||
|
||||
onWorldLoadProgressChanged: (e) ->
|
||||
return unless e.god is @god
|
||||
|
@ -354,6 +361,7 @@ module.exports = class PlayLevelView extends RootView
|
|||
levelType: @level.get('type', true)
|
||||
stayVisible: @showAds()
|
||||
@gameUIState
|
||||
@level # TODO: change from levelType to level
|
||||
}
|
||||
@surface = new Surface(@world, normalSurface, webGLSurface, surfaceOptions)
|
||||
worldBounds = @world.getBounds()
|
||||
|
@ -626,10 +634,12 @@ module.exports = class PlayLevelView extends RootView
|
|||
# Real-time playback
|
||||
onRealTimePlaybackStarted: (e) ->
|
||||
@$el.addClass('real-time').focus()
|
||||
@$('#how-to-play-game-dev-panel').removeClass('hide') if @level.isType('game-dev')
|
||||
@onWindowResize()
|
||||
|
||||
onRealTimePlaybackEnded: (e) ->
|
||||
return unless @$el.hasClass 'real-time'
|
||||
@$('#how-to-play-game-dev-panel').addClass('hide') if @level.isType('game-dev')
|
||||
@$el.removeClass 'real-time'
|
||||
@onWindowResize()
|
||||
if @world.frames.length is @world.totalFrames and not @surface.countdownScreen?.showing
|
||||
|
|
|
@ -66,9 +66,9 @@ module.exports = class WebSurfaceView extends CocoView
|
|||
return { virtualDom: dekuTree, scripts: childScripts, styles: childStyles }
|
||||
|
||||
{ virtualDom, scripts, styles } = recurse(dekuTree)
|
||||
combinedScripts = @combineNodes('script', scripts)
|
||||
combinedStyles = @combineNodes('style', styles)
|
||||
return { virtualDom, scripts: combinedScripts, styles: combinedStyles }
|
||||
wrappedStyles = deku.element('head', {}, styles)
|
||||
wrappedScripts = deku.element('head', {}, scripts)
|
||||
return { virtualDom, scripts: wrappedScripts, styles: wrappedStyles }
|
||||
|
||||
combineNodes: (type, nodes) ->
|
||||
if _.any(nodes, (node) -> node.type isnt type)
|
||||
|
|
|
@ -108,9 +108,9 @@ module.exports = class CourseVictoryModal extends ModalView
|
|||
onDone: ->
|
||||
window.tracker?.trackEvent 'Play Level Victory Modal Done', category: 'Students', levelSlug: @level.get('slug'), ['Mixpanel']
|
||||
if me.isSessionless()
|
||||
link = "/teachers/courses"
|
||||
link = '/teachers/courses'
|
||||
else
|
||||
link = "/courses/#{@courseID}/#{@courseInstanceID}"
|
||||
link = '/courses'
|
||||
application.router.navigate(link, {trigger: true})
|
||||
|
||||
onLadder: ->
|
||||
|
|
25
app/views/play/level/modal/GameDevVictoryModal.coffee
Normal file
25
app/views/play/level/modal/GameDevVictoryModal.coffee
Normal file
|
@ -0,0 +1,25 @@
|
|||
ModalView = require 'views/core/ModalView'
|
||||
|
||||
category = 'Play GameDev Level'
|
||||
|
||||
module.exports = class GameDevVictoryModal extends ModalView
|
||||
id: 'game-dev-victory-modal'
|
||||
template: require 'templates/play/level/modal/game-dev-victory-modal'
|
||||
|
||||
events:
|
||||
'click #replay-game-btn': 'onClickReplayButton'
|
||||
'click #copy-url-btn': 'onClickCopyURLButton'
|
||||
'click #play-more-codecombat-btn': 'onClickPlayMoreCodeCombatButton'
|
||||
|
||||
initialize: ({@shareURL, @eventProperties}) ->
|
||||
|
||||
onClickReplayButton: ->
|
||||
@trigger 'replay'
|
||||
|
||||
onClickCopyURLButton: ->
|
||||
@$('#copy-url-input').val(@shareURL).select()
|
||||
@tryCopy()
|
||||
window.tracker?.trackEvent('Play GameDev Victory Modal - Copy URL', @eventProperties, ['Mixpanel'])
|
||||
|
||||
onClickPlayMoreCodeCombatButton: ->
|
||||
window.tracker?.trackEvent('Play GameDev Victory Modal - Click Play More CodeCombat', @eventProperties, ['Mixpanel'])
|
|
@ -1,5 +1,6 @@
|
|||
CocoView = require 'views/core/CocoView'
|
||||
utils = require 'core/utils'
|
||||
urls = require 'core/urls'
|
||||
|
||||
module.exports = class ProgressView extends CocoView
|
||||
|
||||
|
@ -25,8 +26,7 @@ module.exports = class ProgressView extends CocoView
|
|||
@nextLevel.get('description', true) # Make sure the defaults are available
|
||||
@nextLevelDescription = marked(utils.i18n(@nextLevel.attributesWithDefaults, 'description').replace(/!\[.*?\]\(.*?\)\n*/g, ''))
|
||||
if @level.get('shareable') is 'project'
|
||||
@shareURL = "#{window.location.origin}/play/#{@level.get('type')}-level/#{@level.get('slug')}/#{@session.id}"
|
||||
@shareURL += "?course=#{@course.id}" if @course
|
||||
@shareURL = urls.playDevLevel({@level, @session, @course})
|
||||
|
||||
onClickDoneButton: ->
|
||||
@trigger 'done'
|
||||
|
@ -38,5 +38,19 @@ module.exports = class ProgressView extends CocoView
|
|||
@trigger 'ladder'
|
||||
|
||||
onClickShareLevelButton: ->
|
||||
if _.string.startsWith(@course.get('slug'), 'game-dev')
|
||||
name = 'Student Game Dev - Copy URL'
|
||||
category = 'GameDev'
|
||||
else
|
||||
name = 'Student Web Dev - Copy URL'
|
||||
category = 'WebDev'
|
||||
eventProperties = {
|
||||
levelID: @level.id
|
||||
levelSlug: @level.get('slug')
|
||||
classroomID: @classroom.id
|
||||
courseID: @course.id
|
||||
category
|
||||
}
|
||||
window.tracker?.trackEvent name, eventProperties, ['MixPanel']
|
||||
@$('#share-level-input').val(@shareURL).select()
|
||||
@tryCopy()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
CocoView = require 'views/core/CocoView'
|
||||
template = require 'templates/play/level/tome/cast_button'
|
||||
template = require 'templates/play/level/tome/cast-button-view'
|
||||
{me} = require 'core/auth'
|
||||
LadderSubmissionView = require 'views/play/common/LadderSubmissionView'
|
||||
LevelSession = require 'models/LevelSession'
|
||||
|
@ -12,6 +12,7 @@ module.exports = class CastButtonView extends CocoView
|
|||
'click .cast-button': 'onCastButtonClick'
|
||||
'click .submit-button': 'onCastRealTimeButtonClick'
|
||||
'click .done-button': 'onDoneButtonClick'
|
||||
'click .game-dev-play-btn': 'onClickGameDevPlayButton'
|
||||
|
||||
subscriptions:
|
||||
'tome:spell-changed': 'onSpellChanged'
|
||||
|
@ -74,6 +75,9 @@ module.exports = class CastButtonView extends CocoView
|
|||
Backbone.Mediator.publish 'tome:manual-cast', {realTime: true}
|
||||
@updateReplayability()
|
||||
|
||||
onClickGameDevPlayButton: ->
|
||||
Backbone.Mediator.publish 'tome:manual-cast', {realTime: true}
|
||||
|
||||
onDoneButtonClick: (e) ->
|
||||
return if @options.level.hasLocalChanges() # Don't award achievements when beating level changed in level editor
|
||||
@options.session.recordScores @world?.scores, @options.level
|
||||
|
@ -86,7 +90,7 @@ module.exports = class CastButtonView extends CocoView
|
|||
return if e.preload
|
||||
@casting = true
|
||||
if @hasStartedCastingOnce # Don't play this sound the first time
|
||||
@playSound 'cast', 0.5
|
||||
@playSound 'cast', 0.5 unless @options.level.isType('game-dev')
|
||||
@hasStartedCastingOnce = true
|
||||
@updateCastButton()
|
||||
|
||||
|
@ -98,7 +102,7 @@ module.exports = class CastButtonView extends CocoView
|
|||
onNewWorld: (e) ->
|
||||
@casting = false
|
||||
if @hasCastOnce # Don't play this sound the first time
|
||||
@playSound 'cast-end', 0.5
|
||||
@playSound 'cast-end', 0.5 unless @options.level.isType('game-dev')
|
||||
# Worked great for live beginner tournaments, but probably annoying for asynchronous tournament mode.
|
||||
myHeroID = if me.team is 'ogres' then 'Hero Placeholder 1' else 'Hero Placeholder'
|
||||
if @autoSubmitsToLadder and not e.world.thangMap[myHeroID]?.errorsOut and not me.get('anonymous')
|
||||
|
@ -113,7 +117,7 @@ module.exports = class CastButtonView extends CocoView
|
|||
@winnable = winnable
|
||||
@$el.toggleClass 'winnable', @winnable
|
||||
Backbone.Mediator.publish 'tome:winnability-updated', winnable: @winnable, level: @options.level
|
||||
if @options.level.get('hidesRealTimePlayback') or @options.level.isType('web-dev')
|
||||
if @options.level.get('hidesRealTimePlayback') or @options.level.isType('web-dev', 'game-dev')
|
||||
@$el.find('.done-button').toggle @winnable
|
||||
else if @winnable and @options.level.get('slug') in ['course-thornbush-farm', 'thornbush-farm']
|
||||
@$el.find('.submit-button').show() # Hide submit until first win so that script can explain it.
|
||||
|
|
|
@ -58,6 +58,7 @@ module.exports = class DocFormatter
|
|||
when 'java' then 'hero'
|
||||
when 'coffeescript' then '@'
|
||||
else (if @options.useHero then 'hero' else 'this')
|
||||
ownerName = 'game' if @options.level.isType('game-dev')
|
||||
if @doc.type is 'function'
|
||||
[docName, args] = @getDocNameAndArguments()
|
||||
argNames = args.join ', '
|
||||
|
|
|
@ -85,9 +85,8 @@ module.exports = class SpellPaletteEntryView extends CocoView
|
|||
Backbone.Mediator.publish 'tome:palette-pin-toggled', entry: @, pinned: @popoverPinned
|
||||
|
||||
onClick: (e) =>
|
||||
if true or @options.level.isType('hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder', 'game-dev')
|
||||
# Jiggle instead of pin for hero levels
|
||||
# Actually, do it all the time, because we recently busted the pin CSS. TODO: restore pinning
|
||||
if @options.level.isType('hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder')
|
||||
# Jiggle instead of pin for hero/course levels
|
||||
jigglyPopover = $('.spell-palette-popover.popover')
|
||||
jigglyPopover.addClass 'jiggling'
|
||||
pauseJiggle = =>
|
||||
|
|
|
@ -69,6 +69,7 @@ module.exports = class SpellView extends CocoView
|
|||
@highlightCurrentLine = _.throttle @highlightCurrentLine, 100
|
||||
$(window).on 'resize', @onWindowResize
|
||||
@observing = @session.get('creator') isnt me.id
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
@createACE()
|
||||
|
@ -93,6 +94,7 @@ module.exports = class SpellView extends CocoView
|
|||
@aceSession.setUseWrapMode true
|
||||
@aceSession.setNewLineMode 'unix'
|
||||
@aceSession.setUseSoftTabs true
|
||||
@aceSession.on 'changeAnnotation', @onChangeAnnotation
|
||||
@ace.setTheme 'ace/theme/textmate'
|
||||
@ace.setDisplayIndentGuides false
|
||||
@ace.setShowPrintMargin false
|
||||
|
@ -125,7 +127,7 @@ module.exports = class SpellView extends CocoView
|
|||
addCommand
|
||||
name: 'run-code'
|
||||
bindKey: {win: 'Shift-Enter|Ctrl-Enter', mac: 'Shift-Enter|Command-Enter|Ctrl-Enter'}
|
||||
exec: -> Backbone.Mediator.publish 'tome:manual-cast', {}
|
||||
exec: => Backbone.Mediator.publish 'tome:manual-cast', {realTime: @options.level.isType('game-dev')}
|
||||
unless @observing
|
||||
addCommand
|
||||
name: 'run-code-real-time'
|
||||
|
@ -583,8 +585,8 @@ module.exports = class SpellView extends CocoView
|
|||
# @addZatannaSnippets()
|
||||
@highlightCurrentLine()
|
||||
|
||||
cast: (preload=false, realTime=false) ->
|
||||
Backbone.Mediator.publish 'tome:cast-spell', spell: @spell, thang: @thang, preload: preload, realTime: realTime
|
||||
cast: (preload=false, realTime=false, justBegin=false) ->
|
||||
Backbone.Mediator.publish 'tome:cast-spell', { @spell, @thang, preload, realTime, justBegin }
|
||||
|
||||
notifySpellChanged: =>
|
||||
return if @destroyed
|
||||
|
@ -710,7 +712,7 @@ module.exports = class SpellView extends CocoView
|
|||
@aceDoc.removeListener 'change', @onCodeChangeMetaHandler if @onCodeChangeMetaHandler
|
||||
onSignificantChange = []
|
||||
onAnyChange = [
|
||||
_.debounce @updateAether, 500
|
||||
_.debounce @updateAether, if @options.level.isType('game-dev') then 10 else 500
|
||||
_.debounce @notifyEditingEnded, 1000
|
||||
_.throttle @notifyEditingBegan, 250
|
||||
_.throttle @notifySpellChanged, 300
|
||||
|
@ -794,6 +796,18 @@ module.exports = class SpellView extends CocoView
|
|||
else
|
||||
finishUpdatingAether(aether)
|
||||
|
||||
# NOTE! Because this alone causes the doctype annotation to flicker,
|
||||
# all info annotations have been hidden with CSS in spell.sass
|
||||
# If we ever want info annotations back, we need to remove that.
|
||||
#
|
||||
# This function itself removes the unwanted annotations on a later tick.
|
||||
onChangeAnnotation: (event, session) ->
|
||||
unfilteredAnnotations = session.getAnnotations()
|
||||
filteredAnnotations = _.remove unfilteredAnnotations, (annotation) ->
|
||||
annotation.text is 'Start tag seen without seeing a doctype first. Expected e.g. <!DOCTYPE html>.'
|
||||
if filteredAnnotations.length < unfilteredAnnotations.length
|
||||
session.setAnnotations(filteredAnnotations)
|
||||
|
||||
# Clear annotations and highlights generated by Aether, but not by the ACE worker
|
||||
clearAetherDisplay: ->
|
||||
problem.destroy() for problem in @problems
|
||||
|
@ -873,13 +887,18 @@ module.exports = class SpellView extends CocoView
|
|||
# - Go after specified delay if a) and not b) or c)
|
||||
guessWhetherFinished: (aether) ->
|
||||
valid = not aether.getAllProblems().length
|
||||
return unless valid
|
||||
cursorPosition = @ace.getCursorPosition()
|
||||
currentLine = _.string.rtrim(@aceDoc.$lines[cursorPosition.row].replace(@singleLineCommentRegex(), '')) # trim // unless inside "
|
||||
endOfLine = cursorPosition.column >= currentLine.length # just typed a semicolon or brace, for example
|
||||
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 not incompleteThis and @options.level.isType('game-dev')
|
||||
# TODO: Improve gamedev autocast speed
|
||||
@spell.transpile @getSource()
|
||||
@cast(false, false, true)
|
||||
else if (endOfLine or beginningOfLine) and not incompleteThis
|
||||
@preload()
|
||||
|
||||
singleLineCommentRegex: ->
|
||||
|
@ -1270,3 +1289,4 @@ commentStarts =
|
|||
coffeescript: '#'
|
||||
lua: '--'
|
||||
java: '//'
|
||||
html: '<!--'
|
||||
|
|
|
@ -143,9 +143,9 @@ module.exports = class TomeView extends CocoView
|
|||
|
||||
onCastSpell: (e) ->
|
||||
# A single spell is cast.
|
||||
@cast e?.preload, e?.realTime
|
||||
@cast e?.preload, e?.realTime, e?.justBegin
|
||||
|
||||
cast: (preload=false, realTime=false) ->
|
||||
cast: (preload=false, realTime=false, justBegin=false) ->
|
||||
return if @options.level.isType('web-dev')
|
||||
sessionState = @options.session.get('state') ? {}
|
||||
if realTime
|
||||
|
@ -156,7 +156,17 @@ module.exports = class TomeView extends CocoView
|
|||
difficulty = sessionState.difficulty ? 0
|
||||
if @options.observing
|
||||
difficulty = Math.max 0, difficulty - 1 # Show the difficulty they won, not the next one.
|
||||
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
|
||||
Backbone.Mediator.publish 'tome:cast-spells', {
|
||||
@spells,
|
||||
preload,
|
||||
realTime,
|
||||
justBegin,
|
||||
difficulty,
|
||||
submissionCount: sessionState.submissionCount ? 0,
|
||||
flagHistory: sessionState.flagHistory ? [],
|
||||
god: @options.god,
|
||||
fixedSeed: @options.fixedSeed
|
||||
}
|
||||
|
||||
onClick: (e) ->
|
||||
Backbone.Mediator.publish 'tome:focus-editor', {} unless $(e.target).parents('.popover').length
|
||||
|
|
Loading…
Reference in a new issue