Merge branch 'master' into production

This commit is contained in:
Nick Winter 2014-11-05 10:40:52 -08:00
commit a93daa9e73
25 changed files with 462 additions and 272 deletions

View file

@ -31,6 +31,7 @@ module.exports = class CocoRouter extends Backbone.Router
'admin/clas': go('admin/CLAsView') 'admin/clas': go('admin/CLAsView')
'admin/employers': go('admin/EmployersListView') 'admin/employers': go('admin/EmployersListView')
'admin/files': go('admin/FilesView') 'admin/files': go('admin/FilesView')
'admin/growth': go('admin/GrowthView')
'admin/level-sessions': go('admin/LevelSessionsView') 'admin/level-sessions': go('admin/LevelSessionsView')
'admin/users': go('admin/UsersView') 'admin/users': go('admin/UsersView')
'admin/base': go('admin/BaseView') 'admin/base': go('admin/BaseView')

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -99,7 +99,7 @@ module.exports = class World
continueLaterFn = => continueLaterFn = =>
@loadFrames(loadedCallback, errorCallback, loadProgressCallback, preloadedCallback, skipDeferredLoading, loadUntilFrame) unless @destroyed @loadFrames(loadedCallback, errorCallback, loadProgressCallback, preloadedCallback, skipDeferredLoading, loadUntilFrame) unless @destroyed
if @realTime and not @countdownFinished if @realTime and not @countdownFinished
if @levelID in ['the-first-kithmaze', 'the-second-kithmaze', 'the-final-kithmaze', 'the-gauntlet', 'winding-trail', 'thornbush-farm'] if @levelID in ['the-first-kithmaze', 'haunted-kithmaze', 'the-second-kithmaze', 'the-final-kithmaze', 'the-gauntlet', 'winding-trail', 'thornbush-farm']
@realTimeSpeedFactor = 5 @realTimeSpeedFactor = 5
else if @levelID in ['forgotten-gemsmith', 'descending-further', 'tactical-strike', 'kithgard-gates'] else if @levelID in ['forgotten-gemsmith', 'descending-further', 'tactical-strike', 'kithgard-gates']
@realTimeSpeedFactor = 3 @realTimeSpeedFactor = 3

View file

@ -102,6 +102,8 @@ module.exports = class Level extends CocoModel
levelThangComponent.config.pos.y = placeholderConfig.pos.y levelThangComponent.config.pos.y = placeholderConfig.pos.y
else if placeholderConfig.team # Pull in Allied team else if placeholderConfig.team # Pull in Allied team
levelThangComponent.config.team = placeholderConfig.team levelThangComponent.config.team = placeholderConfig.team
else if placeholderConfig.significantProperty # For levels where we cheat on what counts as an enemy
levelThangComponent.config.significantProperty = placeholderConfig.significantProperty
else if placeholderConfig.programmableMethods else if placeholderConfig.programmableMethods
# Take the ThangType default Programmable and merge level-specific Component config into it # Take the ThangType default Programmable and merge level-specific Component config into it
copy = $.extend true, {}, placeholderConfig copy = $.extend true, {}, placeholderConfig

View file

@ -88,7 +88,7 @@ module.exports = class User extends CocoModel
when 2 then 'choice-explicit' when 2 then 'choice-explicit'
when 3 then 'choice-implicit' when 3 then 'choice-implicit'
@branchingGroup = 'choice-explicit' if me.isAdmin() @branchingGroup = 'choice-explicit' if me.isAdmin()
application.tracker.identify branchingGroup: @branchingGroup application.tracker.identify branchingGroup: @branchingGroup unless me.isAdmin()
@branchingGroup @branchingGroup
getHighlightArrowSoundGroup: -> getHighlightArrowSoundGroup: ->
@ -98,5 +98,15 @@ module.exports = class User extends CocoModel
when 0, 1, 2, 3 then 'sound-off' when 0, 1, 2, 3 then 'sound-off'
when 4, 5, 6, 7 then 'sound-on' when 4, 5, 6, 7 then 'sound-on'
@highlightArrowGroup = 'sound-off' if me.isAdmin() @highlightArrowGroup = 'sound-off' if me.isAdmin()
application.tracker.identify highlightArrowGroup: @highlightArrowGroup application.tracker.identify highlightArrowGroup: @highlightArrowGroup unless me.isAdmin()
@highlightArrowGroup @highlightArrowGroup
getKithmazeGroup: ->
return @kithmazeGroup if @kithmazeGroup
group = me.get('testGroupNumber') % 16
@kithmazeGroup = switch group
when 0, 1, 2, 3, 4, 5, 6, 7 then 'the-first-kithmaze'
when 8, 9, 10, 11, 12, 13, 14, 15 then 'haunted-kithmaze'
@kithmazeGroup = 'haunted-kithmaze' if me.isAdmin()
application.tracker.identify kithmazeGroup: @kithmazeGroup unless me.isAdmin()
@kithmazeGroup

View file

@ -153,40 +153,6 @@ a
cursor: pointer cursor: pointer
// Bigger versions of some Bootstrap icons
// TODO: make the non-white versions of these if we ever need them
.icon.big
background-image: url(/images/pages/base/glyphicons-simplified.png)
.icon-white.big
background-image: url(/images/pages/base/glyphicons-simplified.png)
.icon.big, .icon-white.big
width: 19px
height: 19px
line-height: 19px
.icon-pause.big
background-position: -114px 0px
.icon-play.big
background-position: -95px 0px
.icon-repeat.big
background-position: -76px 0px
.icon-volume-off.big
background-position: -57px 0px
.icon-volume-down.big
background-position: -38px 0px
.icon-volume-up.big
background-position: -19px 0px
.icon-cog.big
background-position: 0px 0px
// loading screens for everything but the play view // loading screens for everything but the play view
.loading-screen .loading-screen
.progress .progress
@ -339,3 +305,7 @@ kbd
&.gem-60 &.gem-60
width: 60px width: 60px
height: 60px height: 60px
.popover
border-image: url(/images/level/popover_background.png) 29 39 fill stretch
border-width: 15px 20px

