Merge branch 'master' into production

This commit is contained in:
Nick Winter 2014-10-09 09:54:08 -07:00
commit b0aa841976
15 changed files with 180 additions and 117 deletions

View file

@ -1,7 +1,20 @@
module.exports = [
{
channel: "god:new-world-created"
noteChain: []
id: "Introduction"
channel: "god:new-world-created"
noteChain: [
name: "Set camera, start music."
surface:
focus:
bounds: [{x: 0, y: 0}, {x: 80, y: 68}]
target: "Hero Placeholder"
zoom: 2
sound:
music:
file: "/music/music_level_2"
play: true
script:
duration: 1
]
}
]

View file

@ -131,9 +131,7 @@ module.exports = class God extends CocoClass
Backbone.Mediator.publish 'god:debug-world-load-progress-changed', progress: event.data.progress
onNewWorldCreated: (e) ->
console.log 'filtering', _.cloneDeep e.world.userCodeMap
@currentUserCodeMap = @filterUserCodeMapWhenFromWorld e.world.userCodeMap
console.log ' ... filtered into', _.cloneDeep e.world.userCodeMap
filterUserCodeMapWhenFromWorld: (worldUserCodeMap) ->
newUserCodeMap = {}

View file

@ -1,28 +1,27 @@
Dropper = class Dropper
lost_frames: 0.0
drop_counter: 0
lostFrames: 0.0
dropCounter: 0
constructor: ->
@listener = (e) => @tick(e)
tick: ->
unless @tickedOnce
@tickedOnce = true # Can't get measured FPS on the 0th frame
@tickedOnce = true # Can't get measured FPS on the 0th frame.
return
# decrement drop counter
@drop_counter -= 1 if @drop_counter > 0
--@dropCounter if @dropCounter > 0
# track number of frames we've lost since the last tick
# Track number of frames we've lost since the last tick.
fps = createjs.Ticker.getFPS()
actual = createjs.Ticker.getMeasuredFPS(1)
@lost_frames += (fps - actual) / fps
@lostFrames += (fps - actual) / fps
# if lost_frames > 1, drop that number for the next tick
@drop_counter += parseInt(@lost_frames)
@lost_frames = @lost_frames % 1
# If lostFrames > 1, drop that number for the next tick.
@dropCounter += parseInt @lostFrames
@lostFrames = @lostFrames % 1
drop: ->
return @drop_counter > 0
return @dropCounter > 0
module.exports = new Dropper()

View file

@ -1,6 +1,9 @@
CocoClass = require 'lib/CocoClass'
module.exports = class PlaybackOverScreen extends CocoClass
subscriptions:
'goal-manager:new-goal-states': 'onNewGoalStates'
constructor: (options) ->
super()
options ?= {}
@ -16,15 +19,13 @@ module.exports = class PlaybackOverScreen extends CocoClass
@dimLayer = new createjs.Container()
@dimLayer.mouseEnabled = @dimLayer.mouseChildren = false
@dimLayer.addChild @dimScreen = new createjs.Shape()
@dimScreen.graphics.beginFill('rgba(0,0,0,0.4)').rect 0, 0, @camera.canvasWidth, @camera.canvasHeight
@dimLayer.cache 0, 0, @camera.canvasWidth, @camera.canvasHeight
@dimLayer.alpha = 0
@layer.addChild @dimLayer
show: ->
return if @showing
@showing = true
@updateColor 'rgba(212, 212, 212, 0.4)' unless @color # If we haven't caught the goal state for the first run, just do something neutral.
@dimLayer.alpha = 0
createjs.Tween.removeTweens @dimLayer
createjs.Tween.get(@dimLayer).to({alpha: 1}, 500)
@ -32,6 +33,22 @@ module.exports = class PlaybackOverScreen extends CocoClass
hide: ->
return unless @showing
@showing = false
createjs.Tween.removeTweens @dimLayer
createjs.Tween.get(@dimLayer).to({alpha: 0}, 500)
onNewGoalStates: (e) ->
success = e.overallStatus is 'success'
failure = e.overallStatus is 'failure'
timedOut = e.timedOut
incomplete = not success and not failure and not timedOut
color = if failure then 'rgba(255, 128, 128, 0.4)' else 'rgba(255, 255, 255, 0.4)'
@updateColor color
updateColor: (color) ->
return if color is @color
@dimScreen.graphics.clear().beginFill(color).rect 0, 0, @camera.canvasWidth, @camera.canvasHeight
if @color
@dimLayer.updateCache()
else
@dimLayer.cache 0, 0, @camera.canvasWidth, @camera.canvasHeight # I wonder if caching is even worth it for just a rect fill.
@color = color

