Merge branch 'master' into production

This commit is contained in:
Nick Winter 2014-10-02 17:04:47 -07:00
commit b2e6bcf872
74 changed files with 40328 additions and 40387 deletions

View file

@ -119,13 +119,12 @@ module.exports = class Angel extends CocoClass
return if @aborting return if @aborting
# Toggle BOX2D_ENABLED during deserialization so that if we have box2d in the namespace, the Collides Components still don't try to create bodies for deserialized Thangs upon attachment. # Toggle BOX2D_ENABLED during deserialization so that if we have box2d in the namespace, the Collides Components still don't try to create bodies for deserialized Thangs upon attachment.
window.BOX2D_ENABLED = false window.BOX2D_ENABLED = false
World.deserialize serialized, @shared.worldClassMap, @shared.lastSerializedWorldFrames, @finishBeholdingWorld(goalStates), startFrame, endFrame, streamingWorld @streamingWorld = World.deserialize serialized, @shared.worldClassMap, @shared.lastSerializedWorldFrames, @finishBeholdingWorld(goalStates), startFrame, endFrame, streamingWorld
window.BOX2D_ENABLED = true window.BOX2D_ENABLED = true
@shared.lastSerializedWorldFrames = serialized.frames @shared.lastSerializedWorldFrames = serialized.frames
finishBeholdingWorld: (goalStates) -> (world) => finishBeholdingWorld: (goalStates) -> (world) =>
return if @aborting return if @aborting
@streamingWorld = world
finished = world.frames.length is world.totalFrames finished = world.frames.length is world.totalFrames
firstChangedFrame = world.findFirstChangedFrame @shared.world firstChangedFrame = world.findFirstChangedFrame @shared.world
eventType = if finished then 'god:new-world-created' else 'god:streaming-world-updated' eventType = if finished then 'god:new-world-created' else 'god:streaming-world-updated'
@ -208,6 +207,8 @@ module.exports = class Angel extends CocoClass
@say 'Fired worker.' @say 'Fired worker.'
@initialized = false @initialized = false
@work = null @work = null
@streamingWorld = null
@deserializationQueue = null
@hireWorker() if rehire @hireWorker() if rehire
hireWorker: -> hireWorker: ->

View file

