Merge branch 'master' into production

This commit is contained in:
Nick Winter 2014-11-19 18:36:40 -08:00
commit bfeb1457a1
68 changed files with 7103 additions and 6920 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View file

@ -130,7 +130,7 @@ module.exports = class Angel extends CocoClass
eventType = if finished then 'god:new-world-created' else 'god:streaming-world-updated'
if finished
@shared.world = world
Backbone.Mediator.publish eventType, world: world, firstWorld: @shared.firstWorld, goalStates: goalStates, team: me.team, firstChangedFrame: firstChangedFrame
Backbone.Mediator.publish eventType, world: world, firstWorld: @shared.firstWorld, goalStates: goalStates, team: me.team, firstChangedFrame: firstChangedFrame, finished: finished
if finished
for scriptNote in @shared.world.scriptNotes
Backbone.Mediator.publish scriptNote.channel, scriptNote.event

View file

@ -123,7 +123,7 @@ class AudioPlayer extends CocoClass
return if filename of cache
name ?= filename
# SoundJS flips out if you try to register the same file twice
createjs.Sound.registerSound(filename, name, 1, true) # 1: 1 channel, true: should preload
result = createjs.Sound.registerSound(filename, name, 1) # 1: 1 channel
cache[filename] = new Media(name)
# PROGRESS CALLBACKS

View file

@ -91,7 +91,9 @@ module.exports = GPlusHandler = class GPlusHandler extends CocoClass
@saveIfAllDone()
saveIfAllDone: =>
console.debug 'Save if all done. Person loaded:', @personLoaded, 'and email loaded:', @emailLoaded
return unless @personLoaded and @emailLoaded
console.debug 'Email, gplusID:', me.get('email'), me.get('gplusID')
return unless me.get('email') and me.get('gplusID')
Backbone.Mediator.publish 'auth:logging-in-with-gplus', {}
@ -104,12 +106,16 @@ module.exports = GPlusHandler = class GPlusHandler extends CocoClass
patch.email = me.get('email')
wasAnonymous = me.get('anonymous')
@trigger 'logging-into-codecombat'
console.debug('Logging into GPlus.')
me.save(patch, {
patch: true
type: 'PUT'
error: backboneFailure,
error: ->
console.debug('Logging into GPlus fail.', arguments)
backboneFailure(arguments...)
url: "/db/user?gplusID=#{gplusID}&gplusAccessToken=#{@accessToken.access_token}"
success: (model) ->
console.debug('GPLus login success!')
window.location.reload() if wasAnonymous and not model.get('anonymous')
})

View file

@ -211,10 +211,12 @@ module.exports = class LevelBus extends Bus
@changedSessionProperties.state = true
@reallySaveSession() # Make sure it saves right away; don't debounce it.
onNewGoalStates: ({goalStates}) ->
onNewGoalStates: (e) ->
# TODO: this log doesn't capture when null-status goals are being set during world streaming. Where can they be coming from?
goalStates = e.goalStates
return console.error("Somehow trying to save null goal states!", newGoalStates) if _.find(newGoalStates, (gs) -> not gs.status)
return unless e.overallStatus is 'success'
newGoalStates = goalStates
state = @session.get('state')
oldGoalStates = state.goalStates or {}

View file

@ -1,5 +1,6 @@
module.exports = LevelOptions =
'dungeons-of-kithgard':
disableSpaces: true
hidesSubmitUntilRun: true
hidesPlayButton: true
hidesRunShortcut: true
@ -9,7 +10,9 @@ module.exports = LevelOptions =
hidesRealTimePlayback: true
requiredGear: {feet: 'simple-boots'}
restrictedGear: {feet: 'leather-boots'}
requiredCode: ['moveRight']
'gems-in-the-deep':
disableSpaces: true
hidesSubmitUntilRun: true
hidesPlayButton: true
hidesRunShortcut: true
@ -20,6 +23,7 @@ module.exports = LevelOptions =
requiredGear: {feet: 'simple-boots'}
restrictedGear: {feet: 'leather-boots'}
'shadow-guard':
disableSpaces: true
hidesSubmitUntilRun: true
hidesPlayButton: true
hidesRunShortcut: true
@ -30,6 +34,7 @@ module.exports = LevelOptions =
requiredGear: {feet: 'simple-boots'}
restrictedGear: {feet: 'leather-boots', 'right-hand': 'simple-sword'}
'kounter-kithwise':
disableSpaces: true
hidesPlayButton: true
hidesRunShortcut: true
hidesHUD: true
@ -48,6 +53,7 @@ module.exports = LevelOptions =
requiredGear: {feet: 'simple-boots'}
restrictedGear: {feet: 'leather-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i'}
'forgetful-gemsmith':
disableSpaces: true
hidesPlayButton: true
hidesRunShortcut: true
hidesHUD: true
@ -57,6 +63,7 @@ module.exports = LevelOptions =
requiredGear: {feet: 'simple-boots'}
restrictedGear: {feet: 'leather-boots', 'programming-book': 'programmaticon-i'}
'true-names':
disableSpaces: true
hidesPlayButton: true
hidesRunShortcut: true
hidesHUD: true
@ -65,7 +72,10 @@ module.exports = LevelOptions =
hidesRealTimePlayback: true
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', waist: 'leather-belt'}
restrictedGear: {feet: 'leather-boots'}
requiredCode: ['Brak']
'favorable-odds':
disableSpaces: true
hidesPlayButton: true
hidesRunShortcut: true
hidesHUD: true
hidesSay: true
@ -74,6 +84,8 @@ module.exports = LevelOptions =
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword'}
restrictedGear: {feet: 'leather-boots'}
'the-raised-sword':
disableSpaces: true
hidesPlayButton: true
hidesRunShortcut: true
hidesHUD: true
hidesSay: true
@ -89,6 +101,7 @@ module.exports = LevelOptions =
hidesRealTimePlayback: true
requiredGear: {feet: 'simple-boots', 'programming-book': 'programmaticon-i'}
restrictedGear: {feet: 'leather-boots'}
requiredCode: ['loop']
'haunted-kithmaze':
hidesRunShortcut: true
hidesHUD: true
@ -97,6 +110,7 @@ module.exports = LevelOptions =
hidesRealTimePlayback: true
requiredGear: {feet: 'simple-boots', 'programming-book': 'programmaticon-i'}
restrictedGear: {feet: 'leather-boots'}
requiredCode: ['loop']
'descending-further':
hidesHUD: true
hidesSay: true
@ -132,6 +146,8 @@ module.exports = LevelOptions =
hidesRealTimePlayback: true
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses', torso: 'leather-tunic'}
restrictedGear: {feet: 'leather-boots'}
requiredCode: ['findNearestEnemy']
suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
'lowly-kithmen':
hidesHUD: true
hidesSay: true
@ -139,6 +155,8 @@ module.exports = LevelOptions =
hidesRealTimePlayback: true
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses', torso: 'leather-tunic'}
restrictedGear: {feet: 'leather-boots'}
requiredCode: ['findNearestEnemy']
suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
'closing-the-distance':
hidesHUD: true
hidesSay: true
@ -146,6 +164,7 @@ module.exports = LevelOptions =
hidesRealTimePlayback: true
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic', eyes: 'crude-glasses'}
restrictedGear: {feet: 'leather-boots'}
suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
'tactical-strike':
hidesHUD: true
hidesSay: true
@ -153,12 +172,14 @@ module.exports = LevelOptions =
hidesRealTimePlayback: true
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic', eyes: 'crude-glasses'}
restrictedGear: {feet: 'leather-boots'}
suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
'the-final-kithmaze':
hidesHUD: true
hidesSay: true
hidesCodeToolbar: true
hidesRealTimePlayback: true
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
'the-gauntlet':
hidesHUD: true
hidesSay: true
@ -166,6 +187,7 @@ module.exports = LevelOptions =
hidesRealTimePlayback: true
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
restrictedGear: {feet: 'leather-boots'}
suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}]
'kithgard-gates':
hidesSay: true
hidesCodeToolbar: true

View file

@ -6,6 +6,7 @@ module.exports = class CoordinateDisplay extends createjs.Container
'surface:mouse-over': 'onMouseOver'
'surface:stage-mouse-down': 'onMouseDown'
'camera:zoom-updated': 'onZoomUpdated'
'level:flag-color-selected': 'onFlagColorSelected'
constructor: (options) ->
super()
@ -60,6 +61,9 @@ module.exports = class CoordinateDisplay extends createjs.Container
@hide()
@show()
onFlagColorSelected: (e) ->
@placingFlag = Boolean e.color
hide: ->
return unless @label.parent
@removeChild @label
@ -154,6 +158,6 @@ module.exports = class CoordinateDisplay extends createjs.Container
@y = sup.y
@addChild @background
@addChild @label
@addChild @pointMarker
@addChild @pointMarker unless @placingFlag
@updateCache()
Backbone.Mediator.publish 'surface:coordinates-shown', {}

View file

@ -491,6 +491,7 @@ module.exports = Lank = class Lank extends CocoClass
bar = createProgressBar(healthColor)
@options.floatingLayer.addCustomGraphic(key, bar, bar.bounds)
hadHealthBar = @healthBar
@healthBar = new createjs.Sprite(@options.floatingLayer.spriteSheet)
@healthBar.gotoAndStop(key)
offset = @getOffset 'aboveHead'
@ -499,6 +500,8 @@ module.exports = Lank = class Lank extends CocoClass
@options.floatingLayer.addChild @healthBar
@updateHealthBar()
@lastHealth = null
if not hadHealthBar
@listenTo @options.floatingLayer, 'new-spritesheet', @addHealthBar
getActionProp: (prop, subProp, def=null) ->
# Get a property or sub-property from an action, falling back to ThangType
@ -664,7 +667,9 @@ module.exports = Lank = class Lank extends CocoClass
updateLabels: ->
return unless @thang
blurb = if @thang.health <= 0 then null else @thang.sayMessage # Dead men tell no tales
@addLabel 'say', Label.STYLE_SAY if blurb
blurb = null if blurb in ['For Thoktar!', 'Bones!', 'Behead!', 'Destroy!', 'Die, humans!'] # Let's just hear, not see, these ones.
labelStyle = if /Hero Placeholder/.test(@thang.id) then Label.STYLE_DIALOGUE else Label.STYLE_SAY
@addLabel 'say', labelStyle if blurb
if @labels.say?.setText blurb
@notifySpeechUpdated blurb: blurb
label.update() for name, label of @labels

View file

@ -13,7 +13,7 @@ module.exports = class SegmentedSprite extends createjs.SpriteContainer
constructor: (@spriteSheet, @thangType, @spriteSheetPrefix, @resolutionFactor=SPRITE_RESOLUTION_FACTOR) ->
@spriteSheet.mcPool ?= {}
@initialize(@spriteSheet)
super(@spriteSheet)
@addEventListener 'tick', @handleTick
destroy: ->

View file

@ -6,7 +6,7 @@ module.exports = class SingularSprite extends createjs.Sprite
childMovieClips: null
constructor: (@spriteSheet, @thangType, @spriteSheetPrefix, @resolutionFactor=SPRITE_RESOLUTION_FACTOR) ->
@initialize(@spriteSheet)
super(@spriteSheet)
destroy: ->
@removeAllEventListeners()

View file

@ -1,5 +1,5 @@
CocoClass = require 'lib/CocoClass'
path = require './path'
TrailMaster = require './TrailMaster'
Dropper = require './Dropper'
AudioPlayer = require 'lib/AudioPlayer'
{me} = require 'lib/auth'
@ -181,7 +181,6 @@ module.exports = Surface = class Surface extends CocoClass
framesDropped = 0
while true
Dropper.tick()
@trailmaster.tick() if @trailmaster
# Skip some frame updates unless we're playing and not at end (or we haven't drawn much yet)
frameAdvanced = (@playing and @currentFrame < lastFrame) or @totalFramesDrawn < 2
if frameAdvanced and @playing
@ -210,7 +209,6 @@ module.exports = Surface = class Surface extends CocoClass
@updateState @currentFrame isnt oldFrame
@drawCurrentFrame e
@onFrameChanged()
@updatePaths() if (@totalFramesDrawn % 4) is 0 or createjs.Ticker.getMeasuredFPS() > createjs.Ticker.getFPS() - 5
Backbone.Mediator.publish('surface:ticked', {dt: 1 / @options.frameRate})
mib = @webGLStage.mouseInBounds
if @mouseInBounds isnt mib
@ -260,7 +258,9 @@ module.exports = Surface = class Surface extends CocoClass
createjs.Tween.removeTweens(@)
@currentFrame = @scrubbingTo
@scrubbingTo = Math.min(Math.round(progress * (@world.frames.length - 1)), @world.frames.length - 1)
@scrubbingTo = Math.round(progress * (@world.frames.length - 1))
@scrubbingTo = Math.max @scrubbingTo, 1
@scrubbingTo = Math.min @scrubbingTo, @world.frames.length - 1
@scrubbingPlaybackSpeed = Math.sqrt(Math.abs(@scrubbingTo - @currentFrame) * @world.dt / (scrubDuration or 0.5))
if scrubDuration
t = createjs.Tween
@ -314,10 +314,12 @@ module.exports = Surface = class Surface extends CocoClass
if paused
@surfacePauseTimeout = _.delay performToggle, 2000
@lankBoss.stop()
@trailmaster?.stop()
@playbackOverScreen.show()
else
performToggle()
@lankBoss.play()
@trailmaster?.play()
@playbackOverScreen.hide()
@ -402,7 +404,7 @@ module.exports = Surface = class Surface extends CocoClass
@playing = (e ? {}).playing ? true
@setPlayingCalled = true
if @playing and @currentFrame >= (@world.totalFrames - 5)
@currentFrame = 0
@currentFrame = 1 # Go back to the beginning (but not frame 0, that frame is weird)
if @fastForwardingToFrame and not @playing
@fastForwardingToFrame = null
@ -456,7 +458,11 @@ module.exports = Surface = class Surface extends CocoClass
@fastForwardingSpeed = lag / intendedLag
else
@fastForwardingToFrame = @fastForwardingSpeed = null
#console.log "on new world, lag", lag, "intended lag", intendedLag, "fastForwardingToFrame", @fastForwardingToFrame, "speed", @fastForwardingSpeed, "cause we are at", @world.age, "of", @world.frames.length * @world.dt
# console.log "on new world, lag", lag, "intended lag", intendedLag, "fastForwardingToFrame", @fastForwardingToFrame, "speed", @fastForwardingSpeed, "cause we are at", @world.age, "of", @world.frames.length * @world.dt
if event.finished
@updatePaths()
else
@hidePaths()
onIdleChanged: (e) ->
@setPaused e.idle unless @ended
@ -540,10 +546,12 @@ module.exports = Surface = class Surface extends CocoClass
#- Camera focus on hero
focusOnHero: ->
hadHero = @heroLank
@heroLank = @lankBoss.lankFor 'Hero Placeholder'
if me.team is 'ogres'
# TODO: do this for real
@heroLank = @lankBoss.lankFor 'Hero Placeholder 1'
@updatePaths() if not hadHero
#- Real-time playback
@ -576,22 +584,16 @@ module.exports = Surface = class Surface extends CocoClass
#- Paths - TODO: move to LankBoss? but only update on frame drawing instead of on every frame update?
updatePaths: ->
return # TODO: Get paths working again with WebGL
return unless @options.paths
return if @casting
return unless @options.paths and @heroLank
return unless me.isAdmin() # TODO: Fix world thang points, targets, then remove this
@hidePaths()
selectedThang = @lankBoss.selectedLank?.thang
return if @world.showPaths is 'never'
return if @world.showPaths is 'paused' and @playing
return if @world.showPaths is 'selected' and not selectedThang
@trailmaster ?= new path.Trailmaster @camera
selectedOnly = @playing and @world.showPaths is 'selected'
@paths = @trailmaster.generatePaths @world, @getCurrentFrame(), selectedThang, @lankBoss.lanks, selectedOnly
layerAdapter = @lankBoss.layerAdapters['Path']
@trailmaster ?= new TrailMaster @camera, layerAdapter
@paths = @trailmaster.generatePaths @world, @heroLank.thang
@paths.name = 'paths'
@lankBoss.layerAdapters['Path'].addChild @paths
layerAdapter.addChild @paths
hidePaths: ->
return if not @paths
@ -699,6 +701,7 @@ module.exports = Surface = class Surface extends CocoClass
@normalStage.clear()
@webGLStage.clear()
@musicPlayer?.destroy()
@trailmaster?.destroy()
@normalStage.removeAllChildren()
@webGLStage.removeAllChildren()
@webGLStage.removeEventListener 'stagemousemove', @onMouseMove

View file