View file

@ -115,6 +115,7 @@ module.exports = Surface = class Surface extends CocoClass
@lankBoss = new LankBoss camera: @camera, webGLStage: @webGLStage, surfaceTextLayer: @surfaceTextLayer, world: @world, thangTypes: @options.thangTypes, choosing: @options.choosing, navigateToSelection: @options.navigateToSelection, showInvisible: @options.showInvisible
@countdownScreen = new CountdownScreen camera: @camera, layer: @screenLayer
@playbackOverScreen = new PlaybackOverScreen camera: @camera, layer: @screenLayer
@normalStage.addChildAt @playbackOverScreen.dimLayer, 0 # Put this below the other layers, actually, so we can more easily read text on the screen.
@waitingScreen = new WaitingScreen camera: @camera, layer: @screenLayer
@initCoordinates()
@webGLStage.enableMouseOver(10)
@ -192,7 +193,7 @@ module.exports = Surface = class Surface extends CocoClass
@currentFrame = Math.min @currentFrame, lastFrame
newWorldFrame = Math.floor @currentFrame
if Dropper.drop()
framesDropped += 1
++framesDropped
else
worldFrameAdvanced = newWorldFrame isnt oldWorldFrame
if worldFrameAdvanced
@ -231,8 +232,8 @@ module.exports = Surface = class Surface extends CocoClass
# world state must have been restored in @restoreWorldState
if @playing and @currentFrame < @world.frames.length - 1 and @heroLank and not @mouseIsDown and @camera.newTarget isnt @heroLank.sprite and @camera.target isnt @heroLank.sprite
@camera.zoomTo @heroLank.sprite, @camera.zoom, 750
@camera.updateZoom()
@lankBoss.update frameChanged
@camera.updateZoom() # Make sure to do this right after the LankBoss updates, not before, so it can properly target sprite positions.
@dimmer?.setSprites @lankBoss.lanks
drawCurrentFrame: (e) ->

View file

@ -448,6 +448,8 @@ module.exports = class World
for thangID, methods of o.userCodeMap
for methodName, serializedAether of methods
for aetherStateKey in ['flow', 'metrics', 'style', 'problems']
w.userCodeMap[thangID] ?= {}
w.userCodeMap[thangID][methodName] ?= {}
w.userCodeMap[thangID][methodName][aetherStateKey] = serializedAether[aetherStateKey]
else
w = new World o.userCodeMap, classMap

View file

@ -236,6 +236,17 @@ table.table
.ui-state-default, .ui-state-focus, .ui-state-active, .ui-state-highlight, .ui-state-error
background-image: none
// DOM highlight pointer arrow
.highlight-pointer
position: absolute
left: 0
top: 0
height: 100px
opacity: 0.0
pointer-events: none
z-index: 10
// Fonts
.header-font

View file

@ -97,15 +97,6 @@ $level-resize-transition-time: 0.5s
bottom: 0
@include transition(width $level-resize-transition-time ease-in-out, right $level-resize-transition-time ease-in-out)
#pointer
position: absolute
left: 0
top: 0
height: 100px
opacity: 0.0
pointer-events: none
z-index: 10
// Level Docs
.ui-effects-transfer
border: 2px dotted gray

View file

@ -5,19 +5,24 @@
position: absolute
left: 10px
top: -100px
@include transition(top 0.5s ease-in-out)
@include transition(0.5s ease-in-out)
background-color: rgba(200,200,200,1.0)
border: black
padding: 15px 7px 2px 5px
box-sizing: border-box
border: 1px solid #333
border: 1px solid #333
border-radius: 5px
z-index: 3
font-size: 14px
&.brighter
font-size: 28px
.goals-status
font-size: 14px
margin: 0
color: black
.success
color: darkgreen
.timed-out

View file