View file

@ -144,14 +144,6 @@ $level-resize-transition-time: 0.5s
left: -3px left: -3px
bottom: 0 bottom: 0
#hud-top-gradient
top: -32px
background: linear-gradient(to bottom, rgba(0,0,0,0) 0%,rgba(0,0,0,0.8*$GI) 100%)
left: 0
right: 0
bottom: 0
height: 3px
#canvas-left-gradient #canvas-left-gradient
left: 0px left: 0px
width: 5px width: 5px

View file

@ -3,36 +3,37 @@
#goals-view #goals-view
position: absolute position: absolute
left: 10px left: -15px
top: -100px top: -100px
@include transition(0.5s ease-in-out) @include transition(0.5s ease-in-out)
background-color: rgba(200,200,200,1.0) background: transparent url(/images/level/goals_background.png)
background-size: 100% 100%
border: black padding: 19px 17px 2px 25px
padding: 15px 7px 2px 5px
box-sizing: border-box
border: 1px solid #333
border-radius: 5px
z-index: 3 z-index: 3
font-size: 14px font-size: 14px
&.brighter &.brighter
font-size: 18px font-size: 18px
font-size: 1.4vw font-size: 1.4vw
@include box-shadow(0px 0px 12px white) //@include box-shadow(0px 0px 12px white)
.goals-status .goals-status
margin: 0 margin: 0
color: black margin-top: 10px
color: white
text-transform: uppercase
.success .success
color: darkgreen color: lightgreen
text-shadow: 1px 1px 0px black
.timed-out .timed-out
color: darkslategray color: rgb(230, 230, 230)
.failure .failure
color: darkred color: rgb(239, 61, 71)
text-shadow: 1px 1px 0px black
.incomplete .incomplete
color: darkgoldenrod color: rgb(245, 170, 49)
ul ul
padding-left: 0 padding-left: 0
@ -59,3 +60,4 @@
#goals-view.collapsed ul #goals-view.collapsed ul
display: none display: none

View file

@ -78,6 +78,7 @@
.start-level-button .start-level-button
font-size: 40px font-size: 40px
font-variant: small-caps
.left-wing, .right-wing .left-wing, .right-wing
width: 100% width: 100%

View file

@ -4,6 +4,8 @@
// TODO: Replace this devart with nice shinies // TODO: Replace this devart with nice shinies
#multiplayer-status-view #multiplayer-status-view
position: absolute
.player-count .player-count
color: white color: white
.players-available .players-available

View file

@ -2,50 +2,52 @@
@import "app/styles/bootstrap/variables" @import "app/styles/bootstrap/variables"
#playback-view #playback-view
$playback-button-color: rgb(248, 197, 146)
// When 75% alpha, it will look like the rgb(194, 154, 114) from Heald's design
width: 55% width: 55%
height: 30px height: 60px
padding-top: 17px
position: relative position: relative
background: #383434 background: transparent url(/images/level/scrubber_background.png)
// Counteract 50px height of absolutely positioned control bar. background-size: 100% 100%
margin-top: 50px // Counteract 50px height of absolutely positioned control bar, but overlap by 10px of jagged transparent top.
margin-top: 50px - 10px
z-index: 2
button button
height: 26px font-size: 26px
margin-left: 10px
background: transparent background: transparent
@include opacity(0.50) @include opacity(0.75)
i color: $playback-button-color
text-shadow: 1px 1px 0px black
.glyphicon
position: relative position: relative
button:hover button:hover
@include opacity(0.75) @include opacity(1)
#play-button, #volume-button, #music-button #play-button, #volume-button, #music-button
float: left float: left
margin-left: 2px
margin-top: 2px
width: 25px
position: relative position: relative
#music-button #music-button
@include opacity(0.25) @include opacity(0.5)
font-size: 20px
span span
position: relative position: relative
left: -3px left: -3px
top: -2px top: -2px
&:hover &:hover
@include opacity(0.50) @include opacity(0.75)
&.music-on &.music-on
@include opacity(0.50) @include opacity(0.75)
&:hover &:hover
@include opacity(0.75) @include opacity(1)
#play-button, #volume-button #play-button, #volume-button
i .glyphicon
display: none display: none
position: absolute
left: 2px
top: 2px
#settings-button #settings-button
padding-left: 4px padding-left: 4px
@ -54,51 +56,58 @@
#playback-settings #playback-settings
float: right float: right
position: relative position: relative
top: 2px margin-right: 10px
margin-right: 2px ul button
ul i
margin: 0 10px margin: 0 10px
li:hover li:hover
background: #add8e6 background: #add8e6
#play-button.disabled i #play-button.disabled .glyphicon
@include opacity(0.5) @include opacity(0.75)
#play-button.playing i.icon-pause #play-button.playing .glyphicon-pause
display: inline-block display: inline-block
#play-button.paused i.icon-play #play-button.paused .glyphicon-play
display: inline-block display: inline-block
#play-button.ended i.icon-repeat #play-button.ended .glyphicon-repeat
display: inline-block display: inline-block
#volume-button.vol-up i.icon-volume-up #volume-button.vol-up .glyphicon.glyphicon-volume-up
display: inline-block display: inline-block
#volume-button.vol-off i.icon-volume-off #volume-button.vol-off .glyphicon.glyphicon-volume-off
display: inline-block display: inline-block
@include opacity(0.50) @include opacity(0.75)
&:hover &:hover
@include opacity(0.75) @include opacity(1)
#volume-button.vol-down i.icon-volume-down #volume-button.vol-down .glyphicon.glyphicon-volume-down
display: inline-block display: inline-block
.scrubber .scrubber
position: absolute position: absolute
left: 100px left: 170px
top: 0px top: 21px
bottom: 0px bottom: 0px
right: 125px right: 175px
background: rgb(3, 3, 3)
height: 28px
border: 1px solid rgb(67, 67, 44)
border-radius: 14px
.scrubber-inner
border: 1px solid rgb(44, 38, 29)
width: 100%
height: 100%
border-radius: 14px
padding: 6px 8px
.progress .progress
float: left float: left
width: 100% width: 100%
height: 14px height: 12px
margin-top: 8px
cursor: pointer cursor: pointer
overflow: visible overflow: visible
border: 1px solid #444 border: 1px solid #444
// Remove gradient background in favor of solid fill background: rgb(80, 67, 53)
background-color: #888 border-radius: 6px
background-image: none
border-radius: 0
border: 0 border: 0
// Can't do this transition because handle then jitters, but would be good for streaming. // Can't do this transition because handle then jitters, but would be good for streaming.
//@include transition(width .2s linear) //@include transition(width .2s linear)
@ -113,19 +122,19 @@
@include transition(width .0s linear) @include transition(width .0s linear)
position: relative position: relative
// Remove gradient background in favor of solid fill // Remove gradient background in favor of solid fill
background-color: #67A4C8 background: rgb(245, 170, 49)
//background-image: none // gradient looks kind of cool though; keep it in border: 1px solid rgb(62, 45, 16)
border-radius: 6px
.scrubber-handle .scrubber-handle
cursor: pointer cursor: pointer
position: absolute position: absolute
right: -16px right: -18px
top: -9px top: -11px
background: transparent url(/images/level/playback_thumb.png) background: transparent url(/images/level/scrubber_knob.png)
width: 32px background-size: contain
height: 32px width: 36px
// z: above the gradient line bordering the playback bar height: 36px
z-index: 6
.ui-slider-handle .ui-slider-handle
height: 100% height: 100%
@ -140,3 +149,5 @@
body.ipad #playback-view body.ipad #playback-view
#playback-settings #playback-settings
display: none display: none
.scrubber
right: 25px

