mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-12-12 16:51:35 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
90d5e9ddce
22 changed files with 859 additions and 532 deletions
|
@ -428,17 +428,18 @@ self.onWorldLoaded = function onWorldLoaded() {
|
||||||
if(self.world.framesSerializedSoFar == self.world.frames.length) return;
|
if(self.world.framesSerializedSoFar == self.world.frames.length) return;
|
||||||
if(self.world.ended)
|
if(self.world.ended)
|
||||||
self.goalManager.worldGenerationEnded();
|
self.goalManager.worldGenerationEnded();
|
||||||
var goalStates = self.goalManager.getGoalStates();
|
|
||||||
var overallStatus = self.goalManager.checkOverallStatus();
|
|
||||||
var totalFrames = self.world.totalFrames;
|
|
||||||
if(self.world.ended) {
|
|
||||||
var lastFrameHash = self.world.frames[totalFrames - 2].hash
|
|
||||||
self.postMessage({type: 'end-load-frames', goalStates: goalStates, overallStatus: overallStatus, totalFrames: totalFrames, lastFrameHash: lastFrameHash});
|
|
||||||
}
|
|
||||||
var t1 = new Date();
|
var t1 = new Date();
|
||||||
var diff = t1 - self.t0;
|
var diff = t1 - self.t0;
|
||||||
|
var goalStates = self.goalManager.getGoalStates();
|
||||||
|
var totalFrames = self.world.totalFrames;
|
||||||
|
if(self.world.ended) {
|
||||||
|
var overallStatus = self.goalManager.checkOverallStatus();
|
||||||
|
var lastFrameHash = self.world.frames[totalFrames - 2].hash
|
||||||
|
var simulationFrameRate = self.world.frames.length / diff * 1000 * 30 / self.world.frameRate
|
||||||
|
self.postMessage({type: 'end-load-frames', goalStates: goalStates, overallStatus: overallStatus, totalFrames: totalFrames, lastFrameHash: lastFrameHash, simulationFrameRate: simulationFrameRate});
|
||||||
if(self.world.headless)
|
if(self.world.headless)
|
||||||
return console.log('Headless simulation completed in ' + diff + 'ms.');
|
return console.log('Headless simulation completed in ' + diff + 'ms, ' + simulationFrameRate.toFixed(1) + ' FPS.');
|
||||||
|
}
|
||||||
|
|
||||||
var worldEnded = self.world.ended;
|
var worldEnded = self.world.ended;
|
||||||
var serialized;
|
var serialized;
|
||||||
|
@ -469,7 +470,7 @@ self.onWorldLoaded = function onWorldLoaded() {
|
||||||
|
|
||||||
if(worldEnded) {
|
if(worldEnded) {
|
||||||
var t3 = new Date();
|
var t3 = new Date();
|
||||||
console.log("And it was so: (" + (diff / totalFrames).toFixed(3) + "ms per frame,", totalFrames, "frames)\nSimulation :", diff + "ms \nSerialization:", (t2 - t1) + "ms\nDelivery :", (t3 - t2) + "ms");
|
console.log("And it was so: (" + (diff / totalFrames).toFixed(3) + "ms per frame,", totalFrames, "frames)\nSimulation :", diff + "ms \nSerialization:", (t2 - t1) + "ms\nDelivery :", (t3 - t2) + "ms\nFPS :", simulationFrameRate.toFixed(1));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -483,7 +484,10 @@ self.onWorldPreloaded = function onWorldPreloaded() {
|
||||||
self.goalManager.worldGenerationEnded();
|
self.goalManager.worldGenerationEnded();
|
||||||
var goalStates = self.goalManager.getGoalStates();
|
var goalStates = self.goalManager.getGoalStates();
|
||||||
var overallStatus = self.goalManager.checkOverallStatus();
|
var overallStatus = self.goalManager.checkOverallStatus();
|
||||||
self.postMessage({type: 'end-preload-frames', goalStates: goalStates, overallStatus: overallStatus});
|
var t1 = new Date();
|
||||||
|
var diff = t1 - self.t0;
|
||||||
|
var simulationFrameRate = self.world.frames.length / diff * 1000 * 30 / self.world.frameRate
|
||||||
|
self.postMessage({type: 'end-preload-frames', goalStates: goalStates, overallStatus: overallStatus, simulationFrameRate: simulationFrameRate});
|
||||||
};
|
};
|
||||||
|
|
||||||
self.onWorldError = function onWorldError(error) {
|
self.onWorldError = function onWorldError(error) {
|
||||||
|
|
|
@ -370,3 +370,103 @@ module.exports.findNextLevel = (levels, currentIndex, needsPractice) ->
|
||||||
|
|
||||||
module.exports.needsPractice = (playtime=0, threshold=2) ->
|
module.exports.needsPractice = (playtime=0, threshold=2) ->
|
||||||
playtime / 60 > threshold
|
playtime / 60 > threshold
|
||||||
|
|
||||||
|
module.exports.usStateCodes =
|
||||||
|
# https://github.com/mdzhang/us-state-codes
|
||||||
|
# generated by js2coffee 2.2.0
|
||||||
|
(->
|
||||||
|
stateNamesByCode =
|
||||||
|
'AL': 'Alabama'
|
||||||
|
'AK': 'Alaska'
|
||||||
|
'AZ': 'Arizona'
|
||||||
|
'AR': 'Arkansas'
|
||||||
|
'CA': 'California'
|
||||||
|
'CO': 'Colorado'
|
||||||
|
'CT': 'Connecticut'
|
||||||
|
'DE': 'Delaware'
|
||||||
|
'DC': 'District of Columbia'
|
||||||
|
'FL': 'Florida'
|
||||||
|
'GA': 'Georgia'
|
||||||
|
'HI': 'Hawaii'
|
||||||
|
'ID': 'Idaho'
|
||||||
|
'IL': 'Illinois'
|
||||||
|
'IN': 'Indiana'
|
||||||
|
'IA': 'Iowa'
|
||||||
|
'KS': 'Kansas'
|
||||||
|
'KY': 'Kentucky'
|
||||||
|
'LA': 'Louisiana'
|
||||||
|
'ME': 'Maine'
|
||||||
|
'MD': 'Maryland'
|
||||||
|
'MA': 'Massachusetts'
|
||||||
|
'MI': 'Michigan'
|
||||||
|
'MN': 'Minnesota'
|
||||||
|
'MS': 'Mississippi'
|
||||||
|
'MO': 'Missouri'
|
||||||
|
'MT': 'Montana'
|
||||||
|
'NE': 'Nebraska'
|
||||||
|
'NV': 'Nevada'
|
||||||
|
'NH': 'New Hampshire'
|
||||||
|
'NJ': 'New Jersey'
|
||||||
|
'NM': 'New Mexico'
|
||||||
|
'NY': 'New York'
|
||||||
|
'NC': 'North Carolina'
|
||||||
|
'ND': 'North Dakota'
|
||||||
|
'OH': 'Ohio'
|
||||||
|
'OK': 'Oklahoma'
|
||||||
|
'OR': 'Oregon'
|
||||||
|
'PA': 'Pennsylvania'
|
||||||
|
'RI': 'Rhode Island'
|
||||||
|
'SC': 'South Carolina'
|
||||||
|
'SD': 'South Dakota'
|
||||||
|
'TN': 'Tennessee'
|
||||||
|
'TX': 'Texas'
|
||||||
|
'UT': 'Utah'
|
||||||
|
'VT': 'Vermont'
|
||||||
|
'VA': 'Virginia'
|
||||||
|
'WA': 'Washington'
|
||||||
|
'WV': 'West Virginia'
|
||||||
|
'WI': 'Wisconsin'
|
||||||
|
'WY': 'Wyoming'
|
||||||
|
stateCodesByName = _.invert(stateNamesByCode)
|
||||||
|
# normalizes case and removes invalid characters
|
||||||
|
# returns null if can't find sanitized code in the state map
|
||||||
|
|
||||||
|
sanitizeStateCode = (code) ->
|
||||||
|
code = if _.isString(code) then code.trim().toUpperCase().replace(/[^A-Z]/g, '') else null
|
||||||
|
if stateNamesByCode[code] then code else null
|
||||||
|
|
||||||
|
# returns a valid state name else null
|
||||||
|
|
||||||
|
getStateNameByStateCode = (code) ->
|
||||||
|
stateNamesByCode[sanitizeStateCode(code)] or null
|
||||||
|
|
||||||
|
# normalizes case and removes invalid characters
|
||||||
|
# returns null if can't find sanitized name in the state map
|
||||||
|
|
||||||
|
sanitizeStateName = (name) ->
|
||||||
|
if !_.isString(name)
|
||||||
|
return null
|
||||||
|
# bad whitespace remains bad whitespace e.g. "O hi o" is not valid
|
||||||
|
name = name.trim().toLowerCase().replace(/[^a-z\s]/g, '').replace(/\s+/g, ' ')
|
||||||
|
tokens = name.split(/\s+/)
|
||||||
|
tokens = _.map(tokens, (token) ->
|
||||||
|
token.charAt(0).toUpperCase() + token.slice(1)
|
||||||
|
)
|
||||||
|
# account for District of Columbia
|
||||||
|
if tokens.length > 2
|
||||||
|
tokens[1] = tokens[1].toLowerCase()
|
||||||
|
name = tokens.join(' ')
|
||||||
|
if stateCodesByName[name] then name else null
|
||||||
|
|
||||||
|
# returns a valid state code else null
|
||||||
|
|
||||||
|
getStateCodeByStateName = (name) ->
|
||||||
|
stateCodesByName[sanitizeStateName(name)] or null
|
||||||
|
|
||||||
|
return {
|
||||||
|
sanitizeStateCode: sanitizeStateCode
|
||||||
|
getStateNameByStateCode: getStateNameByStateCode
|
||||||
|
sanitizeStateName: sanitizeStateName
|
||||||
|
getStateCodeByStateName: getStateCodeByStateName
|
||||||
|
}
|
||||||
|
)()
|
||||||
|
|
|
@ -82,11 +82,10 @@ module.exports = class Angel extends CocoClass
|
||||||
clearTimeout @condemnTimeout
|
clearTimeout @condemnTimeout
|
||||||
when 'end-load-frames'
|
when 'end-load-frames'
|
||||||
clearTimeout @condemnTimeout
|
clearTimeout @condemnTimeout
|
||||||
@beholdGoalStates event.data.goalStates, event.data.overallStatus, false, event.data.totalFrames, event.data.lastFrameHash # Work ends here if we're headless.
|
@beholdGoalStates {goalStates: event.data.goalStates, overallStatus: event.data.overallStatus, preload: false, totalFrames: event.data.totalFrames, lastFrameHash: event.data.lastFrameHash, simulationFrameRate: event.data.simulationFrameRate} # Work ends here if we're headless.
|
||||||
when 'end-preload-frames'
|
when 'end-preload-frames'
|
||||||
clearTimeout @condemnTimeout
|
clearTimeout @condemnTimeout
|
||||||
@beholdGoalStates event.data.goalStates, event.data.overallStatus, true
|
@beholdGoalStates {goalStates: event.data.goalStates, overallStatus: event.data.overallStatus, preload: true, simulationFrameRate: event.data.simulationFrameRate}
|
||||||
|
|
||||||
|
|
||||||
# We have to abort like an infinite loop if we see one of these; they're not really recoverable
|
# We have to abort like an infinite loop if we see one of these; they're not really recoverable
|
||||||
when 'non-user-code-problem'
|
when 'non-user-code-problem'
|
||||||
|
@ -125,11 +124,12 @@ module.exports = class Angel extends CocoClass
|
||||||
else
|
else
|
||||||
@log 'Received unsupported message:', event.data
|
@log 'Received unsupported message:', event.data
|
||||||
|
|
||||||
beholdGoalStates: (goalStates, overallStatus, preload=false, totalFrames=undefined, lastFrameHash=undefined) ->
|
beholdGoalStates: ({goalStates, overallStatus, preload, totalFrames, lastFrameHash, simulationFrameRate}) ->
|
||||||
return if @aborting
|
return if @aborting
|
||||||
event = goalStates: goalStates, preload: preload, overallStatus: overallStatus
|
event = goalStates: goalStates, preload: preload ? false, overallStatus: overallStatus
|
||||||
event.totalFrames = totalFrames if totalFrames?
|
event.totalFrames = totalFrames if totalFrames?
|
||||||
event.lastFrameHash = lastFrameHash if lastFrameHash?
|
event.lastFrameHash = lastFrameHash if lastFrameHash?
|
||||||
|
event.simulationFrameRate = simulationFrameRate if simulationFrameRate?
|
||||||
@publishGodEvent 'goals-calculated', event
|
@publishGodEvent 'goals-calculated', event
|
||||||
@finishWork() if @shared.headless
|
@finishWork() if @shared.headless
|
||||||
|
|
||||||
|
@ -306,7 +306,8 @@ module.exports = class Angel extends CocoClass
|
||||||
work.world.goalManager.worldGenerationEnded() if work.world.ended
|
work.world.goalManager.worldGenerationEnded() if work.world.ended
|
||||||
|
|
||||||
if work.headless
|
if work.headless
|
||||||
@beholdGoalStates goalStates, testGM.checkOverallStatus(), false, work.world.totalFrames, work.world.frames[work.world.totalFrames - 2]?.hash
|
simulationFrameRate = work.world.frames.length / (work.t2 - work.t1) * 1000 * 30 / work.world.frameRate
|
||||||
|
@beholdGoalStates {goalStates, overallStatus: testGM.checkOverallStatus(), preload: false, totalFrames: work.world.totalFrames, lastFrameHash: work.world.frames[work.world.totalFrames - 2]?.hash, simulationFrameRate: simulationFrameRate}
|
||||||
return
|
return
|
||||||
|
|
||||||
serialized = world.serialize()
|
serialized = world.serialize()
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
CocoClass = require 'core/CocoClass'
|
CocoClass = require 'core/CocoClass'
|
||||||
|
GameUIState = require 'models/GameUIState'
|
||||||
|
|
||||||
# If I were the kind of math major who remembered his math, this would all be done with matrix transforms.
|
# If I were the kind of math major who remembered his math, this would all be done with matrix transforms.
|
||||||
|
|
||||||
|
@ -44,12 +45,17 @@ module.exports = class Camera extends CocoClass
|
||||||
'camera:zoom-out': 'onZoomOut'
|
'camera:zoom-out': 'onZoomOut'
|
||||||
'camera:zoom-to': 'onZoomTo'
|
'camera:zoom-to': 'onZoomTo'
|
||||||
'level:restarted': 'onLevelRestarted'
|
'level:restarted': 'onLevelRestarted'
|
||||||
'surface:mouse-scrolled': 'onMouseScrolled'
|
|
||||||
'sprite:mouse-down': 'onMouseDown'
|
|
||||||
'sprite:dragged': 'onMouseDragged'
|
|
||||||
|
|
||||||
constructor: (@canvas, angle=Math.asin(0.75), hFOV=d2r(30)) ->
|
constructor: (@canvas, @options={}) ->
|
||||||
|
angle=Math.asin(0.75)
|
||||||
|
hFOV=d2r(30)
|
||||||
super()
|
super()
|
||||||
|
@gameUIState = @options.gameUIState or new GameUIState()
|
||||||
|
@listenTo @gameUIState, 'surface:stage-mouse-move', @onMouseMove
|
||||||
|
@listenTo @gameUIState, 'surface:stage-mouse-down', @onMouseDown
|
||||||
|
@listenTo @gameUIState, 'surface:stage-mouse-up', @onMouseUp
|
||||||
|
@listenTo @gameUIState, 'surface:mouse-scrolled', @onMouseScrolled
|
||||||
|
@handleEvents = @options.handleEvents ? true
|
||||||
@canvasWidth = parseInt(@canvas.attr('width'), 10)
|
@canvasWidth = parseInt(@canvas.attr('width'), 10)
|
||||||
@canvasHeight = parseInt(@canvas.attr('height'), 10)
|
@canvasHeight = parseInt(@canvas.attr('height'), 10)
|
||||||
@offset = {x: 0, y: 0}
|
@offset = {x: 0, y: 0}
|
||||||
|
@ -155,8 +161,27 @@ module.exports = class Camera extends CocoClass
|
||||||
|
|
||||||
onZoomIn: (e) -> @zoomTo @target, @zoom * 1.15, 300
|
onZoomIn: (e) -> @zoomTo @target, @zoom * 1.15, 300
|
||||||
onZoomOut: (e) -> @zoomTo @target, @zoom / 1.15, 300
|
onZoomOut: (e) -> @zoomTo @target, @zoom / 1.15, 300
|
||||||
|
|
||||||
|
onMouseDown: (e) ->
|
||||||
|
return if @dragDisabled
|
||||||
|
@lastPos = {x: e.originalEvent.rawX, y: e.originalEvent.rawY}
|
||||||
|
@mousePressed = true
|
||||||
|
|
||||||
|
onMouseMove: (e) ->
|
||||||
|
return unless @mousePressed and @gameUIState.get('canDragCamera')
|
||||||
|
return if @dragDisabled
|
||||||
|
target = @boundTarget(@target, @zoom)
|
||||||
|
newPos =
|
||||||
|
x: target.x + (@lastPos.x - e.originalEvent.rawX) / @zoom
|
||||||
|
y: target.y + (@lastPos.y - e.originalEvent.rawY) / @zoom
|
||||||
|
@zoomTo newPos, @zoom, 0
|
||||||
|
@lastPos = {x: e.originalEvent.rawX, y: e.originalEvent.rawY}
|
||||||
|
Backbone.Mediator.publish 'camera:dragged', {}
|
||||||
|
|
||||||
|
onMouseUp: (e) ->
|
||||||
|
@mousePressed = false
|
||||||
|
|
||||||
onMouseScrolled: (e) ->
|
onMouseScrolled: (e) ->
|
||||||
return unless e.canvas is @canvas
|
|
||||||
ratio = 1 + 0.05 * Math.sqrt(Math.abs(e.deltaY))
|
ratio = 1 + 0.05 * Math.sqrt(Math.abs(e.deltaY))
|
||||||
ratio = 1 / ratio if e.deltaY > 0
|
ratio = 1 / ratio if e.deltaY > 0
|
||||||
newZoom = @zoom * ratio
|
newZoom = @zoom * ratio
|
||||||
|
@ -174,22 +199,6 @@ module.exports = class Camera extends CocoClass
|
||||||
target = @target
|
target = @target
|
||||||
@zoomTo target, newZoom, 0
|
@zoomTo target, newZoom, 0
|
||||||
|
|
||||||
onMouseDown: (e) ->
|
|
||||||
return unless e.canvas is @canvas[0]
|
|
||||||
return if @dragDisabled
|
|
||||||
@lastPos = {x: e.originalEvent.rawX, y: e.originalEvent.rawY}
|
|
||||||
|
|
||||||
onMouseDragged: (e) ->
|
|
||||||
return unless e.canvas is @canvas[0]
|
|
||||||
return if @dragDisabled
|
|
||||||
target = @boundTarget(@target, @zoom)
|
|
||||||
newPos =
|
|
||||||
x: target.x + (@lastPos.x - e.originalEvent.rawX) / @zoom
|
|
||||||
y: target.y + (@lastPos.y - e.originalEvent.rawY) / @zoom
|
|
||||||
@zoomTo newPos, @zoom, 0
|
|
||||||
@lastPos = {x: e.originalEvent.rawX, y: e.originalEvent.rawY}
|
|
||||||
Backbone.Mediator.publish 'camera:dragged', {}
|
|
||||||
|
|
||||||
onLevelRestarted: ->
|
onLevelRestarted: ->
|
||||||
@setBounds(@firstBounds, false)
|
@setBounds(@firstBounds, false)
|
||||||
|
|
||||||
|
|
|
@ -58,11 +58,13 @@ module.exports = Lank = class Lank extends CocoClass
|
||||||
'surface:ticked': 'onSurfaceTicked'
|
'surface:ticked': 'onSurfaceTicked'
|
||||||
'sprite:move': 'onMove'
|
'sprite:move': 'onMove'
|
||||||
|
|
||||||
constructor: (@thangType, options) ->
|
constructor: (@thangType, options={}) ->
|
||||||
super()
|
super()
|
||||||
spriteName = @thangType.get('name')
|
spriteName = @thangType.get('name')
|
||||||
@isMissile = /(Missile|Arrow|Spear|Bolt)/.test(spriteName) and not /(Tower|Charge)/.test(spriteName)
|
@isMissile = /(Missile|Arrow|Spear|Bolt)/.test(spriteName) and not /(Tower|Charge)/.test(spriteName)
|
||||||
@options = _.extend($.extend(true, {}, @options), options)
|
@options = _.extend($.extend(true, {}, @options), options)
|
||||||
|
@gameUIState = @options.gameUIState
|
||||||
|
@handleEvents = @options.handleEvents
|
||||||
@setThang @options.thang
|
@setThang @options.thang
|
||||||
if @thang?
|
if @thang?
|
||||||
options = @thang?.getLankOptions?()
|
options = @thang?.getLankOptions?()
|
||||||
|
@ -496,6 +498,7 @@ module.exports = Lank = class Lank extends CocoClass
|
||||||
newEvent = sprite: @, thang: @thang, originalEvent: e, canvas: p.canvas
|
newEvent = sprite: @, thang: @thang, originalEvent: e, canvas: p.canvas
|
||||||
@trigger ourEventName, newEvent
|
@trigger ourEventName, newEvent
|
||||||
Backbone.Mediator.publish ourEventName, newEvent
|
Backbone.Mediator.publish ourEventName, newEvent
|
||||||
|
@gameUIState.trigger(ourEventName, newEvent)
|
||||||
|
|
||||||
addHealthBar: ->
|
addHealthBar: ->
|
||||||
return unless @thang?.health? and 'health' in (@thang?.hudProperties ? []) and @options.floatingLayer
|
return unless @thang?.health? and 'health' in (@thang?.hudProperties ? []) and @options.floatingLayer
|
||||||
|
@ -646,6 +649,10 @@ module.exports = Lank = class Lank extends CocoClass
|
||||||
@marks[name] ?= new Mark name: name, lank: @, camera: @options.camera, layer: layer ? @options.groundLayer, thangType: thangType
|
@marks[name] ?= new Mark name: name, lank: @, camera: @options.camera, layer: layer ? @options.groundLayer, thangType: thangType
|
||||||
@marks[name]
|
@marks[name]
|
||||||
|
|
||||||
|
removeMark: (name) ->
|
||||||
|
@marks[name].destroy()
|
||||||
|
delete @marks[name]
|
||||||
|
|
||||||
notifySpeechUpdated: (e) ->
|
notifySpeechUpdated: (e) ->
|
||||||
e = _.clone(e)
|
e = _.clone(e)
|
||||||
e.sprite = @
|
e.sprite = @
|
||||||
|
|
|
@ -25,10 +25,11 @@ module.exports = class LankBoss extends CocoClass
|
||||||
'surface:flag-appeared': 'onFlagAppeared'
|
'surface:flag-appeared': 'onFlagAppeared'
|
||||||
'surface:remove-selected-flag': 'onRemoveSelectedFlag'
|
'surface:remove-selected-flag': 'onRemoveSelectedFlag'
|
||||||
|
|
||||||
constructor: (@options) ->
|
constructor: (@options={}) ->
|
||||||
super()
|
super()
|
||||||
|
@handleEvents = @options.handleEvents
|
||||||
|
@gameUIState = @options.gameUIState
|
||||||
@dragged = 0
|
@dragged = 0
|
||||||
@options ?= {}
|
|
||||||
@camera = @options.camera
|
@camera = @options.camera
|
||||||
@webGLStage = @options.webGLStage
|
@webGLStage = @options.webGLStage
|
||||||
@surfaceTextLayer = @options.surfaceTextLayer
|
@surfaceTextLayer = @options.surfaceTextLayer
|
||||||
|
@ -38,6 +39,8 @@ module.exports = class LankBoss extends CocoClass
|
||||||
@lankArray = [] # Mirror @lanks, but faster for when we just need to iterate
|
@lankArray = [] # Mirror @lanks, but faster for when we just need to iterate
|
||||||
@createLayers()
|
@createLayers()
|
||||||
@pendingFlags = []
|
@pendingFlags = []
|
||||||
|
if not @handleEvents
|
||||||
|
@listenTo @gameUIState, 'change:selected', @onChangeSelected
|
||||||
|
|
||||||
destroy: ->
|
destroy: ->
|
||||||
@removeLank lank for thangID, lank of @lanks
|
@removeLank lank for thangID, lank of @lanks
|
||||||
|
@ -93,7 +96,16 @@ module.exports = class LankBoss extends CocoClass
|
||||||
@selectionMark = new Mark name: 'selection', camera: @camera, layer: @layerAdapters['Ground'], thangType: 'selection'
|
@selectionMark = new Mark name: 'selection', camera: @camera, layer: @layerAdapters['Ground'], thangType: 'selection'
|
||||||
|
|
||||||
createLankOptions: (options) ->
|
createLankOptions: (options) ->
|
||||||
_.extend options, camera: @camera, resolutionFactor: SPRITE_RESOLUTION_FACTOR, groundLayer: @layerAdapters['Ground'], textLayer: @surfaceTextLayer, floatingLayer: @layerAdapters['Floating'], showInvisible: @options.showInvisible
|
_.extend options, {
|
||||||
|
@camera
|
||||||
|
resolutionFactor: SPRITE_RESOLUTION_FACTOR
|
||||||
|
groundLayer: @layerAdapters['Ground']
|
||||||
|
textLayer: @surfaceTextLayer
|
||||||
|
floatingLayer: @layerAdapters['Floating']
|
||||||
|
showInvisible: @options.showInvisible
|
||||||
|
@gameUIState
|
||||||
|
@handleEvents
|
||||||
|
}
|
||||||
|
|
||||||
onSetDebug: (e) ->
|
onSetDebug: (e) ->
|
||||||
return if e.debug is @debug
|
return if e.debug is @debug
|
||||||
|
@ -256,6 +268,7 @@ module.exports = class LankBoss extends CocoClass
|
||||||
@dragged += 1
|
@dragged += 1
|
||||||
|
|
||||||
onLankMouseUp: (e) ->
|
onLankMouseUp: (e) ->
|
||||||
|
return unless @handleEvents
|
||||||
return if key.shift #and @options.choosing
|
return if key.shift #and @options.choosing
|
||||||
return @dragged = 0 if @dragged > 3
|
return @dragged = 0 if @dragged > 3
|
||||||
@dragged = 0
|
@dragged = 0
|
||||||
|
@ -264,9 +277,27 @@ module.exports = class LankBoss extends CocoClass
|
||||||
@selectLank e, lank
|
@selectLank e, lank
|
||||||
|
|
||||||
onStageMouseDown: (e) ->
|
onStageMouseDown: (e) ->
|
||||||
|
return unless @handleEvents
|
||||||
return if key.shift #and @options.choosing
|
return if key.shift #and @options.choosing
|
||||||
@selectLank e if e.onBackground
|
@selectLank e if e.onBackground
|
||||||
|
|
||||||
|
onChangeSelected: (gameUIState, selected) ->
|
||||||
|
oldLanks = (s.sprite for s in gameUIState.previousAttributes().selected or [])
|
||||||
|
newLanks = (s.sprite for s in selected or [])
|
||||||
|
addedLanks = _.difference(newLanks, oldLanks)
|
||||||
|
removedLanks = _.difference(oldLanks, newLanks)
|
||||||
|
|
||||||
|
for lank in addedLanks
|
||||||
|
layer = if lank.sprite.parent isnt @layerAdapters.Default.container then @layerAdapters.Default else @layerAdapters.Ground
|
||||||
|
mark = new Mark name: 'selection', camera: @camera, layer: layer, thangType: 'selection'
|
||||||
|
mark.toggle true
|
||||||
|
mark.setLank(lank)
|
||||||
|
mark.update()
|
||||||
|
lank.marks.selection = mark # TODO: Figure out how to non-hackily assign lank this mark
|
||||||
|
|
||||||
|
for lank in removedLanks
|
||||||
|
lank.removeMark?('selection')
|
||||||
|
|
||||||
selectThang: (thangID, spellName=null, treemaThangSelected = null) ->
|
selectThang: (thangID, spellName=null, treemaThangSelected = null) ->
|
||||||
return @willSelectThang = [thangID, spellName] unless @lanks[thangID]
|
return @willSelectThang = [thangID, spellName] unless @lanks[thangID]
|
||||||
@selectLank null, @lanks[thangID], spellName, treemaThangSelected
|
@selectLank null, @lanks[thangID], spellName, treemaThangSelected
|
||||||
|
@ -275,6 +306,7 @@ module.exports = class LankBoss extends CocoClass
|
||||||
return if e and (@disabled or @selectLocked) # Ignore clicks for selection/panning/wizard movement while disabled or select is locked
|
return if e and (@disabled or @selectLocked) # Ignore clicks for selection/panning/wizard movement while disabled or select is locked
|
||||||
worldPos = lank?.thang?.pos
|
worldPos = lank?.thang?.pos
|
||||||
worldPos ?= @camera.screenToWorld {x: e.originalEvent.rawX, y: e.originalEvent.rawY} if e?.originalEvent
|
worldPos ?= @camera.screenToWorld {x: e.originalEvent.rawX, y: e.originalEvent.rawY} if e?.originalEvent
|
||||||
|
if @handleEvents
|
||||||
if (not @reallyStopMoving) and worldPos and (@options.navigateToSelection or not lank or treemaThangSelected) and e?.originalEvent?.nativeEvent?.which isnt 3
|
if (not @reallyStopMoving) and worldPos and (@options.navigateToSelection or not lank or treemaThangSelected) and e?.originalEvent?.nativeEvent?.which isnt 3
|
||||||
@camera.zoomTo(lank?.sprite or @camera.worldToSurface(worldPos), @camera.zoom, 1000, true)
|
@camera.zoomTo(lank?.sprite or @camera.worldToSurface(worldPos), @camera.zoom, 1000, true)
|
||||||
lank = null if @options.choosing # Don't select lanks while choosing
|
lank = null if @options.choosing # Don't select lanks while choosing
|
||||||
|
|
|
@ -18,6 +18,7 @@ LankBoss = require './LankBoss'
|
||||||
PointChooser = require './PointChooser'
|
PointChooser = require './PointChooser'
|
||||||
RegionChooser = require './RegionChooser'
|
RegionChooser = require './RegionChooser'
|
||||||
MusicPlayer = require './MusicPlayer'
|
MusicPlayer = require './MusicPlayer'
|
||||||
|
GameUIState = require 'models/GameUIState'
|
||||||
|
|
||||||
resizeDelay = 500 # At least as much as $level-resize-transition-time.
|
resizeDelay = 500 # At least as much as $level-resize-transition-time.
|
||||||
|
|
||||||
|
@ -87,6 +88,10 @@ module.exports = Surface = class Surface extends CocoClass
|
||||||
@normalLayers = []
|
@normalLayers = []
|
||||||
@options = _.clone(@defaults)
|
@options = _.clone(@defaults)
|
||||||
@options = _.extend(@options, givenOptions) if givenOptions
|
@options = _.extend(@options, givenOptions) if givenOptions
|
||||||
|
@handleEvents = @options.handleEvents ? true
|
||||||
|
@gameUIState = @options.gameUIState or new GameUIState({
|
||||||
|
canDragCamera: true
|
||||||
|
})
|
||||||
@initEasel()
|
@initEasel()
|
||||||
@initAudio()
|
@initAudio()
|
||||||
@onResize = _.debounce @onResize, resizeDelay
|
@onResize = _.debounce @onResize, resizeDelay
|
||||||
|
@ -98,7 +103,7 @@ module.exports = Surface = class Surface extends CocoClass
|
||||||
@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
|
@normalStage.nextStage = @webGLStage
|
||||||
@camera = new Camera @webGLCanvas
|
@camera = new Camera(@webGLCanvas, { @gameUIState, @handleEvents })
|
||||||
AudioPlayer.camera = @camera unless @options.choosing
|
AudioPlayer.camera = @camera unless @options.choosing
|
||||||
|
|
||||||
@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
|
||||||
|
@ -112,7 +117,19 @@ module.exports = Surface = class Surface extends CocoClass
|
||||||
canvasHeight = parseInt @normalCanvas.attr('height'), 10
|
canvasHeight = parseInt @normalCanvas.attr('height'), 10
|
||||||
@screenLayer.addChild new Letterbox canvasWidth: canvasWidth, canvasHeight: canvasHeight
|
@screenLayer.addChild new Letterbox canvasWidth: canvasWidth, canvasHeight: canvasHeight
|
||||||
|
|
||||||
@lankBoss = new LankBoss camera: @camera, webGLStage: @webGLStage, surfaceTextLayer: @surfaceTextLayer, world: @world, thangTypes: @options.thangTypes, choosing: @options.choosing, navigateToSelection: @options.navigateToSelection, showInvisible: @options.showInvisible, playerNames: if @options.levelType is 'course-ladder' then @options.playerNames else null
|
@lankBoss = new LankBoss({
|
||||||
|
@camera
|
||||||
|
@webGLStage
|
||||||
|
@surfaceTextLayer
|
||||||
|
@world
|
||||||
|
thangTypes: @options.thangTypes
|
||||||
|
choosing: @options.choosing
|
||||||
|
navigateToSelection: @options.navigateToSelection
|
||||||
|
showInvisible: @options.showInvisible
|
||||||
|
playerNames: if @options.levelType is 'course-ladder' then @options.playerNames else null
|
||||||
|
@gameUIState
|
||||||
|
@handleEvents
|
||||||
|
})
|
||||||
@countdownScreen = new CountdownScreen camera: @camera, layer: @screenLayer, showsCountdown: @world.showsCountdown
|
@countdownScreen = new CountdownScreen camera: @camera, layer: @screenLayer, showsCountdown: @world.showsCountdown
|
||||||
@playbackOverScreen = new PlaybackOverScreen camera: @camera, layer: @screenLayer, playerNames: @options.playerNames
|
@playbackOverScreen = new PlaybackOverScreen camera: @camera, layer: @screenLayer, playerNames: @options.playerNames
|
||||||
@normalStage.addChildAt @playbackOverScreen.dimLayer, 0 # Put this below the other layers, actually, so we can more easily read text on the screen.
|
@normalStage.addChildAt @playbackOverScreen.dimLayer, 0 # Put this below the other layers, actually, so we can more easily read text on the screen.
|
||||||
|
@ -121,7 +138,7 @@ module.exports = Surface = class Surface extends CocoClass
|
||||||
@webGLStage.enableMouseOver(10)
|
@webGLStage.enableMouseOver(10)
|
||||||
@webGLStage.addEventListener 'stagemousemove', @onMouseMove
|
@webGLStage.addEventListener 'stagemousemove', @onMouseMove
|
||||||
@webGLStage.addEventListener 'stagemousedown', @onMouseDown
|
@webGLStage.addEventListener 'stagemousedown', @onMouseDown
|
||||||
@webGLCanvas[0].addEventListener 'mouseup', @onMouseUp
|
@webGLStage.addEventListener 'stagemouseup', @onMouseUp
|
||||||
@webGLCanvas.on 'mousewheel', @onMouseWheel
|
@webGLCanvas.on 'mousewheel', @onMouseWheel
|
||||||
@hookUpChooseControls() if @options.choosing # TODO: figure this stuff out
|
@hookUpChooseControls() if @options.choosing # TODO: figure this stuff out
|
||||||
createjs.Ticker.timingMode = createjs.Ticker.RAF_SYNCHED
|
createjs.Ticker.timingMode = createjs.Ticker.RAF_SYNCHED
|
||||||
|
@ -222,6 +239,7 @@ module.exports = Surface = class Surface extends CocoClass
|
||||||
|
|
||||||
updateState: (frameChanged) ->
|
updateState: (frameChanged) ->
|
||||||
# world state must have been restored in @restoreWorldState
|
# world state must have been restored in @restoreWorldState
|
||||||
|
if @handleEvents
|
||||||
if @playing and @currentFrame < @world.frames.length - 1 and @heroLank and not @mouseIsDown and @camera.newTarget isnt @heroLank.sprite and @camera.target isnt @heroLank.sprite
|
if @playing and @currentFrame < @world.frames.length - 1 and @heroLank and not @mouseIsDown and @camera.newTarget isnt @heroLank.sprite and @camera.target isnt @heroLank.sprite
|
||||||
@camera.zoomTo @heroLank.sprite, @camera.zoom, 750
|
@camera.zoomTo @heroLank.sprite, @camera.zoom, 750
|
||||||
@lankBoss.update frameChanged
|
@lankBoss.update frameChanged
|
||||||
|
@ -371,6 +389,7 @@ module.exports = Surface = class Surface extends CocoClass
|
||||||
target = null
|
target = null
|
||||||
@camera.setBounds e.bounds if e.bounds
|
@camera.setBounds e.bounds if e.bounds
|
||||||
# @cameraBorder.updateBounds @camera.bounds
|
# @cameraBorder.updateBounds @camera.bounds
|
||||||
|
if @handleEvents
|
||||||
@camera.zoomTo target, e.zoom, e.duration # TODO: SurfaceScriptModule perhaps shouldn't assign e.zoom if not set
|
@camera.zoomTo target, e.zoom, e.duration # TODO: SurfaceScriptModule perhaps shouldn't assign e.zoom if not set
|
||||||
|
|
||||||
onZoomUpdated: (e) ->
|
onZoomUpdated: (e) ->
|
||||||
|
@ -476,6 +495,7 @@ module.exports = Surface = class Surface extends CocoClass
|
||||||
@mouseScreenPos = {x: e.stageX, y: e.stageY}
|
@mouseScreenPos = {x: e.stageX, y: e.stageY}
|
||||||
return if @disabled
|
return if @disabled
|
||||||
Backbone.Mediator.publish 'surface:mouse-moved', x: e.stageX, y: e.stageY
|
Backbone.Mediator.publish 'surface:mouse-moved', x: e.stageX, y: e.stageY
|
||||||
|
@gameUIState.trigger('surface:stage-mouse-move', { originalEvent: e })
|
||||||
|
|
||||||
onMouseDown: (e) =>
|
onMouseDown: (e) =>
|
||||||
return if @disabled
|
return if @disabled
|
||||||
|
@ -484,16 +504,19 @@ module.exports = Surface = class Surface extends CocoClass
|
||||||
onBackground = not @webGLStage._getObjectsUnderPoint(e.stageX, e.stageY, null, true)
|
onBackground = not @webGLStage._getObjectsUnderPoint(e.stageX, e.stageY, null, true)
|
||||||
|
|
||||||
wop = @camera.screenToWorld x: e.stageX, y: e.stageY
|
wop = @camera.screenToWorld x: e.stageX, y: e.stageY
|
||||||
event = onBackground: onBackground, x: e.stageX, y: e.stageY, originalEvent: e, worldPos: wop
|
event = { onBackground: onBackground, x: e.stageX, y: e.stageY, originalEvent: e, worldPos: wop }
|
||||||
Backbone.Mediator.publish 'surface:stage-mouse-down', event
|
Backbone.Mediator.publish 'surface:stage-mouse-down', event
|
||||||
Backbone.Mediator.publish 'tome:focus-editor', {}
|
Backbone.Mediator.publish 'tome:focus-editor', {}
|
||||||
|
@gameUIState.trigger('surface:stage-mouse-down', event)
|
||||||
@mouseIsDown = true
|
@mouseIsDown = true
|
||||||
|
|
||||||
onMouseUp: (e) =>
|
onMouseUp: (e) =>
|
||||||
return if @disabled
|
return if @disabled
|
||||||
onBackground = not @webGLStage.hitTest e.stageX, e.stageY
|
onBackground = not @webGLStage.hitTest e.stageX, e.stageY
|
||||||
Backbone.Mediator.publish 'surface:stage-mouse-up', onBackground: onBackground, x: e.stageX, y: e.stageY, originalEvent: e
|
event = { onBackground: onBackground, x: e.stageX, y: e.stageY, originalEvent: e }
|
||||||
|
Backbone.Mediator.publish 'surface:stage-mouse-up', event
|
||||||
Backbone.Mediator.publish 'tome:focus-editor', {}
|
Backbone.Mediator.publish 'tome:focus-editor', {}
|
||||||
|
@gameUIState.trigger('surface:stage-mouse-up', event)
|
||||||
@mouseIsDown = false
|
@mouseIsDown = false
|
||||||
|
|
||||||
onMouseWheel: (e) =>
|
onMouseWheel: (e) =>
|
||||||
|
@ -506,6 +529,7 @@ module.exports = Surface = class Surface extends CocoClass
|
||||||
canvas: @webGLCanvas
|
canvas: @webGLCanvas
|
||||||
event.screenPos = @mouseScreenPos if @mouseScreenPos
|
event.screenPos = @mouseScreenPos if @mouseScreenPos
|
||||||
Backbone.Mediator.publish 'surface:mouse-scrolled', event unless @disabled
|
Backbone.Mediator.publish 'surface:mouse-scrolled', event unless @disabled
|
||||||
|
@gameUIState.trigger('surface:mouse-scrolled', event)
|
||||||
|
|
||||||
|
|
||||||
#- Canvas callbacks
|
#- Canvas callbacks
|
||||||
|
@ -585,6 +609,7 @@ module.exports = Surface = class Surface extends CocoClass
|
||||||
@onResize()
|
@onResize()
|
||||||
_.delay @onResize, resizeDelay + 100 # Do it again just to be double sure that we don't stay zoomed in due to timing problems.
|
_.delay @onResize, resizeDelay + 100 # Do it again just to be double sure that we don't stay zoomed in due to timing problems.
|
||||||
@normalCanvas.add(@webGLCanvas).removeClass 'flag-color-selected'
|
@normalCanvas.add(@webGLCanvas).removeClass 'flag-color-selected'
|
||||||
|
if @handleEvents
|
||||||
if @previousCameraZoom
|
if @previousCameraZoom
|
||||||
@camera.zoomTo @camera.newTarget or @camera.target, @previousCameraZoom, 3000
|
@camera.zoomTo @camera.newTarget or @camera.target, @previousCameraZoom, 3000
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -6,7 +6,7 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
|
||||||
play: "Speel" # The big play button that opens up the campaign view.
|
play: "Speel" # The big play button that opens up the campaign view.
|
||||||
play_campaign_version: "Speel de Verhaallijn" # Shows up under big play button if you only play /courses
|
play_campaign_version: "Speel de Verhaallijn" # Shows up under big play button if you only play /courses
|
||||||
old_browser: "uh-oh, jouw browser is te oud om CodeCombat te kunnen spelen, Sorry!" # Warning that shows up on really old Firefox/Chrome/Safari
|
old_browser: "uh-oh, jouw browser is te oud om CodeCombat te kunnen spelen, Sorry!" # Warning that shows up on really old Firefox/Chrome/Safari
|
||||||
old_browser_suffix: "Je kan alsnog proberen, maar het zal waarschijnlijk niet werken!"
|
old_browser_suffix: "Je kan het alsnog proberen, maar het zal waarschijnlijk niet werken!"
|
||||||
ipad_browser: "Slecht nieuws: CodeCombat draait niet in je browser op de iPad. Goed nieuws: onze iPad-app wordt op het moment beoordeeld door Apple."
|
ipad_browser: "Slecht nieuws: CodeCombat draait niet in je browser op de iPad. Goed nieuws: onze iPad-app wordt op het moment beoordeeld door Apple."
|
||||||
campaign: "Verhaallijn"
|
campaign: "Verhaallijn"
|
||||||
for_beginners: "Voor Beginners"
|
for_beginners: "Voor Beginners"
|
||||||
|
@ -48,10 +48,10 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
|
||||||
kind_of_struggle: "het soort worsteling dat uitmondt in een leerproces dat uitdagend is en "
|
kind_of_struggle: "het soort worsteling dat uitmondt in een leerproces dat uitdagend is en "
|
||||||
motivating: "motiveert"
|
motivating: "motiveert"
|
||||||
not_tedious: "niet vervelend."
|
not_tedious: "niet vervelend."
|
||||||
gaming_is_good: "Studies geven aan dat speels leren goed is voor de hersenen van kinderen. (En dat klopt!)"
|
gaming_is_good: "Studies wijzen uit dat speels leren goed is voor de hersenen van kinderen. (En dat klopt!)"
|
||||||
game_based: "Wanneer spel-gebaseerde leersystemen worden"
|
game_based: "Wanneer spel-gebaseerde leersystemen worden"
|
||||||
compared: "vergeleken"
|
compared: "vergeleken"
|
||||||
conventional: "met conventionele assessment methodes, is het verschil duidelijk: games zijn beter in het stimuleren van leerlingen bij het onthouden van kennis, concentratie en het"
|
conventional: "met conventionele assessment methoden, is het verschil duidelijk: games zijn beter in het stimuleren van leerlingen bij het onthouden van kennis, concentratie en het"
|
||||||
perform_at_higher_level: "niveau van hun prestaties."
|
perform_at_higher_level: "niveau van hun prestaties."
|
||||||
feedback: "Games verschaffen directe feedback, wat leerlingen in staat stelt hun oplossingen te verbeteren en zij een holistisch begrip van de concepten krijgen, in plaats van beperkt zijn tot antwoorden als “correct” of “incorrect”."
|
feedback: "Games verschaffen directe feedback, wat leerlingen in staat stelt hun oplossingen te verbeteren en zij een holistisch begrip van de concepten krijgen, in plaats van beperkt zijn tot antwoorden als “correct” of “incorrect”."
|
||||||
real_game: "Een echt spel, wat je speelt met echte programmeertaal."
|
real_game: "Een echt spel, wat je speelt met echte programmeertaal."
|
||||||
|
@ -1288,11 +1288,11 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
|
||||||
avg_student_exp_varied: "Verschilt enorm per leerling"
|
avg_student_exp_varied: "Verschilt enorm per leerling"
|
||||||
student_age_range_label: "Leeftijdscategorie leerlingen"
|
student_age_range_label: "Leeftijdscategorie leerlingen"
|
||||||
student_age_range_younger: "Jonger dan 6"
|
student_age_range_younger: "Jonger dan 6"
|
||||||
student_age_range_older: "Ouden dan 18"
|
student_age_range_older: "Ouder dan 18"
|
||||||
student_age_range_to: "tot"
|
student_age_range_to: "tot"
|
||||||
create_class: "Maak klas aan"
|
create_class: "Maak klas aan"
|
||||||
class_name: "Klasnaam"
|
class_name: "Klasnaam"
|
||||||
teacher_account_restricted: "Jouw account is een Docenten Account, daarom heeft dit account geen toegang tot inhoud bedoelt voor leerlingen."
|
teacher_account_restricted: "Jouw account is een Docenten Account, daarom heeft dit account geen toegang tot inhoud bedoeld voor leerlingen."
|
||||||
|
|
||||||
teacher:
|
teacher:
|
||||||
teacher_dashboard: "Docent Dashboard" # Navbar
|
teacher_dashboard: "Docent Dashboard" # Navbar
|
||||||
|
@ -1312,7 +1312,7 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
|
||||||
teacher_account_explanation: "Een CodeCombat Docenten account geeft je de mogelijkheid om klassen aan te maken, voortgang van leerlingen te bekijken terwijl ze de cursussen volgen, inschrijvingen beheren en hulpmiddelen te gebruiken voor het opzetten van een leerplan"
|
teacher_account_explanation: "Een CodeCombat Docenten account geeft je de mogelijkheid om klassen aan te maken, voortgang van leerlingen te bekijken terwijl ze de cursussen volgen, inschrijvingen beheren en hulpmiddelen te gebruiken voor het opzetten van een leerplan"
|
||||||
current_classes: "Huidige Klassen"
|
current_classes: "Huidige Klassen"
|
||||||
archived_classes: "Gearchiveerde Klassen"
|
archived_classes: "Gearchiveerde Klassen"
|
||||||
archived_classes_blurb: "Klassen kunnen worden gearchiveerd voor toekomstige refferentie. Dearchiveer een klas om deze weer in de lijst Huidige Klassen te zien"
|
archived_classes_blurb: "Klassen kunnen worden gearchiveerd voor toekomstige referentie. Dearchiveer een klas om deze weer in de lijst Huidige Klassen te zien"
|
||||||
view_class: "bekijk klas"
|
view_class: "bekijk klas"
|
||||||
archive_class: "archiveer klas"
|
archive_class: "archiveer klas"
|
||||||
unarchive_class: "dearchiveer klas"
|
unarchive_class: "dearchiveer klas"
|
||||||
|
@ -1372,9 +1372,9 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
|
||||||
purchased: "Aanschaffing Voltooid!"
|
purchased: "Aanschaffing Voltooid!"
|
||||||
purchase_now: "Nu Aanschaffen"
|
purchase_now: "Nu Aanschaffen"
|
||||||
how_to_enroll: "Hoe Schrijf ik Leerlingen in"
|
how_to_enroll: "Hoe Schrijf ik Leerlingen in"
|
||||||
how_to_enroll_blurb_1: "Als een leerling nog niet is ingeschreven zal er een \"Enroll\" knop naast hun cursus voortgang zijn in je klas."
|
how_to_enroll_blurb_1: "Als een leerling nog niet is ingeschreven, staat er een knop genaamd \"Enroll\" bij hun naam."
|
||||||
how_to_enroll_blurb_2: "Om meerdere leerlingen in bulk in te schrijven, selecteer ze door middel van de selectievakjes aan de linkerkant van de klas pagina en klik op de \"Enroll Selected Students\" knop."
|
how_to_enroll_blurb_2: "Je kunt meerdere leerlingen tegelijkertijd inschrijven. Vink simpelweg de vakjes aan voor de leerlingen die je wilt inschrijven en klik op de knop \"Enroll Selected Students\"."
|
||||||
how_to_enroll_blurb_3: "Zodra een leerling is ingeschreven zal deze toegang hebben tot alle inhoud van de cursus."
|
how_to_enroll_blurb_3: "Zodra een leerling is ingeschreven heeft hij/zij toegang tot alle inhoud."
|
||||||
bulk_pricing_blurb: "Aanschaffen voor meer dan 25 leerlingen? Neem contact met ons op."
|
bulk_pricing_blurb: "Aanschaffen voor meer dan 25 leerlingen? Neem contact met ons op."
|
||||||
total_unenrolled: "Totaal aantal niet ingeschreven"
|
total_unenrolled: "Totaal aantal niet ingeschreven"
|
||||||
export_student_progress: "Exporteer Voortgang Leerlingen (CSV bestand)"
|
export_student_progress: "Exporteer Voortgang Leerlingen (CSV bestand)"
|
||||||
|
|
|
@ -38,7 +38,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
|
||||||
accessible_to: "Доступно для"
|
accessible_to: "Доступно для"
|
||||||
everyone: "каждого"
|
everyone: "каждого"
|
||||||
democratizing: "Демократизация процесса обучения программированию лежит в основе нашей философии. Каждый должен иметь возможность обучаться программированию."
|
democratizing: "Демократизация процесса обучения программированию лежит в основе нашей философии. Каждый должен иметь возможность обучаться программированию."
|
||||||
# forgot_learning: "I think they actually forgot that they were actually learning something."
|
forgot_learning: "Я думаю, что они забудут, что они действительно что-то изучают."
|
||||||
# wanted_to_do: " Coding is something I've always wanted to do, and I never thought I would be able to learn it in school."
|
# wanted_to_do: " Coding is something I've always wanted to do, and I never thought I would be able to learn it in school."
|
||||||
# why_games: "Why is learning through games important?"
|
# why_games: "Why is learning through games important?"
|
||||||
# games_reward: "Games reward the productive struggle."
|
# games_reward: "Games reward the productive struggle."
|
||||||
|
|
29
app/models/GameUIState.coffee
Normal file
29
app/models/GameUIState.coffee
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
CocoModel = require './CocoModel'
|
||||||
|
|
||||||
|
module.exports = class GameUIState extends CocoModel
|
||||||
|
@className: 'GameUIState'
|
||||||
|
@schema: {
|
||||||
|
type: 'object'
|
||||||
|
properties: {
|
||||||
|
|
||||||
|
canDragCamera: {
|
||||||
|
type: 'boolean'
|
||||||
|
description: 'Serves as a lock to enable or disable camera movement.'
|
||||||
|
}
|
||||||
|
|
||||||
|
selected: {
|
||||||
|
# TODO: Turn this into a collection which can be listened to? With Thang models.
|
||||||
|
type: 'object'
|
||||||
|
description: 'Array of selected thangs'
|
||||||
|
properties: {
|
||||||
|
sprite: { description: 'Lank instance' }
|
||||||
|
thang: { description: 'Thang object generated by the world' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defaults: -> {
|
||||||
|
selected: []
|
||||||
|
canDragCamera: true
|
||||||
|
}
|
|
@ -52,6 +52,7 @@ module.exports =
|
||||||
overallStatus: {type: ['string', 'null'], enum: ['success', 'failure', 'incomplete', null]}
|
overallStatus: {type: ['string', 'null'], enum: ['success', 'failure', 'incomplete', null]}
|
||||||
totalFrames: {type: ['integer', 'undefined']}
|
totalFrames: {type: ['integer', 'undefined']}
|
||||||
lastFrameHash: {type: ['number', 'undefined']}
|
lastFrameHash: {type: ['number', 'undefined']}
|
||||||
|
simulationFrameRate: {type: ['number', 'undefined']}
|
||||||
|
|
||||||
'god:world-load-progress-changed': c.object {required: ['progress', 'god']},
|
'god:world-load-progress-changed': c.object {required: ['progress', 'god']},
|
||||||
god: {type: 'object'}
|
god: {type: 'object'}
|
||||||
|
|
|
@ -387,7 +387,7 @@ block content
|
||||||
b CodeCombat Inc.
|
b CodeCombat Inc.
|
||||||
p 301 Howard Street
|
p 301 Howard Street
|
||||||
p Suite No. 830
|
p Suite No. 830
|
||||||
p San Francisco, California 94015
|
p San Francisco, California 94105
|
||||||
a(href="mailto:team@codecombat.com") team@codecombat.com
|
a(href="mailto:team@codecombat.com") team@codecombat.com
|
||||||
.col-sm-4
|
.col-sm-4
|
||||||
p
|
p
|
||||||
|
|
|
@ -6,16 +6,30 @@ block content
|
||||||
|
|
||||||
if !me.isAdmin()
|
if !me.isAdmin()
|
||||||
div You must be logged in as an admin to view this page.
|
div You must be logged in as an admin to view this page.
|
||||||
|
else if !view.countryGraphs || !view.countryGraphs['USA']
|
||||||
|
h3 Loading...
|
||||||
else
|
else
|
||||||
p CodeCombat is now in #{view.totalSchools} schools with #{view.totalStudents} students [and #{view.totalTeachers} teachers] [in #{view.totalStates} states]
|
p
|
||||||
p Students not attached to NCES data: #{view.untriagedStudents}
|
div CodeCombat is now in #{view.countryGraphs['USA'].totalSchools} schools with #{view.countryGraphs['USA'].totalStudents} students [and #{view.countryGraphs['USA'].totalTeachers} teachers] [in #{view.countryGraphs['USA'].totalStates} states] in the USA
|
||||||
|
p
|
||||||
|
div Untriaged students: #{view.untriagedStudents}
|
||||||
|
div Untriaged teachers: #{view.untriagedTeachers}
|
||||||
.small Teacher: owns a classroom or has a teacher role
|
.small Teacher: owns a classroom or has a teacher role
|
||||||
.small Student: member of a classroom or has schoolName set
|
.small Student: member of a classroom or has schoolName set, not in HoC course instance
|
||||||
.small States, Districts, Schools are from NCES
|
.small +3 USA states are GU, PR, DC
|
||||||
|
|
||||||
h2 State Counts
|
p
|
||||||
if view.stateCounts
|
ul
|
||||||
|
li
|
||||||
|
a(href="#usaStates") USA States
|
||||||
|
li
|
||||||
|
a(href="#usaDistrictsByState") USA Districts by State
|
||||||
|
li
|
||||||
|
a(href="#countries") Countries
|
||||||
|
|
||||||
|
a(name="usaStates")
|
||||||
|
h2 USA States
|
||||||
|
if view.countryGraphs['USA'].stateCounts
|
||||||
table.table.table-striped.table-condensed
|
table.table.table-striped.table-condensed
|
||||||
tr
|
tr
|
||||||
th State
|
th State
|
||||||
|
@ -23,7 +37,7 @@ block content
|
||||||
th Schools
|
th Schools
|
||||||
th Teachers
|
th Teachers
|
||||||
th Students
|
th Students
|
||||||
each stateCount in view.stateCounts
|
each stateCount in view.countryGraphs['USA'].stateCounts
|
||||||
tr
|
tr
|
||||||
td= stateCount.state
|
td= stateCount.state
|
||||||
td= stateCount.districts
|
td= stateCount.districts
|
||||||
|
@ -31,8 +45,9 @@ block content
|
||||||
td= stateCount.teachers
|
td= stateCount.teachers
|
||||||
td= stateCount.students
|
td= stateCount.students
|
||||||
|
|
||||||
h2 District Counts by State
|
a(name="usaDistrictsByState")
|
||||||
if view.districtCounts
|
h2 USA Districts by State
|
||||||
|
if view.countryGraphs['USA'].districtCounts
|
||||||
table.table.table-striped.table-condensed
|
table.table.table-striped.table-condensed
|
||||||
tr
|
tr
|
||||||
th State
|
th State
|
||||||
|
@ -40,10 +55,26 @@ block content
|
||||||
th Schools
|
th Schools
|
||||||
th Teachers
|
th Teachers
|
||||||
th Students
|
th Students
|
||||||
each districtCount in view.districtCounts
|
each districtCount in view.countryGraphs['USA'].districtCounts
|
||||||
tr
|
tr
|
||||||
td= districtCount.state
|
td= districtCount.state
|
||||||
td= districtCount.district
|
td= districtCount.district
|
||||||
td= districtCount.schools
|
td= districtCount.schools
|
||||||
td= districtCount.teachers
|
td= districtCount.teachers
|
||||||
td= districtCount.students
|
td= districtCount.students
|
||||||
|
|
||||||
|
a(name="countries")
|
||||||
|
h2 Countries
|
||||||
|
if view.countryCounts
|
||||||
|
table.table.table-striped.table-condensed
|
||||||
|
tr
|
||||||
|
th Country
|
||||||
|
th Schools
|
||||||
|
th Teachers
|
||||||
|
th Students
|
||||||
|
each countryCount in view.countryCounts
|
||||||
|
tr
|
||||||
|
td= countryCount.country
|
||||||
|
td= countryCount.schools
|
||||||
|
td= countryCount.teachers
|
||||||
|
td= countryCount.students
|
||||||
|
|
|
@ -105,6 +105,14 @@ block content
|
||||||
h4.test-failed User Code Problems
|
h4.test-failed User Code Problems
|
||||||
pre.test-failed #{JSON.stringify(test.userCodeProblems, null, 2)}
|
pre.test-failed #{JSON.stringify(test.userCodeProblems, null, 2)}
|
||||||
|
|
||||||
|
if test.simulationFrameRate
|
||||||
|
if test.simulationFrameRate > 90
|
||||||
|
div.test-success ✓ #{test.simulationFrameRate.toFixed(1)} FPS
|
||||||
|
else if test.simulationFrameRate > 30
|
||||||
|
div.test-running ~ #{test.simulationFrameRate.toFixed(1)} FPS
|
||||||
|
else
|
||||||
|
div.test-failed ✘ #{test.simulationFrameRate.toFixed(1)} FPS
|
||||||
|
|
||||||
else
|
else
|
||||||
h1 Loading Level...
|
h1 Loading Level...
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
RootView = require 'views/core/RootView'
|
RootView = require 'views/core/RootView'
|
||||||
CocoCollection = require 'collections/CocoCollection'
|
CocoCollection = require 'collections/CocoCollection'
|
||||||
Classroom = require 'models/Classroom'
|
Classroom = require 'models/Classroom'
|
||||||
|
CourseInstance = require 'models/CourseInstance'
|
||||||
TrialRequest = require 'models/TrialRequest'
|
TrialRequest = require 'models/TrialRequest'
|
||||||
User = require 'models/User'
|
User = require 'models/User'
|
||||||
|
utils = require 'core/utils'
|
||||||
|
|
||||||
# TODO: trim orphaned students: course instances != Single Player, hourOfCode != true
|
|
||||||
# TODO: match anonymous trial requests with real users via email
|
# TODO: match anonymous trial requests with real users via email
|
||||||
|
|
||||||
module.exports = class SchoolCountsView extends RootView
|
module.exports = class SchoolCountsView extends RootView
|
||||||
|
@ -15,6 +16,8 @@ module.exports = class SchoolCountsView extends RootView
|
||||||
return super() unless me.isAdmin()
|
return super() unless me.isAdmin()
|
||||||
@classrooms = new CocoCollection([], { url: "/db/classroom/-/users", model: Classroom })
|
@classrooms = new CocoCollection([], { url: "/db/classroom/-/users", model: Classroom })
|
||||||
@supermodel.loadCollection(@classrooms, 'classrooms', {cache: false})
|
@supermodel.loadCollection(@classrooms, 'classrooms', {cache: false})
|
||||||
|
@courseInstances = new CocoCollection([], { url: "/db/course_instance/-/non-hoc", model: CourseInstance})
|
||||||
|
@supermodel.loadCollection(@courseInstances, 'course-instances', {cache: false})
|
||||||
@students = new CocoCollection([], { url: "/db/user/-/students", model: User })
|
@students = new CocoCollection([], { url: "/db/user/-/students", model: User })
|
||||||
@supermodel.loadCollection(@students, 'students', {cache: false})
|
@supermodel.loadCollection(@students, 'students', {cache: false})
|
||||||
@teachers = new CocoCollection([], { url: "/db/user/-/teachers", model: User })
|
@teachers = new CocoCollection([], { url: "/db/user/-/teachers", model: User })
|
||||||
|
@ -30,39 +33,42 @@ module.exports = class SchoolCountsView extends RootView
|
||||||
|
|
||||||
teacherMap = {} # Used to make sure teachers and students only counted once
|
teacherMap = {} # Used to make sure teachers and students only counted once
|
||||||
studentMap = {} # Used to make sure teachers and students only counted once
|
studentMap = {} # Used to make sure teachers and students only counted once
|
||||||
|
studentNonHocMap = {} # Used to exclude HoC users
|
||||||
teacherStudentMap = {} # Used to link students to their teacher locations
|
teacherStudentMap = {} # Used to link students to their teacher locations
|
||||||
orphanedSchoolStudentMap = {} # Used to link student schoolName to teacher Nces data
|
|
||||||
countryStateDistrictSchoolCountsMap = {} # Data graph
|
countryStateDistrictSchoolCountsMap = {} # Data graph
|
||||||
|
|
||||||
console.log(new Date().toISOString(), 'Processing classrooms...')
|
console.log(new Date().toISOString(), "Processing #{@courseInstances.models.length} course instances...")
|
||||||
|
for courseInstance in @courseInstances.models
|
||||||
|
studentNonHocMap[courseInstance.get('ownerID')] = true
|
||||||
|
studentNonHocMap[studentID] = true for studentID in courseInstance.get('members') ? []
|
||||||
|
|
||||||
|
console.log(new Date().toISOString(), "Processing #{@classrooms.models.length} classrooms...")
|
||||||
for classroom in @classrooms.models
|
for classroom in @classrooms.models
|
||||||
teacherID = classroom.get('ownerID')
|
teacherID = classroom.get('ownerID')
|
||||||
teacherMap[teacherID] ?= {}
|
teacherMap[teacherID] ?= {}
|
||||||
teacherMap[teacherID] = true
|
teacherMap[teacherID] = true
|
||||||
teacherStudentMap[teacherID] ?= {}
|
teacherStudentMap[teacherID] ?= {}
|
||||||
for studentID in classroom.get('members')
|
for studentID in classroom.get('members')
|
||||||
|
continue unless studentNonHocMap[studentID]
|
||||||
studentMap[studentID] = true
|
studentMap[studentID] = true
|
||||||
teacherStudentMap[teacherID][studentID] = true
|
teacherStudentMap[teacherID][studentID] = true
|
||||||
|
|
||||||
console.log(new Date().toISOString(), 'Processing teachers...')
|
console.log(new Date().toISOString(), "Processing #{@teachers.models.length} teachers...")
|
||||||
for teacher in @teachers.models
|
for teacher in @teachers.models
|
||||||
teacherMap[teacher.id] ?= {}
|
teacherMap[teacher.id] ?= {}
|
||||||
delete studentMap[teacher.id]
|
delete studentMap[teacher.id]
|
||||||
|
|
||||||
console.log(new Date().toISOString(), 'Processing students...')
|
console.log(new Date().toISOString(), "Processing #{@students.models.length} students...")
|
||||||
for student in @students.models when not teacherMap[student.id]
|
for student in @students.models when not teacherMap[student.id]
|
||||||
|
continue unless studentNonHocMap[student.id]
|
||||||
schoolName = student.get('schoolName')
|
schoolName = student.get('schoolName')
|
||||||
studentMap[student.id] = true
|
studentMap[student.id] = true
|
||||||
orphanedSchoolStudentMap[schoolName] ?= {}
|
|
||||||
orphanedSchoolStudentMap[schoolName][student.id] = true
|
|
||||||
|
|
||||||
console.log(new Date().toISOString(), 'Processing trial requests...')
|
console.log(new Date().toISOString(), "Processing trial #{@trialRequests.models.length} requests...")
|
||||||
# TODO: this step is crazy slow
|
|
||||||
orphanSchoolsMatched = 0
|
|
||||||
orphanStudentsMatched = 0
|
|
||||||
for trialRequest in @trialRequests.models
|
for trialRequest in @trialRequests.models
|
||||||
teacherID = trialRequest.get('applicant')
|
teacherID = trialRequest.get('applicant')
|
||||||
unless teacherMap[teacherID]
|
unless teacherMap[teacherID]
|
||||||
|
# E.g. parents
|
||||||
# console.log("Skipping non-teacher #{teacherID} trial request #{trialRequest.id}")
|
# console.log("Skipping non-teacher #{teacherID} trial request #{trialRequest.id}")
|
||||||
continue
|
continue
|
||||||
props = trialRequest.get('properties')
|
props = trialRequest.get('properties')
|
||||||
|
@ -78,27 +84,44 @@ module.exports = class SchoolCountsView extends RootView
|
||||||
countryStateDistrictSchoolCountsMap[country][state][district][school].teachers[teacherID] = true
|
countryStateDistrictSchoolCountsMap[country][state][district][school].teachers[teacherID] = true
|
||||||
for studentID, val of teacherStudentMap[teacherID]
|
for studentID, val of teacherStudentMap[teacherID]
|
||||||
countryStateDistrictSchoolCountsMap[country][state][district][school].students[studentID] = true
|
countryStateDistrictSchoolCountsMap[country][state][district][school].students[studentID] = true
|
||||||
for orphanSchool, students of orphanedSchoolStudentMap
|
else if not _.isEmpty(props.country)
|
||||||
if school is orphanSchool or school.replace(/unified|elementary|high|district|#\d+|isd|unified district|school district/ig, '').trim() is orphanSchool.trim()
|
country = props.country?.trim()
|
||||||
orphanSchoolsMatched++
|
country = country[0].toUpperCase() + country.substring(1).toLowerCase()
|
||||||
for studentID, val of students
|
country = 'Taiwan' if /台灣/ig.test(country)
|
||||||
orphanStudentsMatched++
|
country = 'UK' if /^uk$|united kingdom|england/ig.test(country)
|
||||||
|
country = 'USA' if /^u\.s\.?(\.a)?\.?$|^us$|america|united states|usa/ig.test(country)
|
||||||
|
state = props.state ? 'unknown'
|
||||||
|
if country is 'USA'
|
||||||
|
stateName = utils.usStateCodes.sanitizeStateName(state)
|
||||||
|
state = utils.usStateCodes.getStateCodeByStateName(stateName) if stateName
|
||||||
|
state = utils.usStateCodes.sanitizeStateCode(state) ? state
|
||||||
|
district = 'unknown'
|
||||||
|
school = props.organiziation ? 'unknown'
|
||||||
|
countryStateDistrictSchoolCountsMap[country] ?= {}
|
||||||
|
countryStateDistrictSchoolCountsMap[country][state] ?= {}
|
||||||
|
countryStateDistrictSchoolCountsMap[country][state][district] ?= {}
|
||||||
|
countryStateDistrictSchoolCountsMap[country][state][district][school] ?= {students: {}, teachers: {}}
|
||||||
|
countryStateDistrictSchoolCountsMap[country][state][district][school].teachers[teacherID] = true
|
||||||
|
for studentID, val of teacherStudentMap[teacherID]
|
||||||
countryStateDistrictSchoolCountsMap[country][state][district][school].students[studentID] = true
|
countryStateDistrictSchoolCountsMap[country][state][district][school].students[studentID] = true
|
||||||
delete orphanedSchoolStudentMap[school]
|
|
||||||
console.log(new Date().toISOString(), "#{orphanSchoolsMatched} orphanSchoolsMatched #{orphanStudentsMatched} orphanStudentsMatched")
|
|
||||||
|
|
||||||
console.log(new Date().toISOString(), 'Building graph...')
|
console.log(new Date().toISOString(), 'Building country graphs...')
|
||||||
@totalSchools = 0
|
@countryGraphs = {}
|
||||||
@totalStudents = 0
|
@countryCounts = []
|
||||||
@totalTeachers = 0
|
totalStudents = 0
|
||||||
@totalStates = 0
|
totalTeachers = 0
|
||||||
@stateCounts = []
|
|
||||||
stateCountsMap = {}
|
|
||||||
@districtCounts = []
|
|
||||||
for country, stateDistrictSchoolCountsMap of countryStateDistrictSchoolCountsMap
|
for country, stateDistrictSchoolCountsMap of countryStateDistrictSchoolCountsMap
|
||||||
continue unless /usa/ig.test(country)
|
@countryGraphs[country] =
|
||||||
|
districtCounts: []
|
||||||
|
stateCounts: []
|
||||||
|
stateCountsMap: {}
|
||||||
|
totalSchools: 0
|
||||||
|
totalStates: 0
|
||||||
|
totalStudents: 0
|
||||||
|
totalTeachers: 0
|
||||||
for state, districtSchoolCountsMap of stateDistrictSchoolCountsMap
|
for state, districtSchoolCountsMap of stateDistrictSchoolCountsMap
|
||||||
@totalStates++
|
if utils.usStateCodes.sanitizeStateCode(state)? or ['GU', 'PR'].indexOf(state) >= 0
|
||||||
|
@countryGraphs[country].totalStates++
|
||||||
stateData = {state: state, districts: 0, schools: 0, students: 0, teachers: 0}
|
stateData = {state: state, districts: 0, schools: 0, students: 0, teachers: 0}
|
||||||
for district, schoolCountsMap of districtSchoolCountsMap
|
for district, schoolCountsMap of districtSchoolCountsMap
|
||||||
stateData.districts++
|
stateData.districts++
|
||||||
|
@ -106,39 +129,40 @@ module.exports = class SchoolCountsView extends RootView
|
||||||
for school, counts of schoolCountsMap
|
for school, counts of schoolCountsMap
|
||||||
studentCount = Object.keys(counts.students).length
|
studentCount = Object.keys(counts.students).length
|
||||||
teacherCount = Object.keys(counts.teachers).length
|
teacherCount = Object.keys(counts.teachers).length
|
||||||
@totalSchools++
|
@countryGraphs[country].totalSchools++
|
||||||
@totalStudents += studentCount
|
@countryGraphs[country].totalStudents += studentCount
|
||||||
@totalTeachers += teacherCount
|
@countryGraphs[country].totalTeachers += teacherCount
|
||||||
stateData.schools++
|
stateData.schools++
|
||||||
stateData.students += studentCount
|
stateData.students += studentCount
|
||||||
stateData.teachers += teacherCount
|
stateData.teachers += teacherCount
|
||||||
districtData.schools++
|
districtData.schools++
|
||||||
districtData.students += studentCount
|
districtData.students += studentCount
|
||||||
districtData.teachers += teacherCount
|
districtData.teachers += teacherCount
|
||||||
@districtCounts.push(districtData)
|
@countryGraphs[country].districtCounts.push(districtData)
|
||||||
@stateCounts.push(stateData)
|
@countryGraphs[country].stateCounts.push(stateData)
|
||||||
stateCountsMap[state] = stateData
|
@countryGraphs[country].stateCountsMap[state] = stateData
|
||||||
@untriagedStudents = Object.keys(studentMap).length - @totalStudents
|
@countryCounts.push
|
||||||
|
country: country
|
||||||
|
schools: @countryGraphs[country].totalSchools
|
||||||
|
students: @countryGraphs[country].totalStudents
|
||||||
|
teachers: @countryGraphs[country].totalTeachers
|
||||||
|
totalStudents += @countryGraphs[country].totalSchools
|
||||||
|
totalTeachers += @countryGraphs[country].totalTeachers
|
||||||
|
@untriagedStudents = Object.keys(studentMap).length - totalStudents
|
||||||
|
@untriagedTeachers = Object.keys(teacherMap).length - totalTeachers
|
||||||
|
|
||||||
@stateCounts.sort (a, b) ->
|
for country, graph of @countryGraphs
|
||||||
return -1 if a.students > b.students
|
graph.stateCounts.sort (a, b) ->
|
||||||
return 1 if a.students < b.students
|
b.students - a.students or b.teachers - a.teachers or b.schools - a.schools or b.districts - a.districts or b.state.localeCompare(a.state)
|
||||||
return -1 if a.teachers > b.teachers
|
graph.districtCounts.sort (a, b) ->
|
||||||
return 1 if a.teachers < b.teachers
|
|
||||||
return -1 if a.districts > b.districts
|
|
||||||
return 1 if a.districts < b.districts
|
|
||||||
b.state.localeCompare(a.state)
|
|
||||||
@districtCounts.sort (a, b) ->
|
|
||||||
if a.state isnt b.state
|
if a.state isnt b.state
|
||||||
return -1 if stateCountsMap[a.state].students > stateCountsMap[b.state].students
|
stateCountsA = graph.stateCountsMap[a.state]
|
||||||
return 1 if stateCountsMap[a.state].students < stateCountsMap[b.state].students
|
stateCountsB = graph.stateCountsMap[b.state]
|
||||||
return -1 if stateCountsMap[a.state].teachers > stateCountsMap[b.state].teachers
|
stateCountsB.students - stateCountsA.students or stateCountsB.teachers - stateCountsA.teachers or stateCountsB.schools - stateCountsA.schools or stateCountsB.districts - stateCountsA.districts or a.state.localeCompare(b.state)
|
||||||
return 1 if stateCountsMap[a.state].teachers < stateCountsMap[b.state].teachers
|
|
||||||
a.state.localeCompare(b.state)
|
|
||||||
else
|
else
|
||||||
return -1 if a.students > b.students
|
b.students - a.students or b.teachers - a.teachers or b.schools - a.schools or b.district.localeCompare(a.district)
|
||||||
return 1 if a.students < b.students
|
@countryCounts.sort (a, b) ->
|
||||||
return -1 if a.teachers > b.teachers
|
b.students - a.students or b.teachers - a.teachers or b.schools - a.schools or b.country.localeCompare(a.country)
|
||||||
return 1 if a.teachers < b.teachers
|
|
||||||
a.district.localeCompare(b.district)
|
console.log(new Date().toISOString(), 'Done...')
|
||||||
super()
|
super()
|
||||||
|
|
|
@ -11,6 +11,7 @@ Thang = require 'lib/world/thang'
|
||||||
LevelThangEditView = require './LevelThangEditView'
|
LevelThangEditView = require './LevelThangEditView'
|
||||||
ComponentsCollection = require 'collections/ComponentsCollection'
|
ComponentsCollection = require 'collections/ComponentsCollection'
|
||||||
require 'vendor/treema'
|
require 'vendor/treema'
|
||||||
|
GameUIState = require 'models/GameUIState'
|
||||||
|
|
||||||
# Moving the screen while dragging thangs constants
|
# Moving the screen while dragging thangs constants
|
||||||
MOVE_MARGIN = 0.15
|
MOVE_MARGIN = 0.15
|
||||||
|
@ -29,7 +30,6 @@ module.exports = class ThangsTabView extends CocoView
|
||||||
template: thangs_template
|
template: thangs_template
|
||||||
|
|
||||||
subscriptions:
|
subscriptions:
|
||||||
'surface:sprite-selected': 'onExtantThangSelected'
|
|
||||||
'surface:mouse-moved': 'onSurfaceMouseMoved'
|
'surface:mouse-moved': 'onSurfaceMouseMoved'
|
||||||
'surface:mouse-over': 'onSurfaceMouseOver'
|
'surface:mouse-over': 'onSurfaceMouseOver'
|
||||||
'surface:mouse-out': 'onSurfaceMouseOut'
|
'surface:mouse-out': 'onSurfaceMouseOut'
|
||||||
|
@ -39,7 +39,6 @@ module.exports = class ThangsTabView extends CocoView
|
||||||
'editor:view-switched': 'onViewSwitched'
|
'editor:view-switched': 'onViewSwitched'
|
||||||
'sprite:dragged': 'onSpriteDragged'
|
'sprite:dragged': 'onSpriteDragged'
|
||||||
'sprite:mouse-up': 'onSpriteMouseUp'
|
'sprite:mouse-up': 'onSpriteMouseUp'
|
||||||
'sprite:mouse-down': 'onSpriteMouseDown'
|
|
||||||
'sprite:double-clicked': 'onSpriteDoubleClicked'
|
'sprite:double-clicked': 'onSpriteDoubleClicked'
|
||||||
'surface:stage-mouse-down': 'onStageMouseDown'
|
'surface:stage-mouse-down': 'onStageMouseDown'
|
||||||
'surface:stage-mouse-up': 'onStageMouseUp'
|
'surface:stage-mouse-up': 'onStageMouseUp'
|
||||||
|
@ -78,12 +77,17 @@ module.exports = class ThangsTabView extends CocoView
|
||||||
constructor: (options) ->
|
constructor: (options) ->
|
||||||
super options
|
super options
|
||||||
@world = options.world
|
@world = options.world
|
||||||
|
@gameUIState = new GameUIState()
|
||||||
|
@listenTo(@gameUIState, 'sprite:mouse-down', @onSpriteMouseDown)
|
||||||
|
@listenTo(@gameUIState, 'surface:stage-mouse-move', @onStageMouseMove)
|
||||||
|
@listenTo(@gameUIState, 'change:selected', @onChangeSelected)
|
||||||
|
|
||||||
# should load depended-on Components, too
|
# should load depended-on Components, too
|
||||||
@thangTypes = @supermodel.loadCollection(new ThangTypeSearchCollection(), 'thangs').model
|
@thangTypes = @supermodel.loadCollection(new ThangTypeSearchCollection(), 'thangs').model
|
||||||
# just loading all Components for now: https://github.com/codecombat/codecombat/issues/405
|
# just loading all Components for now: https://github.com/codecombat/codecombat/issues/405
|
||||||
@componentCollection = @supermodel.loadCollection(new ComponentsCollection(), 'components').load()
|
@componentCollection = @supermodel.loadCollection(new ComponentsCollection(), 'components').load()
|
||||||
@level = options.level
|
@level = options.level
|
||||||
|
@onThangsChanged = _.debounce(@onThangsChanged)
|
||||||
|
|
||||||
$(document).bind 'contextmenu', @preventDefaultContextMenu
|
$(document).bind 'contextmenu', @preventDefaultContextMenu
|
||||||
|
|
||||||
|
@ -203,7 +207,7 @@ module.exports = class ThangsTabView extends CocoView
|
||||||
initSurface: ->
|
initSurface: ->
|
||||||
webGLCanvas = $('canvas#webgl-surface', @$el)
|
webGLCanvas = $('canvas#webgl-surface', @$el)
|
||||||
normalCanvas = $('canvas#normal-surface', @$el)
|
normalCanvas = $('canvas#normal-surface', @$el)
|
||||||
@surface = new Surface @world, normalCanvas, webGLCanvas, {
|
@surface = new Surface(@world, normalCanvas, webGLCanvas, {
|
||||||
paths: false
|
paths: false
|
||||||
coords: true
|
coords: true
|
||||||
grid: true
|
grid: true
|
||||||
|
@ -212,7 +216,9 @@ module.exports = class ThangsTabView extends CocoView
|
||||||
showInvisible: true
|
showInvisible: true
|
||||||
frameRate: 15
|
frameRate: 15
|
||||||
levelType: @level.get 'type', true
|
levelType: @level.get 'type', true
|
||||||
}
|
@gameUIState
|
||||||
|
handleEvents: false
|
||||||
|
})
|
||||||
@surface.playing = false
|
@surface.playing = false
|
||||||
@surface.setWorld @world
|
@surface.setWorld @world
|
||||||
@surface.lankBoss.suppressSelectionSounds = true
|
@surface.lankBoss.suppressSelectionSounds = true
|
||||||
|
@ -240,38 +246,79 @@ module.exports = class ThangsTabView extends CocoView
|
||||||
@selectAddThang null, true
|
@selectAddThang null, true
|
||||||
@surface?.lankBoss?.selectLank null, null
|
@surface?.lankBoss?.selectLank null, null
|
||||||
|
|
||||||
onSpriteMouseDown: (e) ->
|
|
||||||
@dragged = false
|
|
||||||
# Sprite clicks happen after stage clicks, but we need to know whether a sprite is being clicked.
|
|
||||||
# clearTimeout @backgroundAddClickTimeout
|
|
||||||
# if e.originalEvent.nativeEvent.button == 2
|
|
||||||
# @onSpriteContextMenu e
|
|
||||||
|
|
||||||
onStageMouseDown: (e) ->
|
onStageMouseDown: (e) ->
|
||||||
return unless @addThangLank?.thangType.get('kind') is 'Wall'
|
# initial values for a mouse click lifecycle
|
||||||
@surface.camera.dragDisabled = true
|
@dragged = 0
|
||||||
|
@willUnselectSprite = false
|
||||||
|
@gameUIState.set('canDragCamera', true)
|
||||||
|
|
||||||
|
if @addThangLank?.thangType.get('kind') is 'Wall'
|
||||||
@paintingWalls = true
|
@paintingWalls = true
|
||||||
|
@gameUIState.set('canDragCamera', false)
|
||||||
|
|
||||||
|
else if @addThangLank
|
||||||
|
# We clicked on the background when we had an add Thang selected, so add it
|
||||||
|
@addThang @addThangType, @addThangLank.thang.pos
|
||||||
|
|
||||||
|
else if e.onBackground
|
||||||
|
@gameUIState.set('selected', [])
|
||||||
|
|
||||||
|
onStageMouseMove: (e) ->
|
||||||
|
@dragged += 1
|
||||||
|
|
||||||
onStageMouseUp: (e) ->
|
onStageMouseUp: (e) ->
|
||||||
if @paintingWalls
|
@paintingWalls = false
|
||||||
# We need to stop painting walls, but we may also stop in onExtantThangSelected.
|
|
||||||
_.defer =>
|
|
||||||
@paintingWalls = @paintedWalls = @surface.camera.dragDisabled = false
|
|
||||||
else if @addThangLank
|
|
||||||
@surface.camera.lock()
|
|
||||||
# If we click on the background, we need to add @addThangLank, but not if onSpriteMouseUp will fire.
|
|
||||||
@backgroundAddClickTimeout = _.defer => @onExtantThangSelected {}
|
|
||||||
$('#contextmenu').hide()
|
$('#contextmenu').hide()
|
||||||
|
|
||||||
|
onSpriteMouseDown: (e) ->
|
||||||
|
nativeEvent = e.originalEvent.nativeEvent
|
||||||
|
# update selection
|
||||||
|
selected = []
|
||||||
|
if nativeEvent.metaKey or nativeEvent.ctrlKey
|
||||||
|
selected = _.clone(@gameUIState.get('selected'))
|
||||||
|
if e.thang?.isSelectable
|
||||||
|
alreadySelected = _.find(selected, (s) -> s.thang is e.thang)
|
||||||
|
if alreadySelected
|
||||||
|
# move to end (make it the last selected) and maybe unselect it
|
||||||
|
@willUnselectSprite = true
|
||||||
|
selected = _.without(selected, alreadySelected)
|
||||||
|
selected.push({ thang: e.thang, sprite: e.sprite, spellName: e.spellName })
|
||||||
|
if _.any(selected) and key.alt
|
||||||
|
# Clone selected thang instead of selecting it
|
||||||
|
lastSelected = _.last(selected)
|
||||||
|
@selectAddThangType lastSelected.thang.spriteName, lastSelected.thang
|
||||||
|
selected = []
|
||||||
|
@gameUIState.set('selected', selected)
|
||||||
|
if _.any(selected)
|
||||||
|
@gameUIState.set('canDragCamera', false)
|
||||||
|
|
||||||
onSpriteDragged: (e) ->
|
onSpriteDragged: (e) ->
|
||||||
return unless @selectedExtantThang and e.thang?.id is @selectedExtantThang?.id
|
selected = @gameUIState.get('selected')
|
||||||
@dragged = true
|
return unless _.any(selected) and @dragged > 10
|
||||||
@surface.camera.dragDisabled = true
|
@willUnselectSprite = false
|
||||||
{stageX, stageY} = e.originalEvent
|
{stageX, stageY} = e.originalEvent
|
||||||
|
|
||||||
|
# move the one under the mouse
|
||||||
|
lastSelected = _.last(selected)
|
||||||
cap = @surface.camera.screenToCanvas x: stageX, y: stageY
|
cap = @surface.camera.screenToCanvas x: stageX, y: stageY
|
||||||
wop = @surface.camera.canvasToWorld cap
|
wop = @surface.camera.canvasToWorld cap
|
||||||
wop.z = @selectedExtantThang.depth / 2
|
wop.z = lastSelected.thang.depth / 2
|
||||||
@adjustThangPos @selectedExtantLank, @selectedExtantThang, wop
|
posBefore = _.clone(lastSelected.thang.pos)
|
||||||
|
@adjustThangPos lastSelected.sprite, lastSelected.thang, wop
|
||||||
|
posAfter = lastSelected.thang.pos
|
||||||
|
|
||||||
|
# move any others selected, proportionally to how the 'main' sprite moved
|
||||||
|
xDiff = posAfter.x - posBefore.x
|
||||||
|
yDiff = posAfter.y - posBefore.y
|
||||||
|
if xDiff or yDiff
|
||||||
|
for singleSelected in selected.slice(0, selected.length - 1)
|
||||||
|
newPos = {
|
||||||
|
x: singleSelected.thang.pos.x + xDiff
|
||||||
|
y: singleSelected.thang.pos.y + yDiff
|
||||||
|
}
|
||||||
|
@adjustThangPos singleSelected.sprite, singleSelected.thang, newPos
|
||||||
|
|
||||||
|
# move the camera if we're on the edge of the screen
|
||||||
[w, h] = [@surface.camera.canvasWidth, @surface.camera.canvasHeight]
|
[w, h] = [@surface.camera.canvasWidth, @surface.camera.canvasHeight]
|
||||||
sidebarWidths = ((if @$el.find(id).hasClass('hide') then 0 else (@$el.find(id).outerWidth() / @surface.camera.canvasScaleFactorX)) for id in ['#all-thangs', '#add-thangs-view'])
|
sidebarWidths = ((if @$el.find(id).hasClass('hide') then 0 else (@$el.find(id).outerWidth() / @surface.camera.canvasScaleFactorX)) for id in ['#all-thangs', '#add-thangs-view'])
|
||||||
w -= sidebarWidth for sidebarWidth in sidebarWidths
|
w -= sidebarWidth for sidebarWidth in sidebarWidths
|
||||||
|
@ -279,24 +326,30 @@ module.exports = class ThangsTabView extends CocoView
|
||||||
@calculateMovement(cap.x / w, cap.y / h, w / h)
|
@calculateMovement(cap.x / w, cap.y / h, w / h)
|
||||||
|
|
||||||
onSpriteMouseUp: (e) ->
|
onSpriteMouseUp: (e) ->
|
||||||
clearTimeout @backgroundAddClickTimeout
|
selected = @gameUIState.get('selected')
|
||||||
@surface.camera.unlock()
|
if e.originalEvent.nativeEvent.button == 2 and _.any(selected)
|
||||||
if e.originalEvent.nativeEvent.button == 2 and @selectedExtantThang
|
|
||||||
@onSpriteContextMenu e
|
@onSpriteContextMenu e
|
||||||
clearInterval(@movementInterval) if @movementInterval?
|
clearInterval(@movementInterval) if @movementInterval?
|
||||||
@movementInterval = null
|
@movementInterval = null
|
||||||
@surface.camera.dragDisabled = false
|
|
||||||
return unless @selectedExtantThang and e.thang?.id is @selectedExtantThang?.id
|
|
||||||
pos = @selectedExtantThang.pos
|
|
||||||
|
|
||||||
thang = _.find(@level.get('thangs') ? [], {id: @selectedExtantThang.id})
|
return unless _.any(selected)
|
||||||
|
|
||||||
|
for singleSelected in selected
|
||||||
|
pos = singleSelected.thang.pos
|
||||||
|
|
||||||
|
thang = _.find(@level.get('thangs') ? [], {id: singleSelected.thang.id})
|
||||||
path = "#{@pathForThang(thang)}/components/original=#{LevelComponent.PhysicalID}"
|
path = "#{@pathForThang(thang)}/components/original=#{LevelComponent.PhysicalID}"
|
||||||
physical = @thangsTreema.get path
|
physical = @thangsTreema.get path
|
||||||
return if not physical or (physical.config.pos.x is pos.x and physical.config.pos.y is pos.y)
|
continue if not physical or (physical.config.pos.x is pos.x and physical.config.pos.y is pos.y)
|
||||||
@thangsTreema.set path + '/config/pos', x: pos.x, y: pos.y, z: pos.z
|
@thangsTreema.set path + '/config/pos', x: pos.x, y: pos.y, z: pos.z
|
||||||
|
|
||||||
|
if @willUnselectSprite
|
||||||
|
clickedSprite = _.find(selected, {sprite: e.sprite})
|
||||||
|
@gameUIState.set('selected', _.without(selected, clickedSprite))
|
||||||
|
|
||||||
onSpriteDoubleClicked: (e) ->
|
onSpriteDoubleClicked: (e) ->
|
||||||
return unless e.thang and not @dragged
|
return if @dragged > 10
|
||||||
|
return unless e.thang
|
||||||
@editThang thangID: e.thang.id
|
@editThang thangID: e.thang.id
|
||||||
|
|
||||||
onRandomTerrainGenerated: (e) ->
|
onRandomTerrainGenerated: (e) ->
|
||||||
|
@ -320,35 +373,21 @@ module.exports = class ThangsTabView extends CocoView
|
||||||
@onThangsChanged()
|
@onThangsChanged()
|
||||||
@selectAddThangType null
|
@selectAddThangType null
|
||||||
|
|
||||||
|
onChangeSelected: (gameUIState, selected) ->
|
||||||
|
previousSprite = gameUIState.previousAttributes()?.selected?.sprite
|
||||||
|
sprite = selected?.sprite
|
||||||
|
thang = selected?.thang
|
||||||
|
|
||||||
# TODO: figure out a good way to have all Surface clicks and Treema clicks just proxy in one direction, so we can maintain only one way of handling selection and deletion
|
previousSprite?.setNameLabel?(null) unless previousSprite is sprite
|
||||||
onExtantThangSelected: (e) ->
|
|
||||||
@selectedExtantLank?.setNameLabel? null unless @selectedExtantLank is e.sprite
|
if thang and not (@addThangLank and @addThangType.get('name') in overlappableThangTypeNames)
|
||||||
@selectedExtantThang = e.thang
|
|
||||||
@selectedExtantLank = e.sprite
|
|
||||||
paintedAWall = @paintedWalls
|
|
||||||
@paintingWalls = @paintedWalls = @surface.camera.dragDisabled = false
|
|
||||||
if paintedAWall
|
|
||||||
# Skip adding a wall now, because we already dragged to add one
|
|
||||||
null
|
|
||||||
else if e.thang and (key.alt or key.meta)
|
|
||||||
# We alt-clicked, so create a clone addThang
|
|
||||||
@selectAddThangType e.thang.spriteName, @selectedExtantThang
|
|
||||||
else if @justAdded()
|
|
||||||
# Skip double insert due to extra selection event
|
|
||||||
null
|
|
||||||
else if e.thang and not (@addThangLank and @addThangType.get('name') in overlappableThangTypeNames)
|
|
||||||
# We clicked on a Thang (or its Treema), so select the Thang
|
# We clicked on a Thang (or its Treema), so select the Thang
|
||||||
@selectAddThang null, true
|
@selectAddThang(null, true)
|
||||||
@selectedExtantThangClickTime = new Date()
|
@selectedExtantThangClickTime = new Date()
|
||||||
# Show the label above selected thang, notice that we may get here from thang-edit-view, so it will be selected but no label
|
# Show the label above selected thang, notice that we may get here from thang-edit-view, so it will be selected but no label
|
||||||
@selectedExtantLank.setNameLabel @selectedExtantLank.thangType.get('name') + ': ' + @selectedExtantThang.id
|
sprite.setNameLabel(sprite.thangType.get('name') + ': ' + thang.id)
|
||||||
@selectedExtantLank.updateLabels()
|
sprite.updateLabels()
|
||||||
@selectedExtantLank.updateMarks()
|
sprite.updateMarks()
|
||||||
else if @addThangLank
|
|
||||||
# We clicked on the background when we had an add Thang selected, so add it
|
|
||||||
@addThang @addThangType, @addThangLank.thang.pos
|
|
||||||
@lastAddTime = new Date()
|
|
||||||
|
|
||||||
justAdded: -> @lastAddTime and (new Date() - @lastAddTime) < 150
|
justAdded: -> @lastAddTime and (new Date() - @lastAddTime) < 150
|
||||||
|
|
||||||
|
@ -482,8 +521,11 @@ module.exports = class ThangsTabView extends CocoView
|
||||||
|
|
||||||
deleteSelectedExtantThang: (e) =>
|
deleteSelectedExtantThang: (e) =>
|
||||||
return if $(e.target).hasClass 'treema-node'
|
return if $(e.target).hasClass 'treema-node'
|
||||||
return unless @selectedExtantThang
|
selected = @gameUIState.get('selected')
|
||||||
thang = @getThangByID(@selectedExtantThang.id)
|
return unless _.any(selected)
|
||||||
|
|
||||||
|
for singleSelected in selected
|
||||||
|
thang = @getThangByID(singleSelected.thang.id)
|
||||||
@thangsTreema.delete(@pathForThang(thang))
|
@thangsTreema.delete(@pathForThang(thang))
|
||||||
@deleteEmptyTreema(thang)
|
@deleteEmptyTreema(thang)
|
||||||
Thang.resetThangIDs() # TODO: find some way to do this when we delete from treema, too
|
Thang.resetThangIDs() # TODO: find some way to do this when we delete from treema, too
|
||||||
|
@ -564,17 +606,26 @@ module.exports = class ThangsTabView extends CocoView
|
||||||
@selectAddThangType @addThangType, @cloneSourceThang if @addThangType # make another addThang sprite, since the World just refreshed
|
@selectAddThangType @addThangType, @cloneSourceThang if @addThangType # make another addThang sprite, since the World just refreshed
|
||||||
|
|
||||||
# update selection, since the thangs have been remade
|
# update selection, since the thangs have been remade
|
||||||
if @selectedExtantThang
|
selected = @gameUIState.get('selected')
|
||||||
@selectedExtantLank = @surface.lankBoss.lanks[@selectedExtantThang.id]
|
if _.any(selected)
|
||||||
@selectedExtantThang = @selectedExtantLank?.thang
|
for singleSelected in selected
|
||||||
|
sprite = @surface.lankBoss.lanks[singleSelected.thang.id]
|
||||||
|
if sprite
|
||||||
|
sprite.updateMarks()
|
||||||
|
singleSelected.sprite = sprite
|
||||||
|
singleSelected.thang = sprite.thang
|
||||||
Backbone.Mediator.publish 'editor:thangs-edited', thangs: @world.thangs
|
Backbone.Mediator.publish 'editor:thangs-edited', thangs: @world.thangs
|
||||||
|
|
||||||
onTreemaThangSelected: (e, selectedTreemas) =>
|
onTreemaThangSelected: (e, selectedTreemas) =>
|
||||||
selectedThangID = _.last(selectedTreemas)?.data.id
|
selectedThangTreemas = _.filter(selectedTreemas, (t) -> t instanceof ThangNode)
|
||||||
if selectedThangID isnt @selectedExtantThang?.id
|
thangIDs = (node.data.id for node in selectedThangTreemas)
|
||||||
@surface.lankBoss.selectThang selectedThangID, null, true
|
lanks = (@surface.lankBoss.lanks[thangID] for thangID in thangIDs when thangID)
|
||||||
|
selected = ({ thang: lank.thang, sprite: lank } for lank in lanks when lank)
|
||||||
|
@gameUIState.set('selected', selected)
|
||||||
|
|
||||||
onTreemaThangDoubleClicked: (e, treema) =>
|
onTreemaThangDoubleClicked: (e, treema) =>
|
||||||
|
nativeEvent = e.originalEvent.nativeEvent
|
||||||
|
return if nativeEvent and (nativeEvent.ctrlKey or nativeEvent.metaKey)
|
||||||
id = treema?.data?.id
|
id = treema?.data?.id
|
||||||
@editThang thangID: id if id
|
@editThang thangID: id if id
|
||||||
|
|
||||||
|
@ -655,7 +706,8 @@ module.exports = class ThangsTabView extends CocoView
|
||||||
|
|
||||||
onDuplicateClicked: (e) ->
|
onDuplicateClicked: (e) ->
|
||||||
$('#contextmenu').hide()
|
$('#contextmenu').hide()
|
||||||
@selectAddThangType @selectedExtantThang.spriteName, @selectedExtantThang
|
selected = _.last(@gameUIState.get('selected'))
|
||||||
|
@selectAddThangType(selected.thang.spriteName, selected.thang)
|
||||||
|
|
||||||
onClickRotationButton: (e) ->
|
onClickRotationButton: (e) ->
|
||||||
$('#contextmenu').hide()
|
$('#contextmenu').hide()
|
||||||
|
@ -667,7 +719,8 @@ module.exports = class ThangsTabView extends CocoView
|
||||||
@hush = true
|
@hush = true
|
||||||
thangData = @getThangByID thang.id
|
thangData = @getThangByID thang.id
|
||||||
thangData = $.extend true, {}, thangData
|
thangData = $.extend true, {}, thangData
|
||||||
unless component = _.find thangData.components, {original: componentOriginal}
|
component = _.find thangData.components, {original: componentOriginal}
|
||||||
|
unless component
|
||||||
component = original: componentOriginal, config: {}, majorVersion: 0
|
component = original: componentOriginal, config: {}, majorVersion: 0
|
||||||
thangData.components.push component
|
thangData.components.push component
|
||||||
modificationFunction component
|
modificationFunction component
|
||||||
|
@ -682,34 +735,44 @@ module.exports = class ThangsTabView extends CocoView
|
||||||
lank.setDebug true
|
lank.setDebug true
|
||||||
|
|
||||||
rotateSelectedThangTo: (radians) ->
|
rotateSelectedThangTo: (radians) ->
|
||||||
@modifySelectedThangComponentConfig @selectedExtantThang, LevelComponent.PhysicalID, (component) =>
|
for singleSelected in @gameUIState.get('selected')
|
||||||
|
selectedThang = singleSelected.thang
|
||||||
|
@modifySelectedThangComponentConfig selectedThang, LevelComponent.PhysicalID, (component) =>
|
||||||
component.config.rotation = radians
|
component.config.rotation = radians
|
||||||
@selectedExtantThang.rotation = component.config.rotation
|
selectedThang.rotation = component.config.rotation
|
||||||
|
|
||||||
rotateSelectedThangBy: (radians) ->
|
rotateSelectedThangBy: (radians) ->
|
||||||
@modifySelectedThangComponentConfig @selectedExtantThang, LevelComponent.PhysicalID, (component) =>
|
for singleSelected in @gameUIState.get('selected')
|
||||||
|
selectedThang = singleSelected.thang
|
||||||
|
@modifySelectedThangComponentConfig selectedThang, LevelComponent.PhysicalID, (component) =>
|
||||||
component.config.rotation = ((component.config.rotation ? 0) + radians) % (2 * Math.PI)
|
component.config.rotation = ((component.config.rotation ? 0) + radians) % (2 * Math.PI)
|
||||||
@selectedExtantThang.rotation = component.config.rotation
|
selectedThang.rotation = component.config.rotation
|
||||||
|
|
||||||
moveSelectedThangBy: (xDir, yDir) ->
|
moveSelectedThangBy: (xDir, yDir) ->
|
||||||
@modifySelectedThangComponentConfig @selectedExtantThang, LevelComponent.PhysicalID, (component) =>
|
for singleSelected in @gameUIState.get('selected')
|
||||||
|
selectedThang = singleSelected.thang
|
||||||
|
@modifySelectedThangComponentConfig selectedThang, LevelComponent.PhysicalID, (component) =>
|
||||||
component.config.pos.x += 0.5 * xDir
|
component.config.pos.x += 0.5 * xDir
|
||||||
component.config.pos.y += 0.5 * yDir
|
component.config.pos.y += 0.5 * yDir
|
||||||
@selectedExtantThang.pos.x = component.config.pos.x
|
selectedThang.pos.x = component.config.pos.x
|
||||||
@selectedExtantThang.pos.y = component.config.pos.y
|
selectedThang.pos.y = component.config.pos.y
|
||||||
|
|
||||||
resizeSelectedThangBy: (xDir, yDir) ->
|
resizeSelectedThangBy: (xDir, yDir) ->
|
||||||
@modifySelectedThangComponentConfig @selectedExtantThang, LevelComponent.PhysicalID, (component) =>
|
for singleSelected in @gameUIState.get('selected')
|
||||||
|
selectedThang = singleSelected.thang
|
||||||
|
@modifySelectedThangComponentConfig selectedThang, LevelComponent.PhysicalID, (component) =>
|
||||||
component.config.width = (component.config.width ? 4) + 0.5 * xDir
|
component.config.width = (component.config.width ? 4) + 0.5 * xDir
|
||||||
component.config.height = (component.config.height ? 4) + 0.5 * yDir
|
component.config.height = (component.config.height ? 4) + 0.5 * yDir
|
||||||
@selectedExtantThang.width = component.config.width
|
selectedThang.width = component.config.width
|
||||||
@selectedExtantThang.height = component.config.height
|
selectedThang.height = component.config.height
|
||||||
|
|
||||||
toggleSelectedThangCollision: ->
|
toggleSelectedThangCollision: ->
|
||||||
@modifySelectedThangComponentConfig @selectedExtantThang, LevelComponent.CollidesID, (component) =>
|
for singleSelected in @gameUIState.get('selected')
|
||||||
|
selectedThang = singleSelected.thang
|
||||||
|
@modifySelectedThangComponentConfig selectedThang, LevelComponent.CollidesID, (component) =>
|
||||||
component.config ?= {}
|
component.config ?= {}
|
||||||
component.config.collisionCategory = if component.config.collisionCategory is 'none' then 'ground' else 'none'
|
component.config.collisionCategory = if component.config.collisionCategory is 'none' then 'ground' else 'none'
|
||||||
@selectedExtantThang.collisionCategory = component.config.collisionCategory
|
selectedThang.collisionCategory = component.config.collisionCategory
|
||||||
|
|
||||||
toggleThangsContainer: (e) ->
|
toggleThangsContainer: (e) ->
|
||||||
$('#all-thangs').toggleClass('hide')
|
$('#all-thangs').toggleClass('hide')
|
||||||
|
|
|
@ -81,10 +81,10 @@ module.exports = class VerifierTest extends CocoClass
|
||||||
@updateCallback? state: 'running'
|
@updateCallback? state: 'running'
|
||||||
|
|
||||||
processSingleGameResults: (e) ->
|
processSingleGameResults: (e) ->
|
||||||
console.log(e)
|
|
||||||
@goals = e.goalStates
|
@goals = e.goalStates
|
||||||
@frames = e.totalFrames
|
@frames = e.totalFrames
|
||||||
@lastFrameHash = e.lastFrameHash
|
@lastFrameHash = e.lastFrameHash
|
||||||
|
@simulationFrameRate = e.simulationFrameRate
|
||||||
@state = 'complete'
|
@state = 'complete'
|
||||||
@updateCallback? state: @state
|
@updateCallback? state: @state
|
||||||
@scheduleCleanup()
|
@scheduleCleanup()
|
||||||
|
@ -92,6 +92,7 @@ module.exports = class VerifierTest extends CocoClass
|
||||||
isSuccessful: () ->
|
isSuccessful: () ->
|
||||||
return false unless @solution?
|
return false unless @solution?
|
||||||
return false unless @frames == @solution.frameCount or @options.dontCareAboutFrames
|
return false unless @frames == @solution.frameCount or @options.dontCareAboutFrames
|
||||||
|
return false if @simulationFrameRate < 30
|
||||||
if @goals and @solution.goals
|
if @goals and @solution.goals
|
||||||
for k of @goals
|
for k of @goals
|
||||||
continue if not @solution.goals[k]
|
continue if not @solution.goals[k]
|
||||||
|
|
|
@ -72,6 +72,7 @@ module.exports =
|
||||||
|
|
||||||
|
|
||||||
fetchNextLevel: wrap (req, res) ->
|
fetchNextLevel: wrap (req, res) ->
|
||||||
|
unless req.user? then return res.status(200).send({})
|
||||||
levelOriginal = req.params.levelOriginal
|
levelOriginal = req.params.levelOriginal
|
||||||
unless database.isID(levelOriginal) then throw new errors.UnprocessableEntity('Invalid level original ObjectId')
|
unless database.isID(levelOriginal) then throw new errors.UnprocessableEntity('Invalid level original ObjectId')
|
||||||
sessionID = req.params.sessionID
|
sessionID = req.params.sessionID
|
||||||
|
@ -162,3 +163,9 @@ module.exports =
|
||||||
students: (user.toObject({req: req}) for user in users)
|
students: (user.toObject({req: req}) for user in users)
|
||||||
prepaids: (prepaid.toObject({req: req}) for prepaid in prepaids)
|
prepaids: (prepaid.toObject({req: req}) for prepaid in prepaids)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
fetchNonHoc: wrap (req, res) ->
|
||||||
|
throw new errors.Unauthorized('You must be an administrator.') unless req.user?.isAdmin()
|
||||||
|
query = {$and: [{name: {$ne: 'Single Player'}}, {hourOfCode: {$ne: true}}]}
|
||||||
|
courseInstances = yield CourseInstance.find(query, { members: 1, ownerID: 1}).lean()
|
||||||
|
res.status(200).send(courseInstances)
|
||||||
|
|
|
@ -80,6 +80,7 @@ module.exports.setup = (app) ->
|
||||||
app.get('/db/course/:handle', mw.rest.getByHandle(Course))
|
app.get('/db/course/:handle', mw.rest.getByHandle(Course))
|
||||||
app.get('/db/course/:handle/levels/:levelOriginal/next', mw.courses.fetchNextLevel)
|
app.get('/db/course/:handle/levels/:levelOriginal/next', mw.courses.fetchNextLevel)
|
||||||
|
|
||||||
|
app.get('/db/course_instance/-/non-hoc', mw.auth.checkHasPermission(['admin']), mw.courseInstances.fetchNonHoc)
|
||||||
app.post('/db/course_instance/-/recent', mw.auth.checkHasPermission(['admin']), mw.courseInstances.fetchRecent)
|
app.post('/db/course_instance/-/recent', mw.auth.checkHasPermission(['admin']), mw.courseInstances.fetchRecent)
|
||||||
app.get('/db/course_instance/:handle/levels/:levelOriginal/sessions/:sessionID/next', mw.courseInstances.fetchNextLevel)
|
app.get('/db/course_instance/:handle/levels/:levelOriginal/sessions/:sessionID/next', mw.courseInstances.fetchNextLevel)
|
||||||
app.post('/db/course_instance/:handle/members', mw.auth.checkLoggedIn(), mw.courseInstances.addMembers)
|
app.post('/db/course_instance/:handle/members', mw.auth.checkLoggedIn(), mw.courseInstances.addMembers)
|
||||||
|
|
|
@ -96,24 +96,6 @@ describe 'Camera (Surface point of view)', ->
|
||||||
checkConversionsFromWorldPos wop, cam
|
checkConversionsFromWorldPos wop, cam
|
||||||
checkCameraPos cam, wop
|
checkCameraPos cam, wop
|
||||||
|
|
||||||
it 'works at 90 degrees', ->
|
|
||||||
cam = new Camera {attr: (attr) -> 100}, Math.PI / 2
|
|
||||||
expect(cam.x2y).toBeCloseTo 1
|
|
||||||
expect(cam.x2z).toBeGreaterThan 9001
|
|
||||||
expect(cam.z2y).toBeCloseTo 0
|
|
||||||
|
|
||||||
it 'works at 0 degrees', ->
|
|
||||||
cam = new Camera {attr: (attr) -> 100}, 0
|
|
||||||
expect(cam.x2y).toBeGreaterThan 9001
|
|
||||||
expect(cam.x2z).toBeCloseTo 1
|
|
||||||
expect(cam.z2y).toBeGreaterThan 9001
|
|
||||||
|
|
||||||
it 'works at 45 degrees', ->
|
|
||||||
cam = new Camera {attr: (attr) -> 100}, Math.PI / 4
|
|
||||||
expect(cam.x2y).toBeCloseTo Math.sqrt(2)
|
|
||||||
expect(cam.x2z).toBeCloseTo Math.sqrt(2)
|
|
||||||
expect(cam.z2y).toBeCloseTo 1
|
|
||||||
|
|
||||||
it 'works at default angle of asin(0.75) ~= 48.9 degrees', ->
|
it 'works at default angle of asin(0.75) ~= 48.9 degrees', ->
|
||||||
cam = new Camera {attr: (attr) -> 100}, null
|
cam = new Camera {attr: (attr) -> 100}, null
|
||||||
angle = Math.asin(3 / 4)
|
angle = Math.asin(3 / 4)
|
|
@ -2,6 +2,7 @@ LankBoss = require 'lib/surface/LankBoss'
|
||||||
Camera = require 'lib/surface/Camera'
|
Camera = require 'lib/surface/Camera'
|
||||||
World = require 'lib/world/world'
|
World = require 'lib/world/world'
|
||||||
ThangType = require 'models/ThangType'
|
ThangType = require 'models/ThangType'
|
||||||
|
GameUIState = require 'models/GameUIState'
|
||||||
|
|
||||||
treeData = require 'test/app/fixtures/tree1.thang.type'
|
treeData = require 'test/app/fixtures/tree1.thang.type'
|
||||||
munchkinData = require 'test/app/fixtures/ogre-munchkin-m.thang.type'
|
munchkinData = require 'test/app/fixtures/ogre-munchkin-m.thang.type'
|
||||||
|
@ -53,6 +54,7 @@ describe 'LankBoss', ->
|
||||||
surfaceTextLayer: new createjs.Container()
|
surfaceTextLayer: new createjs.Container()
|
||||||
world: world
|
world: world
|
||||||
thangTypes: thangTypes
|
thangTypes: thangTypes
|
||||||
|
gameUIState: new GameUIState()
|
||||||
}
|
}
|
||||||
|
|
||||||
window.lankBoss = lankBoss = new LankBoss(options)
|
window.lankBoss = lankBoss = new LankBoss(options)
|
||||||
|
|
Loading…
Reference in a new issue