@ -59,6 +59,7 @@ module.exports = class CocoView extends Backbone.View
@undelegateEvents() # removes both events and subs
view.destroy() for id, view of @subviews
$('#modal-wrapper .modal').off 'hidden.bs.modal', @modalClosed
@endHighlight()
@[key] = undefined for key, value of @
@destroyed = true
@off = doNothing
@ -293,6 +294,78 @@ module.exports = class CocoView extends Backbone.View
delete @subviews[view.parentKey]
view.destroy()
# Pointing stuff out
highlightElement: (selector, options) ->
@endHighlight()
options ?= {}
if delay = options.delay
delete options.delay
return @pointerDelayTimeout = _.delay((=> @highlightElement selector, options), delay)
$pointer = @getPointer()
$target = $(selector + ':visible')
return if parseFloat($target.css('opacity')) is 0.0 # Don't point out invisible elements.
return unless offset = $target.offset() # Don't point out elements we can't locate.
targetLeft = offset.left + $target.outerWidth() * 0.5
targetTop = offset.top + $target.outerHeight() * 0.5
if options.sides
if 'left' in options.sides then targetLeft = offset.left
if 'right' in options.sides then targetLeft = offset.left + $target.outerWidth()
if 'top' in options.sides then targetTop = offset.top
if 'bottom' in options.sides then targetTop = offset.top + $target.outerHeight()
else
# Aim to hit the side if the target is entirely on one side of the screen.
if offset.left > @$el.outerWidth() * 0.5
targetLeft = offset.left
else if offset.left + $target.outerWidth() < @$el.outerWidth() * 0.5
targetLeft = offset.left + $target.outerWidth()
# Aim to hit the bottom or top if the target is entirely on the top or bottom of the screen.
if offset.top > @$el.outerWidth() * 0.5
targetTop = offset.top
else if offset.top + $target.outerHeight() < @$el.outerHeight() * 0.5
targetTop = offset.top + $target.outerHeight()
if options.offset
targetLeft += options.offset.x
targetTop += options.offset.y
@pointerRadialDistance = -47
@pointerRotation = options.rotation ? Math.atan2(@$el.outerWidth() * 0.5 - targetLeft, targetTop - @$el.outerHeight() * 0.5)
$pointer.css
opacity: 1.0
transition: 'none'
transform: "rotate(#{@pointerRotation}rad) translate(-3px, #{@pointerRadialDistance}px)"
top: targetTop - 50
left: targetLeft - 50
_.defer =>
return if @destroyed
@animatePointer()
clearInterval @pointerInterval
@pointerInterval = setInterval(@animatePointer, 1200)
if options.duration
@pointerDurationTimeout = _.delay (=> @endHighlight() unless @destroyed), options.duration
animatePointer: =>
$pointer = @getPointer()
$pointer.css transition: 'all 0.6s ease-out', transform: "rotate(#{@pointerRotation}rad) translate(-3px, #{@pointerRadialDistance-50}px)"
#Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'dom_highlight', volume: 0.75 # Never mind, this is currently so annoying
setTimeout (=> $pointer.css transition: 'all 0.4s ease-in', transform: "rotate(#{@pointerRotation}rad) translate(-3px, #{@pointerRadialDistance}px)"), 800
endHighlight: ->
@getPointer().css('opacity', 0.0)
clearInterval @pointerInterval
clearTimeout @pointerDelayTimeout
clearTimeout @pointerDurationTimeout
@pointerInterval = @pointerDelayTimeout = @pointerDurationTimeout = null
getPointer: ->
return $pointer if ($pointer = @$el.find('.highlight-pointer')) and $pointer.length
$pointer = $('<img src="/images/level/pointer.png" class="highlight-pointer">')
@$el.append($pointer)
$pointer
# Utilities
getQueryVariable: (param, defaultValue) -> CocoView.getQueryVariable(param, defaultValue)

View file

@ -89,6 +89,7 @@ module.exports = class WorldMapView extends RootView
@$el.find('.level').tooltip()
@$el.addClass _.string.slugify @terrain
@updateVolume()
@highlightElement '.level.next', delay: 8000, duration: 20000, rotation: 0, sides: ['top']
onSessionsLoaded: (e) ->
for session in @sessions.models

View file