View file

@ -124,14 +124,6 @@
left: -3px left: -3px
bottom: 0 bottom: 0
#hud-top-gradient
top: -32px
background: linear-gradient(to bottom, rgba(0,0,0,0) 0%,rgba(0,0,0,0.8*$GI) 100%)
left: 0
right: 0
bottom: 0
height: 3px
#canvas-left-gradient #canvas-left-gradient
left: 0px left: 0px
width: 5px width: 5px

View file

@ -43,6 +43,9 @@ block content
a(href="/admin/base", data-i18n="admin.av_other_debug_base_url") Base (for debugging base.jade) a(href="/admin/base", data-i18n="admin.av_other_debug_base_url") Base (for debugging base.jade)
li li
a(href="/admin/clas", data-i18n="admin.clas") CLAs a(href="/admin/clas", data-i18n="admin.clas") CLAs
if me.isAdmin()
li
a(href="/admin/growth", data-i18n="admin.growth") Growth
hr hr

View file

@ -0,0 +1,32 @@
extends /templates/base
block content
h1(data-i18n="admin.growth_title") Growth
if me.isAdmin()
if crunchingData
h4 Cruncing Data..
else
h2 Registered Users
h3 Per-Day
h4 Totals
svg.perDayTotal
h4 Added
svg.perDayAdded
table.table.table-striped.table-bordered.table-condensed
-for (var i = 0; i < usersPerDay.length; i++)
tr
td= usersPerDay[i].date
td= usersPerDay[i].added
td= usersPerDay[i].total
h3 Per-Month
h4 Totals
svg.perMonthTotal
h4 Added
svg.perMonthAdded
table.table.table-striped.table-bordered.table-condensed
-for (var i = 0; i < usersPerMonth.length; i++)
tr
td= usersPerMonth[i].date
td= usersPerMonth[i].added
td= usersPerMonth[i].total

View file

@ -1,6 +1,6 @@
ul#primary-goals-list ul#primary-goals-list
div.goals-status div.goals-status
strong(data-i18n="play_level.goals") Goals span(data-i18n="play_level.goals") Goals
span.spr : span.spr :
span(data-i18n="play_level.success").secret.goal-status.success Success! span(data-i18n="play_level.success").secret.goal-status.success Success!
span(data-i18n="play_level.incomplete").secret.goal-status.incomplete Incomplete span(data-i18n="play_level.incomplete").secret.goal-status.incomplete Incomplete

View file

@ -1,5 +1,3 @@
#hud-top-gradient.gradient
.center .center
.thang-canvas-wrapper.thang-elem .thang-canvas-wrapper.thang-elem

View file