@ -0,0 +1,122 @@
PAST_PATH_ALPHA = 0.75
PAST_PATH_WIDTH = 5
FUTURE_PATH_ALPHA = 0.75
FUTURE_PATH_WIDTH = 4
TARGET_ALPHA = 1
TARGET_WIDTH = 10
FUTURE_PATH_INTERVAL_DIVISOR = 4
PAST_PATH_INTERVAL_DIVISOR = 2
Camera = require './Camera'
CocoClass = require 'lib/CocoClass'
module.exports = class TrailMaster extends CocoClass
world: null
constructor: (@camera, @layerAdapter) ->
super()
@tweenedSprites = []
@tweens = []
@listenTo @layerAdapter, 'new-spritesheet', -> @generatePaths(@world, @thang)
generatePaths: (@world, @thang) ->
return if @generatingPaths
@generatingPaths = true
@cleanUp()
@createGraphics()
pathDisplayObject = new createjs.SpriteContainer(@layerAdapter.spriteSheet)
pathDisplayObject.mouseEnabled = pathDisplayObject.mouseChildren = false
pathDisplayObject.addChild @createFuturePath()
# pathDisplayObject.addChild @createPastPath() # Just made the animated path the full path... do we want to have past and future look different again?
pathDisplayObject.addChild @createTargets()
@generatingPaths = false
return pathDisplayObject
cleanUp: ->
createjs.Tween.removeTweens(sprite) for sprite in @tweenedSprites
@tweenedSprites = []
@tweens = []
createGraphics: ->
@targetDotKey = @cachePathDot(TARGET_WIDTH, @colorForThang(@thang.team, TARGET_ALPHA), [0, 0, 0, 1])
@pastDotKey = @cachePathDot(PAST_PATH_WIDTH, @colorForThang(@thang.team, PAST_PATH_ALPHA), [0, 0, 0, 1])
@futureDotKey = @cachePathDot(FUTURE_PATH_WIDTH, [255, 255, 255, FUTURE_PATH_ALPHA], @colorForThang(@thang.team, 1))
cachePathDot: (width, fillColor, strokeColor) ->
key = "path-dot-#{width}-#{fillColor}-#{strokeColor}"
fillColor = createjs.Graphics.getRGB(fillColor...)
strokeColor = createjs.Graphics.getRGB(strokeColor...)
unless key in @layerAdapter.spriteSheet.getAnimations()
circle = new createjs.Shape()
radius = width/2
circle.graphics.setStrokeStyle(width/5).beginFill(fillColor).beginStroke(strokeColor).drawCircle(0, 0, radius)
@layerAdapter.addCustomGraphic(key, circle, [-radius*1.5, -radius*1.5, radius*3, radius*3])
return key
colorForThang: (team, alpha=1.0) ->
rgb = [0, 255, 0]
rgb = [255, 0, 0] if team is 'humans'
rgb = [0, 0, 255] if team is 'ogres'
rgb.push(alpha)
return rgb
createPastPath: ->
return unless points = @world.pointsForThang @thang.id, @camera
interval = Math.max(1, parseInt(@world.frameRate / PAST_PATH_INTERVAL_DIVISOR))
params = { interval: interval, frameKey: @pastDotKey }
return @createPath(points, params)
createFuturePath: ->
return unless points = @world.pointsForThang @thang.id, @camera
interval = Math.max(1, parseInt(@world.frameRate / FUTURE_PATH_INTERVAL_DIVISOR))
params = { interval: interval, animate: true, frameKey: @futureDotKey }
return @createPath(points, params)
createTargets: ->
return unless @thang.allTargets
container = new createjs.SpriteContainer(@layerAdapter.spriteSheet)
for x, i in @thang.allTargets by 2
y = @thang.allTargets[i + 1]
sup = @camera.worldToSurface x: x, y: y
sprite = new createjs.Sprite(@layerAdapter.spriteSheet)
sprite.scaleX = sprite.scaleY = 1 / @layerAdapter.resolutionFactor
sprite.scaleY *= @camera.y2x
sprite.gotoAndStop(@targetDotKey)
sprite.x = sup.x
sprite.y = sup.y
container.addChild(sprite)
return container
createPath: (points, options={}) ->
options = options or {}
interval = options.interval or 8
key = options.frameKey or @pastDotKey
container = new createjs.SpriteContainer(@layerAdapter.spriteSheet)
for x, i in points by interval * 2
y = points[i + 1]
sprite = new createjs.Sprite(@layerAdapter.spriteSheet)
sprite.scaleX = sprite.scaleY = 1 / @layerAdapter.resolutionFactor
sprite.scaleY *= @camera.y2x
sprite.gotoAndStop(key)
sprite.x = x
sprite.y = y
container.addChild(sprite)
if lastSprite and options.animate
tween = createjs.Tween.get(lastSprite, {loop: true}).to({x:x, y:y}, 1000)
@tweenedSprites.push lastSprite
@tweens.push tween
lastSprite = sprite
@logged = true
container
play: ->
tween.setPaused(false) for tween in @tweens
stop: ->
tween.setPaused(true) for tween in @tweens
destroy: ->
@cleanUp()
super()

View file

@ -1,289 +0,0 @@
# paths before the current state taper out,
# and have a different color than the future
PAST_PATH_TAIL_BRIGHTNESS = 150
PAST_PATH_TAIL_ALPHA = 0.3
PAST_PATH_HEAD_BRIGHTNESS = 200
PAST_PATH_HEAD_ALPHA = 0.75
PAST_PATH_HEAD_LENGTH = 50
PAST_PATH_TAIL_WIDTH = 2
PAST_PATH_HEAD_WIDTH = 2
PAST_PATH_MAX_LENGTH = 200
# paths in the future are single color dotted lines
FUT_PATH_BRIGHTNESS = 153
FUT_PATH_ALPHA = 0.8
FUT_PATH_HEAD_LENGTH = 0
FUT_PATH_WIDTH = 1
FUT_PATH_MAX_LENGTH = 2000
# selected paths are single color, and larger, more prominent
# most other properties are the same as non-selected
SELECTED_PATH_TAIL_BRIGHTNESS = 146
SELECTED_PATH_TAIL_ALPHA = 0.5
SELECTED_PATH_HEAD_BRIGHTNESS = 200
SELECTED_PATH_HEAD_ALPHA = 1.0
SELECTED_PAST_PATH_MAX_LENGTH = 2000
FUT_SELECTED_PATH_WIDTH = 3
# for sprites along the path
CLONE_INTERVAL = 250 # distance between them, ignored for new actions
CLONE_SCALE = 1.0
CLONE_ALPHA = 0.4
# path defaults
PATH_DOT_LENGTH = 3
PATH_SEGMENT_LENGTH = 15 # should be > PATH_DOT_LENGTH
Camera = require './Camera'
module.exports.Trailmaster = class Trailmaster
paths: null # dictionary of thang ids to containers for their paths
selectedPath: null # container of path selected
pathDisplayObject: null
world: null
clock: 0
constructor: (@camera) ->
tick: ->
@clock += 1
generatePaths: (@world, @currentFrame, @selectedThang, @sprites, @selectedOnly) ->
@paths = {}
@pathDisplayObject = new createjs.Container()
@pathDisplayObject.mouseEnabled = @pathDisplayObject.mouseChildren = false
for thang in world.thangs
continue unless thang.isSelectable
continue unless thang.isMovable
continue if @selectedOnly and thang isnt @selectedThang
path = @createPathForThang thang
continue if not path
@pathDisplayObject.addChild path
@paths[thang.id] = path
@pathDisplayObject
createPathForThang: (thang) ->
container = new createjs.Container()
path = @createPastPathForThang(thang)
container.addChild(path) if path
path = @createFuturePathForThang(thang)
container.addChild(path) if path
targets = @createTargetsForThang(thang)
container.addChild(targets) if targets
if thang is @selectedThang
sprites = @spritesForThang(thang)
for sprite in sprites
container.addChild(sprite)
container
createPastPathForThang: (thang) ->
maxLength = if thang is @selectedThang then SELECTED_PAST_PATH_MAX_LENGTH else PAST_PATH_MAX_LENGTH
start = Math.max(@currentFrame - maxLength, 0)
start = 0 if thang isnt @selectedThang
resolution = if thang is @selectedThang then 4 else 12
return unless points = @world.pointsForThang thang.id, start, @currentFrame, @camera, resolution
params =
tailWidth: PAST_PATH_TAIL_WIDTH
headWidth: PAST_PATH_HEAD_WIDTH
headLength: PAST_PATH_HEAD_LENGTH
if thang is @selectedThang
params['tailColor'] = colorForThang(thang.team, SELECTED_PATH_TAIL_BRIGHTNESS, SELECTED_PATH_TAIL_ALPHA)
params['headColor'] = colorForThang(thang.team, SELECTED_PATH_HEAD_BRIGHTNESS, SELECTED_PATH_HEAD_ALPHA)
else
params['tailColor'] = colorForThang(thang.team, PAST_PATH_TAIL_BRIGHTNESS, PAST_PATH_TAIL_ALPHA)
params['headColor'] = colorForThang(thang.team, PAST_PATH_HEAD_BRIGHTNESS, PAST_PATH_HEAD_ALPHA)
return createPath(points, params)
createFuturePathForThang: (thang) ->
resolution = 8
return unless points = @world.pointsForThang thang.id, @currentFrame, @currentFrame + FUT_PATH_MAX_LENGTH, @camera, resolution
if thang is @selectedThang
color = colorForThang(thang.team, SELECTED_PATH_HEAD_BRIGHTNESS, SELECTED_PATH_HEAD_ALPHA)
else
color = colorForThang(thang.team, FUT_PATH_BRIGHTNESS, FUT_PATH_ALPHA)
return createPath(points,
tailColor: color
tailWidth: if thang is @selectedThang then FUT_SELECTED_PATH_WIDTH else FUT_PATH_WIDTH
headLength: FUT_PATH_HEAD_LENGTH
dotted: true
dotOffset: @clock
)
createTargetsForThang: (thang) ->
return unless thang.allTargets
g = new createjs.Graphics()
g.setStrokeStyle(0.5)
g.beginStroke(createjs.Graphics.getRGB(0, 0, 0))
color = colorForThang(thang.team)
i = 0
while i < thang.allTargets.length
g.beginStroke(createjs.Graphics.getRGB(0, 0, 0))
g.beginFill(createjs.Graphics.getRGB(color...))
sup = @camera.worldToSurface x: thang.allTargets[i], y: thang.allTargets[i + 1]
g.drawEllipse(sup.x - 5, sup.y - 3, 10, 6)
g.endStroke()
i += 2
s = new createjs.Shape(g)
s.x = 0
s.y = 0
s
spritesForThang: (thang) ->
i = 0
sprites = []
sprite = @sprites[thang.id]
return sprites unless sprite?.actions
lastPos = @camera.surfaceToWorld x: sprite.sprite.x, y: sprite.sprite.y
minDistance = Math.pow(CLONE_INTERVAL * Camera.MPP, 2)
actions = @world.actionsForThang(thang.id)
lastAction = null
for action in actions
continue if action.name in ['idle', 'move']
frame = @world.frames[action.frame]
frame.restoreStateForThang(thang)
if lastPos
diff = Math.pow(lastPos.x - thang.pos.x, 2)
diff += Math.pow(lastPos.y - thang.pos.y, 2)
continue if diff < minDistance and action.name is lastAction
clone = sprite.sprite.clone()
clonePos = @camera.worldToSurface thang.pos
clone.x = clonePos.x
clone.y = clonePos.y
clone.alpha = CLONE_ALPHA
clone.scaleX *= CLONE_SCALE
clone.scaleY *= CLONE_SCALE
if sprite.expandActions # old Sprite
sprite.updateRotation(clone, sprite.data)
animActions = sprite.expandActions(if thang.acts then thang.getActionName() else 'idle')
sprite.applyActionsToSprites(animActions, [clone], true)
animation = clone.spriteSheet.getAnimation(clone.currentAnimation)
clone.currentAnimationFrame = Math.min(@clock % (animation.frames.length * 3), animation.frames.length - 1)
else
continue unless animation = sprite.actions[action.name]
sprite.updateRotation clone
animation = sprite.getActionDirection(animation) ? animation # no idea if this ever works
clone.gotoAndStop animation.name
# TODO: use action-specific framerate here?
# clone.currentAnimationFrame = Math.min(@clock % (animation.frames.length * 3), animation.frames.length - 1)
sprites.push(clone)
lastPos = x: thang.pos.x, y: thang.pos.y
lastAction = action.name
@world.frames[@currentFrame].restoreStateForThang(thang)
sprites
createPath = (points, options={}, g=null) ->
options = options or {}
tailColor = options.tailColor ? options.headColor
headColor = options.headColor ? options.tailColor
oneColor = true
oneColor = oneColor and headColor[i] is tailColor[i] for i in [0..4]
maxLength = options.maxLength or 0
tailWidth = options.tailWidth
headWidth = options.headWidth
oneWidth = headWidth is tailWidth
headLength = options.headLength
dotted = options.dotted
dotOffset = if options.dotOffset? then options.dotOffset else 0
points = points.slice(-maxLength * 2) if maxLength isnt 0
points = points.slice(((points.length / 2 + dotOffset) % PATH_SEGMENT_LENGTH) * 2) if dotOffset
g = new createjs.Graphics() unless g
return new createjs.Shape(g) if not points
g.setStrokeStyle(tailWidth)
g.beginStroke(createjs.Graphics.getRGB(tailColor...))
g.moveTo(points[0], points[1])
headStart = points.length - headLength
[lastX, lastY] = [points[0], points[1]]
for x, i in points by 2
continue if i is 0
y = points[i + 1]
if i >= headStart and not (oneColor and oneWidth)
diff = (i - headStart) / headLength
style = transition(tailWidth, headWidth, diff)
color = colorTransition(tailColor, headColor, diff)
g.setStrokeStyle(style)
g.beginStroke(createjs.Graphics.getRGB(color...))
g.moveTo(lastX, lastY) if lastX?
else if dotted
if false and i < 2
# Test: footprints
g.beginFill(createjs.Graphics.getRGB(tailColor...))
xofs = x - lastX
yofs = y - lastY
theta = Math.atan2(yofs, xofs)
[fdist, fwidth] = [4, 2]
fside = if (i + dotOffset) % 4 is 0 then -1 else 1
fx = [lastX + fside * fdist * (Math.cos(theta) * xofs - Math.sin(theta) * yofs)]
fy = [lastY + fside * fdist * (Math.sin(theta) * xofs - Math.cos(theta) * yofs)]
g.drawCircle(fx, fy, 2)
offset = ((i / 2) % PATH_SEGMENT_LENGTH)
if offset >= PATH_DOT_LENGTH
if offset is PATH_DOT_LENGTH
g.endStroke()
lastX = x
lastY = y
continue
else
if offset is 0
g.beginStroke(createjs.Graphics.getRGB(tailColor...))
g.moveTo(lastX, lastY) if lastX?
g.lineTo(x, y)
lastX = x
lastY = y
g.endStroke()
s = new createjs.Shape(g)
return s
colorTransition = (color1, color2, pct) ->
return color1 if pct <= 0
return color2 if pct >= 1
i = 0
color = []
while i < 4
val = transition(color1[i], color2[i], pct)
val = Math.floor(val) if i isnt 3
color.push(val)
i += 1
color
transition = (num1, num2, pct) ->
return num1 if pct <= 0
return num2 if pct >= 1
num1 + (num2 - num1) * pct
colorForThang = (team, brightness=100, alpha=1.0) =>
# multipliers should sum to 3.0
multipliers = [2.0, 0.5, 0.5] if team is 'humans'
multipliers = [0.5, 0.5, 2.0] if team is 'ogres'
multipliers = [2.0, 0.5, 0.5] if not multipliers
color = _.map(multipliers, (m) -> return parseInt(m * brightness))
color.push(alpha)
return color
module.exports.createPath = createPath

View file

@ -472,7 +472,11 @@ module.exports = class World
perf.t1 = now()
if w.thangs.length
for thangConfig in o.thangs when not w.thangMap[thangConfig.id]
for thangConfig in o.thangs
if thang = w.thangMap[thangConfig.id]
for prop, val of thangConfig.finalState
thang[prop] = val
else
w.thangs.push thang = Thang.deserialize(thangConfig, w, classMap, level.levelComponents)
w.setThang thang
else
@ -551,7 +555,7 @@ module.exports = class World
freeMemoryAfterEachSerialization: ->
@frames[i] = null for frame, i in @frames when i < @frames.length - 1
pointsForThang: (thangID, frameStart=0, frameEnd=null, camera=null, resolution=4) ->
pointsForThang: (thangID, camera=null) ->
# Optimized
@pointsForThangCache ?= {}
cacheKey = thangID
@ -570,16 +574,7 @@ module.exports = class World
allPoints.reverse()
@pointsForThangCache[cacheKey] = allPoints
points = []
[lastX, lastY] = [null, null]
for frameIndex in [Math.floor(frameStart / resolution) ... Math.ceil(frameEnd / resolution)]
x = allPoints[frameIndex * 2 * resolution]
y = allPoints[frameIndex * 2 * resolution + 1]
continue if x is lastX and y is lastY
lastX = x
lastY = y
points.push x, y
points
return allPoints
actionsForThang: (thangID, keepIdle=false) ->
# Optimized

View file