@ -4,9 +4,9 @@ template = require 'templates/play/level/goals'
utils = require 'lib/utils'
stateIconMap =
incomplete: 'icon-minus'
success: 'icon-ok'
failure: 'icon-remove'
incomplete: 'glyphicon-minus'
success: 'glyphicon-ok'
failure: 'glyphicon-remove'
module.exports = class LevelGoalsView extends CocoView
id: 'goals-view'
@ -61,32 +61,47 @@ module.exports = class LevelGoalsView extends CocoView
# This should really get refactored, along with GoalManager, so that goals have a standard
# representation of how many are done, how many are needed, what that means, etc.
li = $('<li></li>').addClass("status-#{state.status}").text(text)
li.prepend($('<i></i>').addClass(stateIconMap[state.status]))
li.prepend($('<i></i>').addClass('glyphicon').addClass(stateIconMap[state.status]))
list.append(li)
goals.push goal
if not firstRun and state.status is 'success' and @previousGoalStatus[goal.id] isnt 'success'
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'goal-success', volume: 1
@soundToPlayWhenPlaybackEnded = 'goal-success'
else if not firstRun and state.status isnt 'success' and @previousGoalStatus[goal.id] is 'success'
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'goal-incomplete-again', volume: 1
@soundToPlayWhenPlaybackEnded = 'goal-incomplete-again'
else
@soundToPlayWhenPlaybackEnded = null
@previousGoalStatus[goal.id] = state.status
@$el.removeClass('secret') if goals.length > 0
if goals.length > 0 and @$el.hasClass 'secret'
@$el.removeClass('secret')
@lastSizeTweenTime = new Date()
@updatePlacement()
onSurfacePlaybackRestarted: ->
@playbackEnded = false
@$el.removeClass 'brighter'
@lastSizeTweenTime = new Date()
@updatePlacement()
onSurfacePlaybackEnded: ->
@playbackEnded = true
@updateHeight()
@$el.addClass 'brighter'
@lastSizeTweenTime = new Date()
@updatePlacement()
if @soundToPlayWhenPlaybackEnded
Backbone.Mediator.publish 'audio-player:play-sound', trigger: @soundToPlayWhenPlaybackEnded, volume: 1
updateHeight: ->
return if @$el.hasClass('brighter') or @$el.hasClass('secret')
return if (new Date() - @lastSizeTweenTime) < 500 # Don't measure this while still animating, might get the wrong value. Should match sass transition time.
@normalHeight = @$el.outerHeight()
updatePlacement: ->
expand = @playbackEnded or @mouseEntered
return if expand is @expanded
@updateHeight()
sound = if expand then 'goals-expand' else 'goals-collapse'
top = if expand then -10 else 26 - @$el.outerHeight()
top = if expand then -10 else 26 - (@normalHeight ? @$el.outerHeight())
@$el.css 'top', top
if @soundTimeout
# Don't play the sound we were going to play after all; the transition has reversed.

View file