@ -1,45 +1,29 @@
button.btn.btn-xs.btn-inverse#play-button.paused(title="Ctrl/Cmd + P: Toggle level play/pause") button.btn.btn-xs.btn-inverse#play-button.paused(title="Ctrl/Cmd + P: Toggle level play/pause")
i.icon-play.icon-white.big .glyphicon.glyphicon-play
i.icon-pause.icon-white.big .glyphicon.glyphicon-pause
i.icon-repeat.icon-white.big .glyphicon.glyphicon-repeat
button.btn.btn-xs.btn-inverse#volume-button(title="Adjust volume") button.btn.btn-xs.btn-inverse#volume-button(title="Adjust volume")
i.icon-volume-off.icon-white.big .glyphicon.glyphicon-volume-off
i.icon-volume-down.icon-white.big .glyphicon.glyphicon-volume-down
i.icon-volume-up.icon-white.big .glyphicon.glyphicon-volume-up
button.btn.btn-xs.btn-inverse#music-button(title="Toggle Music") button.btn.btn-xs.btn-inverse#music-button(title="Toggle Music")
span ♫ span ♫
.scrubber .scrubber
.progress.secret#timeProgress .scrubber-inner
.progress-bar .progress.secret#timeProgress
.scrubber-handle .progress-bar
.popover.fade.top.in#timePopover .scrubber-handle
.arrow .popover.fade.top.in#timePopover
h3.popover-title .arrow
.popover-content h3.popover-title
.popover-content
.btn-group.dropup#playback-settings .btn-group.dropup#playback-settings
button.btn.btn-xs.btn-inverse.toggle-fullscreen(title="Toggle fullscreen") button.btn.btn-xs.btn-inverse.toggle-fullscreen(title="Toggle fullscreen")
i.icon-fullscreen.icon-white .glyphicon.glyphicon-fullscreen
button.btn.btn-xs.btn-inverse#zoom-in-button(title="Zoom In (or scroll down)") button.btn.btn-xs.btn-inverse#zoom-in-button(title="Zoom In (or scroll down)")
i.icon-zoom-in.icon-white .glyphicon.glyphicon-zoom-in
button.btn.btn-xs.btn-inverse#zoom-out-button(title="Zoom Out (or scroll up)") button.btn.btn-xs.btn-inverse#zoom-out-button(title="Zoom Out (or scroll up)")
i.icon-zoom-out.icon-white .glyphicon.glyphicon-zoom-out
button.btn.btn-xs.btn-inverse.dropdown-toggle(data-toggle="dropdown")#settings-button
i.icon-cog.icon-white.big
ul.dropdown-menu
if me.get('name') == "Nick"
li(title="Ctrl/Cmd + \\: Toggle debug display").selectable#debug-toggle
i.icon-globe
| Debug Mode
i.icon-ok.secret
li.selectable#view-keyboard-shortcuts
i.icon-info-sign
span(data-i18n="play_level.keyboard_shortcuts") Key Shortcuts
li.selectable#edit-wizard-settings
i.icon-user
span(data-i18n="play_level.customize_wizard") Customize Wizard
li.selectable#edit-editor-config
i.icon-edit
span(data-i18n="play_level.editor_config") Editor Config

View file