@ -49,13 +49,13 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
play_as: "扮演" # Ladder page
spectate: "旁观他人的游戏" # Ladder page
players: "玩家" # Hover over a level on /play
hours_played: "已经玩过的时间" # Hover over a level on /play
hours_played: "游戏时长" # Hover over a level on /play
items: "道具" # Tooltip on item shop button from /play
unlock: "解锁" # For purchasing items and heroes
confirm: "确认"
owned: "已拥有" # For items you own
locked: "需解锁"
available: "可用"
available: "可用" # Available
skills_granted: "获得技能" # Property documentation details
heroes: "英雄" # Tooltip on hero shop button from /play
achievements: "成就" # Tooltip on achievement list button from /play
@ -64,14 +64,14 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
next: "下一步" # Go from choose hero to choose inventory before playing a level
change_hero: "重新选择英雄" # Go back from choose inventory to choose hero
choose_inventory: "装备道具"
buy_gems: "购买宝石"
buy_gems: "购买宝石" # Buy Gems
older_campaigns: "旧的战役"
anonymous: "匿名玩家"
level_difficulty: "难度:"
campaign_beginner: "新手作战"
awaiting_levels_adventurer_prefix: "我们每周开放五个关卡"
# awaiting_levels_adventurer: "Sign up as an Adventurer"
awaiting_levels_adventurer_suffix: "做第一个玩新关卡的人"
awaiting_levels_adventurer: "注册成为冒险家" #"Sign up as an Adventurer"
awaiting_levels_adventurer_suffix: "来优先尝试新关卡" #to be the first to play new levels."
choose_your_level: "选择关卡" # The rest of this section is the old play view at /play-old and isn't very important.
adventurer_prefix: "你可以选择以下任意关卡,或者讨论以上的关卡。到"
adventurer_forum: "冒险者论坛"
@ -86,8 +86,8 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
campaign_player_created_description: "……在这里你可以与你的小伙伴的创造力战斗 <a href=\"/contribute#artisan\">技术指导</a>."
campaign_classic_algorithms: "经典算法"
campaign_classic_algorithms_description: "... 你可以在此学习到计算机科学中最常用的算法"
# campaign_forest: "Forest Campaign"
# campaign_dungeon: "Dungeon Campaign"
campaign_forest: "森林战役" #Forest Campaign"
campaign_dungeon: "地牢战役" #Dungeon Campaign"
login:
sign_up: "注册"
@ -95,10 +95,10 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
logging_in: "正在登录"
log_out: "登出"
recover: "找回账户"
authenticate_gplus: "通过G+授权"
load_profile: "载入G+资料"
load_email: "载入G+ Email"
finishing: "完成"
authenticate_gplus: "使用 G+ 授权"#Authenticate G+"
load_profile: "载入 G+ 档案" # Load G+ Profile"
load_email: "载入 G+ 电子邮件" #Load G+ Email"
finishing: "完成" #Finishing"
signup:
create_account_title: "创建一个账户来保存进度"
@ -118,8 +118,8 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
recovery_sent: "找回账户邮件已发送."
items:
primary: "主要的"
secondary: "次要的"
primary: "右手"#"Primary"
secondary: "左手"#Secondary"
armor: "盔甲"
accessories: "配饰"
misc: "辅助道具"
@ -172,7 +172,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
medium: "中等"
hard: "困难"
player: "玩家"
# player_level: "Level" # Like player level 5, not like level: Dungeons of Kithgard
player_level: "等级" # Like player level 5, not like level: Dungeons of Kithgard
units:
second: ""
@ -193,7 +193,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
play_level:
done: "完成"
home: "主页" # Not used any more, will be removed soon.
# level: "Level" # Like "Level: Dungeons of Kithgard"
level: "等级" # Like "Level: Dungeons of Kithgard"
skip: "跳过"
game_menu: "游戏菜单"
guide: "指南"

View file

@ -51,6 +51,8 @@ class CocoModel extends Backbone.Model
@jqxhr = null
@loadFromBackup()
getCreationDate: -> new Date(parseInt(@id.slice(0,8), 16)*1000)
getNormalizedURL: -> "#{@urlRoot}/#{@id}"
attributesWithDefaults: undefined

View file

@ -250,11 +250,13 @@ module.exports = class ThangType extends CocoModel
stage.update()
stage.startTalking = ->
sprite.gotoAndPlay 'portrait'
return # TODO: causes infinite recursion in new EaselJS
return if @tick
@tick = (e) => @update(e)
createjs.Ticker.addEventListener 'tick', @tick
stage.stopTalking = ->
sprite.gotoAndStop 'portrait'
return # TODO: just breaks in new EaselJS
@update()
createjs.Ticker.removeEventListener 'tick', @tick
@tick = null

View file

@ -24,6 +24,7 @@ worldUpdatedEventSchema = c.object {required: ['world', 'firstWorld', 'goalState
goalStates: goalStatesSchema
team: {type: 'string'}
firstChangedFrame: {type: 'integer', minimum: 0}
finished: {type: 'boolean'}
module.exports =
'god:user-code-problem': c.object {required: ['problem']},

View file

@ -35,7 +35,7 @@ module.exports =
'tome:toggle-spell-list': c.object {title: 'Toggle Spell List', description: 'Published when you toggle the dropdown for a thang\'s spells'}
'tome:reload-code': c.object {title: 'Reload Code', description: 'Published when you reset a spell to its original source', required: ['spell']},
'tome:reload-code': c.object {title: 'Reload Code', description: 'Published when you reset a spell to its original source', required: []},
spell: {type: 'object'}
'tome:palette-cleared': c.object {title: 'Palette Cleared', description: 'Published when the spell palette is about to be cleared and recreated.'},
@ -122,6 +122,10 @@ module.exports =
'tome:required-code-fragment-deleted': c.object {title: 'Required Code Fragment Deleted', description: 'Published when a required code fragment is deleted from the sample code.', required: ['codeFragment']},
codeFragment: {type: 'string'}
'tome:suspect-code-fragment-added': c.object {title: 'Suspect Code Fragment Added', description: 'Published when a suspect code fragment is added to the sample code.', required: ['codeFragment']},
codeFragment: {type: 'string'}
codeLanguage: {type: 'string'}
'tome:winnability-updated': c.object {title: 'Winnability Updated', description: 'When we think we can now win (or can no longer win), we may want to emphasize the submit button versus the run button (or vice versa), so this fires when we get new goal states (even preloaded goal states) suggesting success or failure change.', required: ['winnable']},
winnable: {type: 'boolean'}

View file

@ -9,6 +9,7 @@ module.exports =
thang: {type: 'object'}
killer: {type: 'object'}
killerHealth: {type: ['number', 'undefined']}
maxHealth: {type: 'number'}
'world:thang-touched-goal': c.object {required: ['actor', 'touched']},
replacedNoteChain: {type: 'array'}

View file

@ -21,6 +21,26 @@
left: -3px
//- Close modal button
#close-modal
position: absolute
left: 769px
top: -5px
width: 60px
height: 60px
color: white
text-align: center
font-size: 30px
padding-top: 17px
cursor: pointer
z-index: 2
@include rotate(-3deg)
&:hover
color: yellow
//- Nav bar
#game-menu-nav

View file

@ -106,11 +106,11 @@ $itemSlotGridHeight: 70px
// opacity: 0.5
&.selected
.placeholder, img.item
.placeholder, .item
border-color: rgb(81,153,236)
background-color: rgb(81,153,236)
@include box-shadow(0 0 10px rgb(81,153,236))
img.item
.item
background: rgb(81,153,236)
&.should-equip
@ -269,7 +269,7 @@ $itemSlotGridHeight: 70px
.placeholder
background-position: (-2 * $itemSlotInnerWidth) 0px
img.item
.item
position: absolute
left: -2px
top: -2px
@ -299,7 +299,7 @@ $itemSlotGridHeight: 70px
//- Available equipment
&.Warrior #unequipped img.item:not(.Warrior), &.Ranger #unequipped img.item:not(.Ranger), &.Wizard #unequipped img.item:not(.Wizard)
&.Warrior #unequipped .item:not(.Warrior), &.Ranger #unequipped .item:not(.Ranger), &.Wizard #unequipped .item:not(.Wizard)
// Our code hides and shows (modifies display), but we can be invisible this other way.
visibility: hidden
position: absolute
@ -313,7 +313,14 @@ $itemSlotGridHeight: 70px
padding: 9px 0 9px 9px
#double-click-hint
margin: 20px 50px 70px 0
margin: 20px 40px 60px 0
border: 3px solid #8585f4
padding: 5px
font-weight: bold
.glyphicon
margin-right: 5px
position: relative
top: 2px
h4
clear: both
@ -323,14 +330,28 @@ $itemSlotGridHeight: 70px
text-transform: uppercase
font-weight: bold
img.item
.item
float: left
border: 1px solid black
margin: 3px
padding: 1px
position: relative
width: 60px
height: 60px
cursor: pointer
img
width: 56px
height: 56px
display: block
button
width: 100%
height: 17px
border: 1px solid rgb(46,46,46)
background: white
font-size: 11px
border-radius: 1px
padding: 0
&.active
background-color: rgb(81,153,236)
@ -417,6 +438,9 @@ $itemSlotGridHeight: 70px
#selected-item-unlock-button
left: 646px
.unequippable
display: none
//- Equip/unequip/extra
@ -437,3 +461,7 @@ $itemSlotGridHeight: 70px
border: 3px solid rgb(46,46,46)
background: white
font-size: 16px
#equip-item-viewed
background: rgb(84,128,44)
color: white

View file

@ -43,6 +43,13 @@ $level-resize-transition-time: 0.5s
visibility: hidden
#gold-view
right: 1%
@include box-shadow(-1px 1px 10px cyan)
.team-gold
font-size: 2vw
line-height: 2vw
img
width: 1.8vw
heighT: 1.8vw
#control-bar-view .title
left: 20%
width: 60%

View file

@ -12,11 +12,13 @@
padding: 19px 0px 2px 25px
z-index: 3
font-size: 14px
min-width: 230px
&.brighter
font-size: 18px
font-size: 1.4vw
border-width: 0.91vw 1.22vw 3.10vw 0.91vw
min-width: 23vw
.goals-status
margin: 5px 0 0 0

View file

@ -9,7 +9,7 @@
z-index: 6
@include transition(box-shadow .2s linear)
@include user-select(none)
padding: 4px
padding: 0.4vw
background: transparent url(/images/level/gold_background.png) no-repeat
background-size: 100% 100%
border-radius: 4px
@ -18,9 +18,9 @@
box-shadow: 2px 2px 2px black
.team-gold
font-size: 18px
font-size: 1.4vw
line-height: 1.4vw
margin: 0
line-height: 20px
color: hsla(205,0%,51%,1)
display: inline-block
padding: 0px 4px
@ -35,11 +35,12 @@
color: hsla(116,80%,51%,1)
img
width: 16px
height: 16px
width: 1.2vw
height: 1.2vw
border-radius: 2px
padding: 1px
margin-top: -1px
padding: 0.1vw
margin-top: -0.2vw
margin-right: 0.1vw
.gold-amount
display: inline-block

View file

@ -55,7 +55,7 @@
line-height: 20px
strong
color: #09B057
color: #FFCCAA
.hud-hint
font-weight: normal

View file

@ -99,7 +99,7 @@
@include gradient-striped()
outline: 2px outset #0099ff
@include box-shadow(1px 1px 4px black)
&.playback-ended .executing
&.playback-ended .executing, &.user-code-problem .executing
background-color: rgba(50, 255, 80, 0.65)
outline: 0
@include box-shadow(0 0 0px black)
@ -125,11 +125,12 @@
// TODO: Pulses too quickly during playback
@include animation(pulseRedBackground 1s infinite)
&:not(.playback-ended)
&:not(.playback-ended):not(.user-code-problem)
.executing:not(.ace_gutter-cell)
background-size: 40px 40px
@include animation(progress-bar-stripes 0.5s linear infinite)
&:not(.user-code-problem)
.ace_gutter-cell.executing:not(.ace_error):not(.ace_warning):not(.ace_info):after
// Experimenting with a larger executing-line-pointer
@ -148,12 +149,6 @@
-webkit-font-smoothing: antialiased
-moz-osx-font-smoothing: grayscale
//display: block
//margin-left: 1px
//background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDowMjgwMTE3NDA3MjA2ODExOEE2REU4Q0M1MTM1MkIxRiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpBQjVEQUNDMzQ4RUIxMUUxOEVGRUUyNzFENDM3RDVFMCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpBQjVEQUNDMjQ4RUIxMUUxOEVGRUUyNzFENDM3RDVFMCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1LjEgTWFjaW50b3NoIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QTU1MjE3RDIzMTIwNjgxMThEQkI4NTlBMjQ1QTEwOTUiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MDI4MDExNzQwNzIwNjgxMThBNkRFOENDNTEzNTJCMUYiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7SazaGAAAAiElEQVR42mL8//8/AzUBEwOVweA3kAWboI2jCyhgDwBx4ZH9ey5Qy4UOQHweaHg/EAtQ08sFUIMDqBmGCkC8HmgoCCtQM1ICoK5toGYsg8KzHmjo+UGbDj8AcSMwORkSnQ7xgA3QtPmApISNBTyAGrSBGl6eAMSGxBhGyIVkZT3G0fKQYgAQYACL+C2ZM6PC7AAAAABJRU5ErkJggg==)
//background-position: 0px center
.ace_gutter-cell.executed:not(.ace_error):not(.ace_warning):not(.ace_info)
margin-left: 1px
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAANhJREFUeNpi/P//PwM1ARMDlcHgMrA428MAiANQBEFhSA4uynIXAOJ+dHFKXDgfiDdSxctAbzYAqQ+9U3ccQJdjIcMwByCVD8SGFEcK0DAFILUeiCcCXfeAIgOBhglADfsAxBNwqSPFy/1AbADEiUDXfSApHQJdcx+I9yPxE4AUCB8AGrYAn62M6HkZ6rX3UG4jEG8A4vNQviO2mMXrQqh3GqHcemi4gcACQobhixRQoMNiUQEaEY1k52WoKwuRhHAmE6KTDdCADdDwu4AvmRCMlOFfwAIEGAD4On+N4aXlhgAAAABJRU5ErkJggg==)

View file

@ -71,7 +71,7 @@
.property-entry-item-group
display: inline-block
min-height: 38px
max-width: 212px
width: 212px
@include flexbox()
@include flex-wrap()
@include flex-center()
@ -93,3 +93,8 @@
width: 174px
width: -webkit-calc(100% - 38px)
width: calc(100% - 38px)
@media only screen and (max-width: 1100px)
#spell-palette-view
// Make sure we have enough room for at least two columns
padding-left: 12px

View file

@ -24,6 +24,10 @@
&.pinned
background-color: darken(#FFFFFF, 25%)
&.single-entry
height: 38px
line-height: 38px
// Originally pulled these colors from the most relevant textmate-theme classes, but then fudged them a lot.
//&.function
// color: black
@ -80,6 +84,7 @@
h1:not(.not-code), h2:not(.not-code), h3:not(.not-code), h4:not(.not-code), h5:not(.not-code), h6:not(.not-code)
font-family: Menlo, Monaco, Consolas, "Courier New", monospace
font-variant: normal
.popover-title
background-color: transparent

View file

@ -21,8 +21,12 @@
position: absolute
left: 860px
top: 126px
width: 330px
width: 353px
height: 449px
.nano-content
right: 24px
//background: rgba(100,100,100,0.5)
#item-container

View file

@ -1,4 +1,82 @@
#play-achievements-modal
.achievement-view
color: black
.modal-header
padding-bottom: 20px
img.icon
float: left
width: 40px
margin-right: 10px
//- Unachieved Panels
.panel
margin: 5px 0
position: relative
border: 2px solid rgb(75,75,75)
padding: 2px
h3
margin: 0 0 0 50px
color: rgb(75,75,75)
p
margin: 0 0 0 50px
color: rgb(75,75,75)
.panel-body
padding: 5px 150px 5px 5px
border: 2px solid rgb(150,150,150)
.created
position: absolute
right: 10px
top: 5px
color: rgb(75,75,75)
font-size: 12px
//- Achieved Panels
.panel.earned
background: rgb(50,40,33)
border: 5px solid rgb(26,21,17)
padding: 0
h3
color: white
p
color: rgb(203,170,148)
.panel-body
border: 2px solid rgb(75,62,51)
.created
color: rgb(255,189,68)
//- Rewards
.rewards
position: absolute
right: .2em
//bottom: 10px
top: 29px
.label
font-size: 18px
margin-left: 5px
color: rgb(50,40,33)
background: rgb(203,170,148)
.gems
background: #94ccc7
.worth
background: #d8c488
img
width: 12px
height: 12px

View file

@ -339,11 +339,13 @@
.nano-content
padding-left: 20px
#item-details-view
#item-title
left: 698px
#item-details-body
left: 648px
#selected-item-unlock-button
#selected-item-unlock-button, .unequippable
left: 645px

View file

@ -1,6 +1,9 @@
.modal-dialog
.modal-content
img(src="/images/pages/play/modal/game-menu-background.png")#game-menu-background
img(src="/images/pages/play/modal/game-menu-background.png", draggable="false")#game-menu-background
div#close-modal
span.glyphicon.glyphicon-remove
ul#game-menu-nav.nav.nav-pills.nav-stacked
li

View file

@ -1,6 +1,6 @@
.modal-dialog
.modal-content
img(src="/images/pages/play/modal/inventory-background.png")#play-items-modal-narrow-bg
img(src="/images/pages/play/modal/inventory-background.png", draggable="false")#play-items-modal-narrow-bg
div#gems-count-container
span#gems-count.big-font= gems
@ -25,21 +25,27 @@
if itemGroups.availableItems.models.length
h4#available-description(data-i18n="inventory.available_item")
for item in itemGroups.availableItems.models
img.item(src=item.getPortraitURL(), class=item.classes, data-item-id=item.id)
.item.available(class=item.classes, data-item-id=item.id)
img(src=item.getPortraitURL())
button.btn.equip-item(data-i18n="inventory.equip")
.clearfix
#double-click-hint.alert.alert-info.secret(data-i18n="inventory.should_equip")
#double-click-hint.alert.alert-info
span.glyphicon.glyphicon-info-sign.spr
span(data-i18n="inventory.should_equip")
if itemGroups.restrictedItems.models.length
h4#restricted-description(data-i18n="inventory.restricted_title")
for item in itemGroups.restrictedItems.models
img.item(src=item.getPortraitURL(), class=item.classes, data-item-id=item.id)
.item(class=item.classes, data-item-id=item.id)
img(src=item.getPortraitURL(), draggable="false")
.clearfix
if itemGroups.lockedItems.models.length
h4#locked-description(data-i18n="play.locked")
for item in itemGroups.lockedItems.models
img.item(src=item.getPortraitURL(), class=item.classes, data-item-id=item.id)
.item(class=item.classes, data-item-id=item.id)
img(src=item.getPortraitURL(), draggable="false")
.clearfix
#item-details-view

View file

@ -1,7 +1,7 @@
#player-avatar-container(title="Click to change your avatar")
if !me.get('photoURL')
.editable-icon.glyphicon.glyphicon-pencil
img.profile-photo(src=me.getPhotoURL(230))
img.profile-photo(src=me.getPhotoURL(230), draggable="false")
.form-group
input#player-name.profile-caption(name="playerName", type="text", value=me.get('name', true))
@ -32,7 +32,7 @@
span.help-block(data-i18n="options.autorun_description") Control automatic code execution.
img.hr(src="/images/pages/play/modal/hr.png")
img.hr(src="/images/pages/play/modal/hr.png", draggable="false")
h3(data-i18n="options.editor_config_title") Editor Configuration

View file

@ -1,8 +1,8 @@
.thang-avatar-wrapper(class="team-" + (thang.team || "neutral"))
//canvas(width=100, height=100, title=thang.id + " - " + thang.team)
//- var title = thang.id + " - " + thang.team + (thang.type ? ' - type: "' + thang.type + '"' : '')
img.avatar(src=avatarURL)
img.avatar-frame(src="/images/level/thang_avatar_frame.png")
img.avatar(src=avatarURL, draggable="false")
img.avatar-frame(src="/images/level/thang_avatar_frame.png", draggable="false")
.badge.problems
.badge.shared-thangs
if includeName

View file

@ -3,7 +3,6 @@ h3.problem-alert-title(data-i18n="play_level.problem_alert_title") Fix Your Code
if hint
span.problem-title!= hint
br
br
span.problem-subtitle!= message
else
span.problem-title!= message

View file

@ -1,4 +1,4 @@
img(src="/images/level/code_palette_wood_background.png").code-palette-background
img(src="/images/level/code_palette_wood_background.png", draggable="false").code-palette-background
span.code-palette-background
if entryGroupSlugs
// Non-hero; group by entry groups, or maybe nothing.
@ -15,3 +15,4 @@ else
// Hero; group by items, no tabs.
h4(data-i18n="play_level.tome_your_skills")
.properties

View file

@ -1,5 +1,5 @@
h4
span= doc.shortName
span.prop-name= doc.shortName
| -
code.prop-type= doc.type == 'function' && doc.owner == 'this' ? 'method' : doc.type
if doc.type != 'function'

View file

@ -7,16 +7,16 @@
.nano
.nano-content
#item-container
img.item-img(src=item.getPortraitURL())
img.item-shadow(src=item.getPortraitURL())
img.item-img(src=item.getPortraitURL(), draggable="false")
img.item-shadow(src=item.getPortraitURL(), draggable="false")
img.hr(src="/images/pages/play/modal/hr.png")
img.hr(src="/images/pages/play/modal/hr.png", draggable="false")
for stat in stats
div.stat-row.big-font
div.stat-label= stat.name
div.stat= stat.display
img.hr(src="/images/pages/play/modal/hr.png" class=stat.isLast ? "" : "faded")
img.hr(src="/images/pages/play/modal/hr.png", class=stat.isLast ? "" : "faded", draggable="false")
if props.length
#skills
@ -33,14 +33,12 @@
p Only admins will see it for now.
if item && !item.owned
if item.equippable
if item.unequippable
// Temp, while we only have Warriors: prevent them from buying non-Warrior stuff
button.btn.big-font.disabled.unequippable #{item.get('heroClass')} Only
else
button#selected-item-unlock-button.btn.big-font.unlock-button(disabled=!item.affordable, data-item-id=item.id)
span(data-i18n="play.unlock") Unlock
span.spl= '('+item.get('gems')
img(src="/images/common/gem.png")
img(src="/images/common/gem.png", draggable="false")
span )
else
// Temp, while we only have Warriors: prevent them from buying non-Warrior stuff
button.btn.big-font.disabled.unequippable #{item.get('heroClass')} Only

View file

@ -4,4 +4,29 @@ block modal-header-content
h3(data-i18n="play.achievements") Achievements
block modal-body-content
p TODO: show all dem achievements
for achievement in achievements
.panel(class=achievement.earned ? 'earned' : '')
.panel-body
img.icon(src=achievement.getImageURL(), draggable="false")
h3= achievement.name
p= achievement.description
if achievement.earnedDate
.created=moment(achievement.earnedDate).fromNow()
else
.created(data-i18n="user.status_unfinished")
.rewards
- rewards = achievement.get('rewards')
- rewards = { gems: 100 }
if rewards && rewards.gems
span.gems.label.label-default
span= rewards.gems
img.gem(src="/images/common/gem.png", draggable="false")
- worth = achievement.get('worth')
if worth
span.worth.label.label-default
span #{worth}xp
// maybe add more icons/numbers for items, heroes, levels, xp?
block modal-footer

View file

@ -1,7 +1,7 @@
.modal-dialog
.modal-content
img(src="/images/pages/play/modal/heroes-background.png")#play-heroes-background
img(src="/images/pages/play/modal/heroes-background.png", draggable="false")#play-heroes-background
h1(data-i18n="choose_hero.choose_hero")
@ -15,13 +15,13 @@
li(data-hero-id=hero.get('original'), title=hero.name, data-slide-to=index, data-target="#hero-carousel", class="hero-indicator hero-index-" + index + (hero.locked ? " locked" : ""))
.hero-avatar
if hero.locked
img.lock-indicator(src="/images/pages/game-menu/lock.png")
img.lock-indicator(src="/images/pages/game-menu/lock.png", draggable="false")
.carousel-inner
for hero in heroes
div(class="item hero-item" + (hero.locked ? " locked" : ""), data-hero-id=hero.get('original'))
canvas.hero-canvas
.hero-feature-image
img
img(draggable="false")
.hero-stats
h2= hero.name
.hero-description= hero.description

View file

@ -1,7 +1,7 @@
.modal-dialog
.modal-content
img(src="/images/pages/play/modal/items-background.png")#play-items-modal-bg
img(src="/images/pages/play/modal/items-background-narrow.png")#play-items-modal-narrow-bg
img(src="/images/pages/play/modal/items-background.png", draggable="false")#play-items-modal-bg
img(src="/images/pages/play/modal/items-background-narrow.png", draggable="false")#play-items-modal-narrow-bg
h1.big-font(data-i18n="play.items")
@ -15,7 +15,7 @@
for category, index in itemCategories
li(class=index ? "" : "active")
a.one-line(href="#item-category-" + category, data-toggle="tab")
img.tab-icon(src="/images/pages/play/modal/item-icon-"+category+".png")
img.tab-icon(src="/images/pages/play/modal/item-icon-"+category+".png", draggable="false")
span.big-font= itemCategoryNames[index]
@ -31,15 +31,15 @@
if item.silhouetted && !item.owned
span.glyphicon.glyphicon-lock.bolder
span.glyphicon.glyphicon-lock
img.item-silhouette(src=item.getPortraitURL())
img.item-silhouette(src=item.getPortraitURL(), draggable="false")
if item.level
.required-level
div(data-i18n="general.player_level")
div= item.level
else
strong.big-font= item.name
img.item-img(src=item.getPortraitURL())
img.item-shadow(src=item.getPortraitURL())
img.item-img(src=item.getPortraitURL(), draggable="false")
img.item-shadow(src=item.getPortraitURL(), draggable="false")
if item.owned
span.big-font.owned(data-i18n="play.owned")
@ -47,7 +47,7 @@
span.big-font.locked(data-i18n="play.locked")
else
span.cost
img(src="/images/common/gem.png")
img(src="/images/common/gem.png", draggable="false")
span.big-font= item.get('gems')
if item.equippable
// Temp, while we only have Warriors: prevent them from buying non-Warrior stuff

View file

@ -3,7 +3,7 @@
.gradient.vertical-gradient.right-gradient
.gradient.horizontal-gradient.bottom-gradient
.gradient.vertical-gradient.left-gradient
img.map-background(src="/images/pages/play/map_" + mapType + ".jpg", alt="")
img.map-background(src="/images/pages/play/map_" + mapType + ".jpg", alt="", draggable="false")
- var seenNext = nextLevel;
each level in campaign.levels
@ -43,10 +43,10 @@
.game-controls.header-font
button.btn.items(data-toggle='coco-modal', data-target='play/modal/PlayItemsModal', data-i18n="[title]play.items")
button.btn.heroes(data-toggle='coco-modal', data-target='play/modal/PlayHeroesModal', data-i18n="[title]play.heroes")
button.btn.achievements(data-toggle='coco-modal', data-target='play/modal/PlayAchievementsModal', data-i18n="[title]play.achievements")
if me.get('anonymous') === false
button.btn.gems(data-toggle='coco-modal', data-target='play/modal/BuyGemsModal', data-i18n="[title]play.buy_gems")
if me.isAdmin()
button.btn.achievements(data-toggle='coco-modal', data-target='play/modal/PlayAchievementsModal', data-i18n="[title]play.achievements")
button.btn.account(data-toggle='coco-modal', data-target='play/modal/PlayAccountModal', data-i18n="[title]play.account")
button.btn.settings(data-toggle='coco-modal', data-target='play/modal/PlaySettingsModal', data-i18n="[title]play.settings")
else if me.get('anonymous', true)

View file

@ -17,6 +17,7 @@ module.exports = class GameMenuModal extends ModalView
'change input.select': 'onSelectionChanged'
'shown.bs.tab #game-menu-nav a': 'onTabShown'
'click #change-hero-tab': -> @trigger 'change-hero'
'click #close-modal': 'hide'
constructor: (options) ->
super options

View file

@ -20,9 +20,10 @@ module.exports = class InventoryModal extends ModalView
events:
'click .item-slot': 'onItemSlotClick'
'click #unequipped img.item': 'onUnequippedItemClick'
'doubletap #unequipped img.item': 'onUnequippedItemDoubleClick'
'doubletap .item-slot img.item': 'onEquippedItemDoubleClick'
'click #unequipped .item': 'onUnequippedItemClick'
'doubletap #unequipped .item': 'onUnequippedItemDoubleClick'
'doubletap .item-slot .item': 'onEquippedItemDoubleClick'
'click button.equip-item': 'onClickEquipItemButton'
'shown.bs.modal': 'onShown'
'click #choose-hero-button': 'onClickChooseHero'
'click #play-level-button': 'onClickPlayLevel'
@ -83,9 +84,11 @@ module.exports = class InventoryModal extends ModalView
# sort into one of the four groups
locked = not (item.get('original') in me.items())
locked = false if me.get('slug') is 'nick'
#locked = false if me.get('slug') is 'nick'
if locked and item.get('slug') isnt 'simple-boots'
if not item.getFrontFacingStats().props.length and not _.size(item.getFrontFacingStats().stats) and not locked # Temp: while there are placeholder items
null # Don't put into a collection
else if locked and item.get('slug') isnt 'simple-boots'
@itemGroups.lockedItems.add(item)
item.classes.push 'locked'
item.classes.push 'silhouette' if item.isSilhouettedItem()
@ -126,6 +129,7 @@ module.exports = class InventoryModal extends ModalView
@insertSubView(@itemDetailsView)
@requireLevelEquipment()
@$el.find('.nano').nanoScroller({alwaysVisible: true})
@onSelectionChanged()
afterInsert: ->
super()
@ -137,7 +141,7 @@ module.exports = class InventoryModal extends ModalView
#- Draggable logic
setUpDraggableEventsForAvailableEquipment: ->
for availableItemEl in @$el.find('#unequipped img.item')
for availableItemEl in @$el.find('#unequipped .item')
availableItemEl = $(availableItemEl)
continue if availableItemEl.hasClass('locked') or availableItemEl.hasClass('restricted')
dragHelper = availableItemEl.clone().addClass('draggable-item')
@ -201,12 +205,12 @@ module.exports = class InventoryModal extends ModalView
onUnequippedItemClick: (e) ->
return if @justDoubleClicked
itemEl = $(e.target).closest('img.item')
itemEl = $(e.target).closest('.item')
@selectUnequippedItem(itemEl)
onUnequippedItemDoubleClick: (e) ->
item = $(e.target).closest('img.item')
return if item.hasClass('locked') or item.hasClass('restricted')
itemEl = $(e.target).closest('.item')
return if itemEl.hasClass('locked') or itemEl.hasClass('restricted')
@equipSelectedItem()
@justDoubleClicked = true
_.defer => @justDoubleClicked = false
@ -215,6 +219,11 @@ module.exports = class InventoryModal extends ModalView
onClickEquipItemViewed: -> @equipSelectedItem()
onClickUnequipItemViewed: -> @unequipSelectedItem()
onClickEquipItemButton: (e) ->
itemEl = $(e.target).closest('.item')
@selectUnequippedItem(itemEl)
@equipSelectedItem()
onUnlockButtonClicked: (e) ->
button = $(e.target).closest('button')
if button.hasClass('confirm')
@ -249,7 +258,7 @@ module.exports = class InventoryModal extends ModalView
selectItemSlot: (slotEl) ->
@clearSelection()
slotEl.addClass('selected')
selectedSlotItemID = slotEl.find('img.item').data('item-id')
selectedSlotItemID = slotEl.find('.item').data('item-id')
item = @items.get(selectedSlotItemID)
if item then @showItemDetails(item, 'unequip')
@onSelectionChanged()
@ -270,7 +279,7 @@ module.exports = class InventoryModal extends ModalView
selectedItemEl.effect('transfer', to: slotEl, duration: 500, easing: 'easeOutCubic')
unequipped = @unequipItemFromSlot(slotEl)
selectedItemEl.addClass('equipped')
slotEl.append(selectedItemEl.clone())
slotEl.append(selectedItemEl.find('img').clone().addClass('item').data('item-id', selectedItem.id))
@clearSelection()
@showItemDetails(selectedItem, 'unequip')
slotEl.addClass('selected')
@ -302,17 +311,17 @@ module.exports = class InventoryModal extends ModalView
@hideItemDetails()
unequipItemFromSlot: (slotEl) ->
itemEl = slotEl.find('img.item')
itemEl = slotEl.find('.item')
itemIDToUnequip = itemEl.data('item-id')
return unless itemIDToUnequip
itemEl.remove()
@$el.find("#unequipped img.item[data-item-id=#{itemIDToUnequip}]").removeClass('equipped')
@$el.find("#unequipped .item[data-item-id=#{itemIDToUnequip}]").removeClass('equipped')
deselectAllSlots: ->
@$el.find('#equipped .item-slot.selected').removeClass('selected')
deselectAllUnequippedItems: ->
@$el.find('#unequipped img.item').removeClass('active')
@$el.find('#unequipped .item').removeClass('active')
getSlot: (name) ->
@$el.find(".item-slot[data-slot=#{name}]")
@ -321,9 +330,13 @@ module.exports = class InventoryModal extends ModalView
@$el.find('#equipped .item-slot.selected')
getSelectedUnequippedItem: ->
@$el.find('#unequipped img.item.active')
@$el.find('#unequipped .item.active')
onSelectionChanged: ->
heroClass = @selectedHero?.get('heroClass')
itemsCanBeEquipped = @$el.find('#unequipped .item.available:not(.equipped)').filter('.'+heroClass).length
toShow = @$el.find('#double-click-hint, #available-description')
if itemsCanBeEquipped then toShow.removeClass('secret') else toShow.addClass('secret')
@delegateEvents()
@ -340,7 +353,7 @@ module.exports = class InventoryModal extends ModalView
config = {}
for slot in @$el.find('.item-slot')
slotName = $(slot).data('slot')
slotItemID = $(slot).find('img.item').data('item-id')
slotItemID = $(slot).find('.item').data('item-id')
continue unless slotItemID
item = _.find @items.models, {id:slotItemID}
config[slotName] = item.get('original')
@ -387,7 +400,6 @@ module.exports = class InventoryModal extends ModalView
@highlightElement availableSlotSelector, delay: 500, sides: ['right'], rotation: Math.PI / 2
@$el.find(availableSlotSelector).addClass 'should-equip'
@$el.find("#equipped div[data-slot='#{slot}']").addClass 'should-equip'
@$el.find('#double-click-hint').removeClass('secret')
@remainingRequiredEquipment.push slot: slot, item: gear[item]
if hadRequired and not @remainingRequiredEquipment.length
@endHighlight()

View file

@ -126,8 +126,6 @@ module.exports = class AuthModal extends ModalView
step.done = false for step in @gplusAuthSteps
handler = application.gplusHandler
@renderGPlusAuthChecklist()
@listenToOnce handler, 'logged-in', ->
@gplusAuthSteps[0].done = true
@renderGPlusAuthChecklist()

View file

@ -102,7 +102,7 @@ module.exports = class WorldMapView extends RootView
context.isIPadApp = application.isIPadApp
context.mapType = _.string.slugify @terrain
context.nextLevel = @nextLevel
context.forestIsAvailable = '541b67f71ccc8eaae19f3c62' in (me.get('earned')?.levels or [])
context.forestIsAvailable = @startedForestLevel or '541b67f71ccc8eaae19f3c62' in (me.get('earned')?.levels or [])
context
afterRender: ->
@ -131,8 +131,10 @@ module.exports = class WorldMapView extends RootView
@openModalView authModal
onSessionsLoaded: (e) ->
forestLevels = (f.id for f in forest)
for session in @sessions.models
@levelStatusMap[session.get('levelID')] = if session.get('state')?.complete then 'complete' else 'started'
@startedForestLevel = true if session.get('levelID') in forestLevels
if @nextLevel and @levelStatusMap[@nextLevel] is 'complete'
@nextLevel = null
@render()
@ -771,3 +773,4 @@ if me.getKithmazeGroup() is 'the-first-kithmaze'
_.remove dungeon, id: 'haunted-kithmaze'
else
_.remove dungeon, id: 'the-first-kithmaze'
_.find(dungeon, id: 'the-raised-sword').nextLevels.continue = 'haunted-kithmaze'

View file

@ -79,7 +79,7 @@ module.exports = class LevelHUDView extends CocoView
wrapper.removeClass (i, css) -> (css.match(/\bteam-\S+/g) or []).join ' '
wrapper.addClass "team-#{team}"
if thangType.get('raster')
wrapper.empty().append($('<img />').addClass('avatar').attr('src', '/file/'+thangType.get('raster')))
wrapper.empty().append($('<img draggable="false"/>').addClass('avatar').attr('src', '/file/'+thangType.get('raster')))
else
return unless stage = thangType.getPortraitStage options, 100
newCanvas = $(stage.canvas).addClass('thang-canvas avatar')
@ -87,7 +87,7 @@ module.exports = class LevelHUDView extends CocoView
stage.update()
@stage?.stopTalking()
@stage = stage
wrapper.append($('<img />').addClass('avatar-frame').attr('src', '/images/level/thang_avatar_frame.png'))
wrapper.append($('<img draggable="false" />').addClass('avatar-frame').attr('src', '/images/level/thang_avatar_frame.png'))
onThangBeganTalking: (e) ->
return unless @stage and @thang is e.thang

View file

@ -210,6 +210,7 @@ module.exports = class LevelPlaybackView extends CocoView
onProgressHover: (e, offsetX) ->
timeRatio = @$progressScrubber.width() / @totalTime
offsetX ?= e.clientX - $(e.target).closest('#timeProgress').offset().left
offsetX = Math.max 0, offsetX
@newTime = offsetX / timeRatio
@updatePopupContent()
@timePopup?.onHover e

View file

@ -6,4 +6,10 @@ module.exports = class ReloadLevelModal extends ModalView
template: template
events:
'click #restart-level-confirm-button': -> Backbone.Mediator.publish 'level:restart', {}
'click #restart-level-confirm-button': 'onClickRestart'
onClickRestart: (e) ->
if key.shift
Backbone.Mediator.publish 'level:restart', {}
else
Backbone.Mediator.publish 'tome:reload-code', {}

View file

@ -34,8 +34,18 @@ module.exports = class ProblemAlertView extends CocoView
getRenderData: (context={}) ->
context = super context
if @problem?
format = (s) -> s?.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br>')
context.message = format @problem.aetherProblem.message
format = (s) -> marked(s.replace(/</g, '&lt;').replace(/>/g, '&gt;')) if s?
message = @problem.aetherProblem.message
# Add time to problem message if hint is for a missing null check
# NOTE: This may need to be updated with Aether error hint changes
if @problem.aetherProblem.hint? and /(?:null|undefined)/.test @problem.aetherProblem.hint
age = @problem.aetherProblem.userInfo?.age
if age?
if /^Line \d+:/.test message
message = message.replace /^(Line \d+)/, "$1, time #{age.toFixed(1)}"
else
message = "Time #{age.toFixed(1)}: #{message}"
context.message = format message
context.hint = format @problem.aetherProblem.hint
context

View file

@ -51,10 +51,20 @@ module.exports = class SpellPaletteView extends CocoView
@entryGroupElements = {}
for group, entries of @entryGroups
@entryGroupElements[group] = itemGroup = $('<div class="property-entry-item-group"></div>').appendTo @$el.find('.properties')
itemGroup.append $('<img class="item-image"></img>').attr('src', entries[0].options.item.getPortraitURL()).css('top', Math.max(0, 19 * (entries.length - 2) / 2) + 2) if entries[0].options.item?.getPortraitURL
for entry in entries
if entries[0].options.item?.getPortraitURL
itemImage = $('<img class="item-image" draggable=false></img>').attr('src', entries[0].options.item.getPortraitURL()).css('top', Math.max(0, 19 * (entries.length - 2) / 2) + 2)
itemGroup.append itemImage
firstEntry = entries[0]
do (firstEntry) ->
itemImage.on "mouseenter", (e) -> firstEntry.onMouseEnter e
itemImage.on "mouseleave", (e) -> firstEntry.onMouseLeave e
for entry, entryIndex in entries
itemGroup.append entry.el
entry.render() # Render after appending so that we can access parent container for popover
if entries.length is 1
entry.$el.addClass 'single-entry'
if entryIndex is 0
entry.$el.addClass 'first-entry'
@$el.addClass 'hero'
@updateMaxHeight() unless application.isIPadApp

View file

@ -189,7 +189,7 @@ module.exports = class SpellView extends CocoView
bindKey: {win: 'Ctrl-Shift-M', mac: 'Command-Shift-M|Ctrl-Shift-M'}
exec: -> Backbone.Mediator.publish 'tome:toggle-maximize', {}
addCommand
# TODO: Restrict to beginner campaign levels
# TODO: Restrict to beginner campaign levels, possibly with a CampaignOptions similar to LevelOptions
name: 'enter-skip-delimiters'
bindKey: 'Enter|Return'
exec: =>
@ -202,6 +202,10 @@ module.exports = class SpellView extends CocoView
newRange.setEnd newRange.end.row, newRange.end.column + delimMatch[1].length
@aceSession.selection.setSelectionRange newRange
@ace.execCommand 'insertstring', '\n'
addCommand
name: 'disable-spaces'
bindKey: 'Space'
exec: => @ace.execCommand 'insertstring', ' ' unless LevelOptions[@options.level.get('slug')]?.disableSpaces
fillACE: ->
@ace.setValue @spell.source
@ -390,7 +394,7 @@ module.exports = class SpellView extends CocoView
@focus() if cast
onCodeReload: (e) ->
return unless e.spell is @spell
return unless e.spell is @spell or not e.spell
@reloadCode true
@ace.clearSelection()
_.delay (=> @ace?.clearSelection()), 500 # Make double sure this gets done (saw some timing issues?)
@ -446,7 +450,8 @@ module.exports = class SpellView extends CocoView
_.throttle @updateLines, 500
_.throttle @hideProblemAlert, 500
]
onSignificantChange.push _.debounce @checkRequiredCode, 1500 if requiredCodePerLevel[@options.level.get('slug')]
onSignificantChange.push _.debounce @checkRequiredCode, 750 if LevelOptions[@options.level.get('slug')]?.requiredCode
onSignificantChange.push _.debounce @checkSuspectCode, 750 if LevelOptions[@options.level.get('slug')]?.suspectCode
@onCodeChangeMetaHandler = =>
return if @eventsSuppressed
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'code-change', volume: 0.5
@ -877,13 +882,26 @@ module.exports = class SpellView extends CocoView
checkRequiredCode: =>
return if @destroyed
source = @getSource().replace @singleLineCommentRegex(), ''
for requiredCodeFragment in requiredCodePerLevel[@options.level.get('slug')]
requiredCodeFragments = LevelOptions[@options.level.get('slug')].requiredCode
for requiredCodeFragment in requiredCodeFragments
# Could make this obey regular expressions like suspectCode if needed
if source.indexOf(requiredCodeFragment) is -1
@warnedCodeFragments ?= {}
unless @warnedCodeFragments[requiredCodeFragment]
Backbone.Mediator.publish 'tome:required-code-fragment-deleted', codeFragment: requiredCodeFragment
@warnedCodeFragments[requiredCodeFragment] = true
checkSuspectCode: =>
return if @destroyed
source = @getSource().replace @singleLineCommentRegex(), ''
suspectCodeFragments = LevelOptions[@options.level.get('slug')].suspectCode
for suspectCodeFragment in suspectCodeFragments
if suspectCodeFragment.pattern.test source
@warnedCodeFragments ?= {}
unless @warnedCodeFragments[suspectCodeFragment.name]
Backbone.Mediator.publish 'tome:suspect-code-fragment-added', codeFragment: suspectCodeFragment.name, codeLanguage: @spell.language
@warnedCodeFragments[suspectCodeFragment] = true
destroy: ->
$(@ace?.container).find('.ace_gutter').off 'click', '.ace_error, .ace_warning, .ace_info', @onAnnotationClick
$(@ace?.container).find('.ace_gutter').off 'click', @onGutterClick
@ -896,11 +914,3 @@ module.exports = class SpellView extends CocoView
@debugView?.destroy()
$(window).off 'resize', @onWindowResize
super()
requiredCodePerLevel =
'dungeons-of-kithgard': ['moveRight']
'true-names': ['Brak']
'the-first-kithmaze': ['loop']
'haunted-kithmaze': ['loop']
'lowly-kithmen': ['findNearestEnemy']

View file

@ -44,7 +44,6 @@ module.exports = class ItemDetailsView extends CocoView
@listenToOnce docs, 'sync', @onDocsLoaded
@render()
@$el.find('.nano:visible').nanoScroller()
onDocsLoaded: (levelComponents) ->
for component in levelComponents.models
@ -55,6 +54,10 @@ module.exports = class ItemDetailsView extends CocoView
@propDocs[propDoc.name] = propDoc
@render()
afterRender: ->
super()
@$el.find('.nano:visible').nanoScroller({alwaysVisible: true})
getRenderData: ->
c = super()
c.item = @item

View file

@ -2,27 +2,92 @@ ModalView = require 'views/kinds/ModalView'
template = require 'templates/play/modal/play-achievements-modal'
CocoCollection = require 'collections/CocoCollection'
Achievement = require 'models/Achievement'
#AchievementView = require 'views/game-menu/AchievementView'
EarnedAchievement = require 'models/EarnedAchievement'
utils = require 'lib/utils'
PAGE_SIZE = 200
module.exports = class PlayAchievementsModal extends ModalView
className: 'modal fade play-modal'
template: template
modalWidthPercent: 90
id: 'play-achievements-modal'
#instant: true
plain: true
#events:
# 'change input.select': 'onSelectionChanged'
earnedMap: {}
constructor: (options) ->
super options
#@achievements = new CocoCollection([], {model: Achievement})
#@achievements.url = '/db/thang.type?view=achievements&project=name,description,components,original,rasterIcon'
#@supermodel.loadCollection(@achievements, 'achievements')
@achievements = new Backbone.Collection()
earnedMap = {}
achievementsFetcher = new CocoCollection([], {url: '/db/achievement', model: Achievement})
achievementsFetcher.setProjection([
'name'
'description'
'icon'
'worth'
'i18n'
'rewards'
'collection'
])
earnedAchievementsFetcher = new CocoCollection([], {url: '/db/earned_achievement', model: EarnedAchievement})
earnedAchievementsFetcher.setProjection([ 'achievement' ])
achievementsFetcher.skip = 0
achievementsFetcher.fetch({data: {skip: 0, limit: PAGE_SIZE}})
earnedAchievementsFetcher.skip = 0
earnedAchievementsFetcher.fetch({data: {skip: 0, limit: PAGE_SIZE}})
@listenTo achievementsFetcher, 'sync', @onAchievementsLoaded
@listenTo earnedAchievementsFetcher, 'sync', @onEarnedAchievementsLoaded
@supermodel.loadCollection(achievementsFetcher, 'achievement')
@supermodel.loadCollection(earnedAchievementsFetcher, 'achievement')
@onEverythingLoaded = _.after(2, @onEverythingLoaded)
onAchievementsLoaded: (fetcher) ->
needMore = fetcher.models.length is PAGE_SIZE
@achievements.add(fetcher.models)
if needMore
fetcher.skip += PAGE_SIZE
fetcher.fetch({data: {skip: fetcher.skip, limit: PAGE_SIZE}})
else
@stopListening(fetcher)
@onEverythingLoaded()
onEarnedAchievementsLoaded: (fetcher) ->
needMore = fetcher.models.length is PAGE_SIZE
for earned in fetcher.models
@earnedMap[earned.get('achievement')] = earned
if needMore
fetcher.skip += PAGE_SIZE
fetcher.fetch({data: {skip: fetcher.skip, limit: PAGE_SIZE}})
else
@stopListening(fetcher)
@onEverythingLoaded()
onEverythingLoaded: =>
@achievements.set(@achievements.filter((m) -> m.get('collection') isnt 'level.sessions'))
for achievement in @achievements.models
if earned = @earnedMap[achievement.id]
achievement.earned = earned
achievement.earnedDate = earned.getCreationDate()
achievement.earnedDate ?= ''
@achievements.comparator = (m) -> m.earnedDate
@achievements.sort()
@achievements.set(@achievements.models.reverse())
for achievement in @achievements.models
achievement.name = utils.i18n achievement.attributes, 'name'
achievement.description = utils.i18n achievement.attributes, 'description'
@render()
getRenderData: (context={}) ->
context = super(context)
#context.achievements = @achievements.models
context.achievements = @achievements.models
context
afterRender: ->

View file

@ -89,7 +89,7 @@ module.exports = class PlayItemsModal extends ModalView
model.affordable = cost <= gemsOwned
model.silhouetted = not model.owned and model.isSilhouettedItem()
model.level = model.levelRequiredForItem() if model.get('tier')?
model.equippable = 'Warrior' in model.getAllowedHeroClasses() # Temp: while there are no wizards/rangers
model.unequippable = not ('Warrior' in model.getAllowedHeroClasses()) # Temp: while there are no wizards/rangers
model.comingSoon = not model.getFrontFacingStats().props.length and not _.size model.getFrontFacingStats().stats and not model.owned # Temp: while there are placeholder items
@idToItem[model.id] = model

View file

@ -1,3 +0,0 @@
cd ~/Desktop/coco
cp ~/Desktop/treema/treema.js ./vendor/scripts/
cp ~/Desktop/treema/treema.css ./vendor/styles/

View file

@ -2,12 +2,6 @@ mongoose = require 'mongoose'
jsonschema = require '../../app/schemas/models/earned_achievement'
EarnedAchievementSchema = new mongoose.Schema({
created:
type: Date
default: Date.now
changed:
type: Date
default: Date.now
notified:
type: Boolean
default: false

View file

@ -16,7 +16,26 @@ class EarnedAchievementHandler extends Handler
get: (req, res) ->
return @getByAchievementIDs(req, res) if req.query.view is 'get-by-achievement-ids'
super(arguments...)
query = { user: req.user._id+''}
projection = {}
if req.query.project
projection[field] = 1 for field in req.query.project.split(',')
q = EarnedAchievement.find(query, projection)
skip = parseInt(req.query.skip)
if skip? and skip < 1000000
q.skip(skip)
limit = parseInt(req.query.limit)
if limit? and limit < 1000
q.limit(limit)
q.exec (err, documents) =>
return @sendDatabaseError(res, err) if err
documents = (@formatEntity(req, doc) for doc in documents)
@sendSuccess(res, documents)
getByAchievementIDs: (req, res) ->
query = { user: req.user._id+''}

View file

@ -129,6 +129,10 @@ module.exports = class Handler
term = req.query.term
matchedObjects = []
filters = if @modelClass.schema.uses_coco_versions or @modelClass.schema.uses_coco_permissions then [filter: {index: true}] else [filter: {}]
skip = parseInt(req.query.skip)
limit = parseInt(req.query.limit)
if @modelClass.schema.uses_coco_permissions and req.user
filters.push {filter: {index: req.user.get('id')}}
projection = null
@ -158,7 +162,14 @@ module.exports = class Handler
else
args = [filter.filter]
args.push projection if projection
@modelClass.find(args...).limit(FETCH_LIMIT).exec callback
q = @modelClass.find(args...)
if skip? and skip < 1000000
q.skip(skip)
if limit? and limit < FETCH_LIMIT
q.limit(limit)
else
q.limit(FETCH_LIMIT)
q.exec callback
# if it's not a text search but the user is an admin, let him try stuff anyway
else if req.user?.isAdmin()
# admins can send any sort of query down the wire

View file

@ -30,8 +30,10 @@
this.createjs = this.createjs||{};
(function() {
"use strict";
/**
/**
* A SpriteContainer is a nestable display list that enables aggressively optimized rendering of bitmap content.
* In order to accomplish these optimizations, SpriteContainer enforces a few restrictions on its content.
*
@ -41,6 +43,7 @@ this.createjs = this.createjs||{};
* - all children (with the exception of DOMElement) MUST use the same spriteSheet.
*
* <h4>Example</h4>
*
* var data = {
* images: ["sprites.jpg"],
* frames: {width:50, height:50},
@ -58,43 +61,24 @@ this.createjs = this.createjs||{};
* @constructor
* @param {SpriteSheet} [spriteSheet] The spriteSheet to use for this SpriteContainer and its children.
**/
var SpriteContainer = function(spriteSheet) {
this.initialize(spriteSheet);
};
var p = SpriteContainer.prototype = new createjs.Container();
function SpriteContainer(spriteSheet) {
this.Container_constructor();
// public properties:
// public properties:
/**
* The SpriteSheet that this container enforces use of.
* @property spriteSheet
* @type {SpriteSheet}
* @readonly
**/
p.spriteSheet = null;
// constructor:
/**
* @property Container_initialize
* @type Function
* @private
**/
p.Container_initialize = p.initialize;
/**
* Initialization method.
* @method initialize
* @param {SpriteSheet} spriteSheet Optional. The spriteSheet to use for this SpriteContainer and its children.
* @protected
*/
p.initialize = function(spriteSheet) {
this.Container_initialize();
this.spriteSheet = spriteSheet;
};
}
var p = createjs.extend(SpriteContainer, createjs.Container);
// public methods:
/**
* Adds a child to the top of the display list.
* Only children of type SpriteContainer, Sprite, Bitmap, BitmapText, or DOMElement are allowed.
@ -163,7 +147,7 @@ var p = SpriteContainer.prototype = new createjs.Container();
}
if (child._spritestage_compatibility <= 4) {
var spriteSheet = child.spriteSheet;
if ((!spriteSheet || !spriteSheet._images || spriteSheet._images.length > 1) || (this.spritesheet && spritesheet !== spritesheet)) {
if ((!spriteSheet || !spriteSheet._images || spriteSheet._images.length > 1) || (this.spriteSheet && this.spriteSheet !== spriteSheet)) {
console && console.log("Error: A child's spriteSheet must be equal to its parent spriteSheet and only use one image. [" + child.toString() + "]");
return child;
}
@ -184,5 +168,6 @@ var p = SpriteContainer.prototype = new createjs.Container();
return "[SpriteContainer (name="+ this.name +")]";
};
createjs.SpriteContainer = SpriteContainer;
createjs.SpriteContainer = createjs.promote(SpriteContainer, "Container");
}());

View file

@ -36,14 +36,17 @@ this.createjs = this.createjs||{};
(function() {
"use strict";
// Set which classes are compatible with SpriteStage.
// The order is important!!! If it's changed/appended, make sure that any logic that
// checks _spritestage_compatibility accounts for it!
[createjs.SpriteContainer, createjs.Sprite, createjs.BitmapText, createjs.Bitmap, createjs.DOMElement].forEach(function(_class, index) {
_class.prototype._spritestage_compatibility = index + 1;
});
/**
// Set which classes are compatible with SpriteStage.
// The order is important!!! If it's changed/appended, make sure that any logic that
// checks _spritestage_compatibility accounts for it!
[createjs.SpriteContainer, createjs.Sprite, createjs.BitmapText, createjs.Bitmap, createjs.DOMElement].forEach(function(_class, index) {
_class.prototype._spritestage_compatibility = index + 1;
});
// constructor:
/**
* A sprite stage is the root level {{#crossLink "Container"}}{{/crossLink}} for an aggressively optimized display list. Each time its {{#crossLink "Stage/tick"}}{{/crossLink}}
* method is called, it will render its display list to its target canvas. WebGL content is fully compatible with the existing Context2D renderer.
* On devices or browsers that don't support WebGL, content will automatically be rendered via canvas 2D.
@ -77,13 +80,193 @@ this.createjs = this.createjs||{};
* @param {Boolean} preserveDrawingBuffer If true, the canvas is NOT auto-cleared by WebGL (spec discourages true). Useful if you want to use p.autoClear = false.
* @param {Boolean} antialias Specifies whether or not the browser's WebGL implementation should try to perform antialiasing.
**/
var SpriteStage = function(canvas, preserveDrawingBuffer, antialias) {
this.initialize(canvas, preserveDrawingBuffer, antialias);
};
var p = SpriteStage.prototype = new createjs.Stage();
function SpriteStage(canvas, preserveDrawingBuffer, antialias) {
this.Stage_constructor(canvas);
// static properties:
// private properties:
/**
* Specifies whether or not the canvas is auto-cleared by WebGL. Spec discourages true.
* If true, the canvas is NOT auto-cleared by WebGL. Value is ignored if `_alphaEnabled` is false.
* Useful if you want to use `autoClear = false`.
* @property _preserveDrawingBuffer
* @protected
* @type {Boolean}
* @default false
**/
this._preserveDrawingBuffer = preserveDrawingBuffer||false;
/**
* Specifies whether or not the browser's WebGL implementation should try to perform antialiasing.
* @property _antialias
* @protected
* @type {Boolean}
* @default false
**/
this._antialias = antialias||false;
/**
* The width of the canvas element.
* @property _viewportWidth
* @protected
* @type {Number}
* @default 0
**/
this._viewportWidth = 0;
/**
* The height of the canvas element.
* @property _viewportHeight
* @protected
* @type {Number}
* @default 0
**/
this._viewportHeight = 0;
/**
* A 2D projection matrix used to convert WebGL's clipspace into normal pixels.
* @property _projectionMatrix
* @protected
* @type {Float32Array}
* @default null
**/
this._projectionMatrix = null;
/**
* The current WebGL canvas context.
* @property _webGLContext
* @protected
* @type {WebGLRenderingContext}
* @default null
**/
this._webGLContext = null;
/**
* Indicates whether or not an error has been detected when dealing with WebGL.
* If the is true, the behavior should be to use Canvas 2D rendering instead.
* @property _webGLErrorDetected
* @protected
* @type {Boolean}
* @default false
**/
this._webGLErrorDetected = false;
/**
* The color to use when the WebGL canvas has been cleared.
* @property _clearColor
* @protected
* @type {Object}
* @default null
**/
this._clearColor = null;
/**
* The maximum number of textures WebGL can work with per draw call.
* @property _maxTexturesPerDraw
* @protected
* @type {Number}
* @default 1
**/
this._maxTexturesPerDraw = 1; // TODO: this is currently unused.
/**
* The maximum total number of boxes points that can be defined per draw call.
* @property _maxBoxesPointsPerDraw
* @protected
* @type {Number}
* @default null
**/
this._maxBoxesPointsPerDraw = null;
/**
* The maximum number of boxes (sprites) that can be drawn in one draw call.
* @property _maxBoxesPerDraw
* @protected
* @type {Number}
* @default null
**/
this._maxBoxesPerDraw = null;
/**
* The maximum number of indices that can be drawn in one draw call.
* @property _maxIndicesPerDraw
* @protected
* @type {Number}
* @default null
**/
this._maxIndicesPerDraw = null;
/**
* The shader program used to draw everything.
* @property _shaderProgram
* @protected
* @type {WebGLProgram}
* @default null
**/
this._shaderProgram = null;
/**
* The vertices data for the current draw call.
* @property _vertices
* @protected
* @type {Float32Array}
* @default null
**/
this._vertices = null;
/**
* The buffer that contains all the vertices data.
* @property _verticesBuffer
* @protected
* @type {WebGLBuffer}
* @default null
**/
this._verticesBuffer = null;
/**
* The indices to the vertices defined in this._vertices.
* @property _indices
* @protected
* @type {Uint16Array}
* @default null
**/
this._indices = null;
/**
* The buffer that contains all the indices data.
* @property _indicesBuffer
* @protected
* @type {WebGLBuffer}
* @default null
**/
this._indicesBuffer = null;
/**
* The current box index being defined for drawing.
* @property _currentBoxIndex
* @protected
* @type {Number}
* @default -1
**/
this._currentBoxIndex = -1;
/**
* The current texture that will be used to draw into the GPU.
* @property _drawTexture
* @protected
* @type {WebGLTexture}
* @default null
**/
this._drawTexture = null;
// setup:
this._initializeWebGL();
}
var p = createjs.extend(SpriteStage, createjs.Stage);
// constants:
/**
* The number of properties defined per vertex in p._verticesBuffer.
* x, y, textureU, textureV, alpha
@ -150,6 +333,7 @@ var p = SpriteStage.prototype = new createjs.Stage();
**/
SpriteStage.MAX_BOXES_POINTS_INCREMENT = SpriteStage.MAX_INDEX_SIZE / 4;
// getter / setters:
/**
* Indicates whether WebGL is being used for rendering. For example, this would be false if WebGL is not
@ -168,209 +352,8 @@ var p = SpriteStage.prototype = new createjs.Stage();
});
} catch (e) {} // TODO: use Log
// private properties:
/**
* Specifies whether or not the canvas is auto-cleared by WebGL. Spec discourages true.
* If true, the canvas is NOT auto-cleared by WebGL. Value is ignored if p._alphaEnabled is false.
* Useful if you want to use p.autoClear = false.
* @property _preserveDrawingBuffer
* @protected
* @type {Boolean}
* @default false
**/
p._preserveDrawingBuffer = false;
/**
* Specifies whether or not the browser's WebGL implementation should try to perform antialiasing.
* @property _antialias
* @protected
* @type {Boolean}
* @default false
**/
p._antialias = false;
/**
* The width of the canvas element.
* @property _viewportWidth
* @protected
* @type {Number}
* @default 0
**/
p._viewportWidth = 0;
/**
* The height of the canvas element.
* @property _viewportHeight
* @protected
* @type {Number}
* @default 0
**/
p._viewportHeight = 0;
/**
* A 2D projection matrix used to convert WebGL's clipspace into normal pixels.
* @property _projectionMatrix
* @protected
* @type {Float32Array}
* @default null
**/
p._projectionMatrix = null;
/**
* The current WebGL canvas context.
* @property _webGLContext
* @protected
* @type {WebGLRenderingContext}
* @default null
**/
p._webGLContext = null;
/**
* Indicates whether or not an error has been detected when dealing with WebGL.
* If the is true, the behavior should be to use Canvas 2D rendering instead.
* @property _webGLErrorDetected
* @protected
* @type {Boolean}
* @default false
**/
p._webGLErrorDetected = false;
/**
* The color to use when the WebGL canvas has been cleared.
* @property _clearColor
* @protected
* @type {Object}
* @default null
**/
p._clearColor = null;
/**
* The maximum number of textures WebGL can work with per draw call.
* @property _maxTexturesPerDraw
* @protected
* @type {Number}
* @default 1
**/
p._maxTexturesPerDraw = 1;
/**
* The maximum total number of boxes points that can be defined per draw call.
* @property _maxBoxesPointsPerDraw
* @protected
* @type {Number}
* @default null
**/
p._maxBoxesPointsPerDraw = null;
/**
* The maximum number of boxes (sprites) that can be drawn in one draw call.
* @property _maxBoxesPerDraw
* @protected
* @type {Number}
* @default null
**/
p._maxBoxesPerDraw = null;
/**
* The maximum number of indices that can be drawn in one draw call.
* @property _maxIndicesPerDraw
* @protected
* @type {Number}
* @default null
**/
p._maxIndicesPerDraw = null;
/**
* The shader program used to draw everything.
* @property _shaderProgram
* @protected
* @type {WebGLProgram}
* @default null
**/
p._shaderProgram = null;
/**
* The vertices data for the current draw call.
* @property _vertices
* @protected
* @type {Float32Array}
* @default null
**/
p._vertices = null;
/**
* The buffer that contains all the vertices data.
* @property _verticesBuffer
* @protected
* @type {WebGLBuffer}
* @default null
**/
p._verticesBuffer = null;
/**
* The indices to the vertices defined in p._vertices.
* @property _indices
* @protected
* @type {Uint16Array}
* @default null
**/
p._indices = null;
/**
* The buffer that contains all the indices data.
* @property _indicesBuffer
* @protected
* @type {WebGLBuffer}
* @default null
**/
p._indicesBuffer = null;
/**
* The current box index being defined for drawing.
* @property _currentBoxIndex
* @protected
* @type {Number}
* @default -1
**/
p._currentBoxIndex = -1;
/**
* The current texture that will be used to draw into the GPU.
* @property _drawTexture
* @protected
* @type {WebGLTexture}
* @default null
**/
p._drawTexture = null;
// constructor:
/**
* @property Stage_initialize
* @type Function
* @private
**/
p.Stage_initialize = p.initialize;
/**
* Initialization method.
* @method initialize
* @param {HTMLCanvasElement | String | Object} canvas A canvas object, or the string id of a canvas object in the current document.
* @param {Boolean} preserveDrawingBuffer If true, the canvas is NOT auto-cleared by WebGL (spec discourages true). Useful if you want to use p.autoClear = false.
* @param {Boolean} antialias Specifies whether or not the browser's WebGL implementation should try to perform antialiasing.
* @protected
**/
p.initialize = function(canvas, preserveDrawingBuffer, antialias) {
this._preserveDrawingBuffer = preserveDrawingBuffer !== undefined ? preserveDrawingBuffer : this._preserveDrawingBuffer;
this._antialias = antialias !== undefined ? antialias : this._antialias;
this.Stage_initialize(canvas);
this._initializeWebGL();
};
// public methods:
/**
* Adds a child to the top of the display list.
* Only children of type SpriteContainer, Sprite, Bitmap, BitmapText, or DOMElement are allowed.
@ -403,6 +386,7 @@ var p = SpriteStage.prototype = new createjs.Stage();
* Children also MUST have either an image or spriteSheet defined on them (unless it's a DOMElement).
*
* <h4>Example</h4>
*
* addChildAt(child1, index);
*
* You can also add multiple children, such as:
@ -432,7 +416,6 @@ var p = SpriteStage.prototype = new createjs.Stage();
if (child._spritestage_compatibility >= 1) {
// The child is compatible with SpriteStage.
} else {
console.trace();
console && console.log("Error: You can only add children of type SpriteContainer, Sprite, Bitmap, BitmapText, or DOMElement. [" + child.toString() + "]");
return child;
}
@ -447,32 +430,10 @@ var p = SpriteStage.prototype = new createjs.Stage();
return child;
};
/**
* Each time the update method is called, the stage will tick all descendants (see: {{#crossLink "DisplayObject/tick"}}{{/crossLink}})
* and then render the display list to the canvas using WebGL. If WebGL is not supported in the browser, it will default to a 2D context.
*
* Any parameters passed to `update()` will be passed on to any
* {{#crossLink "DisplayObject/tick:event"}}{{/crossLink}} event handlers.
*
* Some time-based features in EaselJS (for example {{#crossLink "Sprite/framerate"}}{{/crossLink}} require that
* a tick event object (or equivalent) be passed as the first parameter to update(). For example:
*
* Ticker.addEventListener("tick", handleTick);
* function handleTick(evtObj) {
* // do some work here, then update the stage, passing through the event object:
* myStage.update(evtObj);
* }
*
* @method update
* @param {*} [params]* Params to include when ticking descendants. The first param should usually be a tick event.
**/
p.update = function(params) {
/** docced in super class **/
p.update = function(props) {
if (!this.canvas) { return; }
if (this.tickOnUpdate) {
this.dispatchEvent("tickstart"); // TODO: make cancellable?
this._tick((arguments.length ? arguments : null));
this.dispatchEvent("tickend");
}
if (this.tickOnUpdate) { this.tick(props); }
this.dispatchEvent("drawstart"); // TODO: make cancellable?
if (this.autoClear) { this.clear(); }
var ctx = this._setWebGLContext();
@ -508,13 +469,6 @@ var p = SpriteStage.prototype = new createjs.Stage();
}
};
/**
* @property Stage_draw
* @type {Function}
* @private
**/
p.Stage_draw = p.draw;
/**
* Draws the stage into the specified context (using WebGL) ignoring its visible, alpha, shadow, and transform.
* If WebGL is not supported in the browser, it will default to a 2D context.
@ -881,11 +835,10 @@ var p = SpriteStage.prototype = new createjs.Stage();
for (var i = 0, l = kids.length; i < l; i++) {
kid = kids[i];
if (!kid.isVisible()) { continue; }
mtx = kid._matrix;
mtx = kid._props.matrix;
// Get the kid's global matrix (relative to the stage):
mtx = (parentMVMatrix ? mtx.copy(parentMVMatrix) : mtx.identity())
.appendTransform(kid.x, kid.y, kid.scaleX, kid.scaleY, kid.rotation, kid.skewX, kid.skewY, kid.regX, kid.regY);
mtx = (parentMVMatrix ? mtx.copy(parentMVMatrix) : mtx.identity()).prependTransform(kid.x, kid.y, kid.scaleX, kid.scaleY, kid.rotation, kid.skewX, kid.skewY, kid.regX, kid.regY);
// Set default texture coordinates:
var uStart = 0, uEnd = 1,
@ -1027,5 +980,6 @@ var p = SpriteStage.prototype = new createjs.Stage();
this._drawTexture = null;
};
createjs.SpriteStage = SpriteStage;
createjs.SpriteStage = createjs.promote(SpriteStage, "Stage");
}());

File diff suppressed because it is too large Load diff

View file

@ -27,7 +27,7 @@ this.createjs = this.createjs||{};
* @type String
* @static
**/
s.buildDate = /*date*/"Thu, 06 Mar 2014 22:58:10 GMT"; // injected by build process
s.buildDate = /*date*/"Wed, 22 Oct 2014 16:11:35 GMT"; // injected by build process
})();
/*
@ -91,6 +91,7 @@ var Event = function(type, bubbles, cancelable) {
this.initialize(type, bubbles, cancelable);
};
var p = Event.prototype;
Event.prototype.constructor = Event;
// events:
@ -375,6 +376,7 @@ var EventDispatcher = function() {
/* this.initialize(); */ // not needed.
};
var p = EventDispatcher.prototype;
EventDispatcher.prototype.constructor = EventDispatcher;
/**
@ -579,19 +581,19 @@ var p = EventDispatcher.prototype;
* @param {Object | String | Event} eventObj An object with a "type" property, or a string type.
* While a generic object will work, it is recommended to use a CreateJS Event instance. If a string is used,
* dispatchEvent will construct an Event instance with the specified type.
* @param {Object} [target] The object to use as the target property of the event object. This will default to the
* dispatching object. <b>This parameter is deprecated and will be removed.</b>
* @return {Boolean} Returns the value of eventObj.defaultPrevented.
**/
p.dispatchEvent = function(eventObj, target) {
p.dispatchEvent = function(eventObj) {
if (typeof eventObj == "string") {
// won't bubble, so skip everything if there's no listeners:
var listeners = this._listeners;
if (!listeners || !listeners[eventObj]) { return false; }
eventObj = new createjs.Event(eventObj);
} else if (eventObj.target && eventObj.clone) {
// redispatching an active event object, so clone it:
eventObj = eventObj.clone();
}
// TODO: deprecated. Target param is deprecated, only use case is MouseEvent/mousemove, remove.
eventObj.target = target||this;
try { eventObj.target = this; } catch (e) {} // try/catch allows redispatching of native events
if (!eventObj.bubbles || !this.parent) {
this._dispatchEvent(eventObj, 2);
@ -663,8 +665,8 @@ var p = EventDispatcher.prototype;
if (eventObj && listeners) {
var arr = listeners[eventObj.type];
if (!arr||!(l=arr.length)) { return; }
eventObj.currentTarget = this;
eventObj.eventPhase = eventPhase;
try { eventObj.currentTarget = this; } catch (e) {}
try { eventObj.eventPhase = eventPhase; } catch (e) {}
eventObj.removed = false;
arr = arr.slice(); // to avoid issues with items being removed or added during the dispatch
for (var i=0; i<l && !eventObj.immediatePropagationStopped; i++) {
@ -904,29 +906,37 @@ this.createjs = this.createjs||{};
this.init();
};
AbstractLoader.prototype = new createjs.EventDispatcher(); //TODO: TEST!
var p = AbstractLoader.prototype;
var p = AbstractLoader.prototype = new createjs.EventDispatcher();
AbstractLoader.prototype.constructor = AbstractLoader;
var s = AbstractLoader;
/**
* The RegExp pattern to use to parse file URIs. This supports simple file names, as well as full domain URIs with
* query strings. The resulting match is: protocol:$1 domain:$2 relativePath:$3 path:$4 file:$5 extension:$6 query:$7.
* @property FILE_PATTERN
* @type {RegExp}
* The Regular Expression used to test file URLS for an absolute path.
* @property ABSOLUTE_PATH
* @static
* @protected
* @type {RegExp}
* @since 0.4.2
*/
s.FILE_PATTERN = /^(?:(\w+:)\/{2}(\w+(?:\.\w+)*\/?)|(.{0,2}\/{1}))?([/.]*?(?:[^?]+)?\/)?((?:[^/?]+)\.(\w+))(?:\?(\S+)?)?$/;
s.ABSOLUTE_PATT = /^(?:\w+:)?\/{2}/i;
/**
* The RegExp pattern to use to parse path URIs. This supports protocols, relative files, and paths. The resulting
* match is: protocol:$1 relativePath:$2 path$3.
* @property PATH_PATTERN
* @type {RegExp}
* The Regular Expression used to test file URLS for an absolute path.
* @property RELATIVE_PATH
* @static
* @protected
* @type {RegExp}
* @since 0.4.2
*/
s.PATH_PATTERN = /^(?:(\w+:)\/{2})|(.{0,2}\/{1})?([/.]*?(?:[^?]+)?\/?)?$/;
s.RELATIVE_PATT = (/^[./]*?\//i);
/**
* The Regular Expression used to test file URLS for an extension. Note that URIs must already have the query string
* removed.
* @property EXTENSION_PATT
* @static
* @type {RegExp}
* @since 0.4.2
*/
s.EXTENSION_PATT = /\/?[^/]+\.(\w{1,5})$/i;
/**
* If the loader has completed loading. This provides a quick check, but also ensures that the different approaches
@ -1164,29 +1174,49 @@ this.createjs = this.createjs||{};
};
/**
* Parse a file URI using the {{#crossLink "AbstractLoader/FILE_PATTERN:property"}}{{/crossLink}} RegExp pattern.
* @method _parseURI
* @param {String} path The file path to parse.
* @return {Array} The matched file contents. Please see the FILE_PATTERN property for details on the return value.
* This will return null if it does not match.
* @protected
* Parse a file path to determine the information we need to work with it. Currently, PreloadJS needs to know:
* <ul>
* <li>If the path is absolute. Absolute paths start with a protocol (such as `http://`, `file://`, or
* `//networkPath`)</li>
* <li>If the path is relative. Relative paths start with `../` or `/path` (or similar)</li>
* <li>The file extension. This is determined by the filename with an extension. Query strings are dropped, and
* the file path is expected to follow the format `name.ext`.</li>
* </ul>
*
* <strong>Note:</strong> This has changed from earlier versions, which used a single, complicated Regular Expression, which
* was difficult to maintain, and over-aggressive in determining all file properties. It has been simplified to
* only pull out what it needs.
* @param path
* @returns {Object} An Object with an `absolute` and `relative` Boolean, as well as an optional 'extension` String
* property, which is the lowercase extension.
* @private
*/
p._parseURI = function(path) {
if (!path) { return null; }
return path.match(s.FILE_PATTERN);
};
var info = { absolute: false, relative:false };
if (path == null) { return info; };
/**
* Parse a file URI using the {{#crossLink "AbstractLoader/PATH_PATTERN"}}{{/crossLink}} RegExp pattern.
* @method _parsePath
* @param {String} path The file path to parse.
* @return {Array} The matched path contents. Please see the PATH_PATTERN property for details on the return value.
* This will return null if it does not match.
* @protected
*/
p._parsePath = function(path) {
if (!path) { return null; }
return path.match(s.PATH_PATTERN);
// Drop the query string
var queryIndex = path.indexOf("?");
if (queryIndex > -1) {
path = path.substr(0,queryIndex);
}
// Absolute
var match;
if (s.ABSOLUTE_PATT.test(path)) {
info.absolute = true;
// Relative
} else if (s.RELATIVE_PATT.test(path)) {
info.relative = true;
}
// Extension
if (match = path.match(s.EXTENSION_PATT)) {
info.extension = match[1].toLowerCase();
}
return info;
};
/**
@ -1541,6 +1571,7 @@ TODO: WINDOWS ISSUES
};
var p = LoadQueue.prototype = new createjs.AbstractLoader();
LoadQueue.prototype.constructor = LoadQueue;
var s = LoadQueue;
/**
@ -2078,7 +2109,7 @@ TODO: WINDOWS ISSUES
for (var n in this._loadItemsById) {
this._disposeItem(this._loadItemsById[n]);
}
this.init(this.useXHR);
this.init(this.useXHR, this._basePath, this._crossOrigin);
// Remove specific items
} else {
@ -2593,7 +2624,7 @@ TODO: WINDOWS ISSUES
// Determine Extension, etc.
var match = this._parseURI(item.src);
if (match != null) { item.ext = match[6]; }
if (match.extension) { item.ext = match.extension; }
if (item.type == null) {
item.type = this._getTypeByExtension(item.ext);
}
@ -2602,13 +2633,13 @@ TODO: WINDOWS ISSUES
var bp = ""; // Store the generated basePath
var useBasePath = basePath || this._basePath;
var autoId = item.src;
if (match && match[1] == null && match[3] == null) {
if (!match.absolute && !match.relative) {
if (path) {
bp = path;
var pathMatch = this._parsePath(path);
var pathMatch = this._parseURI(path);
autoId = path + autoId;
// Also append basePath
if (useBasePath != null && pathMatch && pathMatch[1] == null && pathMatch[2] == null) {
if (useBasePath != null && !pathMatch.absolute && !pathMatch.relative) {
bp = useBasePath + bp;
}
} else if (useBasePath != null) {
@ -2666,8 +2697,8 @@ TODO: WINDOWS ISSUES
// Update the extension in case the type changed:
match = this._parseURI(item.src);
if (match != null && match[6] != null) {
item.ext = match[6].toLowerCase();
if (match.extension != null) {
item.ext = match.extension;
}
}
}
@ -3252,6 +3283,7 @@ this.createjs = this.createjs||{};
};
var p = TagLoader.prototype = new createjs.AbstractLoader();
TagLoader.prototype.constructor = TagLoader;
// Protected
@ -3396,7 +3428,16 @@ this.createjs = this.createjs||{};
item.type == createjs.LoadQueue.CSS) {
this._startTagVisibility = tag.style.visibility;
tag.style.visibility = "hidden";
(document.body || document.getElementsByTagName("body")[0]).appendChild(tag);
var node = document.body || document.getElementsByTagName("body")[0];
if (node == null) {
if (item.type == createjs.LoadQueue.SVG) {
this._handleSVGError();
return;
} else {
node = document.head || document.getElementsByTagName("head");
}
}
node.appendChild(tag);
}
// Note: Previous versions didn't seem to work when we called load() for OGG tags in Firefox. Seems fixed in 15.0.1
@ -3405,6 +3446,13 @@ this.createjs = this.createjs||{};
}
};
p._handleSVGError = function() {
this._clean();
var event = new createjs.Event("error");
event.text = "SVG_NO_BODY";
this._sendError(event);
};
p._handleJSONPLoad = function(data) {
this._jsonResult = data;
};
@ -3488,7 +3536,7 @@ this.createjs = this.createjs||{};
// case createjs.LoadQueue.CSS:
//LM: We may need to remove CSS tags loaded using a LINK
tag.style.visibility = this._startTagVisibility;
(document.body || document.getElementsByTagName("body")[0]).removeChild(tag);
tag.parentNode && tag.parentNode.contains(tag) && tag.parentNode.removeChild(tag);
break;
default:
}
@ -3618,6 +3666,7 @@ this.createjs = this.createjs || {};
];
var p = XHRLoader.prototype = new createjs.AbstractLoader();
XHRLoader.prototype.constructor = XHRLoader;
//Protected
/**

File diff suppressed because it is too large Load diff

View file

@ -59,6 +59,7 @@ var Event = function(type, bubbles, cancelable) {
this.initialize(type, bubbles, cancelable);
};
var p = Event.prototype;
Event.prototype.constructor = Event;
// events:
@ -343,6 +344,7 @@ var EventDispatcher = function() {
/* this.initialize(); */ // not needed.
};
var p = EventDispatcher.prototype;
EventDispatcher.prototype.constructor = EventDispatcher;
/**
@ -547,19 +549,19 @@ var p = EventDispatcher.prototype;
* @param {Object | String | Event} eventObj An object with a "type" property, or a string type.
* While a generic object will work, it is recommended to use a CreateJS Event instance. If a string is used,
* dispatchEvent will construct an Event instance with the specified type.
* @param {Object} [target] The object to use as the target property of the event object. This will default to the
* dispatching object. <b>This parameter is deprecated and will be removed.</b>
* @return {Boolean} Returns the value of eventObj.defaultPrevented.
**/
p.dispatchEvent = function(eventObj, target) {
p.dispatchEvent = function(eventObj) {
if (typeof eventObj == "string") {
// won't bubble, so skip everything if there's no listeners:
var listeners = this._listeners;
if (!listeners || !listeners[eventObj]) { return false; }
eventObj = new createjs.Event(eventObj);
} else if (eventObj.target && eventObj.clone) {
// redispatching an active event object, so clone it:
eventObj = eventObj.clone();
}
// TODO: deprecated. Target param is deprecated, only use case is MouseEvent/mousemove, remove.
eventObj.target = target||this;
try { eventObj.target = this; } catch (e) {} // try/catch allows redispatching of native events
if (!eventObj.bubbles || !this.parent) {
this._dispatchEvent(eventObj, 2);
@ -631,8 +633,8 @@ var p = EventDispatcher.prototype;
if (eventObj && listeners) {
var arr = listeners[eventObj.type];
if (!arr||!(l=arr.length)) { return; }
eventObj.currentTarget = this;
eventObj.eventPhase = eventPhase;
try { eventObj.currentTarget = this; } catch (e) {}
try { eventObj.eventPhase = eventPhase; } catch (e) {}
eventObj.removed = false;
arr = arr.slice(); // to avoid issues with items being removed or added during the dispatch
for (var i=0; i<l && !eventObj.immediatePropagationStopped; i++) {
@ -651,6 +653,613 @@ var p = EventDispatcher.prototype;
createjs.EventDispatcher = EventDispatcher;
}());
/*
* Ticker
* Visit http://createjs.com/ for documentation, updates and examples.
*
* Copyright (c) 2010 gskinner.com, inc.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* @module CreateJS
*/
// namespace:
this.createjs = this.createjs||{};
(function() {
"use strict";
// constructor:
/**
* The Ticker provides a centralized tick or heartbeat broadcast at a set interval. Listeners can subscribe to the tick
* event to be notified when a set time interval has elapsed.
*
* Note that the interval that the tick event is called is a target interval, and may be broadcast at a slower interval
* during times of high CPU load. The Ticker class uses a static interface (ex. <code>Ticker.getPaused()</code>) and
* should not be instantiated.
*
* <h4>Example</h4>
*
* createjs.Ticker.addEventListener("tick", handleTick);
* function handleTick(event) {
* // Actions carried out each frame
* if (!event.paused) {
* // Actions carried out when the Ticker is not paused.
* }
* }
*
* To update a stage every tick, the {{#crossLink "Stage"}}{{/crossLink}} instance can also be used as a listener, as
* it will automatically update when it receives a tick event:
*
* createjs.Ticker.addEventListener("tick", stage);
*
* @class Ticker
* @uses EventDispatcher
* @static
**/
function Ticker() {
throw "Ticker cannot be instantiated.";
}
// constants:
/**
* In this mode, Ticker uses the requestAnimationFrame API, but attempts to synch the ticks to target framerate. It
* uses a simple heuristic that compares the time of the RAF return to the target time for the current frame and
* dispatches the tick when the time is within a certain threshold.
*
* This mode has a higher variance for time between frames than TIMEOUT, but does not require that content be time
* based as with RAF while gaining the benefits of that API (screen synch, background throttling).
*
* Variance is usually lowest for framerates that are a divisor of the RAF frequency. This is usually 60, so
* framerates of 10, 12, 15, 20, and 30 work well.
*
* Falls back on TIMEOUT if the requestAnimationFrame API is not supported.
* @property RAF_SYNCHED
* @static
* @type {String}
* @default "synched"
* @readonly
**/
Ticker.RAF_SYNCHED = "synched";
/**
* In this mode, Ticker passes through the requestAnimationFrame heartbeat, ignoring the target framerate completely.
* Because requestAnimationFrame frequency is not deterministic, any content using this mode should be time based.
* You can leverage {{#crossLink "Ticker/getTime"}}{{/crossLink}} and the tick event object's "delta" properties
* to make this easier.
*
* Falls back on TIMEOUT if the requestAnimationFrame API is not supported.
* @property RAF
* @static
* @type {String}
* @default "raf"
* @readonly
**/
Ticker.RAF = "raf";
/**
* In this mode, Ticker uses the setTimeout API. This provides predictable, adaptive frame timing, but does not
* provide the benefits of requestAnimationFrame (screen synch, background throttling).
* @property TIMEOUT
* @static
* @type {String}
* @default "timer"
* @readonly
**/
Ticker.TIMEOUT = "timeout";
// static events:
/**
* Dispatched each tick. The event will be dispatched to each listener even when the Ticker has been paused using
* {{#crossLink "Ticker/setPaused"}}{{/crossLink}}.
*
* <h4>Example</h4>
*
* createjs.Ticker.addEventListener("tick", handleTick);
* function handleTick(event) {
* console.log("Paused:", event.paused, event.delta);
* }
*
* @event tick
* @param {Object} target The object that dispatched the event.
* @param {String} type The event type.
* @param {Boolean} paused Indicates whether the ticker is currently paused.
* @param {Number} delta The time elapsed in ms since the last tick.
* @param {Number} time The total time in ms since Ticker was initialized.
* @param {Number} runTime The total time in ms that Ticker was not paused since it was initialized. For example,
* you could determine the amount of time that the Ticker has been paused since initialization with time-runTime.
* @since 0.6.0
*/
// public static properties:
/**
* Deprecated in favour of {{#crossLink "Ticker/timingMode"}}{{/crossLink}}, and will be removed in a future version. If true, timingMode will
* use {{#crossLink "Ticker/RAF_SYNCHED"}}{{/crossLink}} by default.
* @deprecated Deprecated in favour of {{#crossLink "Ticker/timingMode"}}{{/crossLink}}.
* @property useRAF
* @static
* @type {Boolean}
* @default false
**/
Ticker.useRAF = false;
/**
* Specifies the timing api (setTimeout or requestAnimationFrame) and mode to use. See
* {{#crossLink "Ticker/TIMEOUT"}}{{/crossLink}}, {{#crossLink "Ticker/RAF"}}{{/crossLink}}, and
* {{#crossLink "Ticker/RAF_SYNCHED"}}{{/crossLink}} for mode details.
* @property timingMode
* @static
* @type {String}
* @default Ticker.TIMEOUT
**/
Ticker.timingMode = null;
/**
* Specifies a maximum value for the delta property in the tick event object. This is useful when building time
* based animations and systems to prevent issues caused by large time gaps caused by background tabs, system sleep,
* alert dialogs, or other blocking routines. Double the expected frame duration is often an effective value
* (ex. maxDelta=50 when running at 40fps).
*
* This does not impact any other values (ex. time, runTime, etc), so you may experience issues if you enable maxDelta
* when using both delta and other values.
*
* If 0, there is no maximum.
* @property maxDelta
* @static
* @type {number}
* @default 0
*/
Ticker.maxDelta = 0;
// mix-ins:
// EventDispatcher methods:
Ticker.removeEventListener = null;
Ticker.removeAllEventListeners = null;
Ticker.dispatchEvent = null;
Ticker.hasEventListener = null;
Ticker._listeners = null;
createjs.EventDispatcher.initialize(Ticker); // inject EventDispatcher methods.
Ticker._addEventListener = Ticker.addEventListener;
Ticker.addEventListener = function() {
!Ticker._inited&&Ticker.init();
return Ticker._addEventListener.apply(Ticker, arguments);
};
// private static properties:
/**
* @property _paused
* @type {Boolean}
* @protected
**/
Ticker._paused = false;
/**
* @property _inited
* @type {Boolean}
* @protected
**/
Ticker._inited = false;
/**
* @property _startTime
* @type {Number}
* @protected
**/
Ticker._startTime = 0;
/**
* @property _pausedTime
* @type {Number}
* @protected
**/
Ticker._pausedTime=0;
/**
* The number of ticks that have passed
* @property _ticks
* @type {Number}
* @protected
**/
Ticker._ticks = 0;
/**
* The number of ticks that have passed while Ticker has been paused
* @property _pausedTicks
* @type {Number}
* @protected
**/
Ticker._pausedTicks = 0;
/**
* @property _interval
* @type {Number}
* @protected
**/
Ticker._interval = 50;
/**
* @property _lastTime
* @type {Number}
* @protected
**/
Ticker._lastTime = 0;
/**
* @property _times
* @type {Array}
* @protected
**/
Ticker._times = null;
/**
* @property _tickTimes
* @type {Array}
* @protected
**/
Ticker._tickTimes = null;
/**
* Stores the timeout or requestAnimationFrame id.
* @property _timerId
* @type {Number}
* @protected
**/
Ticker._timerId = null;
/**
* True if currently using requestAnimationFrame, false if using setTimeout.
* @property _raf
* @type {Boolean}
* @protected
**/
Ticker._raf = true;
// public static methods:
/**
* Starts the tick. This is called automatically when the first listener is added.
* @method init
* @static
**/
Ticker.init = function() {
if (Ticker._inited) { return; }
Ticker._inited = true;
Ticker._times = [];
Ticker._tickTimes = [];
Ticker._startTime = Ticker._getTime();
Ticker._times.push(Ticker._lastTime = 0);
Ticker.setInterval(Ticker._interval);
};
/**
* Stops the Ticker and removes all listeners. Use init() to restart the Ticker.
* @method reset
* @static
**/
Ticker.reset = function() {
if (Ticker._raf) {
var f = window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.oCancelAnimationFrame || window.msCancelAnimationFrame;
f&&f(Ticker._timerId);
} else {
clearTimeout(Ticker._timerId);
}
Ticker.removeAllEventListeners("tick");
Ticker._timerId = null;
Ticker._inited = false;
};
/**
* Sets the target time (in milliseconds) between ticks. Default is 50 (20 FPS).
*
* Note actual time between ticks may be more than requested depending on CPU load.
* @method setInterval
* @static
* @param {Number} interval Time in milliseconds between ticks. Default value is 50.
**/
Ticker.setInterval = function(interval) {
Ticker._interval = interval;
if (!Ticker._inited) { return; }
Ticker._setupTick();
};
/**
* Returns the current target time between ticks, as set with {{#crossLink "Ticker/setInterval"}}{{/crossLink}}.
* @method getInterval
* @static
* @return {Number} The current target interval in milliseconds between tick events.
**/
Ticker.getInterval = function() {
return Ticker._interval;
};
/**
* Sets the target frame rate in frames per second (FPS). For example, with an interval of 40, <code>getFPS()</code>
* will return 25 (1000ms per second divided by 40 ms per tick = 25fps).
* @method setFPS
* @static
* @param {Number} value Target number of ticks broadcast per second.
**/
Ticker.setFPS = function(value) {
Ticker.setInterval(1000/value);
};
/**
* Returns the target frame rate in frames per second (FPS). For example, with an interval of 40, <code>getFPS()</code>
* will return 25 (1000ms per second divided by 40 ms per tick = 25fps).
* @method getFPS
* @static
* @return {Number} The current target number of frames / ticks broadcast per second.
**/
Ticker.getFPS = function() {
return 1000/Ticker._interval;
};
/**
* Returns the average time spent within a tick. This can vary significantly from the value provided by getMeasuredFPS
* because it only measures the time spent within the tick execution stack.
*
* Example 1: With a target FPS of 20, getMeasuredFPS() returns 20fps, which indicates an average of 50ms between
* the end of one tick and the end of the next. However, getMeasuredTickTime() returns 15ms. This indicates that
* there may be up to 35ms of "idle" time between the end of one tick and the start of the next.
*
* Example 2: With a target FPS of 30, getFPS() returns 10fps, which indicates an average of 100ms between the end of
* one tick and the end of the next. However, getMeasuredTickTime() returns 20ms. This would indicate that something
* other than the tick is using ~80ms (another script, DOM rendering, etc).
* @method getMeasuredTickTime
* @static
* @param {Number} [ticks] The number of previous ticks over which to measure the average time spent in a tick.
* Defaults to the number of ticks per second. To get only the last tick's time, pass in 1.
* @return {Number} The average time spent in a tick in milliseconds.
**/
Ticker.getMeasuredTickTime = function(ticks) {
var ttl=0, times=Ticker._tickTimes;
if (!times || times.length < 1) { return -1; }
// by default, calculate average for the past ~1 second:
ticks = Math.min(times.length, ticks||(Ticker.getFPS()|0));
for (var i=0; i<ticks; i++) { ttl += times[i]; }
return ttl/ticks;
};
/**
* Returns the actual frames / ticks per second.
* @method getMeasuredFPS
* @static
* @param {Number} [ticks] The number of previous ticks over which to measure the actual frames / ticks per second.
* Defaults to the number of ticks per second.
* @return {Number} The actual frames / ticks per second. Depending on performance, this may differ
* from the target frames per second.
**/
Ticker.getMeasuredFPS = function(ticks) {
var times = Ticker._times;
if (!times || times.length < 2) { return -1; }
// by default, calculate fps for the past ~1 second:
ticks = Math.min(times.length-1, ticks||(Ticker.getFPS()|0));
return 1000/((times[0]-times[ticks])/ticks);
};
/**
* Changes the "paused" state of the Ticker, which can be retrieved by the {{#crossLink "Ticker/getPaused"}}{{/crossLink}}
* method, and is passed as the "paused" property of the <code>tick</code> event. When the ticker is paused, all
* listeners will still receive a tick event, but the <code>paused</code> property will be false.
*
* Note that in EaselJS v0.5.0 and earlier, "pauseable" listeners would <strong>not</strong> receive the tick
* callback when Ticker was paused. This is no longer the case.
*
* <h4>Example</h4>
*
* createjs.Ticker.addEventListener("tick", handleTick);
* createjs.Ticker.setPaused(true);
* function handleTick(event) {
* console.log("Paused:", event.paused, createjs.Ticker.getPaused());
* }
*
* @method setPaused
* @static
* @param {Boolean} value Indicates whether to pause (true) or unpause (false) Ticker.
**/
Ticker.setPaused = function(value) {
Ticker._paused = value;
};
/**
* Returns a boolean indicating whether Ticker is currently paused, as set with {{#crossLink "Ticker/setPaused"}}{{/crossLink}}.
* When the ticker is paused, all listeners will still receive a tick event, but this value will be false.
*
* Note that in EaselJS v0.5.0 and earlier, "pauseable" listeners would <strong>not</strong> receive the tick
* callback when Ticker was paused. This is no longer the case.
*
* <h4>Example</h4>
*
* createjs.Ticker.addEventListener("tick", handleTick);
* createjs.Ticker.setPaused(true);
* function handleTick(event) {
* console.log("Paused:", createjs.Ticker.getPaused());
* }
*
* @method getPaused
* @static
* @return {Boolean} Whether the Ticker is currently paused.
**/
Ticker.getPaused = function() {
return Ticker._paused;
};
/**
* Returns the number of milliseconds that have elapsed since Ticker was initialized via {{#crossLink "Ticker/init"}}.
* Returns -1 if Ticker has not been initialized. For example, you could use
* this in a time synchronized animation to determine the exact amount of time that has elapsed.
* @method getTime
* @static
* @param {Boolean} [runTime=false] If true only time elapsed while Ticker was not paused will be returned.
* If false, the value returned will be total time elapsed since the first tick event listener was added.
* @return {Number} Number of milliseconds that have elapsed since Ticker was initialized or -1.
**/
Ticker.getTime = function(runTime) {
return Ticker._startTime ? Ticker._getTime() - Ticker._startTime - (runTime ? Ticker._pausedTime : 0) : -1;
};
/**
* Similar to getTime(), but returns the time included with the current (or most recent) tick event object.
* @method getEventTime
* @param runTime {Boolean} [runTime=false] If true, the runTime property will be returned instead of time.
* @returns {number} The time or runTime property from the most recent tick event or -1.
*/
Ticker.getEventTime = function(runTime) {
return Ticker._startTime ? (Ticker._lastTime || Ticker._startTime) - (runTime ? Ticker._pausedTime : 0) : -1;
};
/**
* Returns the number of ticks that have been broadcast by Ticker.
* @method getTicks
* @static
* @param {Boolean} pauseable Indicates whether to include ticks that would have been broadcast
* while Ticker was paused. If true only tick events broadcast while Ticker is not paused will be returned.
* If false, tick events that would have been broadcast while Ticker was paused will be included in the return
* value. The default value is false.
* @return {Number} of ticks that have been broadcast.
**/
Ticker.getTicks = function(pauseable) {
return Ticker._ticks - (pauseable ?Ticker._pausedTicks : 0);
};
// private static methods:
/**
* @method _handleSynch
* @static
* @protected
**/
Ticker._handleSynch = function() {
Ticker._timerId = null;
Ticker._setupTick();
// run if enough time has elapsed, with a little bit of flexibility to be early:
if (Ticker._getTime() - Ticker._lastTime >= (Ticker._interval-1)*0.97) {
Ticker._tick();
}
};
/**
* @method _handleRAF
* @static
* @protected
**/
Ticker._handleRAF = function() {
Ticker._timerId = null;
Ticker._setupTick();
Ticker._tick();
};
/**
* @method _handleTimeout
* @static
* @protected
**/
Ticker._handleTimeout = function() {
Ticker._timerId = null;
Ticker._setupTick();
Ticker._tick();
};
/**
* @method _setupTick
* @static
* @protected
**/
Ticker._setupTick = function() {
if (Ticker._timerId != null) { return; } // avoid duplicates
var mode = Ticker.timingMode||(Ticker.useRAF&&Ticker.RAF_SYNCHED);
if (mode == Ticker.RAF_SYNCHED || mode == Ticker.RAF) {
var f = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame;
if (f) {
Ticker._timerId = f(mode == Ticker.RAF ? Ticker._handleRAF : Ticker._handleSynch);
Ticker._raf = true;
return;
}
}
Ticker._raf = false;
Ticker._timerId = setTimeout(Ticker._handleTimeout, Ticker._interval);
};
/**
* @method _tick
* @static
* @protected
**/
Ticker._tick = function() {
var time = Ticker._getTime();
var adjTime = time-Ticker._startTime;
var elapsedTime = time-Ticker._lastTime;
var paused = Ticker._paused;
Ticker._ticks++;
if (paused) {
Ticker._pausedTicks++;
Ticker._pausedTime += elapsedTime;
}
Ticker._lastTime = time;
if (Ticker.hasEventListener("tick")) {
var event = new createjs.Event("tick");
var maxDelta = Ticker.maxDelta;
event.delta = (maxDelta && elapsedTime > maxDelta) ? maxDelta : elapsedTime;
event.paused = paused;
event.time = adjTime;
event.runTime = adjTime-Ticker._pausedTime;
Ticker.dispatchEvent(event);
}
Ticker._tickTimes.unshift(Ticker._getTime()-time);
while (Ticker._tickTimes.length > 100) { Ticker._tickTimes.pop(); }
Ticker._times.unshift(adjTime);
while (Ticker._times.length > 100) { Ticker._times.pop(); }
};
/**
* @method _getTime
* @static
* @protected
**/
var now = window.performance && (performance.now || performance.mozNow || performance.msNow || performance.oNow || performance.webkitNow);
Ticker._getTime = function() {
return (now&&now.call(performance))||(new Date().getTime());
};
createjs.Ticker = Ticker;
}());
/*
* Tween
* Visit http://createjs.com/ for documentation, updates and examples.
*
@ -710,11 +1319,6 @@ createjs.EventDispatcher = EventDispatcher;
* //Tween complete
* }
*
* <h4>Required Support<h4>
* Tweenjs requires a ticker function, which is included in <a href="http://www.easeljs.com">EaselJS</a>.
* If you are not using EaselJS, you must build your own ticker function that calls {{#crossLink "Tween/tick"}}{{/crossLink}}
* on the tweens.
*
* <h4>Browser Support</h4>
* TweenJS will work in all browsers.
*
@ -764,7 +1368,7 @@ this.createjs = this.createjs||{};
* All properties default to false. Supported props are:<UL>
* <LI> loop: sets the loop property on this tween.</LI>
* <LI> useTicks: uses ticks for all durations instead of milliseconds.</LI>
* <LI> ignoreGlobalPause: sets the ignoreGlobalPause property on this tween.</LI>
* <LI> ignoreGlobalPause: sets the {{#crossLink "Tween/ignoreGlobalPause:property"}}{{/crossLink}} property on this tween.</LI>
* <LI> override: if true, `Tween.removeTweens(target)` will be called to remove any other tweens with the same target.
* <LI> paused: indicates whether to start the tween paused.</LI>
* <LI> position: indicates the initial position for this tween.</LI>
@ -779,6 +1383,7 @@ var Tween = function(target, props, pluginData) {
this.initialize(target, props, pluginData);
};
var p = Tween.prototype = new createjs.EventDispatcher();
Tween.prototype.constructor = Tween;
// static interface:
/**
@ -843,7 +1448,7 @@ var p = Tween.prototype = new createjs.EventDispatcher();
* All properties default to false. Supported props are:<UL>
* <LI> loop: sets the loop property on this tween.</LI>
* <LI> useTicks: uses ticks for all durations instead of milliseconds.</LI>
* <LI> ignoreGlobalPause: sets the ignoreGlobalPause property on this tween.</LI>
* <LI> ignoreGlobalPause: sets the {{#crossLink "Tween/ignoreGlobalPause:property"}}{{/crossLink}} property on this tween.</LI>
* <LI> override: if true, Tween.removeTweens(target) will be called to remove any other tweens with the same target.
* <LI> paused: indicates whether to start the tween paused.</LI>
* <LI> position: indicates the initial position for this tween.</LI>
@ -863,15 +1468,13 @@ var p = Tween.prototype = new createjs.EventDispatcher();
};
/**
* Advances all tweens. This typically uses the Ticker class (available in the EaselJS library), but you can call it
* Advances all tweens. This typically uses the {{#crossLink "Ticker"}}{{/crossLink}} class, but you can call it
* manually if you prefer to use your own "heartbeat" implementation.
*
* Note: Currently, EaselJS must be included <em>before</em> TweenJS to ensure Ticker exists during initialization.
* @method tick
* @param {Number} delta The change in time in milliseconds since the last tick. Required unless all tweens have
* <code>useTicks</code> set to true.
* @param {Boolean} paused Indicates whether a global pause is in effect. Tweens with <code>ignoreGlobalPause</code>
* will ignore this, but all others will pause if this is true.
* @param {Boolean} paused Indicates whether a global pause is in effect. Tweens with {{#crossLink "Tween/ignoreGlobalPause:property"}}{{/crossLink}}
* will ignore this, but all others will pause if this is `true`.
* @static
*/
Tween.tick = function(delta, paused) {
@ -887,7 +1490,8 @@ var p = Tween.prototype = new createjs.EventDispatcher();
* Handle events that result from Tween being used as an event handler. This is included to allow Tween to handle
* tick events from <code>createjs.Ticker</code>. No other events are handled in Tween.
* @method handleEvent
* @param {Object} event An event object passed in by the EventDispatcher. Will usually be of type "tick".
* @param {Object} event An event object passed in by the {{#crossLink "EventDispatcher"}}{{/crossLink}}. Will
* usually be of type "tick".
* @private
* @static
* @since 0.4.2
@ -909,9 +1513,10 @@ var p = Tween.prototype = new createjs.EventDispatcher();
if (!target.tweenjs_count) { return; }
var tweens = Tween._tweens;
for (var i=tweens.length-1; i>=0; i--) {
if (tweens[i]._target == target) {
tweens[i]._paused = true;
tweens.splice(i,1);
var tween = tweens[i];
if (tween._target == target) {
tween._paused = true;
tweens.splice(i, 1);
}
}
target.tweenjs_count = 0;
@ -927,7 +1532,7 @@ var p = Tween.prototype = new createjs.EventDispatcher();
var tweens = Tween._tweens;
for (var i= 0, l=tweens.length; i<l; i++) {
var tween = tweens[i];
tween.paused = true;
tween._paused = true;
tween.target.tweenjs_count = 0;
}
tweens.length = 0;
@ -947,8 +1552,8 @@ var p = Tween.prototype = new createjs.EventDispatcher();
};
/**
* Installs a plugin, which can modify how certain properties are handled when tweened. See the CSSPlugin for an
* example of how to write TweenJS plugins.
* Installs a plugin, which can modify how certain properties are handled when tweened. See the {{#crossLink "CSSPlugin"}}{{/crossLink}}
* for an example of how to write TweenJS plugins.
* @method installPlugin
* @static
* @param {Object} plugin The plugin class to install
@ -1000,9 +1605,10 @@ var p = Tween.prototype = new createjs.EventDispatcher();
// public properties:
/**
* Causes this tween to continue playing when a global pause is active. For example, if TweenJS is using Ticker,
* then setting this to true (the default) will cause this tween to be paused when <code>Ticker.setPaused(true)</code> is called.
* See Tween.tick() for more info. Can be set via the props param.
* Causes this tween to continue playing when a global pause is active. For example, if TweenJS is using {{#crossLink "Ticker"}}{{/crossLink}},
* then setting this to true (the default) will cause this tween to be paused when <code>Ticker.setPaused(true)</code>
* is called. See the Tween {{#crossLink "Tween/tick"}}{{/crossLink}} method for more info. Can be set via the props
* parameter.
* @property ignoreGlobalPause
* @type Boolean
* @default false
@ -1360,7 +1966,8 @@ var p = Tween.prototype = new createjs.EventDispatcher();
/**
* Advances this tween by the specified amount of time in milliseconds (or ticks if <code>useTicks</code> is true).
* This is normally called automatically by the Tween engine (via <code>Tween.tick</code>), but is exposed for advanced uses.
* This is normally called automatically by the Tween engine (via <code>Tween.tick</code>), but is exposed for
* advanced uses.
* @method tick
* @param {Number} delta The time to advance in milliseconds (or ticks if <code>useTicks</code> is true).
*/
@ -1376,6 +1983,7 @@ var p = Tween.prototype = new createjs.EventDispatcher();
* @return {Tween} This tween instance (for chaining calls)
*/
p.setPaused = function(value) {
if (this._paused === !!value) { return this; }
this._paused = !!value;
Tween._register(this, !value);
return this;
@ -1630,6 +2238,7 @@ var Timeline = function(tweens, labels, props) {
this.initialize(tweens, labels, props);
};
var p = Timeline.prototype = new createjs.EventDispatcher();
Timeline.prototype.constructor = Timeline;
// public properties:
@ -1953,7 +2562,7 @@ var p = Timeline.prototype = new createjs.EventDispatcher();
* @param {String|Number} positionOrLabel A numeric position value or label string.
**/
p.resolve = function(positionOrLabel) {
var pos = parseFloat(positionOrLabel);
var pos = Number(positionOrLabel);
if (isNaN(pos)) { pos = this._labels[positionOrLabel]; }
return pos;
};
@ -2755,6 +3364,6 @@ this.createjs = this.createjs || {};
* @type String
* @static
**/
s.buildDate = /*date*/"Thu, 12 Dec 2013 23:37:07 GMT"; // injected by build process
s.buildDate = /*date*/"Fri, 24 Oct 2014 16:09:53 GMT"; // injected by build process
})();