@ -49,7 +49,7 @@ module.exports = class PlayLevelView extends RootView
'level:set-volume': (e) -> createjs.Sound.setVolume(if e.volume is 1 then 0.6 else e.volume) # Quieter for now until individual sound FX controls work again.
'level:show-victory': 'onShowVictory'
'level:restart': 'onRestartLevel'
'level:highlight-dom': 'onHighlightDom'
'level:highlight-dom': 'onHighlightDOM'
'level:end-highlight-dom': 'onEndHighlight'
'level:focus-dom': 'onFocusDom'
'level:disable-controls': 'onDisableControls'
@ -430,9 +430,7 @@ module.exports = class PlayLevelView extends RootView
viewArgs: [{supermodel: @supermodel, autoUnveil: true}, @levelID]
}
onWindowResize: (s...) ->
$('#pointer').css('opacity', 0.0)
clearInterval(@pointerInterval)
onWindowResize: (e) -> @endHighlight()
onDisableControls: (e) ->
return if e.controls and not ('level' in e.controls)
@ -500,73 +498,12 @@ module.exports = class PlayLevelView extends RootView
return null unless @getNextLevelID()
"/play/level/#{@getNextLevelID()}"
onHighlightDom: (e) ->
if e.delay
delay = e.delay
delete e.delay
@pointerInterval = _.delay((=> @onHighlightDom e), delay)
return
@addPointer()
selector = e.selector + ':visible'
dom = $(selector)
return if parseFloat(dom.css('opacity')) is 0.0
offset = dom.offset()
return if not offset
target_left = offset.left + dom.outerWidth() * 0.5
target_top = offset.top + dom.outerHeight() * 0.5
onHighlightDOM: (e) -> @highlightElement e.selector, delay: e.delay, sides: e.sides, offset: e.offset, rotation: e.rotation
if e.sides
if 'left' in e.sides then target_left = offset.left
if 'right' in e.sides then target_left = offset.left + dom.outerWidth()
if 'top' in e.sides then target_top = offset.top
if 'bottom' in e.sides then target_top = offset.top + dom.outerHeight()
else
# aim to hit the side if the target is entirely on one side of the screen
if offset.left > @$el.outerWidth()*0.5
target_left = offset.left
else if offset.left + dom.outerWidth() < @$el.outerWidth()*0.5
target_left = offset.left + dom.outerWidth()
# aim to hit the bottom or top if the target is entirely on the top or bottom of the screen
if offset.top > @$el.outerWidth()*0.5
target_top = offset.top
else if offset.top + dom.outerHeight() < @$el.outerHeight()*0.5
target_top = offset.top + dom.outerHeight()
if e.offset
target_left += e.offset.x
target_top += e.offset.y
@pointerRadialDistance = -47 # - Math.sqrt(Math.pow(dom.outerHeight()*0.5, 2), Math.pow(dom.outerWidth()*0.5))
@pointerRotation = e.rotation ? Math.atan2(@$el.outerWidth()*0.5 - target_left, target_top - @$el.outerHeight()*0.5)
pointer = $('#pointer')
pointer
.css('opacity', 1.0)
.css('transition', 'none')
.css('transform', "rotate(#{@pointerRotation}rad) translate(-3px, #{@pointerRadialDistance}px)")
.css('top', target_top - 50)
.css('left', target_left - 50)
setTimeout(()=>
return if @destroyed
@animatePointer()
clearInterval(@pointerInterval)
@pointerInterval = setInterval(@animatePointer, 1200)
, 1)
animatePointer: =>
pointer = $('#pointer')
pointer.css('transition', 'all 0.6s ease-out')
pointer.css('transform', "rotate(#{@pointerRotation}rad) translate(-3px, #{@pointerRadialDistance-50}px)")
#Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'dom_highlight', volume: 0.75 # Never mind, this is currently so annoying
setTimeout((=>
pointer.css('transform', "rotate(#{@pointerRotation}rad) translate(-3px, #{@pointerRadialDistance}px)").css('transition', 'all 0.4s ease-in')), 800)
onEndHighlight: -> @endHighlight()
onFocusDom: (e) -> $(e.selector).focus()
onEndHighlight: ->
$('#pointer').css('opacity', 0.0)
clearInterval(@pointerInterval)
onMultiplayerChanged: (e) ->
if @session.get('multiplayer')
@bus.connect()
@ -574,11 +511,6 @@ module.exports = class PlayLevelView extends RootView
@bus.removeFirebaseData =>
@bus.disconnect()
addPointer: ->
p = $('#pointer')
return if p.length
@$el.append($('<img src="/images/level/pointer.png" id="pointer">'))
preloadNextLevel: =>
# TODO: Loading models in the middle of gameplay causes stuttering. Most of the improvement in loading time is simply from passing the supermodel from this level to the next, but if we can find a way to populate the level early without it being noticeable, that would be even better.
# return if @destroyed
@ -652,7 +584,6 @@ module.exports = class PlayLevelView extends RootView
createjs.Tween.get(ambientSound).to({volume: 0.0}, 1500).call -> ambientSound.stop()
$(window).off 'resize', @onWindowResize
delete window.world # not sure where this is set, but this is one way to clean it up
clearInterval(@pointerInterval)
@bus?.destroy()
#@instance.save() unless @instance.loading
delete window.nextLevelURL

View file

@ -107,10 +107,14 @@ module.exports = class DocFormatter
v = @options.thang[@doc.name]
else
v = window[@doc.owner][@doc.name] # grab Math or Vector
if @doc.type is 'number' and not isNaN v
if @doc.type is 'number' and not _.isNaN v
if v == Math.round v
return v
return v?.toFixed(2) ? 'null'
if _.isNumber v
return v.toFixed 2
unless v
return 'null'
return '' + v
if _.isString v
return "\"#{v}\""
if v?.id

View file

@ -199,6 +199,8 @@ module.exports = class SpellView extends CocoView
completers:
keywords: false
text: false
autoLineEndings:
javascript: ';'
updateAutocomplete: (@autocomplete) ->
@zatanna?.set 'snippets', @autocomplete