@ -0,0 +1,192 @@
RootView = require 'views/kinds/RootView'
template = require 'templates/admin/growth'
RealTimeCollection = require 'collections/RealTimeCollection'
# Growth View ###################
#
# Display interesting growth data.
#
# Currently shows:
# Registered user totals and added, per-day and per-month
# 7-day moving average for registered users added per-day
#
# TODO: @padding isn't applied correctly
# TODO: aggregate recent data if missing?
#
module.exports = class GrowthView extends RootView
id: 'admin-growth-view'
template: template
height: 300
width: 1000
xAxisGuideHeight: 80
yAxisGuideWidth: 60
padding: 10
constructor: (options) ->
super options
@usersPerMonth = new RealTimeCollection 'growth/users/registered/per-month'
@usersPerMonth.on 'add', @refreshData
@usersPerDay = new RealTimeCollection 'growth/users/registered/per-day'
@usersPerDay.on 'add', @refreshData
destroy: ->
@usersPerMonth.off 'add', @refreshData
@usersPerDay.off 'add', @refreshData
refreshData: =>
@render()
getRenderData: ->
c = super()
c.crunchingData = @usersPerMonth.length is 0 and @usersPerDay.length is 0
c.usersPerDay = []
# @usersPerDay.each (item) ->
# c.usersPerDay.push date: item.get('id'), added: item.get('added'), total: item.get('total')
c.usersPerMonth = []
# @usersPerMonth.each (item) ->
# c.usersPerMonth.push date: item.get('id'), added: item.get('added'), total: item.get('total')
c
afterRender: ->
super()
if me.isAdmin()
@createPerDayChart()
@createPerMonthChart()
createPerDayChart: ->
addedData = []
totalData = []
@usersPerDay.each (item) ->
addedData.push id: item.get('id'), value: item.get('added')
totalData.push id: item.get('id'), value: item.get('total')
@createLineChart ".perDayTotal", totalData, 1000
@createLineChart ".perDayAdded", addedData, 10, true
createPerMonthChart: ->
addedData = []
totalData = []
@usersPerMonth.each (item) ->
addedData.push id: item.get('id'), value: item.get('added')
totalData.push id: item.get('id'), value: item.get('total')
@createLineChart ".perMonthTotal", totalData, 1000
@createLineChart ".perMonthAdded", addedData, 1000
createLineChart: (selector, data, guidelineSpacing, sevenDayAverage=false) ->
return unless data.length > 1
minVal = d3.min(data, (d) -> d.value)
maxVal = d3.max(data, (d) -> d.value)
widthSpacing = (@width - @yAxisGuideWidth - @padding) / (data.length - 1)
y = d3.scale.linear()
.domain([minVal, maxVal])
.range([@height - @xAxisGuideHeight - 2 * @padding, 0])
points = []
for i in [0...data.length]
points.push id: data[i].id, x: i * widthSpacing + @yAxisGuideWidth, y: y(data[i].value) + @padding
links = []
for i in [0...points.length - 1]
if points[i] and points[i + 1]
links.push start: points[i], end: points[i + 1]
guidelines = []
diff = maxVal - minVal
interval = Math.floor(diff / 5)
for i in [0..4]
yVal = i * interval + minVal
yVal = Math.floor(yVal / guidelineSpacing) * guidelineSpacing
guidelines.push start: {id: yVal, x: 0, y: y(yVal)}, end: {id: yVal, x: @width, y: y(yVal)}
sevenPoints = []
sevenLinks = []
if sevenDayAverage
sevenTotal = 0
for i in [0...data.length]
sevenTotal += data[i].value
if i > 5
sevenAvg = sevenTotal / 7
sevenPoints.push x: i * widthSpacing + @yAxisGuideWidth, y: y(sevenAvg) + @padding
if i > 6
sevenTotal -= data[i - 7].value
for i in [0...sevenPoints.length - 1]
if sevenPoints[i] and sevenPoints[i + 1]
sevenLinks.push start: sevenPoints[i], end: sevenPoints[i + 1]
chart = d3.select(selector)
.attr("width", @width)
.attr("height", @height)
chart.selectAll(".circle")
.data(points)
.enter()
.append("circle")
.attr("cx", (d) -> d.x )
.attr("cy", (d) -> d.y )
.attr("r", "2px")
.attr("fill", "black")
chart.selectAll(".text")
.data(points)
.enter()
.append("text")
.attr("dy", ".35em")
.attr("transform", (d, i) => "translate(" + d.x + "," + @height + ") rotate(270)")
.text((d) ->
if d.id.length is 8
return "#{parseInt(d.id[4..5])}/#{parseInt(d.id[6..7])}/#{d.id[0..3]}"
else
return "#{parseInt(d.id[4..5])}/#{d.id[0..3]}"
)
chart.selectAll('.line')
.data(links)
.enter()
.append("line")
.attr("x1", (d) -> d.start.x )
.attr("y1", (d) -> d.start.y )
.attr("x2", (d) -> d.end.x )
.attr("y2", (d) -> d.end.y )
.style("stroke", "rgb(6,120,155)")
chart.selectAll(".circle")
.data(sevenPoints)
.enter()
.append("circle")
.attr("cx", (d) -> d.x )
.attr("cy", (d) -> d.y )
.attr("r", "2px")
.attr("fill", "purple")
chart.selectAll('.line')
.data(sevenLinks)
.enter()
.append("line")
.attr("x1", (d) -> d.start.x )
.attr("y1", (d) -> d.start.y )
.attr("x2", (d) -> d.end.x )
.attr("y2", (d) -> d.end.y )
.style("stroke", "rgb(200,0,0)")
chart.selectAll('.line')
.data(guidelines)
.enter()
.append("line")
.attr("x1", (d) -> d.start.x )
.attr("y1", (d) -> d.start.y )
.attr("x2", (d) -> d.end.x )
.attr("y2", (d) -> d.end.y )
.style("stroke", "rgb(140,140,140)")
chart.selectAll(".text")
.data(guidelines)
.enter()
.append("text")
.attr("x", (d) -> d.start.x)
.attr("y", (d) -> d.start.y - 6)
.attr("dy", ".35em")
.text((d) -> d.start.id)

View file

