mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-04-26 14:03:28 -04:00
Press Ctrl+Shift+Enter to cast in real-time, and click on the ground to use your wizard as a flag. OMG, it not only works, but it is really fun!
This commit is contained in:
parent
6fee881efd
commit
9c0869e566
9 changed files with 63 additions and 98 deletions
app
assets/javascripts/workers
lib
views/play/level/tome
|
@ -346,6 +346,7 @@ self.runWorld = function runWorld(args) {
|
|||
self.world.loadFromLevel(args.level, true);
|
||||
self.world.preloading = args.preload;
|
||||
self.world.headless = args.headless;
|
||||
self.world.realTime = args.realTime;
|
||||
self.goalManager = new GoalManager(self.world);
|
||||
self.goalManager.setGoals(args.goals);
|
||||
self.goalManager.setCode(args.userCodeMap);
|
||||
|
@ -458,6 +459,11 @@ self.finalizePreload = function finalizePreload() {
|
|||
self.world.finalizePreload(self.onWorldLoaded);
|
||||
};
|
||||
|
||||
self.updateFlags = function updateFlags(flags) {
|
||||
if(!self.world || self.world.framesSerializedSoFar == self.world.frames.length) return;
|
||||
self.world.updateFlags(flags);
|
||||
};
|
||||
|
||||
self.addEventListener('message', function(event) {
|
||||
self[event.data.func](event.data.args);
|
||||
});
|
||||
|
|
|
@ -12,6 +12,9 @@ module.exports = class Angel extends CocoClass
|
|||
infiniteLoopTimeoutDuration: 7500 # wait this long for a response when checking
|
||||
abortTimeoutDuration: 500 # give in-process or dying workers this long to give up
|
||||
|
||||
subscriptions:
|
||||
'self-wizard:target-changed': 'onSelfWizardTargetChanged'
|
||||
|
||||
constructor: (@shared) ->
|
||||
super()
|
||||
@say 'Got my wings.'
|
||||
|
@ -205,6 +208,11 @@ module.exports = class Angel extends CocoClass
|
|||
@worker.addEventListener 'message', @onWorkerMessage
|
||||
@worker.creationTime = new Date()
|
||||
|
||||
onSelfWizardTargetChanged: (e) ->
|
||||
return unless @running and @work.realTime
|
||||
targetPos = e.sender.targetPos
|
||||
@worker.postMessage func: 'updateFlags', args: [{type: 'wizard', targetPos: targetPos}]
|
||||
|
||||
|
||||
#### Synchronous code for running worlds on main thread (profiling / IE9) ####
|
||||
simulateSync: (work) =>
|
||||
|
|
|
@ -53,9 +53,9 @@ module.exports = class God extends CocoClass
|
|||
setWorldClassMap: (worldClassMap) -> @angelsShare.worldClassMap = worldClassMap
|
||||
|
||||
onTomeCast: (e) ->
|
||||
@createWorld e.spells, e.preload
|
||||
@createWorld e.spells, e.preload, e.realTime
|
||||
|
||||
createWorld: (spells, preload=false) ->
|
||||
createWorld: (spells, preload=false, realTime=false) ->
|
||||
console.log "#{@nick}: Let there be light upon #{@level.name}! (preload: #{preload})"
|
||||
userCodeMap = @getUserCodeMap spells
|
||||
|
||||
|
@ -84,6 +84,7 @@ module.exports = class God extends CocoClass
|
|||
headless: @angelsShare.headless
|
||||
preload: preload
|
||||
synchronous: not Worker? # Profiling world simulation is easier on main thread, or we are IE9.
|
||||
realTime: realTime
|
||||
angel.workIfIdle() for angel in @angelsShare.angels
|
||||
|
||||
getUserCodeMap: (spells) ->
|
||||
|
|
|
@ -197,6 +197,7 @@ module.exports = class LevelBus extends Bus
|
|||
onNewGoalStates: ({goalStates})->
|
||||
state = @session.get 'state'
|
||||
unless utils.kindaEqual state.goalStates, goalStates # Only save when goals really change
|
||||
return console.error("Somehow trying to save null goal states!", goalStates) if _.find(goalStates, (gs) -> not gs.status)
|
||||
state.goalStates = goalStates
|
||||
@session.set 'state', state
|
||||
@changedSessionProperties.state = true
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
CocoClass = require 'lib/CocoClass'
|
||||
|
||||
module.exports = class CastingScreen extends CocoClass
|
||||
subscriptions:
|
||||
'tome:cast-spells': 'onCastingBegins'
|
||||
'god:new-world-created': 'onCastingEnds'
|
||||
'god:world-load-progress-changed': 'onWorldLoadProgressChanged'
|
||||
|
||||
constructor: (options) ->
|
||||
super()
|
||||
options ?= {}
|
||||
@camera = options.camera
|
||||
@layer = options.layer
|
||||
console.error @toString(), 'needs a camera.' unless @camera
|
||||
console.error @toString(), 'needs a layer.' unless @layer
|
||||
@build()
|
||||
|
||||
onCastingBegins: (e) -> @show() unless e.preload
|
||||
onCastingEnds: (e) -> @hide()
|
||||
|
||||
toString: -> '<CastingScreen>'
|
||||
|
||||
build: ->
|
||||
@dimLayer = new createjs.Container()
|
||||
@dimLayer.mouseEnabled = @dimLayer.mouseChildren = false
|
||||
@dimLayer.layerIndex = -11
|
||||
@dimLayer.addChild @dimScreen = new createjs.Shape()
|
||||
@dimScreen.graphics.beginFill('rgba(0,0,0,0.5)').rect 0, 0, @camera.canvasWidth, @camera.canvasHeight
|
||||
@dimLayer.alpha = 0
|
||||
@layer.addChild @dimLayer
|
||||
@dimLayer.addChild @makeProgressBar()
|
||||
@dimLayer.addChild @makeCastingText()
|
||||
|
||||
onWorldLoadProgressChanged: (e) ->
|
||||
if new Date().getTime() - @t0 > 500
|
||||
createjs.Tween.removeTweens @progressBar
|
||||
createjs.Tween.get(@progressBar).to({scaleX: e.progress}, 200)
|
||||
|
||||
makeProgressBar: ->
|
||||
BAR_PIXEL_HEIGHT = 3
|
||||
BAR_PCT_WIDTH = .75
|
||||
pixelWidth = parseInt(@camera.canvasWidth * BAR_PCT_WIDTH)
|
||||
pixelMargin = (@camera.canvasWidth - (@camera.canvasWidth * BAR_PCT_WIDTH)) / 2
|
||||
barY = 3 * (@camera.canvasHeight / 5)
|
||||
|
||||
g = new createjs.Graphics()
|
||||
g.beginFill(createjs.Graphics.getRGB(255, 255, 255))
|
||||
g.drawRoundRect(0, 0, pixelWidth, BAR_PIXEL_HEIGHT, 3)
|
||||
@progressBar = new createjs.Shape(g)
|
||||
@progressBar.x = pixelMargin
|
||||
@progressBar.y = barY
|
||||
@progressBar.scaleX = 0
|
||||
@dimLayer.addChild(@progressBar)
|
||||
|
||||
makeCastingText: ->
|
||||
size = @camera.canvasHeight / 15
|
||||
text = new createjs.Text('Casting', "#{size}px cursive", '#aaaaaa')
|
||||
text.regX = text.getMeasuredWidth() / 2
|
||||
text.regY = text.getMeasuredHeight() / 2
|
||||
text.x = @camera.canvasWidth / 2
|
||||
text.y = @camera.canvasHeight / 2
|
||||
@text = text
|
||||
return text
|
||||
|
||||
show: ->
|
||||
return if @showing
|
||||
@showing = true
|
||||
@t0 = new Date().getTime()
|
||||
|
||||
@progressBar.scaleX = 0
|
||||
@dimLayer.alpha = 0
|
||||
createjs.Tween.removeTweens @dimLayer
|
||||
createjs.Tween.get(@dimLayer).to({alpha: 1}, 500)
|
||||
|
||||
hide: ->
|
||||
return unless @showing
|
||||
@showing = false
|
||||
|
||||
createjs.Tween.removeTweens @progressBar
|
||||
createjs.Tween.removeTweens @dimLayer
|
||||
createjs.Tween.get(@dimLayer).to({alpha: 0}, 500)
|
|
@ -8,7 +8,6 @@ CameraBorder = require './CameraBorder'
|
|||
Layer = require './Layer'
|
||||
Letterbox = require './Letterbox'
|
||||
Dimmer = require './Dimmer'
|
||||
CastingScreen = require './CastingScreen'
|
||||
PlaybackOverScreen = require './PlaybackOverScreen'
|
||||
DebugDisplay = require './DebugDisplay'
|
||||
CoordinateDisplay = require './CoordinateDisplay'
|
||||
|
@ -97,7 +96,6 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
@spriteBoss.destroy()
|
||||
@chooser?.destroy()
|
||||
@dimmer?.destroy()
|
||||
@castingScreen?.destroy()
|
||||
@playbackOverScreen?.destroy()
|
||||
@stage.clear()
|
||||
@musicPlayer?.destroy()
|
||||
|
@ -403,7 +401,6 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
@surfaceLayer.addChild @cameraBorder = new CameraBorder bounds: @camera.bounds
|
||||
@screenLayer.addChild new Letterbox canvasWidth: canvasWidth, canvasHeight: canvasHeight
|
||||
@spriteBoss = new SpriteBoss camera: @camera, surfaceLayer: @surfaceLayer, surfaceTextLayer: @surfaceTextLayer, world: @world, thangTypes: @options.thangTypes, choosing: @options.choosing, navigateToSelection: @options.navigateToSelection, showInvisible: @options.showInvisible
|
||||
#@castingScreen ?= new CastingScreen camera: @camera, layer: @screenLayer # STREAM: Not needed with world streaming.
|
||||
@playbackOverScreen ?= new PlaybackOverScreen camera: @camera, layer: @screenLayer
|
||||
@stage.enableMouseOver(10)
|
||||
@stage.addEventListener 'stagemousemove', @onMouseMove
|
||||
|
|
|
@ -12,6 +12,8 @@ Component = require 'lib/world/component'
|
|||
System = require 'lib/world/system'
|
||||
PROGRESS_UPDATE_INTERVAL = 100
|
||||
DESERIALIZATION_INTERVAL = 10
|
||||
REAL_TIME_BUFFER_MIN = 2 * PROGRESS_UPDATE_INTERVAL
|
||||
REAL_TIME_BUFFER_MAX = 5 * PROGRESS_UPDATE_INTERVAL
|
||||
ITEM_ORIGINAL = '53e12043b82921000051cdf9'
|
||||
|
||||
module.exports = class World
|
||||
|
@ -89,6 +91,8 @@ module.exports = class World
|
|||
console.log 'Warning: loadFrames called on empty World (no thangs).'
|
||||
t1 = now()
|
||||
@t0 ?= t1
|
||||
@worldLoadStartTime ?= t1
|
||||
@lastRealTimeUpdate ?= 0
|
||||
if loadUntilFrame
|
||||
frameToLoadUntil = loadUntilFrame + 1
|
||||
else
|
||||
|
@ -112,8 +116,16 @@ module.exports = class World
|
|||
return unless errorCallback error # errorCallback tells us whether the error is recoverable
|
||||
@unhandledRuntimeErrors = []
|
||||
t2 = now()
|
||||
if t2 - t1 > PROGRESS_UPDATE_INTERVAL
|
||||
loadProgressCallback? i / @totalFrames unless @preloading
|
||||
if @realTime
|
||||
shouldUpdateProgress = @shouldUpdateRealTimePlayback t2
|
||||
shouldDelayRealTimeSimulation = not shouldUpdateProgress and @shouldDelayRealTimeSimulation t2
|
||||
else
|
||||
shouldUpdateProgress = t2 - t1 > PROGRESS_UPDATE_INTERVAL
|
||||
shouldDelayRealTimeSimulation = false
|
||||
if shouldUpdateProgress or shouldDelayRealTimeSimulation
|
||||
if shouldUpdateProgress
|
||||
@lastRealTimeUpdate = i * @dt if @realTime
|
||||
loadProgressCallback? i / @totalFrames unless @preloading
|
||||
t1 = t2
|
||||
if t2 - @t0 > 1000
|
||||
console.log ' Loaded', i, 'of', @totalFrames, '(+' + (t2 - @t0).toFixed(0) + 'ms)'
|
||||
|
@ -127,7 +139,8 @@ module.exports = class World
|
|||
if skipDeferredLoading
|
||||
continueFn()
|
||||
else
|
||||
setTimeout(continueFn, 0)
|
||||
delay = if shouldDelayRealTimeSimulation then PROGRESS_UPDATE_INTERVAL else 0
|
||||
setTimeout(continueFn, delay)
|
||||
return
|
||||
unless @debugging
|
||||
@ended = true
|
||||
|
@ -136,6 +149,19 @@ module.exports = class World
|
|||
loadProgressCallback? 1
|
||||
loadedCallback()
|
||||
|
||||
shouldDelayRealTimeSimulation: (t) ->
|
||||
return false unless @realTime
|
||||
timeSinceStart = t - @worldLoadStartTime
|
||||
timeLoaded = @frames.length * @dt * 1000
|
||||
timeBuffered = timeLoaded - timeSinceStart
|
||||
timeBuffered > REAL_TIME_BUFFER_MAX
|
||||
|
||||
shouldUpdateRealTimePlayback: (t) ->
|
||||
return false unless @realTime
|
||||
timeSinceStart = t - @worldLoadStartTime
|
||||
remainingBuffer = @lastRealTimeUpdate * 1000 - timeSinceStart
|
||||
remainingBuffer < REAL_TIME_BUFFER_MIN
|
||||
|
||||
finalizePreload: (loadedCallback) ->
|
||||
@preloading = false
|
||||
loadedCallback() if @ended
|
||||
|
@ -143,6 +169,9 @@ module.exports = class World
|
|||
abort: ->
|
||||
@aborted = true
|
||||
|
||||
updateFlags: (@flags) ->
|
||||
console.log "updated flags", @flags
|
||||
|
||||
loadFromLevel: (level, willSimulate=true) ->
|
||||
@levelComponents = level.levelComponents
|
||||
@thangTypes = level.thangTypes
|
||||
|
|
|
@ -114,6 +114,10 @@ module.exports = class SpellView extends CocoView
|
|||
name: 'run-code'
|
||||
bindKey: {win: 'Shift-Enter|Ctrl-Enter', mac: 'Shift-Enter|Command-Enter|Ctrl-Enter'}
|
||||
exec: -> Backbone.Mediator.publish 'tome:manual-cast', {}
|
||||
addCommand
|
||||
name: 'run-code-real-time'
|
||||
bindKey: {win: 'Ctrl-Shift-Enter', mac: 'Command-Shift-Enter|Ctrl-Shift-Enter'}
|
||||
exec: -> Backbone.Mediator.publish 'tome:manual-cast', {realTime: true}
|
||||
addCommand
|
||||
name: 'no-op'
|
||||
bindKey: {win: 'Ctrl-S', mac: 'Command-S|Ctrl-S'}
|
||||
|
@ -269,8 +273,8 @@ module.exports = class SpellView extends CocoView
|
|||
# @addZatannaSnippets()
|
||||
@highlightCurrentLine()
|
||||
|
||||
cast: (preload=false) ->
|
||||
Backbone.Mediator.publish 'tome:cast-spell', spell: @spell, thang: @thang, preload: preload
|
||||
cast: (preload=false, realTime=false) ->
|
||||
Backbone.Mediator.publish 'tome:cast-spell', spell: @spell, thang: @thang, preload: preload, realTime: realTime
|
||||
|
||||
notifySpellChanged: =>
|
||||
Backbone.Mediator.publish 'tome:spell-changed', spell: @spell
|
||||
|
@ -285,7 +289,7 @@ module.exports = class SpellView extends CocoView
|
|||
|
||||
onManualCast: (e) ->
|
||||
cast = @$el.parent().length
|
||||
@recompile cast
|
||||
@recompile cast, e.realTime
|
||||
@focus() if cast
|
||||
|
||||
onCodeReload: (e) ->
|
||||
|
@ -299,12 +303,12 @@ module.exports = class SpellView extends CocoView
|
|||
recompileIfNeeded: =>
|
||||
@recompile() if @recompileNeeded
|
||||
|
||||
recompile: (cast=true) ->
|
||||
recompile: (cast=true, realTime=false) ->
|
||||
@setRecompileNeeded false
|
||||
return if @spell.source is @getSource()
|
||||
@spell.transpile @getSource()
|
||||
@updateAether true, false
|
||||
@cast() if cast
|
||||
@cast(false, realTime) if cast
|
||||
@notifySpellChanged()
|
||||
|
||||
updateACEText: (source) ->
|
||||
|
@ -472,7 +476,7 @@ module.exports = class SpellView extends CocoView
|
|||
onSessionWillSave: (e) ->
|
||||
return unless @spellHasChanged
|
||||
setTimeout(=>
|
||||
unless @spellHasChanged
|
||||
unless @destroyed or @spellHasChanged
|
||||
@$el.find('.save-status').finish().show().fadeOut(2000)
|
||||
, 1000)
|
||||
@spellHasChanged = false
|
||||
|
|
|
@ -152,10 +152,10 @@ module.exports = class TomeView extends CocoView
|
|||
onCastSpell: (e) ->
|
||||
# A single spell is cast.
|
||||
# Hmm; do we need to make sure other spells are all cast here?
|
||||
@cast e?.preload
|
||||
@cast e?.preload, e?.realTime
|
||||
|
||||
cast: (preload=false) ->
|
||||
Backbone.Mediator.publish 'tome:cast-spells', spells: @spells, preload: preload
|
||||
cast: (preload=false, realTime=false) ->
|
||||
Backbone.Mediator.publish 'tome:cast-spells', spells: @spells, preload: preload, realTime: realTime
|
||||
|
||||
onToggleSpellList: (e) ->
|
||||
@spellList.rerenderEntries()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue