Merge branch 'master' of github.com:codecombat/codecombat

This commit is contained in:
iAladdin 2014-11-19 11:19:41 +08:00
commit 43f56c7ff7
55 changed files with 7031 additions and 7101 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

@ -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
@ -10,6 +11,7 @@ module.exports = LevelOptions =
requiredGear: {feet: 'simple-boots'}
restrictedGear: {feet: 'leather-boots'}
'gems-in-the-deep':
disableSpaces: true
hidesSubmitUntilRun: true
hidesPlayButton: true
hidesRunShortcut: true
@ -20,6 +22,7 @@ module.exports = LevelOptions =
requiredGear: {feet: 'simple-boots'}
restrictedGear: {feet: 'leather-boots'}
'shadow-guard':
disableSpaces: true
hidesSubmitUntilRun: true
hidesPlayButton: true
hidesRunShortcut: true
@ -30,6 +33,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 +52,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 +62,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
@ -66,6 +72,7 @@ module.exports = LevelOptions =
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', waist: 'leather-belt'}
restrictedGear: {feet: 'leather-boots'}
'favorable-odds':
disableSpaces: true
hidesRunShortcut: true
hidesHUD: true
hidesSay: true
@ -74,6 +81,7 @@ module.exports = LevelOptions =
requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword'}
restrictedGear: {feet: 'leather-boots'}
'the-raised-sword':
disableSpaces: true
hidesRunShortcut: true
hidesHUD: true
hidesSay: true

View file

@ -25,6 +25,7 @@ module.exports = class MusicPlayer extends CocoClass
@onPlayMusic(@standingBy) if @standingBy
onPlayMusic: (e) ->
return if application.isIPadApp # Hard to measure, but just guessing this will save memory.
src = e.file
src = "/file#{e.file}#{AudioPlayer.ext}"
if (not e.file) or src is @currentMusic?.src

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
@ -314,10 +312,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()
@ -456,7 +456,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 +544,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 +582,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 +699,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,9 +472,13 @@ module.exports = class World
perf.t1 = now()
if w.thangs.length
for thangConfig in o.thangs when not w.thangMap[thangConfig.id]
w.thangs.push thang = Thang.deserialize(thangConfig, w, classMap, level.levelComponents)
w.setThang thang
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
w.thangs = (Thang.deserialize(thang, w, classMap, level.levelComponents) for thang in o.thangs)
w.setThang thang for thang in w.thangs
@ -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

@ -1,15 +1,15 @@
module.exports = nativeDescription: "български език", englishDescription: "Bulgarian", translation:
home:
slogan: "Научи се да програмираш, докато играеш игра "
no_ie: "CodeCombat не работи под Internet Explorer 8 или по-стар. Съжалявам!" # Warning that only shows up in IE8 and older
no_ie: "CodeCombat не работи под Internet Explorer 8 или по-стари версии. Съжалявам!" # Warning that only shows up in IE8 and older
no_mobile: "CodeCombat не е направен за мобилни устройства и може да не работи!" # Warning that shows up on mobile devices
play: "Играй" # The big play button that just starts playing a level
# old_browser: "Uh oh, your browser is too old to run CodeCombat. Sorry!" # Warning that shows up on really old Firefox/Chrome/Safari
# old_browser_suffix: "You can try anyway, but it probably won't work."
old_browser: "О, не! Браузърът ти е твърде стар за CodeCombat. Съжалявам!" #"Uh oh, your browser is too old to run CodeCombat. Sorry!" # Warning that shows up on really old Firefox/Chrome/Safari
old_browser_suffix: "Все пак можеш да опиваш, но най-вероятно няма да проработи." # "You can try anyway, but it probably won't work."
# campaign: "Campaign"
# for_beginners: "For Beginners"
for_beginners: "За начинаещи" # "For Beginners"
# multiplayer: "Multiplayer" # Not currently shown on home page
# for_developers: "For Developers" # Not currently shown on home page.
for_developers: "За разработчици" # "For Developers" # Not currently shown on home page.
nav:
play: "Нива" # The top nav bar entry where players choose which levels to play
@ -19,16 +19,16 @@ module.exports = nativeDescription: "български език", englishDescri
forum: "Форум"
account: "Сметката"
profile: "Профил"
# stats: "Stats"
stats: "Статистики"
# code: "Code"
# admin: "Admin" # Only shows up when you are an admin
home: "Начало"
# contribute: "Contribute"
contribute: "Допринеси" # "Contribute"
# legal: "Legal"
about: "За нас"
contact: "Контакти"
# twitter_follow: "Follow"
# teachers: "Teachers"
twitter_follow: "Започни да следиш" # "Follow"
teachers: "Учители" # "Teachers"
modal:
close: "Затвори"
@ -38,23 +38,23 @@ module.exports = nativeDescription: "български език", englishDescri
page_not_found: "Страницата не е намерена"
diplomat_suggestion:
title: "Дай да помогни да преводи CodeCombat!" # This shows up when a player switches to a non-English language using the language selector.
# sub_heading: "We need your language skills."
title: "Помогни да преведем CodeCombat!" # This shows up when a player switches to a non-English language using the language selector.
sub_heading: "Имаме нужда от твоите езикови познания!" # "We need your language skills."
pitch_body: "We develop CodeCombat in English, but we already have players all over the world. Many of them want to play in Bulgarian but don't speak English, so if you can speak both, please consider signing up to be a Diplomat and help translate both the CodeCombat website and all the levels into Bulgarian."
missing_translations: "Until we can translate everything into Bulgarian, you'll see English when Bulgarian isn't available."
learn_more: "Научи повече за ставане Дипломат"
# subscribe_as_diplomat: "Subscribe as a Diplomat"
learn_more: "Научи повече за това как да станеш Дипломат"
subscribe_as_diplomat: "Стани дипломат" # "Subscribe as a Diplomat"
play:
# play_as: "Play As" # Ladder page
# spectate: "Spectate" # Ladder page
# players: "players" # Hover over a level on /play
# hours_played: "hours played" # Hover over a level on /play
# items: "Items" # Tooltip on item shop button from /play
# unlock: "Unlock" # For purchasing items and heroes
# confirm: "Confirm"
items: "Предмети" # "Items" # Tooltip on item shop button from /play
unlock: "Отключи" # "Unlock" # For purchasing items and heroes
confirm: "Потвърди"
# owned: "Owned" # For items you own
# locked: "Locked"
locked: "Заключено"
# available: "Available"
# skills_granted: "Skills Granted" # Property documentation details
# heroes: "Heroes" # Tooltip on hero shop button from /play
@ -64,27 +64,27 @@ module.exports = nativeDescription: "български език", englishDescri
# next: "Next" # Go from choose hero to choose inventory before playing a level
# change_hero: "Change Hero" # Go back from choose inventory to choose hero
# choose_inventory: "Equip Items"
# buy_gems: "Buy Gems"
# older_campaigns: "Older Campaigns"
# anonymous: "Anonymous Player"
# level_difficulty: "Difficulty: "
# campaign_beginner: "Beginner Campaign"
buy_gems: "Купи скъпоценни камъни" # "Buy Gems"
older_campaigns: "Предишни капмании" # "Older Campaigns"
anonymous: "Анонимен играч" # "Anonymous Player"
level_difficulty: "Трудност" # "Difficulty: "
campaign_beginner: "Кампания за начинаещи" # "Beginner Campaign"
# awaiting_levels_adventurer_prefix: "We release five levels per week."
# awaiting_levels_adventurer: "Sign up as an Adventurer"
# awaiting_levels_adventurer_suffix: "to be the first to play new levels."
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: "You can jump to any level below, or discuss the levels on "
# adventurer_forum: "the Adventurer forum"
adventurer_forum: "Приключенският форум" # "the Adventurer forum"
# adventurer_suffix: "."
# campaign_old_beginner: "Old Beginner Campaign"
campaign_old_beginner: "Предишни кампании за начинаещи" # "Old Beginner Campaign"
# campaign_old_beginner_description: "... in which you learn the wizardry of programming."
# campaign_dev: "Random Harder Levels"
campaign_dev: "Случайни трудни нива" # "Random Harder Levels"
# campaign_dev_description: "... in which you learn the interface while doing something a little harder."
# campaign_multiplayer: "Multiplayer Arenas"
# campaign_multiplayer_description: "... in which you code head-to-head against other players."
# campaign_player_created: "Player-Created"
# campaign_player_created_description: "... in which you battle against the creativity of your fellow <a href=\"/contribute#artisan\">Artisan Wizards</a>."
# campaign_classic_algorithms: "Classic Algorithms"
campaign_classic_algorithms: "Класически алгоритми" # "Classic Algorithms"
# campaign_classic_algorithms_description: "... in which you learn the most popular algorithms in Computer Science."
# campaign_forest: "Forest Campaign"
# campaign_dungeon: "Dungeon Campaign"
@ -101,27 +101,27 @@ module.exports = nativeDescription: "български език", englishDescri
# finishing: "Finishing"
signup:
create_account_title: "Создавай нов сметката за да записва прогрес"
create_account_title: "Създавай нов акаунт, за да запазиш прогреса си"
description: "Е безплатен. Само ще трабва неколко неща и ти ще си готов:"
email_announcements: "Получава анонци през имейл"
email_announcements: "Получава анонси по имейл"
# coppa: "13+ or non-USA "
coppa_why: "(Защо?)"
creating: "Създаване на профил..."
sign_up: "Регистриране"
log_in: "Вход с парола"
social_signup: "Или, можеш да зарегистрираваш през Facebook или G+:"
required: "Трабва да влезиш преди можеш да ходиш на там."
social_signup: "Или, можеш да се регистрираш през Facebook или G+:"
required: "Трабва да влезеш преди можеш да ходиш на там."
recover:
recover_account_title: "Възстанови Акаунт"
send_password: "Изпрати парола за възстановяване"
# recovery_sent: "Recovery email sent."
recovery_sent: "Писмото за възстановяване на парола е изпратено." # "Recovery email sent."
# items:
# primary: "Primary"
# secondary: "Secondary"
# armor: "Armor"
# accessories: "Accessories"
accessories: "Аксесоари" # "Accessories"
# misc: "Misc"
# books: "Books"
@ -132,8 +132,8 @@ module.exports = nativeDescription: "български език", englishDescri
send: "Изпрати"
cancel: "Отказ"
save: "Запис"
publish: "Публикирай"
create: "Создавай"
publish: "Публикувай"
create: "Създай"
# manual: "Manual"
# fork: "Fork"
# play: "Play" # When used as an action verb, like "Play next level"
@ -145,11 +145,11 @@ module.exports = nativeDescription: "български език", englishDescri
general:
and: "и"
name: "Име"
# date: "Date"
date: "Дата" # "Date"
# body: "Body"
version: "Версия"
# commit_msg: "Commit Message"
# version_history: "Version History"
version_history: "Предишни версии" # "Version History"
# version_history_for: "Version History for: "
# result: "Result"
results: "Резултати"
@ -157,7 +157,7 @@ module.exports = nativeDescription: "български език", englishDescri
or: "или"
# subject: "Subject"
email: "Email"
# password: "Password"
password: "Парола" # "Password"
message: "Съобщение"
# code: "Code"
# ladder: "Ladder"
@ -405,10 +405,10 @@ module.exports = nativeDescription: "български език", englishDescri
# cla_prefix: "To save changes, first you must agree to our"
# cla_url: "CLA"
# cla_suffix: "."
# cla_agree: "I AGREE"
cla_agree: "СЪГЛАСЕН СЪМ" # "I AGREE"
# contact:
# contact_us: "Contact CodeCombat"
contact_us: "Свържи се с CodeCombat" # "Contact CodeCombat"
# welcome: "Good to hear from you! Use this form to send us email. "
# contribute_prefix: "If you're interested in contributing, check out our "
# contribute_page: "contribute page"
@ -430,17 +430,17 @@ module.exports = nativeDescription: "български език", englishDescri
# password_tab: "Password"
# emails_tab: "Emails"
# admin: "Admin"
# new_password: "New Password"
new_password: "Нова парола" # "New Password"
# new_password_verify: "Verify"
# email_subscriptions: "Email Subscriptions"
# email_subscriptions_none: "No Email Subscriptions."
# email_announcements: "Announcements"
email_announcements: "Съобщения" # "Announcements"
# email_announcements_description: "Get emails on the latest news and developments at CodeCombat."
# email_notifications: "Notifications"
email_notifications: "Известия" # "Notifications"
# email_notifications_summary: "Controls for personalized, automatic email notifications related to your CodeCombat activity."
# email_any_notes: "Any Notifications"
# email_any_notes_description: "Disable to stop all activity notification emails."
# email_news: "News"
email_news: "Новини" # "News"
# email_recruit_notes: "Job Opportunities"
# email_recruit_notes_description: "If you play really well, we may contact you about getting you a (better) job."
# contributor_emails: "Contributor Class Emails"

View file

@ -64,14 +64,14 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
next: "Nächster" # Go from choose hero to choose inventory before playing a level
change_hero: "Held wechseln" # Go back from choose inventory to choose hero
choose_inventory: "Gegenstände ausrüsten"
# buy_gems: "Buy Gems"
buy_gems: "Edelsteine kaufen"
older_campaigns: "Ältere Kampagne"
anonymous: "Anonymer Spieler"
level_difficulty: "Schwierigkeit: "
campaign_beginner: "Anfängerkampagne"
# awaiting_levels_adventurer_prefix: "We release five levels per week."
# awaiting_levels_adventurer: "Sign up as an Adventurer"
# awaiting_levels_adventurer_suffix: "to be the first to play new levels."
awaiting_levels_adventurer_prefix: "Wir veröffentlichen fünf Levels pro Woche."
awaiting_levels_adventurer: "Registriere dich als ein Abenteurer"
awaiting_levels_adventurer_suffix: "sei der Erste, der neue Levels spielt."
choose_your_level: "Wähle dein Level" # The rest of this section is the old play view at /play-old and isn't very important.
adventurer_prefix: "Du kannst zu jedem Level springen oder diskutiere die Level "
adventurer_forum: "im Abenteurerforum"
@ -95,10 +95,10 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
logging_in: "Logge ein"
log_out: "Ausloggen"
recover: "Account wiederherstellen"
# authenticate_gplus: "Authenticate G+"
# load_profile: "Load G+ Profile"
# load_email: "Load G+ Email"
# finishing: "Finishing"
authenticate_gplus: "Authentifiziere G+"
load_profile: "Lade G+ Profil"
load_email: "Lade G+ Email"
finishing: "Fertigstellen"
signup:
create_account_title: "Account anlegen, um Fortschritt zu speichern"
@ -172,7 +172,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
medium: "Mittel"
hard: "Schwer"
player: "Spieler"
# player_level: "Level" # Like player level 5, not like level: Dungeons of Kithgard
player_level: "Level" # Like player level 5, not like level: Dungeons of Kithgard
units:
second: "Sekunde"
@ -307,25 +307,25 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
choose_inventory: "Gegenstände ausrüsten"
equipped_item: "Hinzugefügt"
available_item: "Verfügbar"
# restricted_title: "Restricted"
restricted_title: "Eingeschränkt"
should_equip: "(Doppelklick zum Hinzufügen)"
equipped: "(hinzugefügt)"
locked: "(gesperrt)"
restricted: "(benötigt für dieses Level)"
# equip: "Equip"
# unequip: "Unequip"
equip: "Ausrüsten"
unequip: "Abrüsten"
# buy_gems:
# few_gems: "A few gems"
# pile_gems: "Pile of gems"
# chest_gems: "Chest of gems"
buy_gems:
few_gems: "Ein paar Edelsteine"
pile_gems: "Stapel von Edelsteinen"
chest_gems: "Kiste von Edelsteinen"
choose_hero:
choose_hero: "Wähle deinen Helden"
programming_language: "Programmiersprache"
programming_language_description: "Welche Programmiersprache möchtest du verwenden?"
# default: "Default"
# experimental: "Experimental"
default: "Standard"
experimental: "Experimentell"
python_blurb: "Einfach jedoch leistungsfähig, Python ist eine gute Allzweck-Programmiersprache."
javascript_blurb: "Die Sprache des Web."
coffeescript_blurb: "Schönere JavaScript Syntax."
@ -466,7 +466,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
enter: "Eingabetaste"
escape: "Escape"
shift: "Umschalttaste"
# run_code: "Run current code."
run_code: "Starte aktuellen Code."
run_real_time: "Führe in Echtzeit aus."
continue_script: "Setze nach aktuellenm Skript fort."
skip_scripts: "Überspringe alle überspringbaren Skripte."
@ -544,7 +544,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
level_tab_thangs_add: "Thangs hinzufügen"
delete: "Löschen"
duplicate: "Duplizieren"
# rotate: "Rotate"
rotate: "Drehen"
level_settings_title: "Einstellungen"
level_component_tab_title: "Aktuelle Komponenten"
level_component_btn_new: "neue Komponente erstellen"
@ -688,14 +688,14 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
summary_your: "Deine "
summary_matches: "Matches - "
summary_wins: " Siege, "
# summary_losses: " Losses"
summary_losses: " Verluste"
# rank_no_code: "No New Code to Rank"
# rank_my_game: "Rank My Game!"
# rank_submitting: "Submitting..."
rank_submitting: "Übermitteln..."
# rank_submitted: "Submitted for Ranking"
# rank_failed: "Failed to Rank"
# rank_being_ranked: "Game Being Ranked"
# rank_last_submitted: "submitted "
rank_last_submitted: "übermittelt "
help_simulate: "Hilf Spiele zu simulieren?"
# code_being_simulated: "Your new code is being simulated by other players for ranking. This will refresh as new matches come in."
# no_ranked_matches_pre: "No ranked matches for the "
@ -744,16 +744,16 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
# last_earned: "Last Earned"
amount_achieved: "Anzahl"
achievement: "Achievement"
# category_contributor: "Contributor"
category_contributor: "Mitwirkender"
category_miscellaneous: "Sonstiges"
category_levels: "Level"
category_undefined: "ohne Kategorie"
# current_xp_prefix: ""
# current_xp_postfix: " in total"
current_xp_postfix: " Gesamt"
# new_xp_prefix: ""
# new_xp_postfix: " earned"
# left_xp_prefix: ""
# left_xp_infix: " until level "
left_xp_infix: " bis Level "
# left_xp_postfix: ""
account:
@ -877,7 +877,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
mit_license_url: "MIT Lizenz"
code_description_suffix: "Dies beihnhaltet all den Code in Systemen und Komponenten der für die Erstellung von Levels durch CodeCombat zu Verfügung gestellt wird."
art_title: "Grafiken/Musik - Creative Commons "
# art_description_prefix: "All common content is available under the"
art_description_prefix: "Gemeinsamer Inhalt ist verfügbar unter"
cc_license_url: "Creative Commons Attribution 4.0 International License"
# art_description_suffix: "Common content is anything made generally available by CodeCombat for the purpose of creating Levels. This includes:"
art_music: "Musik"
@ -915,8 +915,8 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
in_cash: "in Bar"
custom_wizard: "Benutzerdefinierter CodeCombat Zauberer"
custom_avatar: "Benutzerdefinierter CodeCombat Avatar"
# heap: "for six months of \"Startup\" access"
# credits: "credits"
heap: "für sechs Monate \"Startup\" Zugriff"
credits: "Guthaben"
one_month_coupon: "Gutschein: Wähle entweder Rails oder HTML"
one_month_discount: "30% Rabatt: Wähle entweder Rails oder HTML"
license: "Lizenz"
@ -952,7 +952,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
active: "Suche jetzt nach Interview-Angeboten"
inactive: "Suche momentan nicht nach Angeboten"
complete: "komplett"
# next: "Next"
next: "Nächstes"
next_city: "Stadt?"
next_country: "Wähle dein Land."
next_name: "Name?"

View file

@ -207,6 +207,8 @@
failing: "Failing"
action_timeline: "Action Timeline"
click_to_select: "Click on a unit to select it."
control_bar_multiplayer: "Multiplayer"
control_bar_join_game: "Join Game"
reload: "Reload"
reload_title: "Reload All Code?"
reload_really: "Are you sure you want to reload this level back to the beginning?"

View file

@ -69,7 +69,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
anonymous: "匿名玩家"
level_difficulty: "难度:"
campaign_beginner: "新手作战"
awaiting_levels_adventurer_prefix: "我们每周发布 5 个关卡。"#"We release five levels per week."
awaiting_levels_adventurer_prefix: "我们每周开放五个关卡"
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.
@ -218,13 +218,13 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
victory_rate_the_level: "评估关卡:" # Only in old-style levels.
victory_return_to_ladder: "返回"
victory_play_continue: "继续游戏"
# victory_play_skip: "Skip Ahead"
victory_play_skip: "跳过并继续"
victory_play_next_level: "下一关"
# victory_play_more_practice: "More Practice"
# victory_play_too_easy: "Too Easy"
# victory_play_just_right: "Just Right"
# victory_play_too_hard: "Too Hard"
# victory_saving_progress: "Saving Progress"
victory_play_more_practice: "更多练习"
victory_play_too_easy: "太简单"
victory_play_just_right: "刚刚好"
victory_play_too_hard: "太难"
victory_saving_progress: "保存进度"
victory_go_home: "返回主页" # Only in old-style levels.
victory_review: "给我们反馈!" # Only in old-style levels.
victory_hour_of_code_done: "你完成了吗?"
@ -244,7 +244,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
tome_available_spells: "可用的法术"
tome_your_skills: "你的技能"
# tome_current_method: "Current Method"
# hud_continue_short: "Continue"
hud_continue_short: "继续"
code_saved: "代码已保存"
skip_tutorial: "跳过esc"
keyboard_shortcuts: "快捷键"
@ -268,7 +268,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
tip_debugging_program: "如果说调试是清除Bug的过程那么编码就是放置Bug的过程。- Edsger W. Dijkstra"
tip_forums: "到论坛去告诉我们你的想法!"
tip_baby_coders: "在未来,就算小孩都能成为大法师."
# tip_morale_improves: "Loading will continue until morale improves."
tip_morale_improves: "在士气提升之前会一直进行读取."
tip_all_species: "我们相信学习编程的机会对任何种族都是平等的。"
# tip_reticulating: "Reticulating spines."
tip_harry: "巫师, "
@ -289,43 +289,43 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
customize_wizard: "自定义向导"
game_menu:
# inventory_tab: "Inventory"
inventory_tab: "道具箱"
save_load_tab: "保存/打开"
options_tab: "设置"
guide_tab: "使用向导"
multiplayer_tab: "多人游戏"
# auth_tab: "Sign Up"
# inventory_caption: "Equip your hero"
# choose_hero_caption: "Choose hero, language"
# save_load_caption: "... and view history"
# options_caption: "Configure settings"
# guide_caption: "Docs and tips"
# multiplayer_caption: "Play with friends!"
auth_tab: "注册"
inventory_caption: "装备你的英雄"
choose_hero_caption: "选择英雄和语言"
save_load_caption: "... 观看历史"
options_caption: "确认设置"
guide_caption: "文档和提示"
multiplayer_caption: "与你的朋友一起玩!"
auth_caption: "保存进度"
inventory:
choose_inventory: "装备道具"
# equipped_item: "Equipped"
# available_item: "Available"
# restricted_title: "Restricted"
equipped_item: "已装备"
available_item: "可用"
restricted_title: "被限制"
should_equip: "(双击装备此道具)"
equipped: "(已装备)"
locked: "(需解锁)"
restricted: "(本关卡不得使用)"
# equip: "Equip"
# unequip: "Unequip"
equip: "装备"
unequip: "取消装备"
# buy_gems:
# few_gems: "A few gems"
# pile_gems: "Pile of gems"
# chest_gems: "Chest of gems"
few_gems: "几个宝石"
pile_gems: "一堆宝石"
chest_gems: "一箱宝石"
choose_hero:
choose_hero: "请选择您的英雄"
programming_language: "编程语言"
programming_language_description: "您希望使用那门编程语言?"
# default: "Default"
# experimental: "Experimental"
# default: "默认"
experimental: "实验性的"
python_blurb: "简单而强大, Python是一个伟大的通用编程语言。"
javascript_blurb: "为web开发而生的语言。"
coffeescript_blurb: "一种更好的JavaScript语法."
@ -353,18 +353,18 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
# general_options: "General Options" # Check out the Options tab in the Game Menu while playing a level
volume_label: "音量"
music_label: "音乐"
# music_description: "Turn background music on/off."
# autorun_label: "Autorun"
music_description: "开/关背景音乐"
autorun_label: "自动运行"
# autorun_description: "Control automatic code execution."
editor_config: "编辑器配置"
editor_config_title: "编辑器配置"
# editor_config_level_language_label: "Language for This Level"
editor_config_level_language_label: "这个等级的语言"
# editor_config_level_language_description: "Define the programming language for this particular level."
# editor_config_default_language_label: "Default Programming Language"
# editor_config_default_language_description: "Define the programming language you want to code in when starting new levels."
editor_config_default_language_label: "默认编程语言"
editor_config_default_language_description: "在开始新游戏前确认你要在这个等级的游戏中使用的编程语言。"
editor_config_keybindings_label: "按键设置s"
editor_config_keybindings_default: "默认 (Ace)"
# editor_config_keybindings_description: "Adds additional shortcuts known from the common editors."
editor_config_keybindings_description: "在命令编辑器中增加已知的快捷键。"
# editor_config_livecompletion_label: "Live Autocompletion"
# editor_config_livecompletion_description: "Displays autocomplete suggestions while typing."
editor_config_invisibles_label: "显示空白字符"
@ -464,10 +464,10 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
keyboard_shortcuts: "热键"
space: "空格"
enter: "回车"
# escape: "Escape"
# shift: "Shift"
# run_code: "Run current code."
# run_real_time: "Run in real time."
escape: "Esc"
shift: "Shift"
run_code: "运行当前代码"
run_real_time: "实时运行"
# continue_script: "Continue past current script."
# skip_scripts: "Skip past all skippable scripts."
toggle_playback: "继续/暂停按钮"
@ -478,7 +478,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
# toggle_grid: "Toggle grid overlay."
# toggle_pathfinding: "Toggle pathfinding overlay."
beautify: "利用标准编码格式美化你的代码。"
# maximize_editor: "Maximize/minimize code editor."
maximize_editor: "最大化/最小化代码编辑器"
move_wizard: "在关卡中移动你的巫师角色。"
community:
@ -491,12 +491,12 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
article_editor_prefix: "你在游戏中发现了错误了吗?想要自己设计一些指令吗?来看看我们的"
article_editor_suffix: "来帮助玩家从游戏中学到更多的知识。"
find_us: "通过这些站点联系我们"
# social_blog: "Read the CodeCombat blog on Sett"
# social_discource: "Join the discussion on our Discourse forum"
social_blog: "阅读 CodeCombat 在 Sett 上面的博客"
social_discource: "在我们的论坛参与讨论"
social_facebook: "关注 CodeCombat 的 Facebook 主页"
social_twitter: "关注 CodeCombat 的 Twitter"
social_gplus: "关注 CodeCombat 的 Google+ 主页"
# social_hipchat: "Chat with us in the public CodeCombat HipChat room"
social_hipchat: "在公共的 CodeCombat HipChat 房间与我们交流"
contribute_to_the_project: "为项目做贡献"
classes:
@ -647,9 +647,9 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
diplomat_launch_url: "十月的发布"
diplomat_introduction_suf: "中得到了什么启发:那就是全世界的人都对 CodeCombat 很感兴趣。我们召集了一群翻译者,尽快地把网站上的信息翻译成各国文字。如果你对即将发布的新内容很感兴趣,想让你的国家的人们玩上,就快来成为外交官吧。"
diplomat_attribute_1: "既会说流利的英语,也熟悉自己的语言。编程是一件很复杂的事情,而要翻译复杂的概念,你必须对两种语言都在行!"
# diplomat_i18n_page_prefix: "You can start translating our levels by going to our"
# diplomat_i18n_page: "translations page"
# diplomat_i18n_page_suffix: ", or our interface and website on GitHub."
diplomat_i18n_page_prefix: "你可以在我们的"
diplomat_i18n_page: "翻译页面"
diplomat_i18n_page_suffix: "开始翻译游戏或者在GibHub我们的页面上进行。"
diplomat_join_pref_github: ""
diplomat_github_url: " GitHub "
diplomat_join_suf_github: "找到你的语言文件 (中文的是: codecombat/app/locale/zh-HNAS.coffee),在线编辑它,然后提交一个合并请求。同时,选中下面这个复选框来关注最新的国际化开发!"
@ -659,7 +659,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
ambassador_introduction: "这是一个正在成长的社区而你将成为我们与世界的联结点。大家可以通过Olark即时聊天、邮件、参与者众多的社交网络来认识了解讨论我们的游戏。如果你想帮助大家尽早参与进来、获得乐趣、感受CodeCombat的脉搏、与我们同行那么这将是一个适合你的职业。"
ambassador_attribute_1: "有出色的沟通能力。能够辨识出玩家遇到的问题并帮助他们解决这些问题。与此同时,和我们保持联系,及时反馈玩家的喜恶和愿望!"
ambassador_join_desc: "介绍一下你自己:你做过什么?你喜欢做什么?我们将从这里开始了解你!"
# ambassador_join_note_strong: "Note"
ambassador_join_note_strong: "注意"
# ambassador_join_note_desc: "One of our top priorities is to build multiplayer where players having difficulty solving levels can summon higher level wizards to help them. This will be a great way for ambassadors to do their thing. We'll keep you posted!"
more_about_ambassador: "了解如何成为一名使节"
ambassador_subscribe_desc: "通过电子邮件获得支持系统的现状,以及多人游戏方面的新进展。"
@ -694,14 +694,14 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
rank_submitting: "正在提交..."
# rank_submitted: "Submitted for Ranking"
rank_failed: "评分失败"
# rank_being_ranked: "Game Being Ranked"
# rank_last_submitted: "submitted "
rank_being_ranked: "已评价"
rank_last_submitted: "已提交"
# help_simulate: "Help simulate games?"
# code_being_simulated: "Your new code is being simulated by other players for ranking. This will refresh as new matches come in."
# no_ranked_matches_pre: "No ranked matches for the "
# no_ranked_matches_post: " team! Play against some competitors and then come back here to get your game ranked."
choose_opponent: "选择一个对手"
# select_your_language: "Select your language!"
select_your_language: "选择你使用的语言!"
tutorial_play: "玩教程"
tutorial_recommended: "如果你从未玩过的话,推荐先玩下教程"
tutorial_skip: "跳过教材"
@ -709,11 +709,11 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
tutorial_play_first: "先玩一次教程."
simple_ai: "简单电脑"
warmup: "热身"
# friends_playing: "Friends Playing"
# log_in_for_friends: "Log in to play with your friends!"
# social_connect_blurb: "Connect and play against your friends!"
# invite_friends_to_battle: "Invite your friends to join you in battle!"
# fight: "Fight!"
friends_playing: "联机"
log_in_for_friends: "登陆然后跟朋友一起玩!"
social_connect_blurb: "连接然后与朋友对战!"
invite_friends_to_battle: "邀请你的朋友参加战斗!"
fight: "战斗!"
# watch_victory: "Watch your victory"
# defeat_the: "Defeat the"
# tournament_ends: "Tournament ends"
@ -722,8 +722,8 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
# tournament_blurb: "Write code, collect gold, build armies, crush foes, win prizes, and upgrade your career in our $40,000 Greed tournament! Check out the details"
# tournament_blurb_criss_cross: "Win bids, construct paths, outwit opponents, grab gems, and upgrade your career in our Criss-Cross tournament! Check out the details"
# tournament_blurb_blog: "on our blog"
# rules: "Rules"
# winners: "Winners"
rules: "规则"
winners: "胜利者"
user:
stats: "成就"
@ -744,14 +744,14 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
last_earned: "最近取得的时间"
amount_achieved: "数量"
achievement: "成就"
# category_contributor: "Contributor"
category_contributor: "贡献"
# category_miscellaneous: "Miscellaneous"
# category_levels: "Levels"
category_levels: "等级"
# category_undefined: "Uncategorized"
# current_xp_prefix: ""
# current_xp_postfix: " in total"
# new_xp_prefix: ""
# new_xp_postfix: " earned"
new_xp_postfix: "获取"
# left_xp_prefix: ""
# left_xp_infix: " until level "
# left_xp_postfix: ""
@ -798,9 +798,9 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
# level_session: "Your Session"
# opponent_session: "Opponent Session"
# article: "Article"
# user_names: "User Names"
user_names: "用户名"
# thang_names: "Thang Names"
# files: "Files"
files: "文件"
# top_simulators: "Top Simulators"
# source_document: "Source Document"
# document: "Document"
@ -811,13 +811,13 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
# user_remark: "User Remark"
# user_remarks: "User Remarks"
# versions: "Versions"
# items: "Items"
items: "物品"
heroes: "英雄"
# wizard: "Wizard"
# achievement: "Achievement"
wizard: "巫师"
achievement: "成就"
# clas: "CLAs"
# play_counts: "Play Counts"
# feedback: "Feedback"
feedback: "反馈"
# delta:
# added: "Added"
@ -1091,10 +1091,10 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
admin:
# av_espionage: "Espionage" # Really not important to translate /admin controls.
# av_espionage_placeholder: "Email or username"
# av_usersearch: "User Search"
# av_usersearch_placeholder: "Email, username, name, whatever"
# av_usersearch_search: "Search"
av_espionage_placeholder: "邮箱或用户名"
av_usersearch: "用户搜索"
av_usersearch_placeholder: "邮箱、用户名、姓名、任何东西"
av_usersearch_search: "搜索"
av_title: "管理员视图"
av_entities_sub_title: "实体"
av_entities_users_url: "用户"

View file

@ -68,13 +68,17 @@ module.exports = class Level extends CocoModel
placeholders = {}
placeholdersUsed = {}
placeholderThangType = supermodel.getModelByOriginal(ThangType, levelThang.thangType)
for defaultPlaceholderComponent in placeholderThangType.get('components')
placeholders[defaultPlaceholderComponent.original] = defaultPlaceholderComponent
for thangComponent in levelThang.components
placeholders[thangComponent.original] = thangComponent
levelThang.components = [] # We have stored the placeholder values, so we can inherit everything else.
heroThangType = session?.get('heroConfig')?.thangType
levelThang.thangType = heroThangType if heroThangType
unless placeholderThangType
console.error "Couldn't find placeholder ThangType for the hero!"
isHero = false
else
for defaultPlaceholderComponent in placeholderThangType.get('components')
placeholders[defaultPlaceholderComponent.original] = defaultPlaceholderComponent
for thangComponent in levelThang.components
placeholders[thangComponent.original] = thangComponent
levelThang.components = [] # We have stored the placeholder values, so we can inherit everything else.
heroThangType = session?.get('heroConfig')?.thangType
levelThang.thangType = heroThangType if heroThangType
thangType = supermodel.getModelByOriginal(ThangType, levelThang.thangType, (m) -> m.get('components')?)

View file

@ -181,6 +181,7 @@ module.exports = class SuperModel extends Backbone.Model
@num += r.value
_.defer @updateProgress
r.clean()
@stopListening r, 'failed', @onResourceFailed
@trigger 'resource-loaded', r
onResourceFailed: (r) ->

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

@ -143,15 +143,18 @@ module.exports = class User extends CocoModel
application.tracker.identify experimentalLangGroup: @experimentalLangGroup
@experimentalLangGroup
getHighlightArrowSoundGroup: ->
return @highlightArrowGroup if @highlightArrowGroup
group = me.get('testGroupNumber') % 8
@highlightArrowGroup = switch group
when 0, 1, 2, 3 then 'sound-off'
when 4, 5, 6, 7 then 'sound-on'
@highlightArrowGroup = 'sound-off' if me.isAdmin()
application.tracker.identify highlightArrowGroup: @highlightArrowGroup unless me.isAdmin()
@highlightArrowGroup
## Test complete; feel free to repurpose for another test.
# https://mixpanel.com/report/227350/segmentation/#action:segment,arb_event:'Saw%20Victory',bool_op:and,chart_type:bar,from_date:-17,segfilter:!((filter:(operand:!('Dungeons%20of%20Kithgard'),operator:%3D%3D),property:level,selected_property_type:string,type:string),(filter:(operand:'',operator:set),property:highlightArrowGroup,selected_property_type:string,type:string),(property:highlightArrowGroup,selected_property_type:string,type:string)),segment_type:string,to_date:0,type:unique,unit:day
# https://www.dropbox.com/s/jot1ikz7nk7jti5/Screenshot%202014-11-18%2008.04.00.png?dl=0
#getHighlightArrowSoundGroup: ->
# return @highlightArrowGroup if @highlightArrowGroup
# group = me.get('testGroupNumber') % 8
# @highlightArrowGroup = switch group
# when 0, 1, 2, 3 then 'sound-off'
# when 4, 5, 6, 7 then 'sound-on'
# @highlightArrowGroup = 'sound-off' if me.isAdmin()
# application.tracker.identify highlightArrowGroup: @highlightArrowGroup unless me.isAdmin()
# @highlightArrowGroup
getKithmazeGroup: ->
return @kithmazeGroup if @kithmazeGroup

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

@ -2,10 +2,12 @@ c = require 'schemas/schemas'
module.exports =
'ipad:products': c.object {required: ['products']},
products: c.array {},
products: c.array {},
c.object {},
price: { type: 'string' }
id: { type: 'string' }
'ipad:iap-complete': c.object {},
productID: { type: 'string' }
'ipad:memory-warning': c.object {}

View file

@ -20,7 +20,27 @@
top: -146px
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)
@ -437,3 +458,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

@ -112,6 +112,36 @@
color: white
font-size: 18px
.multiplayer-area-container
position: relative
width: 100%
height: 50px
pointer-events: none
.multiplayer-area
min-width: 200px
max-width: 293px
height: 60px
margin: 0 auto
padding: 8px
border-image: url(/images/level/control_bar_level_name_background.png) 30 fill round
border-width: 0 15px 15px 15px
text-align: center
position: absolute
left: 50%
cursor: pointer
pointer-events: all
@include translate(-50%, 0)
.multiplayer-label
font-size: 12px
color: $control-yellow-highlight
margin-bottom: -5px
.multiplayer-status
color: white
font-size: 18px
.buttons-area
position: absolute
right: 35px
@ -167,6 +197,12 @@ html.no-borderimage
background: transparent url(/images/level/control_bar_level_name_background.png)
background-size: contain
background-repeat: no-repeat
#control-bar-view .multiplayer-area
border: 0
background: transparent url(/images/level/control_bar_level_name_background.png)
background-size: contain
background-repeat: no-repeat
body:not(.ipad)
@media only screen and (max-width: 1300px)

View file

@ -1,16 +0,0 @@
@import "app/styles/mixins"
@import "app/styles/bootstrap/variables"
// TODO: Replace this devart with nice shinies
#multiplayer-status-view
position: absolute
.player-count
color: white
.players-available
color: lightblue
.players-unavailable
color: fuchsia
.game-status
color: lightgreen

View file

@ -91,7 +91,7 @@
background-color: rgba(255, 255, 255, 0.4)
.ace_content
.executing, .executed, .problem-marker-info, .problem-marker-warning, .problem-marker-error
.executing, .executed, .problem-marker-info, .problem-marker-warning, .problem-marker-error, .problem-line
position: absolute
.executing
// https://github.com/codecombat/codecombat/issues/6
@ -108,11 +108,11 @@
+keyframes(pulseRedBackground)
from
background-color: rgba(255, 45, 27, 0.25)
background-color: rgba(255, 45, 27, 0.4)
50%
background-color: rgba(255, 45, 27, 0.125)
background-color: rgba(255, 45, 27, 0.2)
to
background-color: rgba(255, 45, 27, 0.25)
background-color: rgba(255, 45, 27, 0.4)
// problem-marker-#{@aetherProblem.level} set in Problem.coffee
.problem-marker-info
@ -120,10 +120,11 @@
.problem-marker-warning
background-color: rgba(100, 65, 20, 0.25)
.problem-marker-error
background-color: rgba(255, 45, 27, 0.5)
.problem-line
// TODO: Pulses too quickly during playback
@include animation(pulseRedBackground 1s infinite)
&:not(.playback-ended)
.executing:not(.ace_gutter-cell)
background-size: 40px 40px

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

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

View file

@ -2,6 +2,9 @@
.modal-content
img(src="/images/pages/play/modal/game-menu-background.png")#game-menu-background
div#close-modal
span.glyphicon.glyphicon-remove
ul#game-menu-nav.nav.nav-pills.nav-stacked
li
a#change-hero-tab

View file

@ -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())
.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())
.clearfix
#item-details-view

View file

@ -9,10 +9,19 @@
.glyphicon.glyphicon-play
span(data-i18n="nav.play").home-text Levels
.level-name-area-container
.level-name-area
.level-label(data-i18n="play_level.level")
.level-name= worldName
if isMultiplayerLevel
.multiplayer-area-container
.multiplayer-area
.multiplayer-label(data-i18n="play_level.control_bar_multiplayer")
if multiplayerStatus
.multiplayer-status= multiplayerStatus
else
.multiplayer-status(data-i18n="play_level.control_bar_join_game")
else
.level-name-area-container
.level-name-area
.level-label(data-i18n="play_level.level")
.level-name= worldName
.buttons-area

View file

@ -1,12 +0,0 @@
//- TODO: Replace this devart with nice shinies
div
button#multiplayer-button Multiplayer
//- span.spr
//- span.player-count #{playerCount} total players
span.spr
span.players-available #{playersAvailable} available
span.spr
span.players-unavailable #{playersUnavailable} playing
span.spr
span.game-status #{status}

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'
@ -126,6 +127,7 @@ module.exports = class InventoryModal extends ModalView
@insertSubView(@itemDetailsView)
@requireLevelEquipment()
@$el.find('.nano').nanoScroller({alwaysVisible: true})
@onSelectionChanged()
afterInsert: ->
super()
@ -137,7 +139,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 +203,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 +217,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 +256,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 +277,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 +309,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 +328,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 +351,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 +398,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

@ -99,7 +99,7 @@ module.exports = class CocoView extends Backbone.View
@$el.find(selector).replaceWith(newTemplate.find(selector))
@delegateEvents()
@$el.i18n()
render: ->
return @ unless me
view.destroy() for id, view of @subviews
@ -358,8 +358,6 @@ module.exports = class CocoView extends Backbone.View
animatePointer: =>
$pointer = @getPointer()
$pointer.css transition: 'all 0.6s ease-out', transform: "rotate(#{@pointerRotation}rad) translate(-3px, #{@pointerRadialDistance-50}px)"
if me.getHighlightArrowSoundGroup() is 'sound-on'
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'dom_highlight', volume: 0.5
setTimeout (=> $pointer.css transition: 'all 0.4s ease-in', transform: "rotate(#{@pointerRotation}rad) translate(-3px, #{@pointerRadialDistance}px)"), 800
endHighlight: ->

View file

@ -31,6 +31,8 @@ module.exports = class WorldMapView extends RootView
'click #volume-button': 'onToggleVolume'
constructor: (options, @terrain) ->
if options and application.isIPAdApp # TODO: later only clear the SuperModel if it has received a memory warning (not in app store yet)
options.supermodel = null
@terrain ?= 'dungeon' # or 'forest'
super options
@nextLevel = @getQueryVariable 'next'
@ -282,6 +284,7 @@ module.exports = class WorldMapView extends RootView
storage.save("loaded-menu-music-#{@terrain}", true) unless @probablyCachedMusic
preloadTopHeroes: ->
return # Don't do this because these two have feature images, so we don't need the raw vector data for them. Later they'll all have feature images...
for heroID in ['captain', 'knight']
url = "/db/thang.type/#{ThangType.heroes[heroID]}/version"
continue if @supermodel.getModel url

View file

@ -3,8 +3,10 @@ template = require 'templates/play/level/control_bar'
{me} = require 'lib/auth'
GameMenuModal = require 'views/game-menu/GameMenuModal'
RealTimeModel = require 'models/RealTimeModel'
RealTimeCollection = require 'collections/RealTimeCollection'
LevelSetupManager = require 'lib/LevelSetupManager'
GameMenuModal = require 'views/game-menu/GameMenuModal'
module.exports = class ControlBarView extends CocoView
id: 'control-bar-view'
@ -14,19 +16,24 @@ module.exports = class ControlBarView extends CocoView
'bus:player-states-changed': 'onPlayerStatesChanged'
'level:disable-controls': 'onDisableControls'
'level:enable-controls': 'onEnableControls'
'ipad:memory-warning': 'onIPadMemoryWarning'
events:
'click #next-game-button': -> Backbone.Mediator.publish 'level:next-game-pressed', {}
'click #game-menu-button': 'showGameMenuModal'
'click': -> Backbone.Mediator.publish 'tome:focus-editor', {}
'click .home a': 'onClickHome'
'click .multiplayer-area': 'onClickMultiplayer'
constructor: (options) ->
@worldName = options.worldName
@session = options.session
@level = options.level
@levelID = @level.get('slug')
@spectateGame = options.spectateGame ? false
super options
if @isMultiplayerLevel = @level.get('type') in ['hero-ladder']
@multiplayerStatusManager = new MultiplayerStatusManager @levelID, @onMultiplayerStateChanged
setBus: (@bus) ->
@ -40,13 +47,17 @@ module.exports = class ControlBarView extends CocoView
text += " (#{numPlayers})" if numPlayers > 1
$('#multiplayer-button', @$el).text(text)
onMultiplayerStateChanged: => @render?()
getRenderData: (c={}) ->
super c
c.worldName = @worldName
c.multiplayerEnabled = @session.get('multiplayer')
c.ladderGame = @level.get('type') in ['ladder', 'hero-ladder']
if c.isMultiplayerLevel = @isMultiplayerLevel
c.multiplayerStatus = @multiplayerStatusManager?.status
c.spectateGame = @spectateGame
@homeViewArgs = [{supermodel: @supermodel}]
@homeViewArgs = [{supermodel: if @hasReceivedMemoryWarning then null else @supermodel}]
if @level.get('type', true) in ['ladder', 'ladder-tutorial', 'hero-ladder']
levelID = @level.get('slug').replace /\-tutorial$/, ''
@homeLink = c.homeLink = '/play/ladder/' + levelID
@ -71,7 +82,7 @@ module.exports = class ControlBarView extends CocoView
@openModalView gameMenuModal
@listenToOnce gameMenuModal, 'change-hero', ->
@setupManager?.destroy()
@setupManager = new LevelSetupManager({supermodel: @supermodel, levelID: @level.get('slug'), parent: @, session: @session})
@setupManager = new LevelSetupManager({supermodel: @supermodel, levelID: @levelID, parent: @, session: @session})
@setupManager.open()
onClickHome: (e) ->
@ -79,6 +90,9 @@ module.exports = class ControlBarView extends CocoView
e.stopImmediatePropagation()
Backbone.Mediator.publish 'router:navigate', route: @homeLink, viewClass: @homeViewClass, viewArgs: @homeViewArgs
onClickMultiplayer: (e) ->
@openModalView new GameMenuModal showTab: 'multiplayer', level: @level, session: @session, supermodel: @supermodel
onDisableControls: (e) -> @toggleControls e, false
onEnableControls: (e) -> @toggleControls e, true
toggleControls: (e, enabled) ->
@ -92,6 +106,67 @@ module.exports = class ControlBarView extends CocoView
for level in campaign.levels
return campaign.id if level.id is slug
onIPadMemoryWarning: (e) ->
@hasReceivedMemoryWarning = true
destroy: ->
@setupManager?.destroy()
@multiplayerStatusManager?.destroy()
super()
# MultiplayerStatusManager ######################################################
#
# Manages the multiplayer status, and calls @statusChangedCallback when it changes.
#
# It monitors these:
# Real-time multiplayer players
# Internal multiplayer status
#
# Real-time state variables:
# @playersCollection - Real-time multiplayer players
#
# TODO: Not currently using player counts. Should remove if we keep simple design.
#
class MultiplayerStatusManager
constructor: (@levelID, @statusChangedCallback) ->
@status = ''
# @players = {}
# @playersCollection = new RealTimeCollection('multiplayer_players/' + @levelID)
# @playersCollection.on 'add', @onPlayerAdded
# @playersCollection.each (player) => @onPlayerAdded player
Backbone.Mediator.subscribe 'real-time-multiplayer:player-status', @onMultiplayerPlayerStatus
destroy: ->
Backbone.Mediator.unsubscribe 'real-time-multiplayer:player-status', @onMultiplayerPlayerStatus
# @playersCollection?.off 'add', @onPlayerAdded
# player.off 'change', @onPlayerChanged for id, player of @players
onMultiplayerPlayerStatus: (e) =>
@status = e.status
@statusChangedCallback()
# onPlayerAdded: (player) =>
# unless player.id is me.id
# @players[player.id] = new RealTimeModel('multiplayer_players/' + @levelID + '/' + player.id)
# @players[player.id].on 'change', @onPlayerChanged
# @countPlayers player
#
# onPlayerChanged: (player) =>
# @countPlayers player
#
# countPlayers: (changedPlayer) =>
# # TODO: save this stale hearbeat threshold setting somewhere
# staleHeartbeat = new Date()
# staleHeartbeat.setMinutes staleHeartbeat.getMinutes() - 3
# @playerCount = 0
# @playersCollectionAvailable = 0
# @playersCollectionUnavailable = 0
# @playersCollection.each (player) =>
# # Assume changedPlayer is fresher than entry in @playersCollection collection
# player = changedPlayer if changedPlayer? and player.id is changedPlayer.id
# unless staleHeartbeat >= new Date(player.get('heartbeat'))
# @playerCount++
# @playersCollectionAvailable++ if player.get('state') is 'available'
# @playersCollectionUnavailable++ if player.get('state') is 'unavailable'
# @statusChangedCallback()

View file

@ -1,93 +0,0 @@
CocoView = require 'views/kinds/CocoView'
template = require 'templates/play/level/multiplayer-status'
{me} = require 'lib/auth'
RealTimeModel = require 'models/RealTimeModel'
RealTimeCollection = require 'collections/RealTimeCollection'
GameMenuModal = require 'views/game-menu/GameMenuModal'
# Real-time Multiplayer ######################################################
#
# This view displays the real-time multiplayer status for the current level.
#
# It performs these actions:
# Multiplayer button into game-menu multiplayer section
# Display number of players waiting for an opponent in this level
# Display number of players current playing a pvp game in this level
# Status for user's current real-time multiplayer session
#
# It monitors these:
# Real-time multiplayer players
# Internal multiplayer status
#
# Real-time state variables:
# @playersCollection - Real-time multiplayer players
module.exports = class MultiplayerStatusView extends CocoView
id: 'multiplayer-status-view'
template: template
subscriptions:
'real-time-multiplayer:player-status': 'onRealTimeMultiplayerPlayerStatus'
events:
'click #multiplayer-button': 'onClickMultiplayerButton'
constructor: (options) ->
super(options)
@session = options.session
@level = options.level
@levelID = options.levelID
@status = ''
@players = {}
@playersCollection = new RealTimeCollection('multiplayer_players/' + @levelID)
@playersCollection.on 'add', @onPlayerAdded
@playersCollection.each (player) => @onPlayerAdded player
destroy: ->
@playersCollection?.off 'add', @onPlayerAdded
player.off 'change', @onPlayerChanged for id, player of @players
super()
getRenderData: ->
c = super()
c.playerCount = @playerCount
c.playersAvailable = @playersCollectionAvailable
c.playersUnavailable = @playersCollectionUnavailable
c.status = @status
c
onRealTimeMultiplayerPlayerStatus: (e) ->
@status = e.status
@render?()
onClickMultiplayerButton: (e) ->
@openModalView new GameMenuModal showTab: 'multiplayer', level: @level, session: @session, supermodel: @supermodel
onPlayerAdded: (player) =>
# console.log 'MultiplayerStatusView onPlayerAdded', player
unless player.id is me.id
@players[player.id] = new RealTimeModel('multiplayer_players/' + @levelID + '/' + player.id)
@players[player.id].on 'change', @onPlayerChanged
@countPlayers player
onPlayerChanged: (player) =>
# console.log 'MultiplayerStatusView onPlayerChanged', player
@countPlayers player
countPlayers: (changedPlayer) =>
# TODO: save this stale hearbeat threshold setting somewhere
staleHeartbeat = new Date()
staleHeartbeat.setMinutes staleHeartbeat.getMinutes() - 3
@playerCount = 0
@playersCollectionAvailable = 0
@playersCollectionUnavailable = 0
@playersCollection.each (player) =>
# Assume changedPlayer is fresher than entry in @playersCollection collection
player = changedPlayer if changedPlayer? and player.id is changedPlayer.id
unless staleHeartbeat >= new Date(player.get('heartbeat'))
@playerCount++
@playersCollectionAvailable++ if player.get('state') is 'available'
@playersCollectionUnavailable++ if player.get('state') is 'unavailable'
# console.log 'MultiplayerStatusView countPlayers', @playerCount, @playersCollectionAvailable, @playersCollectionUnavailable
@render()

View file

@ -36,7 +36,6 @@ GoldView = require './LevelGoldView'
VictoryModal = require './modal/VictoryModal'
HeroVictoryModal = require './modal/HeroVictoryModal'
InfiniteLoopModal = require './modal/InfiniteLoopModal'
MultiplayerStatusView = require './MultiplayerStatusView'
LevelSetupManager = require 'lib/LevelSetupManager'
PROFILE_ME = false
@ -78,6 +77,7 @@ module.exports = class PlayLevelView extends RootView
'real-time-multiplayer:joined-game': 'onRealTimeMultiplayerJoinedGame'
'real-time-multiplayer:left-game': 'onRealTimeMultiplayerLeftGame'
'real-time-multiplayer:manual-cast': 'onRealTimeMultiplayerCast'
'ipad:memory-warning': 'onIPadMemoryWarning'
events:
'click #level-done-button': 'onDonePressed'
@ -255,8 +255,6 @@ module.exports = class PlayLevelView extends RootView
@insertSubView new HUDView {level: @level}
@insertSubView new LevelDialogueView {level: @level}
@insertSubView new ChatView levelID: @levelID, sessionID: @session.id, session: @session
if @level.get('type') in ['hero-ladder']
@insertSubView new MultiplayerStatusView levelID: @levelID, session: @session, level: @level
@insertSubView new ProblemAlertView {}
worldName = utils.i18n @level.attributes, 'name'
@controlBar = @insertSubView new ControlBarView {worldName: worldName, session: @session, level: @level, supermodel: @supermodel}
@ -438,7 +436,7 @@ module.exports = class PlayLevelView extends RootView
showVictory: ->
@endHighlight()
options = {level: @level, supermodel: @supermodel, session: @session}
options = {level: @level, supermodel: @supermodel, session: @session, hasReceivedMemoryWarning: @hasReceivedMemoryWarning}
ModalClass = if @level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop'] then HeroVictoryModal else VictoryModal
victoryModal = new ModalClass(options)
@openModalView(victoryModal)
@ -462,7 +460,7 @@ module.exports = class PlayLevelView extends RootView
Backbone.Mediator.publish 'router:navigate', {
route: nextLevelURL,
viewClass: PlayLevelView,
viewArgs: [{supermodel: @supermodel}, nextLevelID]}
viewArgs: [{supermodel: if @hasReceivedMemoryWarning then null else @supermodel}, nextLevelID]}
getNextLevel: ->
return null unless nextLevelOriginal = @level.get('nextLevel')?.original
@ -905,3 +903,6 @@ module.exports = class PlayLevelView extends RootView
if sessionState?
# TODO: Don't hardcode spellName
Backbone.Mediator.publish 'level:select-sprite', thangID: sessionState.selected, spellName: 'plan'
onIPadMemoryWarning: (e) ->
@hasReceivedMemoryWarning = true

View file

@ -298,7 +298,7 @@ module.exports = class HeroVictoryModal extends ModalView
skipPrompt ||= not (@skipAheadLevelLink or @morePractiveLevelLink) and me.getBranchingGroup() is 'choice-explicit'
if skipPrompt
# Preserve the supermodel as we navigate back to the world map.
Backbone.Mediator.publish 'router:navigate', route: nextLevelLink, viewClass: require('views/play/WorldMapView'), viewArgs: [{supermodel: @supermodel}, @getNextLevelCampaign()]
Backbone.Mediator.publish 'router:navigate', route: nextLevelLink, viewClass: require('views/play/WorldMapView'), viewArgs: [{supermodel: if @options.hasReceivedMemoryWarning then null else @supermodel}, @getNextLevelCampaign()]
else
# Hide everything except the buttons prompting them for which kind of next level to do
@$el.find('.modal-footer, .modal-body > *').hide()
@ -309,10 +309,10 @@ module.exports = class HeroVictoryModal extends ModalView
route = $(e.target).data('href') or "/play/#{@getNextLevelCampaign()}"
application.tracker?.trackEvent 'Branch Selected', level: @level.get('slug'), label: @level.get('slug'), branch: $(e.target).data('branch-key'), branchingGroup: me.getBranchingGroup(), route: route
# Preserve the supermodel as we navigate back to world map.
Backbone.Mediator.publish 'router:navigate', route: route, viewClass: require('views/play/WorldMapView'), viewArgs: [{supermodel: @supermodel}, @getNextLevelCampaign()]
Backbone.Mediator.publish 'router:navigate', route: route, viewClass: require('views/play/WorldMapView'), viewArgs: [{supermodel: if @options.hasReceivedMemoryWarning then null else @supermodel}, @getNextLevelCampaign()]
onClickReturnToLadder: (e) ->
e.preventDefault()
route = $(e.target).data('href')
# Preserve the supermodel as we navigate back to the ladder.
Backbone.Mediator.publish 'router:navigate', route: route, viewClass: require('views/play/ladder/LadderView'), viewArgs: [{supermodel: @supermodel}, @level.get('slug')]
Backbone.Mediator.publish 'router:navigate', route: route, viewClass: require('views/play/ladder/LadderView'), viewArgs: [{supermodel: if @options.hasReceivedMemoryWarning then null else @supermodel}, @level.get('slug')]

View file

@ -10,7 +10,7 @@ module.exports = class Problem
Backbone.Mediator.publish("problem:problem-created", line: @annotation.row, text: @annotation.text) if application.isIPadApp
destroy: ->
@removeMarkerRange()
@removeMarkerRanges()
@userCodeProblem.off() if @userCodeProblem
buildAnnotation: ->
@ -27,14 +27,23 @@ module.exports = class Problem
buildMarkerRange: ->
return unless @aetherProblem.range
[start, end] = @aetherProblem.range
clazz = "problem-marker-#{@aetherProblem.level}"
@markerRange = new Range start.row, start.col, end.row, end.col
@markerRange.start = @ace.getSession().getDocument().createAnchor @markerRange.start
@markerRange.end = @ace.getSession().getDocument().createAnchor @markerRange.end
@markerRange.id = @ace.getSession().addMarker @markerRange, clazz, 'fullLine'
textClazz = "problem-marker-#{@aetherProblem.level}"
@textMarkerRange = new Range start.row, start.col, end.row, end.col
@textMarkerRange.start = @ace.getSession().getDocument().createAnchor @textMarkerRange.start
@textMarkerRange.end = @ace.getSession().getDocument().createAnchor @textMarkerRange.end
@textMarkerRange.id = @ace.getSession().addMarker @textMarkerRange, textClazz, 'text'
lineClazz = "problem-line"
@lineMarkerRange = new Range start.row, start.col, end.row, end.col
@lineMarkerRange.start = @ace.getSession().getDocument().createAnchor @lineMarkerRange.start
@lineMarkerRange.end = @ace.getSession().getDocument().createAnchor @lineMarkerRange.end
@lineMarkerRange.id = @ace.getSession().addMarker @lineMarkerRange, lineClazz, 'fullLine'
removeMarkerRange: ->
return unless @markerRange
@ace.getSession().removeMarker @markerRange.id
@markerRange.start.detach()
@markerRange.end.detach()
removeMarkerRanges: ->
if @textMarkerRange
@ace.getSession().removeMarker @textMarkerRange.id
@textMarkerRange.start.detach()
@textMarkerRange.end.detach()
if @lineMarkerRange
@ace.getSession().removeMarker @lineMarkerRange.id
@lineMarkerRange.start.detach()
@lineMarkerRange.end.detach()

View file

@ -10,6 +10,7 @@ module.exports = class ProblemAlertView extends CocoView
subscriptions:
'tome:show-problem-alert': 'onShowProblemAlert'
'tome:hide-problem-alert': 'onHideProblemAlert'
'level:restart': 'onHideProblemAlert'
'tome:jiggle-problem-alert': 'onJiggleProblemAlert'
'tome:manual-cast': 'onHideProblemAlert'
'real-time-multiplayer:manual-cast': 'onHideProblemAlert'

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

View file

@ -222,7 +222,8 @@ module.exports = class TomeView extends CocoView
if spellName
spell = _.find selectedThangSpells, {name: spellName}
else
spell = _.find selectedThangSpells, (spell) -> true # Just grab one
spell = _.find selectedThangSpells, (spell) -> spell.canWrite()
spell ?= _.find selectedThangSpells, (spell) -> spell.canRead()
spell
reloadAllCode: ->

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
@ -54,6 +53,10 @@ module.exports = class ItemDetailsView extends CocoView
else
@propDocs[propDoc.name] = propDoc
@render()
afterRender: ->
super()
@$el.find('.nano:visible').nanoScroller({alwaysVisible: true})
getRenderData: ->
c = super()

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