@ -63,7 +63,13 @@ module.exports = class SpriteParser
instructions.push {t: c.t, gn: c.gn} instructions.push {t: c.t, gn: c.gn}
break break
@addContainer {c: instructions, b: container.bounds}, container.name @addContainer {c: instructions, b: container.bounds}, container.name
for movieClip in movieClips for movieClip, index in movieClips
if index is 0
for bounds in movieClip.frameBounds
bounds[0] -= @width / 2
bounds[1] -= @height / 2
movieClip.bounds[0] -= @width / 2
movieClip.bounds[1] -= @height / 2
localGraphics = @getGraphicsFromBlock(movieClip, source) localGraphics = @getGraphicsFromBlock(movieClip, source)
[shapeKeys, localShapes] = @getShapesFromBlock movieClip, source [shapeKeys, localShapes] = @getShapesFromBlock movieClip, source
localContainers = @getContainersFromMovieClip movieClip, source, true localContainers = @getContainersFromMovieClip movieClip, source, true
@ -174,7 +180,7 @@ module.exports = class SpriteParser
frameBoundsSource = @subSourceFromRange frameBoundsRange, source frameBoundsSource = @subSourceFromRange frameBoundsRange, source
if frameBoundsSource.search(/\[rect/) is -1 # some other statement; we don't have multiframe bounds if frameBoundsSource.search(/\[rect/) is -1 # some other statement; we don't have multiframe bounds
console.log 'Didn\'t have multiframe bounds for this movie clip.' console.log 'Didn\'t have multiframe bounds for this movie clip.'
frameBounds = [nominalBounds] frameBounds = [_.clone(nominalBounds)]
else else
lastRect = nominalBounds lastRect = nominalBounds
frameBounds = [] frameBounds = []
@ -192,12 +198,7 @@ module.exports = class SpriteParser
bounds = [0, 0, 1, 1] # Let's try this. bounds = [0, 0, 1, 1] # Let's try this.
frameBounds.push _.clone bounds frameBounds.push _.clone bounds
else else
frameBounds = [nominalBounds] frameBounds = [_.clone(nominalBounds)]
# Subtract half of width/height parsed from lib.properties
for bounds in frameBounds
bounds[0] -= @width / 2
bounds[1] -= @height / 2
functionExpressions.push {name: name, bounds: nominalBounds, frameBounds: frameBounds, expression: node.parent.parent, kind: kind} functionExpressions.push {name: name, bounds: nominalBounds, frameBounds: frameBounds, expression: node.parent.parent, kind: kind}
@walk ast, null, gatherFunctionExpressions @walk ast, null, gatherFunctionExpressions

View file

@ -141,9 +141,9 @@ module.exports = Lank = class Lank extends CocoClass
playAction: (action) -> playAction: (action) ->
return if @isRaster return if @isRaster
@currentAction = action @currentAction = action
return @hide() unless action.animation or action.container or action.relatedActions return @hide() unless action.animation or action.container or action.relatedActions or action.goesTo
@show() @show()
return @updateActionDirection() unless action.animation or action.container return @updateActionDirection() unless action.animation or action.container or action.goesTo
return if @sprite.placeholder return if @sprite.placeholder
m = if action.container then 'gotoAndStop' else 'gotoAndPlay' m = if action.container then 'gotoAndStop' else 'gotoAndPlay'
@sprite[m]?(action.name) @sprite[m]?(action.name)

View file

@ -340,7 +340,7 @@ module.exports = LayerAdapter = class LayerAdapter extends CocoClass
if action.container if action.container
containersToRender[action.container] = true containersToRender[action.container] = true
else if action.animation else if action.animation
animationContainers = @getContainersForAnimation(thangType, action.animation) animationContainers = @getContainersForAnimation(thangType, action.animation, action)
containersToRender[container.gn] = true for container in animationContainers containersToRender[container.gn] = true for container in animationContainers
spriteBuilder = new SpriteBuilder(thangType, {colorConfig: colorConfig}) spriteBuilder = new SpriteBuilder(thangType, {colorConfig: colorConfig})
@ -355,10 +355,13 @@ module.exports = LayerAdapter = class LayerAdapter extends CocoClass
frame = spriteSheetBuilder.addFrame(container, null, @resolutionFactor * (thangType.get('scale') or 1)) frame = spriteSheetBuilder.addFrame(container, null, @resolutionFactor * (thangType.get('scale') or 1))
spriteSheetBuilder.addAnimation(containerKey, [frame], false) spriteSheetBuilder.addAnimation(containerKey, [frame], false)
getContainersForAnimation: (thangType, animation) -> getContainersForAnimation: (thangType, animation, action) ->
containers = thangType.get('raw').animations[animation].containers rawAnimation = thangType.get('raw').animations[animation]
if not rawAnimation
console.error 'thang type', thangType.get('name'), 'is missing animation', animation, 'from action', action
containers = rawAnimation.containers
for animation in thangType.get('raw').animations[animation].animations for animation in thangType.get('raw').animations[animation].animations
containers = containers.concat(@getContainersForAnimation(thangType, animation.gn)) containers = containers.concat(@getContainersForAnimation(thangType, animation.gn, action))
return containers return containers
#- Rendering sprite sheets for singular thang types #- Rendering sprite sheets for singular thang types

View file

@ -1,5 +1,13 @@
SpriteBuilder = require 'lib/sprites/SpriteBuilder' SpriteBuilder = require 'lib/sprites/SpriteBuilder'
# Put this on MovieClips
specialGoToAndStop = (frame) ->
if frame is @currentFrame and @childrenCopy
@addChild(@childrenCopy...)
else
@gotoAndStop(frame)
@childrenCopy = @children.slice(0)
module.exports = class SegmentedSprite extends createjs.SpriteContainer module.exports = class SegmentedSprite extends createjs.SpriteContainer
childMovieClips: null childMovieClips: null
@ -59,9 +67,14 @@ module.exports = class SegmentedSprite extends createjs.SpriteContainer
else else
@currentFrame = 0 @currentFrame = 0
@baseMovieClip.gotoAndStop(@currentFrame) @baseMovieClip.specialGoToAndStop(@currentFrame)
movieClip.gotoAndStop(@currentFrame) for movieClip in @childMovieClips for movieClip in @childMovieClips
@takeChildrenFromMovieClip() if movieClip.mode is 'single'
movieClip.specialGoToAndStop(movieClip.startPosition)
else
movieClip.specialGoToAndStop(@currentFrame)
@takeChildrenFromMovieClip(@baseMovieClip, @)
@loop = action.loops isnt false @loop = action.loops isnt false
@goesTo = action.goesTo @goesTo = action.goesTo
@notifyActionNeedsRender(action) if @actionNotSupported @notifyActionNeedsRender(action) if @actionNotSupported
@ -93,7 +106,11 @@ module.exports = class SegmentedSprite extends createjs.SpriteContainer
sprite.scaleX = sprite.scaleY = 1 / @resolutionFactor sprite.scaleX = sprite.scaleY = 1 / @resolutionFactor
@children = [] @children = []
@addChild(sprite) @addChild(sprite)
else if action.goesTo
@goto(action.goesTo, @paused)
return
@scaleX *= -1 if action.flipX @scaleX *= -1 if action.flipX
@scaleY *= -1 if action.flipY @scaleY *= -1 if action.flipY
@baseScaleX = @scaleX @baseScaleX = @scaleX
@ -101,7 +118,7 @@ module.exports = class SegmentedSprite extends createjs.SpriteContainer
return return
notifyActionNeedsRender: (action) -> notifyActionNeedsRender: (action) ->
@sprite?.trigger('action-needs-render', @sprite, action) @lank?.trigger('action-needs-render', @lank, action)
buildMovieClip: (animationName, mode, startPosition, loops) -> buildMovieClip: (animationName, mode, startPosition, loops) ->
key = JSON.stringify([@spriteSheetPrefix].concat(arguments)) key = JSON.stringify([@spriteSheetPrefix].concat(arguments))
@ -115,7 +132,6 @@ module.exports = class SegmentedSprite extends createjs.SpriteContainer
raw = @thangType.get('raw') raw = @thangType.get('raw')
animData = raw.animations[animationName] animData = raw.animations[animationName]
@lastAnimData = animData @lastAnimData = animData
movieClip = new createjs.MovieClip()
locals = {} locals = {}
_.extend locals, @buildMovieClipContainers(animData.containers) _.extend locals, @buildMovieClipContainers(animData.containers)
@ -127,6 +143,7 @@ module.exports = class SegmentedSprite extends createjs.SpriteContainer
anim = new createjs.MovieClip() anim = new createjs.MovieClip()
anim.initialize(mode ? createjs.MovieClip.INDEPENDENT, startPosition ? 0, loops ? true) anim.initialize(mode ? createjs.MovieClip.INDEPENDENT, startPosition ? 0, loops ? true)
anim.specialGoToAndStop = specialGoToAndStop
for tweenData in animData.tweens for tweenData in animData.tweens
stopped = false stopped = false
@ -221,7 +238,7 @@ module.exports = class SegmentedSprite extends createjs.SpriteContainer
else if not @loop else if not @loop
@paused = true @paused = true
newFrame = @animLength - 1 newFrame = @animLength - 1
@dispatchEvent('animationend') _.defer => @dispatchEvent('animationend')
else else
newFrame = newFrame % @animLength newFrame = newFrame % @animLength
@ -243,30 +260,23 @@ module.exports = class SegmentedSprite extends createjs.SpriteContainer
@currentFrame = newFrame @currentFrame = newFrame
return if translatedFrame is @baseMovieClip.currentFrame return if translatedFrame is @baseMovieClip.currentFrame
@baseMovieClip.gotoAndStop(translatedFrame) @baseMovieClip.specialGoToAndStop(translatedFrame)
movieClip.gotoAndStop(newFrame) for movieClip in @childMovieClips for movieClip in @childMovieClips
movieClip.specialGoToAndStop(if movieClip.mode is 'single' then movieClip.startPosition else newFrame)
@children = [] @children = []
@takeChildrenFromMovieClip() @takeChildrenFromMovieClip(@baseMovieClip, @)
takeChildrenFromMovieClip: -> takeChildrenFromMovieClip: (movieClip, recipientContainer) ->
i = 0 for child in movieClip.childrenCopy
while i < @baseMovieClip.children.length
child = @baseMovieClip.children[i]
if child instanceof createjs.MovieClip if child instanceof createjs.MovieClip
newChild = new createjs.SpriteContainer(@spriteSheet) childRecipient = new createjs.SpriteContainer(@spriteSheet)
j = 0 @takeChildrenFromMovieClip(child, childRecipient)
while j < child.children.length for prop in ['regX', 'regY', 'rotation', 'scaleX', 'scaleY', 'skewX', 'skewY', 'x', 'y']
grandChild = child.children[j] childRecipient[prop] = child[prop]
if grandChild instanceof createjs.MovieClip recipientContainer.addChild(childRecipient)
console.error('MovieClip Segmentedsprites not currently working at this depth!')
continue
newChild.addChild(grandChild)
for prop in ['regX', 'regY', 'rotation', 'scaleX', 'scaleY', 'skewX', 'skewY', 'x', 'y']
newChild[prop] = child[prop]
@addChild(newChild)
i += 1
else else
@addChild(child) recipientContainer.addChild(child)
# _getBounds: createjs.SpriteContainer.prototype.getBounds # _getBounds: createjs.SpriteContainer.prototype.getBounds

View file

@ -75,4 +75,4 @@ module.exports = class SingularSprite extends createjs.Sprite
return return
notifyActionNeedsRender: (action) -> notifyActionNeedsRender: (action) ->
@sprite?.trigger('action-needs-render', @sprite, action) @lank?.trigger('action-needs-render', @lank, action)

View file

@ -98,6 +98,7 @@ module.exports = Surface = class Surface extends CocoClass
initEasel: -> initEasel: ->
@normalStage = new createjs.Stage(@normalCanvas[0]) @normalStage = new createjs.Stage(@normalCanvas[0])
@webGLStage = new createjs.SpriteStage(@webGLCanvas[0]) @webGLStage = new createjs.SpriteStage(@webGLCanvas[0])
@normalStage.nextStage = @webGLStage
@camera = AudioPlayer.camera = new Camera @webGLCanvas @camera = AudioPlayer.camera = new Camera @webGLCanvas
@normalLayers.push @surfaceTextLayer = new Layer name: 'Surface Text', layerPriority: 1, transform: Layer.TRANSFORM_SURFACE_TEXT, camera: @camera @normalLayers.push @surfaceTextLayer = new Layer name: 'Surface Text', layerPriority: 1, transform: Layer.TRANSFORM_SURFACE_TEXT, camera: @camera

View file

@ -364,7 +364,6 @@ module.exports = class World
o.trackedPropertiesPerThangValuesOffsets = [] # Needed to reconstruct ArrayBufferViews on other end, since Firefox has bugs transfering those: https://bugzilla.mozilla.org/show_bug.cgi?id=841904 and https://bugzilla.mozilla.org/show_bug.cgi?id=861925 # Actually, as of January 2014, it should be fixed. So we could try to undo the workaround. o.trackedPropertiesPerThangValuesOffsets = [] # Needed to reconstruct ArrayBufferViews on other end, since Firefox has bugs transfering those: https://bugzilla.mozilla.org/show_bug.cgi?id=841904 and https://bugzilla.mozilla.org/show_bug.cgi?id=861925 # Actually, as of January 2014, it should be fixed. So we could try to undo the workaround.
transferableStorageBytesNeeded = 0 transferableStorageBytesNeeded = 0
nFrames = endFrame - startFrame nFrames = endFrame - startFrame
streaming = nFrames < @totalFrames
for thang in @thangs for thang in @thangs
# Don't serialize empty trackedProperties for stateless Thangs which haven't changed (like obstacles). # Don't serialize empty trackedProperties for stateless Thangs which haven't changed (like obstacles).
# Check both, since sometimes people mark stateless Thangs but then change them, and those should still be tracked, and the inverse doesn't work on the other end (we'll just think it doesn't exist then). # Check both, since sometimes people mark stateless Thangs but then change them, and those should still be tracked, and the inverse doesn't work on the other end (we'll just think it doesn't exist then).
@ -477,6 +476,7 @@ module.exports = class World
w.frames = [] unless streamingWorld w.frames = [] unless streamingWorld
clearTimeout @deserializationTimeout if @deserializationTimeout clearTimeout @deserializationTimeout if @deserializationTimeout
@deserializationTimeout = _.delay @deserializeSomeFrames, 1, o, w, finishedWorldCallback, perf, startFrame, endFrame @deserializationTimeout = _.delay @deserializeSomeFrames, 1, o, w, finishedWorldCallback, perf, startFrame, endFrame
w # Return in-progress deserializing world
# Spread deserialization out across multiple calls so the interface stays responsive # Spread deserialization out across multiple calls so the interface stays responsive
@deserializeSomeFrames: (o, w, finishedWorldCallback, perf, startFrame, endFrame) => @deserializeSomeFrames: (o, w, finishedWorldCallback, perf, startFrame, endFrame) =>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -58,7 +58,7 @@
select select
margin-top: 10px margin-top: 10px
#marker-button, #end-button #marker-button, #play-button, #stop-button
margin: 10px 10px 10px 0 margin: 10px 10px 10px 0
.slider-cell .slider-cell

View file

@ -1,4 +1,12 @@
#employers-view #employers-view
.deprecation-warning
text-align: center
margin-bottom: 50px
.deprecated
cursor: default
opacity: 0.25
.artisanal-claim .artisanal-claim
background: transparent url(/images/pages/employer/artisanal_claim.png) no-repeat center background: transparent url(/images/pages/employer/artisanal_claim.png) no-repeat center
margin-bottom: 5px margin-bottom: 5px

View file

@ -5,7 +5,8 @@ $mapHeight: 1536
$forestMapWidth: 2500 $forestMapWidth: 2500
$dungeonMapWidth: 2350 $dungeonMapWidth: 2350
$forestMapSeaBackground: #71bad0 $forestMapSeaBackground: #71bad0
$dungeonMapCaveBackground: rgb(54, 43, 34) $dungeonMapCaveBackground: rgba(68, 54, 45, 1)
$dungeonMapCaveBackgroundTransparent: rgba(68, 54, 45, 0)
$levelDotWidth: 2% $levelDotWidth: 2%
$levelDotHeight: $levelDotWidth * $forestMapWidth / $mapHeight $levelDotHeight: $levelDotWidth * $forestMapWidth / $mapHeight
$levelDotZ: $levelDotHeight * 0.25 $levelDotZ: $levelDotHeight * 0.25
@ -30,6 +31,7 @@ $gameControlMargin: 30px
#world-map-view #world-map-view
width: 100% width: 100%
height: 100% height: 100%
position: absolute
&.forest &.forest
background-color: $forestMapSeaBackground background-color: $forestMapSeaBackground
@ -37,6 +39,36 @@ $gameControlMargin: 30px
&.dungeon &.dungeon
background-color: $dungeonMapCaveBackground background-color: $dungeonMapCaveBackground
.gradient
position: absolute
z-index: 0
&.horizontal-gradient
left: 0
right: 0
height: 3%
&.vertical-gradient
top: 0
bottom: 0
width: 3%
&.top-gradient
top: 0
background: linear-gradient(to bottom, $dungeonMapCaveBackground 0%, $dungeonMapCaveBackgroundTransparent 100%)
&.right-gradient
right: 0
background: linear-gradient(to left, $dungeonMapCaveBackground 0%, $dungeonMapCaveBackgroundTransparent 100%)
&.bottom-gradient
bottom: 0
background: linear-gradient(to top, $dungeonMapCaveBackground 0%, $dungeonMapCaveBackgroundTransparent 100%)
&.left-gradient
left: 0
background: linear-gradient(to right, $dungeonMapCaveBackground 0%, $dungeonMapCaveBackgroundTransparent 100%)
.map .map
position: relative position: relative

View file

@ -104,11 +104,11 @@ block content
h3.panel-title h3.panel-title
i.glyphicon.glyphicon-wrench i.glyphicon.glyphicon-wrench
a(href="account/settings#password" data-i18n="general.password") Password a(href="account/settings#password" data-i18n="general.password") Password
.panel.panel-default //.panel.panel-default
.panel-heading // .panel-heading
h3.panel-title // h3.panel-title
i.glyphicon.glyphicon-briefcase // i.glyphicon.glyphicon-briefcase
a(href="account/settings#job-profile" data-i18n="account_settings.job_profile") Job Profile // a(href="account/settings#job-profile" data-i18n="account_settings.job_profile") Job Profile
.col-sm-6 .col-sm-6
h2(data-i18n="user.recently_played") Recently Played h2(data-i18n="user.recently_played") Recently Played
hr hr

View file

@ -63,10 +63,7 @@ body
.footer.clearfix .footer.clearfix
.content .content
p.footer-link-text p.footer-link-text
if pathname == "/" || (me.get('permissions', true)).indexOf('employer') != -1 a(href='/', tabindex=-1, data-i18n="nav.home") Home
a(href='/employers', tabindex=-1, data-i18n="nav.employers") Employers
else
a(href='/', tabindex=-1, data-i18n="nav.home") Home
a(href='/play/ladder', tabindex=-1, data-i18n="home.multiplayer") Multiplayer a(href='/play/ladder', tabindex=-1, data-i18n="home.multiplayer") Multiplayer
a(href='/community', tabindex=-1, data-i18n="nav.community") Community a(href='/community', tabindex=-1, data-i18n="nav.community") Community
a(href='/contribute', tabindex=-1, data-i18n="nav.contribute") Contribute a(href='/contribute', tabindex=-1, data-i18n="nav.contribute") Contribute

View file

@ -90,7 +90,7 @@ block outer_content
div#thang-type-treema div#thang-type-treema
.clearfix .clearfix
div#display-col.well div#display-col.well
canvas#canvas(width="400", height="400") canvas#canvas(width="400", height="600")
select#animations-select.form-control select#animations-select.form-control
for animation in animations for animation in animations
option #{animation} option #{animation}
@ -98,7 +98,10 @@ block outer_content
button.btn.btn-small.btn-primary#marker-button button.btn.btn-small.btn-primary#marker-button
i.icon-map-marker i.icon-map-marker
span.spl Markers span.spl Markers
button.btn.btn-small.btn-primary#end-button button.btn.btn-small.btn-primary#play-button
i.icon-play
span.spl Play
button.btn.btn-small.btn-primary#stop-button
i.icon-stop i.icon-stop
span.spl Stop span.spl Stop
div.slider-cell div.slider-cell

View file

@ -1,183 +1,188 @@
extends /templates/recruitment_base extends /templates/recruitment_base
block content block content
.artisanal-claim .deprecation-warning
if me.get('anonymous') h1(data-i18n="employers.deprecation_warning_title") Sorry, CodeCombat is not recruiting right now.
a#login-link(data-i18n="login.log_in") Log In p(data-i18n="employers.deprecation_warning") We are focusing on beginner levels instead of finding expert developers for the time being.
br
if !isEmployer && !me.isAdmin() .deprecated
#tagline .artisanal-claim
h1(data-i18n="employers.hire_developers_not_credentials") Hire developers, not credentials. if me.get('anonymous')
button.btn.get-started-button.employer-button(data-i18n="employers.get_started") Get Started a#login-link(data-i18n="login.log_in") Log In
else
if !me.get('anonymous')
a#logout-link(data-i18n="login.log_out") Log Out
br br
.row if !isEmployer && !me.isAdmin()
- var fullProfiles = isEmployer || me.isAdmin(); #tagline
h1(data-i18n="employers.hire_developers_not_credentials") Hire developers, not credentials.
if fullProfiles
#filter-column.col-md-3
#filter
.panel-group#filter_panel
a#filter-link(data-toggle="collapse" data-target="#collapseOne")
.panel.panel-default
.panel-heading
h4.panel-title
span.glyphicon.glyphicon-folder-open#folder-icon
| Filter
.panel-collapse.collapse.in#collapseOne
.panel-body
p
strong(data-i18n="employers.already_screened") We've already technically screened all our candidates
span(data-i18n="employers.filter_further") , but you can also filter further:
form#filters
.filter_section#visa_filter
h4(data-i18n="employers.filter_visa") Visa
label
input(type="checkbox" name="visa" value="Authorized to work in the US")
span(data-i18n="employers.filter_visa_yes") US Authorized
| (#{candidatesInFilter("visa","Authorized to work in the US")})
label
input(type="checkbox" name="visa" value="Need visa sponsorship")
span(data-i18n="employers.filter_visa_no") Not Authorized
| (#{candidatesInFilter("visa","Need visa sponsorship")})
.filter_section#school_filter
h4(data-i18n="account_profile.education") Education
label
input(type="checkbox" name="schoolFilter" value="Top School")
span(data-i18n="employers.filter_education_top") Top School
| (#{candidatesInFilter("schoolFilter","Top School")})
label
input(type="checkbox" name="schoolFilter" value="Other")
span(data-i18n="employers.filter_education_other") Other
| (#{candidatesInFilter("schoolFilter","Other")})
.filter_section#role_filter
h4(data-i18n="employers.candidate_role") Role
label
input(type="checkbox" name="roleFilter" value="Web Developer")
span(data-i18n="employers.filter_role_web_developer") Web Developer
| (#{candidatesInFilter("roleFilter","Web Developer")})
label
input(type="checkbox" name="roleFilter" value="Software Developer")
span(data-i18n="employers.filter_role_software_developer") Software Developer
| (#{candidatesInFilter("roleFilter","Software Developer")})
label
input(type="checkbox" name="roleFilter" value="Mobile Developer")
span(data-i18n="employers.filter_role_mobile_developer") Mobile Developer
| (#{candidatesInFilter("roleFilter","Mobile Developer")})
.filter_section#seniority_filter
h4(data-i18n="employers.filter_experience") Experience
label
input(type="checkbox" name="seniorityFilter" value="Senior")
span(data-i18n="employers.filter_experience_senior") Senior
| (#{candidatesInFilter("seniorityFilter","Senior")})
label
input(type="checkbox" name="seniorityFilter" value="Junior")
span(data-i18n="employers.filter_experience_junior") Junior
| (#{candidatesInFilter("seniorityFilter","Junior")})
label
input(type="checkbox" name="seniorityFilter" value="Recent Grad")
span(data-i18n="employers.filter_experience_recent_grad") Recent Grad
| (#{candidatesInFilter("seniorityFilter","Recent Grad")})
label
input(type="checkbox" name="seniorityFilter" value="College Student")
span(data-i18n="employers.filter_experience_student") College Student
| (#{candidatesInFilter("seniorityFilter","College Student")})
//input#select_all_checkbox(type="checkbox" name="select_all" checked)
//| Select all
p#results
| #{numberOfCandidates}
span(data-i18n="employers.results") results
h4#filter-alerts-heading Filter Email Alerts
p Get an email whenever a candidate meeting certain criteria enters the system.
table#saved-filter-table
thead
tr
th Filters
th Remove
tbody
button.btn#create-alert-button Create Alert with Current Filters
#candidates-column(class=fullProfiles ? "full-profiles col-md-9" : "teaser-profiles col-md-12")
if candidates.length
#candidate-table
table
tbody
for candidate, index in featuredCandidates
- var profile = candidate.get('jobProfile');
- var authorized = candidate.id; // If we have the id, then we are authorized.
- var profileAge = (new Date() - new Date(profile.updated)) / 86400 / 1000;
- var expired = profileAge > 2 * 30.4;
- var curated = profile.curated;
- var photoSize = fullProfiles ? 75 : 50;
tr.candidate-row(data-candidate-id=candidate.id, id=candidate.id, class=expired ? "expired" : "")
td(rowspan=3)
- var photoURL = candidate.getPhotoURL(photoSize, false, true);
div(class="candidate-picture " + (/^\/file/.test(photoURL) ? "" : "anonymous"), style='background-image: url(' + encodeURI(photoURL) + ')')
if fullProfiles
td.candidate-name-cell
strong= profile.name
| -
span= profile.jobTitle
tr.description_row(data-candidate-id=candidate.id)
if curated && curated.shortDescription
td.candidate-description
div #{curated.shortDescription}
else
td.candidate-description
div #{profile.shortDescription}
tr.border_row(data-candidate-id=candidate.id)
if curated
- var workHistory = curated.workHistory.join(",");
if !fullProfiles
td.tag_column
img(src="/images/pages/employer/tag.png")
| #{profile.jobTitle}
td.location_column
img(src="/images/pages/employer/location.png")
| #{curated.location}
td.education_column
img(src="/images/pages/employer/education.png")
| #{curated.education}
td.work_column
if workHistory
img(src="/images/pages/employer/briefcase.png")
| #{workHistory}
if !fullProfiles
div#info_wrapper
span.hiring-call-to-action
h2#start-hiring(data-i18n="employers.start_hiring") Start hiring.
button.btn.get-started-button.employer-button(data-i18n="employers.get_started") Get Started button.btn.get-started-button.employer-button(data-i18n="employers.get_started") Get Started
else
h2#hiring-reasons.hiring-call-to-action(data-i18n="employers.reasons") Three reasons you should hire through us: if !me.get('anonymous')
.reasons#top_row a#logout-link(data-i18n="login.log_out") Log Out
.reason br
img.employer_icon(src="/images/pages/employer/employer_icon2.png") .row
h3(data-i18n="employers.everyone_looking") Everyone here is looking for their next opportunity. - var fullProfiles = isEmployer || me.isAdmin();
p(data-i18n="employers.everyone_looking_blurb") Forget about 20% LinkedIn InMail response rates. Everyone that we list on this site wants to find their next position and will respond to your request for an introduction.
.reason if fullProfiles
img.employer_icon(src="/images/pages/employer/employer_icon6.png") #filter-column.col-md-3
h3(data-i18n="employers.weeding") Sit back; we've done the weeding for you. #filter
p(data-i18n="employers.weeding_blurb") Every player that we list has been screened for technical ability. We also perform phone screens for select candidates and make notes on their profiles to save you time. .panel-group#filter_panel
.reason a#filter-link(data-toggle="collapse" data-target="#collapseOne")
img(class="employer_icon" src="/images/pages/employer/employer_icon3.png") .panel.panel-default
h3(data-i18n="employers.pass_screen") They will pass your technical screen. .panel-heading
p(data-i18n="employers.pass_screen_blurb") Review each candidate's code before reaching out. One employer found that 5x as many of our devs passed their technical screen than hiring from Hacker News. h4.panel-title
span.hiring-call-to-action span.glyphicon.glyphicon-folder-open#folder-icon
h2(data-i18n="employers.make_hiring_easier") Make my hiring easier, please. | Filter
button.btn.get-started-button.employer-button(data-i18n="employers.get_started") Get Started .panel-collapse.collapse.in#collapseOne
.reasons#bottom_row .panel-body
.reason_long p
img.employer_icon(src="/images/pages/employer/employer_icon1.png") strong(data-i18n="employers.already_screened") We've already technically screened all our candidates
.reason_text span(data-i18n="employers.filter_further") , but you can also filter further:
h3(data-i18n="employers.what") What is CodeCombat? form#filters
p(data-i18n="employers.what_blurb") CodeCombat is a multiplayer browser programming game. Players write code to control their forces in battle against other developers. We support JavaScript, Python, Lua, Clojure, CoffeeScript, and Io. .filter_section#visa_filter
.reason_long h4(data-i18n="employers.filter_visa") Visa
img.employer_icon(src="/images/pages/employer/employer_icon5.png") label
.reason_text input(type="checkbox" name="visa" value="Authorized to work in the US")
h3(data-i18n="employers.cost") How much do we charge? span(data-i18n="employers.filter_visa_yes") US Authorized
p(data-i18n="employers.cost_blurb") We charge 15% of first year's salary and offer a 100% money back guarantee for 90 days. We don't charge for candidates who are already actively being interviewed at your company. | (#{candidatesInFilter("visa","Authorized to work in the US")})
label
input(type="checkbox" name="visa" value="Need visa sponsorship")
span(data-i18n="employers.filter_visa_no") Not Authorized
| (#{candidatesInFilter("visa","Need visa sponsorship")})
.filter_section#school_filter
h4(data-i18n="account_profile.education") Education
label
input(type="checkbox" name="schoolFilter" value="Top School")
span(data-i18n="employers.filter_education_top") Top School
| (#{candidatesInFilter("schoolFilter","Top School")})
label
input(type="checkbox" name="schoolFilter" value="Other")
span(data-i18n="employers.filter_education_other") Other
| (#{candidatesInFilter("schoolFilter","Other")})
.filter_section#role_filter
h4(data-i18n="employers.candidate_role") Role
label
input(type="checkbox" name="roleFilter" value="Web Developer")
span(data-i18n="employers.filter_role_web_developer") Web Developer
| (#{candidatesInFilter("roleFilter","Web Developer")})
label
input(type="checkbox" name="roleFilter" value="Software Developer")
span(data-i18n="employers.filter_role_software_developer") Software Developer
| (#{candidatesInFilter("roleFilter","Software Developer")})
label
input(type="checkbox" name="roleFilter" value="Mobile Developer")
span(data-i18n="employers.filter_role_mobile_developer") Mobile Developer
| (#{candidatesInFilter("roleFilter","Mobile Developer")})
.filter_section#seniority_filter
h4(data-i18n="employers.filter_experience") Experience
label
input(type="checkbox" name="seniorityFilter" value="Senior")
span(data-i18n="employers.filter_experience_senior") Senior
| (#{candidatesInFilter("seniorityFilter","Senior")})
label
input(type="checkbox" name="seniorityFilter" value="Junior")
span(data-i18n="employers.filter_experience_junior") Junior
| (#{candidatesInFilter("seniorityFilter","Junior")})
label
input(type="checkbox" name="seniorityFilter" value="Recent Grad")
span(data-i18n="employers.filter_experience_recent_grad") Recent Grad
| (#{candidatesInFilter("seniorityFilter","Recent Grad")})
label
input(type="checkbox" name="seniorityFilter" value="College Student")
span(data-i18n="employers.filter_experience_student") College Student
| (#{candidatesInFilter("seniorityFilter","College Student")})
//input#select_all_checkbox(type="checkbox" name="select_all" checked)
//| Select all
p#results
| #{numberOfCandidates}
span(data-i18n="employers.results") results
h4#filter-alerts-heading Filter Email Alerts
p Get an email whenever a candidate meeting certain criteria enters the system.
table#saved-filter-table
thead
tr
th Filters
th Remove
tbody
button.btn#create-alert-button Create Alert with Current Filters
#candidates-column(class=fullProfiles ? "full-profiles col-md-9" : "teaser-profiles col-md-12")
if candidates.length
#candidate-table
table
tbody
for candidate, index in featuredCandidates
- var profile = candidate.get('jobProfile');
- var authorized = candidate.id; // If we have the id, then we are authorized.
- var profileAge = (new Date() - new Date(profile.updated)) / 86400 / 1000;
- var expired = profileAge > 2 * 30.4;
- var curated = profile.curated;
- var photoSize = fullProfiles ? 75 : 50;
tr.candidate-row(data-candidate-id=candidate.id, id=candidate.id, class=expired ? "expired" : "")
td(rowspan=3)
- var photoURL = candidate.getPhotoURL(photoSize, false, true);
div(class="candidate-picture " + (/^\/file/.test(photoURL) ? "" : "anonymous"), style='background-image: url(' + encodeURI(photoURL) + ')')
if fullProfiles
td.candidate-name-cell
strong= profile.name
| -
span= profile.jobTitle
tr.description_row(data-candidate-id=candidate.id)
if curated && curated.shortDescription
td.candidate-description
div #{curated.shortDescription}
else
td.candidate-description
div #{profile.shortDescription}
tr.border_row(data-candidate-id=candidate.id)
if curated
- var workHistory = curated.workHistory.join(",");
if !fullProfiles
td.tag_column
img(src="/images/pages/employer/tag.png")
| #{profile.jobTitle}
td.location_column
img(src="/images/pages/employer/location.png")
| #{curated.location}
td.education_column
img(src="/images/pages/employer/education.png")
| #{curated.education}
td.work_column
if workHistory
img(src="/images/pages/employer/briefcase.png")
| #{workHistory}
if !fullProfiles
div#info_wrapper
span.hiring-call-to-action
h2#start-hiring(data-i18n="employers.start_hiring") Start hiring.
button.btn.get-started-button.employer-button(data-i18n="employers.get_started") Get Started
h2#hiring-reasons.hiring-call-to-action(data-i18n="employers.reasons") Three reasons you should hire through us:
.reasons#top_row
.reason
img.employer_icon(src="/images/pages/employer/employer_icon2.png")
h3(data-i18n="employers.everyone_looking") Everyone here is looking for their next opportunity.
p(data-i18n="employers.everyone_looking_blurb") Forget about 20% LinkedIn InMail response rates. Everyone that we list on this site wants to find their next position and will respond to your request for an introduction.
.reason
img.employer_icon(src="/images/pages/employer/employer_icon6.png")
h3(data-i18n="employers.weeding") Sit back; we've done the weeding for you.
p(data-i18n="employers.weeding_blurb") Every player that we list has been screened for technical ability. We also perform phone screens for select candidates and make notes on their profiles to save you time.
.reason
img(class="employer_icon" src="/images/pages/employer/employer_icon3.png")
h3(data-i18n="employers.pass_screen") They will pass your technical screen.
p(data-i18n="employers.pass_screen_blurb") Review each candidate's code before reaching out. One employer found that 5x as many of our devs passed their technical screen than hiring from Hacker News.
span.hiring-call-to-action
h2(data-i18n="employers.make_hiring_easier") Make my hiring easier, please.
button.btn.get-started-button.employer-button(data-i18n="employers.get_started") Get Started
.reasons#bottom_row
.reason_long
img.employer_icon(src="/images/pages/employer/employer_icon1.png")
.reason_text
h3(data-i18n="employers.what") What is CodeCombat?
p(data-i18n="employers.what_blurb") CodeCombat is a multiplayer browser programming game. Players write code to control their forces in battle against other developers. We support JavaScript, Python, Lua, Clojure, CoffeeScript, and Io.
.reason_long
img.employer_icon(src="/images/pages/employer/employer_icon5.png")
.reason_text
h3(data-i18n="employers.cost") How much do we charge?
p(data-i18n="employers.cost_blurb") We charge 15% of first year's salary and offer a 100% money back guarantee for 90 days. We don't charge for candidates who are already actively being interviewed at your company.

View file

@ -1,8 +1,8 @@
extends /templates/modal/modal_base extends /templates/modal/modal_base
block modal-header-content block modal-header-content
h1#choose-hero-header(data-i18n="choose_hero.choose_hero") Choose Your Hero h1#choose-hero-header.choose-hero-active.secret(data-i18n="choose_hero.choose_hero") Choose Your Hero
h1#choose-inventory-header.secret(data-i18n="inventory.choose_inventory") Equip Items h1#choose-inventory-header.choose-inventory-active.secret(data-i18n="inventory.choose_inventory") Equip Items
block modal-body-content block modal-body-content
#choose-hero-view #choose-hero-view
@ -10,7 +10,7 @@ block modal-body-content
#inventory-view #inventory-view
block modal-footer-content block modal-footer-content
button#choose-inventory-button.btn.btn-lg.btn-success(data-i18n="play.next") Next button#choose-inventory-button.btn.btn-lg.btn-success.choose-hero-active.secret(data-i18n="play.next") Next
button#choose-hero-button.btn.btn-lg.btn-primary.secret.pull-left(data-i18n="play.previous") Previous button#choose-hero-button.btn.btn-lg.btn-primary.choose-inventory-active.secret.pull-left(data-i18n="play.change_hero") Change Hero
button#play-level-button.btn.btn-lg.btn-success.secret(data-i18n="common.play") Play button#play-level-button.btn.btn-lg.btn-success.choose-inventory-active.secret(data-i18n="common.play") Play

View file

@ -1,4 +1,8 @@
.map .map
.gradient.horizontal-gradient.top-gradient
.gradient.vertical-gradient.right-gradient
.gradient.horizontal-gradient.bottom-gradient
.gradient.vertical-gradient.left-gradient
img.map-background(src="/images/pages/play/map_" + mapType + ".jpg", alt="") img.map-background(src="/images/pages/play/map_" + mapType + ".jpg", alt="")
- var seenNext = false; - var seenNext = false;
@ -17,7 +21,7 @@
each i in Array(level.difficulty) each i in Array(level.difficulty)
i.icon-star i.icon-star
- var playCount = levelPlayCountMap[level.id] - var playCount = levelPlayCountMap[level.id]
if playCount && playCount > 20 if playCount && playCount.sessions > 20
div div
span.spr #{playCount.sessions} span.spr #{playCount.sessions}
span(data-i18n="play.players") players span(data-i18n="play.players") players

View file

@ -336,7 +336,7 @@ module.exports = class ThangsTabView extends CocoView
return if e? and $(e.target).closest('#thang-search').length # Ignore if you're trying to search thangs return if e? and $(e.target).closest('#thang-search').length # Ignore if you're trying to search thangs
return unless (e? and $(e.target).closest('#thangs-tab-view').length) or key.isPressed('esc') or forceDeselect return unless (e? and $(e.target).closest('#thangs-tab-view').length) or key.isPressed('esc') or forceDeselect
if e then target = $(e.target) else target = @$el.find('.add-thangs-palette') # pretend to click on background if no event if e then target = $(e.target) else target = @$el.find('.add-thangs-palette') # pretend to click on background if no event
return true if target.attr('id') is 'surface' return true if target.attr('id') is 'webgl-surface'
target = target.closest('.add-thang-palette-icon') target = target.closest('.add-thang-palette-icon')
wasSelected = target.hasClass 'selected' wasSelected = target.hasClass 'selected'
@$el.find('.add-thangs-palette .add-thang-palette-icon.selected').removeClass('selected') @$el.find('.add-thangs-palette .add-thang-palette-icon.selected').removeClass('selected')
@ -400,6 +400,7 @@ module.exports = class ThangsTabView extends CocoView
pos.y = Math.round((pos.y - (thang.height ? 1) / 2) / snap.y) * snap.y + (thang.height ? 1) / 2 pos.y = Math.round((pos.y - (thang.height ? 1) / 2) / snap.y) * snap.y + (thang.height ? 1) / 2
pos.z = thang.depth / 2 pos.z = thang.depth / 2
thang.pos = pos thang.pos = pos
thang.stateChanged = true
@surface.lankBoss.update true # Make sure Obstacle layer resets cache @surface.lankBoss.update true # Make sure Obstacle layer resets cache
onSurfaceMouseMoved: (e) -> onSurfaceMouseMoved: (e) ->

View file

@ -28,6 +28,7 @@ module.exports = class ThangTypeEditView extends RootView
health: 10.0 health: 10.0
maxHealth: 10.0 maxHealth: 10.0
hudProperties: ['health'] hudProperties: ['health']
acts: true
events: events:
'click #clear-button': 'clearRawData' 'click #clear-button': 'clearRawData'
@ -35,7 +36,8 @@ module.exports = class ThangTypeEditView extends RootView
'change #real-upload-button': 'animationFileChosen' 'change #real-upload-button': 'animationFileChosen'
'change #animations-select': 'showAnimation' 'change #animations-select': 'showAnimation'
'click #marker-button': 'toggleDots' 'click #marker-button': 'toggleDots'
'click #end-button': 'endAnimation' 'click #stop-button': 'stopAnimation'
'click #play-button': 'playAnimation'
'click #history-button': 'showVersionHistory' 'click #history-button': 'showVersionHistory'
'click #fork-start-button': 'startForking' 'click #fork-start-button': 'startForking'
'click #save-button': 'openSaveModal' 'click #save-button': 'openSaveModal'
@ -74,13 +76,8 @@ module.exports = class ThangTypeEditView extends RootView
context.fileSizeString = @fileSizeString context.fileSizeString = @fileSizeString
context context
getAnimationNames: -> getAnimationNames: -> _.keys(@thangType.get('actions') or {})
raw = _.keys((@thangType.get('raw') or {}).animations)
return [] unless raw
raw = ("raw:#{name}" for name in raw)
main = _.keys(@thangType.get('actions') or {})
main.concat(raw)
afterRender: -> afterRender: ->
super() super()
return unless @supermodel.finished() return unless @supermodel.finished()
@ -113,17 +110,20 @@ module.exports = class ThangTypeEditView extends RootView
makeDot: (color) -> makeDot: (color) ->
circle = new createjs.Shape() circle = new createjs.Shape()
circle.graphics.beginFill(color).beginStroke('black').drawCircle(0, 0, 5) circle.graphics.beginFill(color).beginStroke('black').drawCircle(0, 0, 5)
circle.x = CENTER.x circle.scaleY = 0.2
circle.y = CENTER.y circle.scaleX = 0.5
circle.scaleY = 0.5
circle circle
initStage: -> initStage: ->
canvas = @$el.find('#canvas') canvas = @$el.find('#canvas')
@stage = new createjs.Stage(canvas[0]) @stage = new createjs.Stage(canvas[0])
@layerAdapter = new LayerAdapter({name:'Default', webGL: true}) @layerAdapter = new LayerAdapter({name:'Default', webGL: true})
@topLayer = new createjs.Container()
@layerAdapter.container.x = @topLayer.x = CENTER.x
@layerAdapter.container.y = @topLayer.y = CENTER.y
@stage.addChild(@layerAdapter.container, @topLayer)
@listenTo @layerAdapter, 'new-spritesheet', @onNewSpriteSheet @listenTo @layerAdapter, 'new-spritesheet', @onNewSpriteSheet
@stage.addChild(@layerAdapter.container)
@camera?.destroy() @camera?.destroy()
@camera = new Camera canvas @camera = new Camera canvas
@ -131,35 +131,39 @@ module.exports = class ThangTypeEditView extends RootView
@mouthDot = @makeDot('yellow') @mouthDot = @makeDot('yellow')
@aboveHeadDot = @makeDot('green') @aboveHeadDot = @makeDot('green')
@groundDot = @makeDot('red') @groundDot = @makeDot('red')
@stage.addChild(@groundDot, @torsoDot, @mouthDot, @aboveHeadDot) @topLayer.addChild(@groundDot, @torsoDot, @mouthDot, @aboveHeadDot)
@updateGrid() @updateGrid()
_.defer @refreshAnimation _.defer @refreshAnimation
@toggleDots(false)
createjs.Ticker.setFPS(30) createjs.Ticker.setFPS(30)
createjs.Ticker.addEventListener('tick', @stage) createjs.Ticker.addEventListener('tick', @stage)
toggleDots: -> toggleDots: (newShowDots) ->
@showDots = not @showDots @showDots = if typeof(newShowDots) is 'boolean' then newShowDots else not @showDots
@updateDots() @updateDots()
updateDots: -> updateDots: ->
@stage.removeChild(@torsoDot, @mouthDot, @aboveHeadDot, @groundDot) @topLayer.removeChild(@torsoDot, @mouthDot, @aboveHeadDot, @groundDot)
return unless @currentLank return unless @currentLank
return unless @showDots return unless @showDots
torso = @currentLank.getOffset 'torso' torso = @currentLank.getOffset 'torso'
mouth = @currentLank.getOffset 'mouth' mouth = @currentLank.getOffset 'mouth'
aboveHead = @currentLank.getOffset 'aboveHead' aboveHead = @currentLank.getOffset 'aboveHead'
@torsoDot.x = CENTER.x + torso.x * @scale @torsoDot.x = torso.x
@torsoDot.y = CENTER.y + torso.y * @scale @torsoDot.y = torso.y
@mouthDot.x = CENTER.x + mouth.x * @scale @mouthDot.x = mouth.x
@mouthDot.y = CENTER.y + mouth.y * @scale @mouthDot.y = mouth.y
@aboveHeadDot.x = CENTER.x + aboveHead.x * @scale @aboveHeadDot.x = aboveHead.x
@aboveHeadDot.y = CENTER.y + aboveHead.y * @scale @aboveHeadDot.y = aboveHead.y
@stage.addChild(@groundDot, @torsoDot, @mouthDot, @aboveHeadDot) @topLayer.addChild(@groundDot, @torsoDot, @mouthDot, @aboveHeadDot)
endAnimation: -> stopAnimation: ->
@currentLank?.queueAction('idle') @currentLank?.queueAction('idle')
playAnimation: ->
@currentLank?.queueAction(@$el.find('#animations-select').val())
updateGrid: -> updateGrid: ->
grid = new createjs.Container() grid = new createjs.Container()
line = new createjs.Shape() line = new createjs.Shape()
@ -247,19 +251,18 @@ module.exports = class ThangTypeEditView extends RootView
$('#spritesheets').empty() $('#spritesheets').empty()
for image in @layerAdapter.spriteSheet._images for image in @layerAdapter.spriteSheet._images
$('#spritesheets').append(image) $('#spritesheets').append(image)
@layerAdapter.container.x = CENTER.x
@layerAdapter.container.y = CENTER.y
@updateScale() @updateScale()
showAnimation: (animationName) -> showAnimation: (animationName) ->
animationName = @$el.find('#animations-select').val() unless _.isString animationName animationName = @$el.find('#animations-select').val() unless _.isString animationName
return unless animationName return unless animationName
if animationName.startsWith('raw:') @mockThang.action = animationName
animationName = animationName[4...] @showAction(animationName)
@showMovieClip(animationName)
else
@showAction(animationName)
@updateRotation() @updateRotation()
@updateScale() # must happen after update rotation, because updateRotation calls the sprite update() method. @updateScale() # must happen after update rotation, because updateRotation calls the sprite update() method.
showMovieClip: (animationName) -> showMovieClip: (animationName) ->
vectorParser = new SpriteBuilder(@thangType) vectorParser = new SpriteBuilder(@thangType)
movieClip = vectorParser.buildMovieClip(animationName) movieClip = vectorParser.buildMovieClip(animationName)
@ -268,6 +271,9 @@ module.exports = class ThangTypeEditView extends RootView
if reg if reg
movieClip.regX = -reg.x movieClip.regX = -reg.x
movieClip.regY = -reg.y movieClip.regY = -reg.y
scale = @thangType.get('scale')
if scale
movieClip.scaleX = movieClip.scaleY = scale
@showSprite(movieClip) @showSprite(movieClip)
getLankOptions: -> {resolutionFactor: @resolution, thang: @mockThang} getLankOptions: -> {resolutionFactor: @resolution, thang: @mockThang}
@ -292,23 +298,16 @@ module.exports = class ThangTypeEditView extends RootView
@layerAdapter.resetSpriteSheet() @layerAdapter.resetSpriteSheet()
@layerAdapter.addLank(lank) @layerAdapter.addLank(lank)
@currentLank = lank @currentLank = lank
lank.sprite.x = CENTER.x
lank.sprite.y = CENTER.y
lank.on 'new-sprite', ->
lank.sprite.x = CENTER.x
lank.sprite.y = CENTER.y
showSprite: (sprite) -> showSprite: (sprite) ->
@clearDisplayObject() @clearDisplayObject()
@clearLank() @clearLank()
sprite.x = CENTER.x @topLayer.addChild(sprite)
sprite.y = CENTER.y
@stage.addChildAt(sprite, 1)
@currentObject = sprite @currentObject = sprite
@updateDots() @updateDots()
clearDisplayObject: -> clearDisplayObject: ->
@stage.removeChild(@currentObject) if @currentObject? @topLayer.removeChild(@currentObject) if @currentObject?
clearLank: -> clearLank: ->
@layerAdapter.removeLank(@currentLank) if @currentLank @layerAdapter.removeLank(@currentLank) if @currentLank
@ -330,18 +329,12 @@ module.exports = class ThangTypeEditView extends RootView
@currentLank.update(true) @currentLank.update(true)
updateScale: => updateScale: =>
resValue = (@resolutionSlider.slider('value') + 1) / 10
scaleValue = (@scaleSlider.slider('value') + 1) / 10 scaleValue = (@scaleSlider.slider('value') + 1) / 10
@layerAdapter.container.scaleX = @layerAdapter.container.scaleY = @topLayer.scaleX = @topLayer.scaleY = scaleValue
fixed = scaleValue.toFixed(1) fixed = scaleValue.toFixed(1)
@scale = scaleValue @scale = scaleValue
@$el.find('.scale-label').text " #{fixed}x " @$el.find('.scale-label').text " #{fixed}x "
if @currentLank
@currentLank.sprite.scaleX = @currentLank.sprite.baseScaleX * scaleValue
@currentLank.sprite.scaleY = @currentLank.sprite.baseScaleY * scaleValue
else if @currentObject?
@currentObject.scaleX = @currentObject.scaleY = scaleValue / resValue
@updateGrid() @updateGrid()
@updateDots()
updateResolution: => updateResolution: =>
value = (@resolutionSlider.slider('value') + 1) / 10 value = (@resolutionSlider.slider('value') + 1) / 10
@ -427,6 +420,7 @@ module.exports = class ThangTypeEditView extends RootView
onSelectNode: (e, selected) => onSelectNode: (e, selected) =>
selected = selected[0] selected = selected[0]
@topLayer.removeChild(@boundsBox) if @boundsBox
return @stopShowingSelectedNode() if not selected return @stopShowingSelectedNode() if not selected
path = selected.getPath() path = selected.getPath()
parts = path.split('/') parts = path.split('/')
@ -437,15 +431,16 @@ module.exports = class ThangTypeEditView extends RootView
obj = vectorParser.buildMovieClip(key) if type is 'animations' obj = vectorParser.buildMovieClip(key) if type is 'animations'
obj = vectorParser.buildContainerFromStore(key) if type is 'containers' obj = vectorParser.buildContainerFromStore(key) if type is 'containers'
obj = vectorParser.buildShapeFromStore(key) if type is 'shapes' obj = vectorParser.buildShapeFromStore(key) if type is 'shapes'
if obj?.bounds
obj.regX = obj.bounds.x + obj.bounds.width / 2 bounds = obj?.bounds or obj?.nominalBounds
obj.regY = obj.bounds.y + obj.bounds.height / 2 if bounds
else if obj?.frameBounds?[0] @boundsBox = new createjs.Shape()
bounds = obj.frameBounds[0] @boundsBox.graphics.beginFill('#aaaaaa').beginStroke('black').drawRect(bounds.x, bounds.y, bounds.width, bounds.height)
obj.regX = bounds.x + bounds.width / 2 @topLayer.addChild(@boundsBox)
obj.regY = bounds.y + bounds.height / 2 obj.regX = @boundsBox.regX = bounds.x + bounds.width / 2
obj.regY = @boundsBox.regY = bounds.y + bounds.height / 2
@showSprite(obj) if obj @showSprite(obj) if obj
obj.y = 200 if obj # truly center the container
@showingSelectedNode = true @showingSelectedNode = true
@currentLank?.destroy() @currentLank?.destroy()
@currentLank = null @currentLank = null

View file

@ -115,6 +115,9 @@ module.exports = class InventoryView extends CocoView
@$el.find('#selected-items').hide() # Hide until one is selected @$el.find('#selected-items').hide() # Hide until one is selected
@delegateEvents() @delegateEvents()
if @selectedHero and not @startedLoadingFirstHero
@loadHero()
afterInsert: -> afterInsert: ->
super() super()
@canvasWidth = @$el.find('canvas').innerWidth() @canvasWidth = @$el.find('canvas').innerWidth()
@ -329,7 +332,8 @@ module.exports = class InventoryView extends CocoView
@loadHero() @loadHero()
loadHero: -> loadHero: ->
return unless @selectedHero and not @$el.hasClass 'secret' return unless @supermodel.finished() and @selectedHero and not @$el.hasClass 'secret'
@startedLoadingFirstHero = true
@stage?.removeAllChildren() @stage?.removeAllChildren()
if @selectedHero.loaded and movieClip = @movieClips?[@selectedHero.get('original')] if @selectedHero.loaded and movieClip = @movieClips?[@selectedHero.get('original')]
@stage.addChild(movieClip) @stage.addChild(movieClip)

View file

@ -17,6 +17,7 @@ class LevelSessionsCollection extends CocoCollection
module.exports = class WorldMapView extends RootView module.exports = class WorldMapView extends RootView
id: 'world-map-view' id: 'world-map-view'
template: template template: template
terrain: 'Dungeon' # or Grass
events: events:
'click .map-background': 'onClickMap' 'click .map-background': 'onClickMap'
@ -36,6 +37,7 @@ module.exports = class WorldMapView extends RootView
$(window).on 'resize', @onWindowResize $(window).on 'resize', @onWindowResize
@playAmbientSound() @playAmbientSound()
@preloadTopHeroes() @preloadTopHeroes()
@hadEverChosenHero = me.get('heroConfig')?.thangType
destroy: -> destroy: ->
$(window).off 'resize', @onWindowResize $(window).off 'resize', @onWindowResize
@ -75,7 +77,7 @@ module.exports = class WorldMapView extends RootView
context.levelStatusMap = @levelStatusMap context.levelStatusMap = @levelStatusMap
context.levelPlayCountMap = @levelPlayCountMap context.levelPlayCountMap = @levelPlayCountMap
context.isIPadApp = application.isIPadApp context.isIPadApp = application.isIPadApp
context.mapType = 'dungeon' context.mapType = _.string.slugify @terrain
context context
afterRender: -> afterRender: ->
@ -84,6 +86,7 @@ module.exports = class WorldMapView extends RootView
unless application.isIPadApp unless application.isIPadApp
_.defer => @$el.find('.game-controls .btn').tooltip() # Have to defer or i18n doesn't take effect. _.defer => @$el.find('.game-controls .btn').tooltip() # Have to defer or i18n doesn't take effect.
@$el.find('.level').tooltip() @$el.find('.level').tooltip()
@$el.addClass _.string.slugify @terrain
onSessionsLoaded: (e) -> onSessionsLoaded: (e) ->
for session in @sessions.models for session in @sessions.models
@ -113,7 +116,7 @@ module.exports = class WorldMapView extends RootView
@startLevel $(e.target).parents('.level-info-container') @startLevel $(e.target).parents('.level-info-container')
startLevel: (levelElement) -> startLevel: (levelElement) ->
playLevelModal = new PlayLevelModal supermodel: @supermodel, levelID: levelElement.data('level-id'), levelPath: levelElement.data('level-path'), levelName: levelElement.data('level-name') playLevelModal = new PlayLevelModal supermodel: @supermodel, levelID: levelElement.data('level-id'), levelPath: levelElement.data('level-path'), levelName: levelElement.data('level-name'), hadEverChosenHero: @hadEverChosenHero
@openModalView playLevelModal @openModalView playLevelModal
@$levelInfo?.hide() @$levelInfo?.hide()
@ -148,28 +151,45 @@ module.exports = class WorldMapView extends RootView
@$levelInfo.css('top', top) @$levelInfo.css('top', top)
onWindowResize: (e) => onWindowResize: (e) =>
mapHeight = 1536 mapHeight = iPadHeight = 1536
mapWidth = 2350 # 2500 for forest mapWidth = if @terrain is 'Dungeon' then 2350 else 2500
iPadWidth = 2048
aspectRatio = mapWidth / mapHeight aspectRatio = mapWidth / mapHeight
iPadAspectRatio = iPadWidth / iPadHeight
pageWidth = $(window).width() pageWidth = $(window).width()
pageHeight = $(window).height() pageHeight = $(window).height()
widthRatio = pageWidth / mapWidth widthRatio = pageWidth / mapWidth
heightRatio = pageHeight / mapHeight heightRatio = pageHeight / mapHeight
if widthRatio > heightRatio iPadWidthRatio = pageWidth / iPadWidth
resultingWidth = pageWidth if @terrain is 'Dungeon'
resultingHeight = resultingWidth / aspectRatio # Make sure we can see almost the whole map, fading to background in one dimension.
if heightRatio <= iPadWidthRatio
# Full width, full height, left and right margin
resultingHeight = pageHeight
resultingWidth = resultingHeight * aspectRatio
else if iPadWidthRatio < heightRatio * (iPadAspectRatio / aspectRatio)
# Cropped width, full height, left and right margin
resultingWidth = pageWidth
resultingHeight = resultingWidth / aspectRatio
else
# Cropped width, full height, top and bottom margin
resultingWidth = pageWidth * aspectRatio / iPadAspectRatio
resultingHeight = resultingWidth / aspectRatio
else else
resultingHeight = pageHeight # Scale it in either dimension so that we're always full on one of the dimensions.
resultingWidth = resultingHeight * aspectRatio if heightRatio > widthRatio
resultingHeight = pageHeight
resultingWidth = resultingHeight * aspectRatio
else
resultingWidth = pageWidth
resultingHeight = resultingWidth / aspectRatio
resultingMarginX = (pageWidth - resultingWidth) / 2 resultingMarginX = (pageWidth - resultingWidth) / 2
resultingMarginY = (pageHeight - resultingHeight) / 2 resultingMarginY = (pageHeight - resultingHeight) / 2
@$el.find('.map').css(width: resultingWidth, height: resultingHeight, 'margin-left': resultingMarginX, 'margin-top': resultingMarginY) @$el.find('.map').css(width: resultingWidth, height: resultingHeight, 'margin-left': resultingMarginX, 'margin-top': resultingMarginY)
playAmbientSound: -> playAmbientSound: ->
return if @ambientSound return if @ambientSound
#terrain = 'Grass' return unless file = {Dungeon: 'ambient-dungeon', Grass: 'ambient-map-grass'}[@terrain]
terrain = 'Dungeon'
return unless file = {Dungeon: 'ambient-dungeon', Grass: 'ambient-map-grass'}[terrain]
src = "/file/interface/#{file}#{AudioPlayer.ext}" src = "/file/interface/#{file}#{AudioPlayer.ext}"
unless AudioPlayer.getStatus(src)?.loaded unless AudioPlayer.getStatus(src)?.loaded
AudioPlayer.preloadSound src AudioPlayer.preloadSound src
@ -531,8 +551,8 @@ hero = [
id: 'dungeons-of-kithgard' id: 'dungeons-of-kithgard'
original: '528110f30268d018e3000001' original: '528110f30268d018e3000001'
description: 'Grab the gem, but touch nothing else. Start here.' description: 'Grab the gem, but touch nothing else. Start here.'
x: 20.24 x: 14
y: 32.93 y: 15.5
} }
{ {
name: 'Gems in the Deep' name: 'Gems in the Deep'
@ -541,8 +561,8 @@ hero = [
id: 'gems-in-the-deep' id: 'gems-in-the-deep'
original: '54173c90844506ae0195a0b4' original: '54173c90844506ae0195a0b4'
description: 'Quickly collect the gems; you will need them.' description: 'Quickly collect the gems; you will need them.'
x: 18.47 x: 32
y: 49.78 y: 15.5
} }
{ {
name: 'Shadow Guard' name: 'Shadow Guard'
@ -551,8 +571,8 @@ hero = [
id: 'shadow-guard' id: 'shadow-guard'
original: '54174347844506ae0195a0b8' original: '54174347844506ae0195a0b8'
description: 'Evade the Kithgard minion.' description: 'Evade the Kithgard minion.'
x: 30.89 x: 54
y: 61.30 y: 9
} }
{ {
name: 'True Names' name: 'True Names'
@ -561,8 +581,8 @@ hero = [
id: 'true-names' id: 'true-names'
original: '541875da4c16460000ab990f' original: '541875da4c16460000ab990f'
description: 'Learn an enemy\'s true name to defeat it.' description: 'Learn an enemy\'s true name to defeat it.'
x: 44.39 x: 74
y: 57.39 y: 12
} }
{ {
name: 'The Raised Sword' name: 'The Raised Sword'
@ -571,8 +591,8 @@ hero = [
id: 'the-raised-sword' id: 'the-raised-sword'
original: '5418aec24c16460000ab9aa6' original: '5418aec24c16460000ab9aa6'
description: 'Learn to equip yourself for combat.' description: 'Learn to equip yourself for combat.'
x: 41.83 x: 85
y: 41.74 y: 20
} }
{ {
name: 'The First Kithmaze' name: 'The First Kithmaze'
@ -581,8 +601,8 @@ hero = [
id: 'the-first-kithmaze' id: 'the-first-kithmaze'
original: '5418b9d64c16460000ab9ab4' original: '5418b9d64c16460000ab9ab4'
description: 'The builders of Kith constructed many mazes to confuse travelers.' description: 'The builders of Kith constructed many mazes to confuse travelers.'
x: 57.39 x: 70
y: 48.15 y: 28
} }
{ {
name: 'The Second Kithmaze' name: 'The Second Kithmaze'
@ -591,8 +611,8 @@ hero = [
id: 'the-second-kithmaze' id: 'the-second-kithmaze'
original: '5418cf256bae62f707c7e1c3' original: '5418cf256bae62f707c7e1c3'
description: 'Many have tried, few have found their way through this maze.' description: 'Many have tried, few have found their way through this maze.'
x: 61.72 x: 67
y: 37.07 y: 41
} }
{ {
name: 'New Sight' name: 'New Sight'
@ -611,8 +631,8 @@ hero = [
id: 'lowly-kithmen' id: 'lowly-kithmen'
original: '541b24511ccc8eaae19f3c1f' original: '541b24511ccc8eaae19f3c1f'
description: 'Use your glasses to seek out and attack the Kithmen.' description: 'Use your glasses to seek out and attack the Kithmen.'
x: 70.53 x: 74
y: 27.93 y: 48
} }
{ {
name: 'A Bolt in the Dark' name: 'A Bolt in the Dark'
@ -621,8 +641,8 @@ hero = [
id: 'a-bolt-in-the-dark' id: 'a-bolt-in-the-dark'
original: '541b288e1ccc8eaae19f3c25' original: '541b288e1ccc8eaae19f3c25'
description: 'Kithmen are not the only ones to stand in your way.' description: 'Kithmen are not the only ones to stand in your way.'
x: 86.08 x: 76
y: 40.76 y: 60
} }
{ {
name: 'The Final Kithmaze' name: 'The Final Kithmaze'
@ -631,8 +651,8 @@ hero = [
id: 'the-final-kithmaze' id: 'the-final-kithmaze'
original: '541b434e1ccc8eaae19f3c33' original: '541b434e1ccc8eaae19f3c33'
description: 'To escape you must find your way through an Elder Kithman\'s maze.' description: 'To escape you must find your way through an Elder Kithman\'s maze.'
x: 96.95 x: 82
y: 58.15 y: 70
} }
{ {
name: 'Kithgard Gates' name: 'Kithgard Gates'
@ -642,8 +662,8 @@ hero = [
original: '541c9a30c6362edfb0f34479' original: '541c9a30c6362edfb0f34479'
description: 'Escape the Kithgard dungeons and don\'t let the guardians get you.' description: 'Escape the Kithgard dungeons and don\'t let the guardians get you.'
disabled: true disabled: true
x: 84.02 x: 89
y: 72.39 y: 82
} }
{ {
name: 'Defence of Plainswood' name: 'Defence of Plainswood'

View file

@ -45,8 +45,14 @@ module.exports = class PlayLevelModal extends ModalView
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'game-menu-open', volume: 1 Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'game-menu-open', volume: 1
@insertSubView @chooseHeroView = new ChooseHeroView @options @insertSubView @chooseHeroView = new ChooseHeroView @options
@insertSubView @inventoryView = new InventoryView @options @insertSubView @inventoryView = new InventoryView @options
@inventoryView.$el.addClass 'secret' if @options.hadEverChosenHero
@chooseHeroView.onShown() @$el.find('.choose-hero-active').add(@chooseHeroView.$el).addClass 'secret'
@$el.find('.choose-inventory-active').removeClass 'secret'
@inventoryView.onShown()
else
@$el.find('.choose-inventory-active').add(@inventoryView.$el).addClass 'secret'
@$el.find('.choose-hero-active').removeClass 'secret'
@chooseHeroView.onShown()
onHidden: -> onHidden: ->
unless @navigatingToPlay unless @navigatingToPlay

View file

@ -75,7 +75,7 @@
"javascript-brunch": "> 1.0 < 1.8", "javascript-brunch": "> 1.0 < 1.8",
"coffee-script-brunch": "https://github.com/brunch/coffee-script-brunch/tarball/master", "coffee-script-brunch": "https://github.com/brunch/coffee-script-brunch/tarball/master",
"coffeelint-brunch": "> 1.0 < 1.8", "coffeelint-brunch": "> 1.0 < 1.8",
"sass-brunch": "1.7.0", "sass-brunch": "1.7.2",
"css-brunch": "> 1.0 < 1.8", "css-brunch": "> 1.0 < 1.8",
"jade-brunch": "> 1.0 < 1.8", "jade-brunch": "> 1.0 < 1.8",
"uglify-js-brunch": "~1.7.4", "uglify-js-brunch": "~1.7.4",
@ -86,6 +86,7 @@
"marked": "0.2.x", "marked": "0.2.x",
"telepath-brunch": "https://github.com/nwinter/telepath-brunch/tarball/master", "telepath-brunch": "https://github.com/nwinter/telepath-brunch/tarball/master",
"bower": "~1.3.8", "bower": "~1.3.8",
"bless-brunch": "https://github.com/ThomasConner/bless-brunch/tarball/master",
"karma-script-launcher": "~0.1.0", "karma-script-launcher": "~0.1.0",
"karma-chrome-launcher": "~0.1.2", "karma-chrome-launcher": "~0.1.2",
"karma-firefox-launcher": "~0.1.3", "karma-firefox-launcher": "~0.1.3",

View file

@ -21,20 +21,20 @@ module.exports.setup = (app) ->
setupScheduledEmails = -> setupScheduledEmails = ->
testForLockManager() testForLockManager()
mailTasks = [ mailTasks = [
taskFunction: candidateUpdateProfileTask # taskFunction: candidateUpdateProfileTask
frequencyMs: 10 * 60 * 1000 #10 minutes # frequencyMs: 10 * 60 * 1000 #10 minutes
, #,
taskFunction: internalCandidateUpdateTask # taskFunction: internalCandidateUpdateTask
frequencyMs: 10 * 60 * 1000 #10 minutes # frequencyMs: 10 * 60 * 1000 #10 minutes
, #,
taskFunction: employerNewCandidatesAvailableTask # taskFunction: employerNewCandidatesAvailableTask
frequencyMs: 10 * 60 * 1000 #10 minutes # frequencyMs: 10 * 60 * 1000 #10 minutes
, #,
taskFunction: unapprovedCandidateFinishProfileTask # taskFunction: unapprovedCandidateFinishProfileTask
frequencyMs: 10 * 60 * 1000 # frequencyMs: 10 * 60 * 1000
, #,
taskFunction: emailUserRemarkTaskRemindersTask # taskFunction: emailUserRemarkTaskRemindersTask
frequencyMs: 10 * 60 * 1000 # frequencyMs: 10 * 60 * 1000
] ]
for mailTask in mailTasks for mailTask in mailTasks