@ -340,23 +340,25 @@ module.exports = class InventoryView extends CocoView
'simple-sword': '53e218d853457600003e3ebe' 'simple-sword': '53e218d853457600003e3ebe'
'leather-tunic': '53e22eac53457600003e3efc' 'leather-tunic': '53e22eac53457600003e3efc'
'leather-boots': '53e2384453457600003e3f07' 'leather-boots': '53e2384453457600003e3f07'
'leather-belt': '5437002a7beba4a82024a97d'
'programmaticon-i': '53e4108204c00d4607a89f78' 'programmaticon-i': '53e4108204c00d4607a89f78'
'crude-glasses': '53e238df53457600003e3f0b' 'crude-glasses': '53e238df53457600003e3f0b'
'builders-hammer': '53f4e6e3d822c23505b74f42' 'builders-hammer': '53f4e6e3d822c23505b74f42'
gearByLevel = gearByLevel =
'dungeons-of-kithgard': {feet: 'simple-boots'} 'dungeons-of-kithgard': {feet: 'simple-boots'}
'gems-in-the-deep': {feet: 'simple-boots'} 'gems-in-the-deep': {feet: 'simple-boots'}
'forgetful-gemsmith': {feet: 'simple-boots'}
'shadow-guard': {feet: 'simple-boots'} 'shadow-guard': {feet: 'simple-boots'}
'kounter-kithwise': {feet: 'simple-boots'} 'kounter-kithwise': {feet: 'simple-boots'}
'crawlways-of-kithgard': {feet: 'simple-boots'} 'crawlways-of-kithgard': {feet: 'simple-boots'}
'true-names': {feet: 'simple-boots', 'right-hand': 'simple-sword'} 'forgetful-gemsmith': {feet: 'simple-boots'}
'true-names': {feet: 'simple-boots', 'right-hand': 'simple-sword', waist: 'leather-belt'}
'favorable-odds': {feet: 'simple-boots', 'right-hand': 'simple-sword'} 'favorable-odds': {feet: 'simple-boots', 'right-hand': 'simple-sword'}
'the-raised-sword': {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic'} 'the-raised-sword': {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic'}
'the-first-kithmaze': {feet: 'simple-boots', 'programming-book': 'programmaticon-i'} 'the-first-kithmaze': {feet: 'simple-boots', 'programming-book': 'programmaticon-i'}
'haunted-kithmaze': {feet: 'simple-boots', 'programming-book': 'programmaticon-i'}
'descending-further': {feet: 'simple-boots', 'programming-book': 'programmaticon-i'} 'descending-further': {feet: 'simple-boots', 'programming-book': 'programmaticon-i'}
'the-second-kithmaze': {feet: 'simple-boots', 'programming-book': 'programmaticon-i'} 'the-second-kithmaze': {feet: 'simple-boots', 'programming-book': 'programmaticon-i'}
'new-sight': {'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i'} 'dread-door': {'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i'}
'known-enemy': {'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i'} 'known-enemy': {'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i'}
'master-of-names': {feet: 'simple-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'} 'master-of-names': {feet: 'simple-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
'lowly-kithmen': {feet: 'simple-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'} 'lowly-kithmen': {feet: 'simple-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}

View file

@ -630,22 +630,8 @@ dungeon = [
x: 29 x: 29
y: 12 y: 12
nextLevels: nextLevels:
more_practice: 'forgetful-gemsmith'
continue: 'shadow-guard' continue: 'shadow-guard'
skip_ahead: 'true-names' skip_ahead: 'forgetful-gemsmith'
}
{
name: 'Forgetful Gemsmith'
type: 'hero'
difficulty: 1
id: 'forgetful-gemsmith'
original: '544a98f62d002f0000fe331a'
description: 'Grab even more gems as you practice moving.'
x: 38
y: 12
nextLevels:
continue: 'shadow-guard'
practice: true
} }
{ {
name: 'Shadow Guard' name: 'Shadow Guard'
@ -654,11 +640,11 @@ dungeon = [
id: 'shadow-guard' id: 'shadow-guard'
original: '54174347844506ae0195a0b8' original: '54174347844506ae0195a0b8'
description: 'Evade the Kithgard minion.' description: 'Evade the Kithgard minion.'
x: 50 x: 41
y: 11 y: 13
nextLevels: nextLevels:
more_practice: 'kounter-kithwise' more_practice: 'kounter-kithwise'
continue: 'true-names' continue: 'forgetful-gemsmith'
} }
{ {
name: 'Kounter Kithwise' name: 'Kounter Kithwise'
@ -667,25 +653,37 @@ dungeon = [
id: 'kounter-kithwise' id: 'kounter-kithwise'
original: '54527a6257e83800009730c7' original: '54527a6257e83800009730c7'
description: 'Practice your evasion skills with more guards.' description: 'Practice your evasion skills with more guards.'
x: 58 x: 50
y: 10 y: 14
nextLevels: nextLevels:
more_practice: 'crawlways-of-kithgard' #more_practice: 'crawlways-of-kithgard'
continue: 'true-names' continue: 'true-names'
practice: true practice: true
} }
#{
# name: 'Crawlways of Kithgard'
# type: 'hero'
# difficulty: 1
# id: 'crawlways-of-kithgard'
# original: '545287ef57e83800009730d5'
# description: 'Dart in and grab the gemat the right moment.'
# x: 57
# y: 12
# nextLevels:
# continue: 'true-names'
# practice: true
#}
{ {
name: 'Crawlways of Kithgard' name: 'Forgetful Gemsmith'
type: 'hero' type: 'hero'
difficulty: 1 difficulty: 1
id: 'crawlways-of-kithgard' id: 'forgetful-gemsmith'
original: '545287ef57e83800009730d5' original: '544a98f62d002f0000fe331a'
description: 'Dart in and grab the gemat the right moment.' description: 'Grab even more gems as you practice moving.'
x: 67 x: 63
y: 10 y: 13
nextLevels: nextLevels:
continue: 'true-names' continue: 'shadow-guard'
practice: true
} }
{ {
name: 'True Names' name: 'True Names'
@ -695,7 +693,7 @@ dungeon = [
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: 74 x: 74
y: 12 y: 14
nextLevels: nextLevels:
more_practice: 'favorable-odds' more_practice: 'favorable-odds'
continue: 'the-raised-sword' continue: 'the-raised-sword'
@ -737,7 +735,21 @@ dungeon = [
nextLevels: nextLevels:
more_practice: 'descending-further' more_practice: 'descending-further'
continue: 'the-second-kithmaze' continue: 'the-second-kithmaze'
skip_ahead: 'new-sight' skip_ahead: 'dread-door'
}
{
name: 'Haunted Kithmaze'
type: 'hero'
difficulty: 1
id: 'haunted-kithmaze'
original: '545a5914d820eb0000f6dc0a'
description: 'The builders of Kithgard constructed many mazes to confuse travelers.'
x: 78
y: 29
nextLevels:
more_practice: 'descending-further'
continue: 'the-second-kithmaze'
skip_ahead: 'dread-door'
} }
{ {
name: 'Descending Further' name: 'Descending Further'
@ -762,15 +774,15 @@ dungeon = [
x: 59 x: 59
y: 25 y: 25
nextLevels: nextLevels:
continue: 'new-sight' continue: 'dread-door'
} }
{ {
name: 'New Sight' name: 'Dread Door'
type: 'hero' type: 'hero'
difficulty: 1 difficulty: 1
id: 'new-sight' id: 'dread-door'
original: '5418d40f4c16460000ab9ac2' original: '5418d40f4c16460000ab9ac2'
description: 'A true name can only be seen with the correct lenses.' description: 'Behind a dread door lies a chest full of riches.'
x: 60 x: 60
y: 34 y: 34
nextLevels: nextLevels:
@ -1006,3 +1018,9 @@ WorldMapView.campaigns = campaigns = [
{id: 'dungeon', name: 'Dungeon Campaign', levels: dungeon } {id: 'dungeon', name: 'Dungeon Campaign', levels: dungeon }
{id: 'forest', name: 'Forest Campaign', levels: forest } {id: 'forest', name: 'Forest Campaign', levels: forest }
] ]
# A/B testing first kithmaze level: The First Kithmaze vs. Haunted Kithmaze
if me.getKithmazeGroup() is 'the-first-kithmaze'
_.remove dungeon, id: 'haunted-kithmaze'
else
_.remove dungeon, id: 'the-first-kithmaze'

View file

@ -101,7 +101,7 @@ module.exports = class LevelGoalsView extends CocoView
return if expand is @expanded return if expand is @expanded
@updateHeight() @updateHeight()
sound = if expand then 'goals-expand' else 'goals-collapse' sound = if expand then 'goals-expand' else 'goals-collapse'
top = if expand then -10 else 26 - (@normalHeight ? @$el.outerHeight()) top = if expand then -5 else 36 - (@normalHeight ? @$el.outerHeight())
@$el.css 'top', top @$el.css 'top', top
if @soundTimeout if @soundTimeout
# Don't play the sound we were going to play after all; the transition has reversed. # Don't play the sound we were going to play after all; the transition has reversed.

View file

@ -30,7 +30,7 @@ module.exports = class LevelHUDView extends CocoView
afterRender: -> afterRender: ->
super() super()
@$el.addClass 'no-selection' @$el.addClass 'no-selection'
if @options.level.get('slug') in ['dungeons-of-kithgard', 'gems-in-the-deep', 'forgetful-gemsmith', 'shadow-guard', 'kounter-kithwise', 'crawlways-of-kithgard', 'true-names', 'favorable-odds', 'the-raised-sword', 'the-first-kithmaze', 'descending-further', 'the-second-kithmaze', 'new-sight', 'known-enemy', 'master-of-names', 'lowly-kithmen', 'closing-the-distance', 'tactical-strike', 'the-final-kithmaze', 'the-gauntlet'] if @options.level.get('slug') in ['dungeons-of-kithgard', 'gems-in-the-deep', 'forgetful-gemsmith', 'shadow-guard', 'kounter-kithwise', 'crawlways-of-kithgard', 'true-names', 'favorable-odds', 'the-raised-sword', 'the-first-kithmaze', 'haunted-kithmaze', 'descending-further', 'the-second-kithmaze', 'dread-door', 'known-enemy', 'master-of-names', 'lowly-kithmen', 'closing-the-distance', 'tactical-strike', 'the-final-kithmaze', 'the-gauntlet']
@hidesHUD = true @hidesHUD = true
@$el.addClass 'hide-hud-properties' @$el.addClass 'hide-hud-properties'

View file

@ -17,7 +17,6 @@ module.exports = class LevelPlaybackView extends CocoView
'level:scrub-forward': 'onScrubForward' 'level:scrub-forward': 'onScrubForward'
'level:scrub-back': 'onScrubBack' 'level:scrub-back': 'onScrubBack'
'level:set-volume': 'onSetVolume' 'level:set-volume': 'onSetVolume'
'level:set-debug': 'onSetDebug'
'surface:frame-changed': 'onFrameChanged' 'surface:frame-changed': 'onFrameChanged'
'god:new-world-created': 'onNewWorld' 'god:new-world-created': 'onNewWorld'
'god:streaming-world-updated': 'onNewWorld' 'god:streaming-world-updated': 'onNewWorld'
@ -28,10 +27,6 @@ module.exports = class LevelPlaybackView extends CocoView
'real-time-multiplayer:manual-cast': 'onRealTimeMultiplayerCast' 'real-time-multiplayer:manual-cast': 'onRealTimeMultiplayerCast'
events: events:
'click #debug-toggle': 'onToggleDebug'
'click #edit-wizard-settings': 'onEditWizardSettings'
'click #edit-editor-config': 'onEditEditorConfig'
'click #view-keyboard-shortcuts': 'onViewKeyboardShortcuts'
'click #music-button': 'onToggleMusic' 'click #music-button': 'onToggleMusic'
'click #zoom-in-button': -> Backbone.Mediator.publish 'camera:zoom-in', {} unless @shouldIgnore() 'click #zoom-in-button': -> Backbone.Mediator.publish 'camera:zoom-in', {} unless @shouldIgnore()
'click #zoom-out-button': -> Backbone.Mediator.publish 'camera:zoom-out', {} unless @shouldIgnore() 'click #zoom-out-button': -> Backbone.Mediator.publish 'camera:zoom-out', {} unless @shouldIgnore()
@ -52,59 +47,6 @@ module.exports = class LevelPlaybackView extends CocoView
'⌘+], ctrl+]': 'onScrubForward' '⌘+], ctrl+]': 'onScrubForward'
'⌘+⇧+], ctrl+⇧+]': 'onSingleScrubForward' '⌘+⇧+], ctrl+⇧+]': 'onSingleScrubForward'
# popover that shows at the current mouse position on the progressbar, using the bootstrap popover.
# Could make this into a jQuery plugins itself theoretically.
class HoverPopup extends $.fn.popover.Constructor
constructor: () ->
@enabled = true
@shown = false
@type = 'HoverPopup'
@options =
placement: 'top'
container: 'body'
animation: true
html: true
delay:
show: 400
@$element = $('#timeProgress')
@$tip = $('#timePopover')
@content = ''
getContent: -> @content
show: ->
unless @shown
super()
@shown = true
updateContent: (@content) ->
@setContent()
@$tip.addClass('fade top in')
onHover: (@e) ->
pos = @getPosition()
actualWidth = @$tip[0].offsetWidth
actualHeight = @$tip[0].offsetHeight
calculatedOffset =
top: pos.top - actualHeight
left: pos.left + pos.width / 2 - actualWidth / 2
this.applyPlacement(calculatedOffset, 'top')
getPosition: ->
top: @$element.offset().top
left: if @e? then @e.pageX else @$element.offset().left
height: 0
width: 0
hide: ->
super()
@shown = false
disable: ->
super()
@hide()
constructor: -> constructor: ->
super(arguments...) super(arguments...)
me.on('change:music', @updateMusicButton, @) me.on('change:music', @updateMusicButton, @)
@ -192,20 +134,6 @@ module.exports = class LevelPlaybackView extends CocoView
@currentTime = 0 @currentTime = 0
@lastLoadedFrameCount = loadedFrameCount @lastLoadedFrameCount = loadedFrameCount
onToggleDebug: ->
return if @shouldIgnore()
flag = $('#debug-toggle i.icon-ok')
Backbone.Mediator.publish('level:set-debug', {debug: flag.hasClass('invisible')})
onEditWizardSettings: ->
Backbone.Mediator.publish 'level:edit-wizard-settings', {}
onEditEditorConfig: ->
@openModalView new EditorConfigModal session: @options.session
onViewKeyboardShortcuts: ->
@openModalView new KeyboardShortcutsModal()
onDisableControls: (e) -> onDisableControls: (e) ->
if not e.controls or 'playback' in e.controls if not e.controls or 'playback' in e.controls
@disabled = true @disabled = true
@ -340,10 +268,6 @@ module.exports = class LevelPlaybackView extends CocoView
Backbone.Mediator.publish 'level:set-letterbox', on: false Backbone.Mediator.publish 'level:set-letterbox', on: false
Backbone.Mediator.publish 'playback:real-time-playback-ended', {} Backbone.Mediator.publish 'playback:real-time-playback-ended', {}
onSetDebug: (e) ->
flag = $('#debug-toggle i.icon-ok')
flag.toggleClass 'invisible', not e.debug
# to refactor # to refactor
hookUpScrubber: -> hookUpScrubber: ->
@ -423,3 +347,56 @@ module.exports = class LevelPlaybackView extends CocoView
$(window).off('resize', @onWindowResize) $(window).off('resize', @onWindowResize)
@onWindowResize = null @onWindowResize = null
super() super()
# popover that shows at the current mouse position on the progressbar, using the bootstrap popover.
# Could make this into a jQuery plugins itself theoretically.
class HoverPopup extends $.fn.popover.Constructor
constructor: () ->
@enabled = true
@shown = false
@type = 'HoverPopup'
@options =
placement: 'top'
container: 'body'
animation: true
html: true
delay:
show: 400
@$element = $('#timeProgress')
@$tip = $('#timePopover')
@content = ''
getContent: -> @content
show: ->
unless @shown
super()
@shown = true
updateContent: (@content) ->
@setContent()
@$tip.addClass('fade top in')
onHover: (@e) ->
pos = @getPosition()
actualWidth = @$tip[0].offsetWidth
actualHeight = @$tip[0].offsetHeight
calculatedOffset =
top: pos.top - actualHeight
left: pos.left + pos.width / 2 - actualWidth / 2
this.applyPlacement(calculatedOffset, 'top')
getPosition: ->
top: @$element.offset().top
left: if @e? then @e.pageX else @$element.offset().left
height: 0
width: 0
hide: ->
super()
@shown = false
disable: ->
super()
@hide()

View file

@ -108,7 +108,7 @@ module.exports = class CastButtonView extends CocoView
else if castable else if castable
s = $.i18n.t('play_level.tome_cast_button_run') s = $.i18n.t('play_level.tome_cast_button_run')
s = $.i18n.t('play_level.tome_cast_button_casting') if s is 'Run' and me.get('preferredLanguage').split('-')[0] isnt 'en' # Temporary, if tome_cast_button_running isn't translated. s = $.i18n.t('play_level.tome_cast_button_casting') if s is 'Run' and me.get('preferredLanguage').split('-')[0] isnt 'en' # Temporary, if tome_cast_button_running isn't translated.
unless @options.levelID in ['dungeons-of-kithgard', 'gems-in-the-deep', 'shadow-guard', 'true-names', 'the-raised-sword', 'the-first-kithmaze'] # Hide for first few. unless @options.levelID in ['dungeons-of-kithgard', 'gems-in-the-deep', 'shadow-guard', 'forgetful-gemsmith', 'kounter-kithwise', 'true-names', 'the-raised-sword', 'favorable-odds', 'the-first-kithmaze', 'haunted-kithmaze'] # Hide for first few.
s += ' ' + @castShortcut s += ' ' + @castShortcut
else else
s = $.i18n.t('play_level.tome_cast_button_ran') s = $.i18n.t('play_level.tome_cast_button_ran')

View file

@ -871,4 +871,5 @@ requiredCodePerLevel =
'dungeons-of-kithgard': ['moveRight'] 'dungeons-of-kithgard': ['moveRight']
'true-names': ['Brak'] 'true-names': ['Brak']
'the-first-kithmaze': ['loop'] 'the-first-kithmaze': ['loop']
'haunted-kithmaze': ['loop']
'lowly-kithmen': ['findNearestEnemy'] 'lowly-kithmen': ['findNearestEnemy']