@ -3,7 +3,7 @@
* 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
@ -12,10 +12,10 @@
* 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
@ -30,71 +30,55 @@
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.
*
* Restrictions:
* - only Sprite, SpriteContainer, BitmapText and DOMElement are allowed to be added as children.
* - a spriteSheet MUST be either be passed into the constructor or defined on the first child added.
* - 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},
* animations: {run:[0,4], jump:[5,8,"run"]}
* };
* var spriteSheet = new createjs.SpriteSheet(data);
* var container = new createjs.SpriteContainer(spriteSheet);
* container.addChild(spriteInstance, spriteInstance2);
* container.x = 100;
*
* <strong>Note:</strong> SpriteContainer is not included in the minified version of EaselJS.
*
* @class SpriteContainer
* @extends Container
* @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();
// public properties:
/**
* The SpriteSheet that this container enforces use of.
* @property spriteSheet
* @type {SpriteSheet}
* @readonly
* 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.
*
* Restrictions:
* - only Sprite, SpriteContainer, BitmapText and DOMElement are allowed to be added as children.
* - a spriteSheet MUST be either be passed into the constructor or defined on the first child added.
* - 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},
* animations: {run:[0,4], jump:[5,8,"run"]}
* };
* var spriteSheet = new createjs.SpriteSheet(data);
* var container = new createjs.SpriteContainer(spriteSheet);
* container.addChild(spriteInstance, spriteInstance2);
* container.x = 100;
*
* <strong>Note:</strong> SpriteContainer is not included in the minified version of EaselJS.
*
* @class SpriteContainer
* @extends Container
* @constructor
* @param {SpriteSheet} [spriteSheet] The spriteSheet to use for this SpriteContainer and its children.
**/
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();
function SpriteContainer(spriteSheet) {
this.Container_constructor();
// public properties:
/**
* The SpriteSheet that this container enforces use of.
* @property spriteSheet
* @type {SpriteSheet}
* @readonly
**/
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.
@ -127,7 +111,7 @@ var p = SpriteContainer.prototype = new createjs.Container();
* Only children of type SpriteContainer, Sprite, Bitmap, BitmapText, or DOMElement are allowed.
* The child must have the same spritesheet as this container (unless it's a DOMElement).
* If a spritesheet hasn't been defined, this container uses this child's spritesheet.
*
*
* <h4>Example</h4>
* addChildAt(child1, index);
*
@ -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,54 +36,237 @@ 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;
});
/**
* 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.
*
* Restrictions:
* - only Sprite, SpriteContainer, BitmapText, Bitmap and DOMElement are allowed to be added to the display list.
* - a child being added (with the exception of DOMElement) MUST have an image or spriteSheet defined on it.
* - a child's image/spriteSheet MUST never change while being on the display list.
*
* <h4>Example</h4>
* This example creates a sprite stage, adds a child to it, then uses {{#crossLink "Ticker"}}{{/crossLink}} to update the child
* and redraw the stage using {{#crossLink "SpriteStage/update"}}{{/crossLink}}.
*
* var stage = new createjs.SpriteStage("canvasElementId", false, false);
* stage.updateViewport(800, 600);
* var image = new createjs.Bitmap("imagePath.png");
* stage.addChild(image);
* createjs.Ticker.addEventListener("tick", handleTick);
* function handleTick(event) {
* image.x += 10;
* stage.update();
* }
*
* <strong>Note:</strong> SpriteStage is not included in the minified version of EaselJS.
*
* @class SpriteStage
* @extends Stage
* @constructor
* @param {HTMLCanvasElement | String | Object} canvas A canvas object that the SpriteStage will render to, 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.
**/
var SpriteStage = function(canvas, preserveDrawingBuffer, antialias) {
this.initialize(canvas, preserveDrawingBuffer, antialias);
};
var p = SpriteStage.prototype = new createjs.Stage();
// 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;
});
// static properties:
// 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.
*
* Restrictions:
* - only Sprite, SpriteContainer, BitmapText, Bitmap and DOMElement are allowed to be added to the display list.
* - a child being added (with the exception of DOMElement) MUST have an image or spriteSheet defined on it.
* - a child's image/spriteSheet MUST never change while being on the display list.
*
* <h4>Example</h4>
* This example creates a sprite stage, adds a child to it, then uses {{#crossLink "Ticker"}}{{/crossLink}} to update the child
* and redraw the stage using {{#crossLink "SpriteStage/update"}}{{/crossLink}}.
*
* var stage = new createjs.SpriteStage("canvasElementId", false, false);
* stage.updateViewport(800, 600);
* var image = new createjs.Bitmap("imagePath.png");
* stage.addChild(image);
* createjs.Ticker.addEventListener("tick", handleTick);
* function handleTick(event) {
* image.x += 10;
* stage.update();
* }
*
* <strong>Note:</strong> SpriteStage is not included in the minified version of EaselJS.
*
* @class SpriteStage
* @extends Stage
* @constructor
* @param {HTMLCanvasElement | String | Object} canvas A canvas object that the SpriteStage will render to, 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.
**/
function SpriteStage(canvas, preserveDrawingBuffer, antialias) {
this.Stage_constructor(canvas);
// 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
@ -149,7 +332,8 @@ var p = SpriteStage.prototype = new createjs.Stage();
* @readonly
**/
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");
}());

3
vendor/scripts/checkout.js vendored Normal file

File diff suppressed because one or more lines are too long

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
})();
/*
@ -78,7 +78,7 @@ this.createjs = this.createjs||{};
/**
* Contains properties and methods shared by all events for use with
* {{#crossLink "EventDispatcher"}}{{/crossLink}}.
*
*
* Note that Event objects are often reused, so you should never
* rely on an event object's state outside of the call stack it was received in.
* @class Event
@ -91,6 +91,7 @@ var Event = function(type, bubbles, cancelable) {
this.initialize(type, bubbles, cancelable);
};
var p = Event.prototype;
Event.prototype.constructor = Event;
// events:
@ -194,7 +195,7 @@ var p = Event.prototype;
* @readonly
*/
p.immediatePropagationStopped = false;
/**
* Indicates if {{#crossLink "Event/remove"}}{{/crossLink}} has been called on this event.
* @property removed
@ -249,21 +250,21 @@ var p = Event.prototype;
p.stopImmediatePropagation = function() {
this.immediatePropagationStopped = this.propagationStopped = true;
};
/**
* Causes the active listener to be removed via removeEventListener();
*
*
* myBtn.addEventListener("click", function(evt) {
* // do stuff...
* evt.remove(); // removes this listener.
* });
*
*
* @method remove
**/
p.remove = function() {
this.removed = true;
};
/**
* Returns a clone of the Event instance.
* @method clone
@ -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,8 +3536,8 @@ 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);
break;
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

@ -46,7 +46,7 @@ this.createjs = this.createjs||{};
/**
* Contains properties and methods shared by all events for use with
* {{#crossLink "EventDispatcher"}}{{/crossLink}}.
*
*
* Note that Event objects are often reused, so you should never
* rely on an event object's state outside of the call stack it was received in.
* @class Event
@ -59,6 +59,7 @@ var Event = function(type, bubbles, cancelable) {
this.initialize(type, bubbles, cancelable);
};
var p = Event.prototype;
Event.prototype.constructor = Event;
// events:
@ -162,7 +163,7 @@ var p = Event.prototype;
* @readonly
*/
p.immediatePropagationStopped = false;
/**
* Indicates if {{#crossLink "Event/remove"}}{{/crossLink}} has been called on this event.
* @property removed
@ -217,21 +218,21 @@ var p = Event.prototype;
p.stopImmediatePropagation = function() {
this.immediatePropagationStopped = this.propagationStopped = true;
};
/**
* Causes the active listener to be removed via removeEventListener();
*
*
* myBtn.addEventListener("click", function(evt) {
* // do stuff...
* evt.remove(); // removes this listener.
* });
*
*
* @method remove
**/
p.remove = function() {
this.removed = true;
};
/**
* Returns a clone of the Event instance.
* @method clone
@ -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
@ -974,7 +1579,7 @@ var p = Tween.prototype = new createjs.EventDispatcher();
* Registers or unregisters a tween with the ticking system.
* @method _register
* @param {Tween} tween The tween instance to register or unregister.
* @param {Boolean} value If true, the tween is registered. If false the tween is unregistered.
* @param {Boolean} value If true, the tween is registered. If false the tween is unregistered.
* @static
* @protected
*/
@ -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:
@ -1701,7 +2310,7 @@ var p = Timeline.prototype = new createjs.EventDispatcher();
* @protected
**/
p._labels = null;
/**
* @property _labelList
* @type Array[Object]
@ -1828,7 +2437,7 @@ var p = Timeline.prototype = new createjs.EventDispatcher();
p.setLabels = function(o) {
this._labels = o ? o : {};
};
/**
* Returns a sorted list of the labels defined on this timeline.
* @method getLabels
@ -1846,7 +2455,7 @@ var p = Timeline.prototype = new createjs.EventDispatcher();
}
return list;
};
/**
* Returns the name of the label on or immediately before the current position. For example, given a timeline with
* two labels, "first" on frame index 4, and "second" on frame 8, getCurrentLabel would return:<UL>
@ -1867,7 +2476,7 @@ var p = Timeline.prototype = new createjs.EventDispatcher();
}
return null;
};
/**
* Unpauses this timeline and jumps to the specified position or label.
* @method gotoAndPlay
@ -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
})();