mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-12-12 08:41:46 -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.ended)
|
||||
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 diff = t1 - self.t0;
|
||||
if(self.world.headless)
|
||||
return console.log('Headless simulation completed in ' + diff + 'ms.');
|
||||
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)
|
||||
return console.log('Headless simulation completed in ' + diff + 'ms, ' + simulationFrameRate.toFixed(1) + ' FPS.');
|
||||
}
|
||||
|
||||
var worldEnded = self.world.ended;
|
||||
var serialized;
|
||||
|
@ -469,7 +470,7 @@ self.onWorldLoaded = function onWorldLoaded() {
|
|||
|
||||
if(worldEnded) {
|
||||
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();
|
||||
var goalStates = self.goalManager.getGoalStates();
|
||||
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) {
|
||||
|
|
|
@ -370,3 +370,103 @@ module.exports.findNextLevel = (levels, currentIndex, needsPractice) ->
|
|||
|
||||
module.exports.needsPractice = (playtime=0, threshold=2) ->
|
||||
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
|
||||
when 'end-load-frames'
|
||||
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'
|
||||
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
|
||||
when 'non-user-code-problem'
|
||||
|
@ -125,11 +124,12 @@ module.exports = class Angel extends CocoClass
|
|||
else
|
||||
@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
|
||||
event = goalStates: goalStates, preload: preload, overallStatus: overallStatus
|
||||
event = goalStates: goalStates, preload: preload ? false, overallStatus: overallStatus
|
||||
event.totalFrames = totalFrames if totalFrames?
|
||||
event.lastFrameHash = lastFrameHash if lastFrameHash?
|
||||
event.simulationFrameRate = simulationFrameRate if simulationFrameRate?
|
||||
@publishGodEvent 'goals-calculated', event
|
||||
@finishWork() if @shared.headless
|
||||
|
||||
|
@ -306,7 +306,8 @@ module.exports = class Angel extends CocoClass
|
|||
work.world.goalManager.worldGenerationEnded() if work.world.ended
|
||||
|
||||
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
|
||||
|
||||
serialized = world.serialize()
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
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.
|
||||
|
||||
|
@ -44,12 +45,17 @@ module.exports = class Camera extends CocoClass
|
|||
'camera:zoom-out': 'onZoomOut'
|
||||
'camera:zoom-to': 'onZoomTo'
|
||||
'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()
|
||||
@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)
|
||||
@canvasHeight = parseInt(@canvas.attr('height'), 10)
|
||||
@offset = {x: 0, y: 0}
|
||||
|
@ -155,8 +161,27 @@ module.exports = class Camera extends CocoClass
|
|||
|
||||
onZoomIn: (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) ->
|
||||
return unless e.canvas is @canvas
|
||||
ratio = 1 + 0.05 * Math.sqrt(Math.abs(e.deltaY))
|
||||
ratio = 1 / ratio if e.deltaY > 0
|
||||
newZoom = @zoom * ratio
|
||||
|
@ -174,22 +199,6 @@ module.exports = class Camera extends CocoClass
|
|||
target = @target
|
||||
@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: ->
|
||||
@setBounds(@firstBounds, false)
|
||||
|
||||
|
|
|
@ -58,11 +58,13 @@ module.exports = Lank = class Lank extends CocoClass
|
|||
'surface:ticked': 'onSurfaceTicked'
|
||||
'sprite:move': 'onMove'
|
||||
|
||||
constructor: (@thangType, options) ->
|
||||
constructor: (@thangType, options={}) ->
|
||||
super()
|
||||
spriteName = @thangType.get('name')
|
||||
@isMissile = /(Missile|Arrow|Spear|Bolt)/.test(spriteName) and not /(Tower|Charge)/.test(spriteName)
|
||||
@options = _.extend($.extend(true, {}, @options), options)
|
||||
@gameUIState = @options.gameUIState
|
||||
@handleEvents = @options.handleEvents
|
||||
@setThang @options.thang
|
||||
if @thang?
|
||||
options = @thang?.getLankOptions?()
|
||||
|
@ -496,6 +498,7 @@ module.exports = Lank = class Lank extends CocoClass
|
|||
newEvent = sprite: @, thang: @thang, originalEvent: e, canvas: p.canvas
|
||||
@trigger ourEventName, newEvent
|
||||
Backbone.Mediator.publish ourEventName, newEvent
|
||||
@gameUIState.trigger(ourEventName, newEvent)
|
||||
|
||||
addHealthBar: ->
|
||||
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]
|
||||
|
||||
removeMark: (name) ->
|
||||
@marks[name].destroy()
|
||||
delete @marks[name]
|
||||
|
||||
notifySpeechUpdated: (e) ->
|
||||
e = _.clone(e)
|
||||
e.sprite = @
|
||||
|
|
|
@ -25,10 +25,11 @@ module.exports = class LankBoss extends CocoClass
|
|||
'surface:flag-appeared': 'onFlagAppeared'
|
||||
'surface:remove-selected-flag': 'onRemoveSelectedFlag'
|
||||
|
||||
constructor: (@options) ->
|
||||
constructor: (@options={}) ->
|
||||
super()
|
||||
@handleEvents = @options.handleEvents
|
||||
@gameUIState = @options.gameUIState
|
||||
@dragged = 0
|
||||
@options ?= {}
|
||||
@camera = @options.camera
|
||||
@webGLStage = @options.webGLStage
|
||||
@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
|
||||
@createLayers()
|
||||
@pendingFlags = []
|
||||
if not @handleEvents
|
||||
@listenTo @gameUIState, 'change:selected', @onChangeSelected
|
||||
|
||||
destroy: ->
|
||||
@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'
|
||||
|
||||
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) ->
|
||||
return if e.debug is @debug
|
||||
|
@ -256,6 +268,7 @@ module.exports = class LankBoss extends CocoClass
|
|||
@dragged += 1
|
||||
|
||||
onLankMouseUp: (e) ->
|
||||
return unless @handleEvents
|
||||
return if key.shift #and @options.choosing
|
||||
return @dragged = 0 if @dragged > 3
|
||||
@dragged = 0
|
||||
|
@ -264,9 +277,27 @@ module.exports = class LankBoss extends CocoClass
|
|||
@selectLank e, lank
|
||||
|
||||
onStageMouseDown: (e) ->
|
||||
return unless @handleEvents
|
||||
return if key.shift #and @options.choosing
|
||||
@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) ->
|
||||
return @willSelectThang = [thangID, spellName] unless @lanks[thangID]
|
||||
@selectLank null, @lanks[thangID], spellName, treemaThangSelected
|
||||
|
@ -275,8 +306,9 @@ 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
|
||||
worldPos = lank?.thang?.pos
|
||||
worldPos ?= @camera.screenToWorld {x: e.originalEvent.rawX, y: e.originalEvent.rawY} if e?.originalEvent
|
||||
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)
|
||||
if @handleEvents
|
||||
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)
|
||||
lank = null if @options.choosing # Don't select lanks while choosing
|
||||
if lank isnt @selectedLank
|
||||
@selectedLank?.selected = false
|
||||
|
|
|
@ -18,6 +18,7 @@ LankBoss = require './LankBoss'
|
|||
PointChooser = require './PointChooser'
|
||||
RegionChooser = require './RegionChooser'
|
||||
MusicPlayer = require './MusicPlayer'
|
||||
GameUIState = require 'models/GameUIState'
|
||||
|
||||
resizeDelay = 500 # At least as much as $level-resize-transition-time.
|
||||
|
||||
|
@ -87,6 +88,10 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
@normalLayers = []
|
||||
@options = _.clone(@defaults)
|
||||
@options = _.extend(@options, givenOptions) if givenOptions
|
||||
@handleEvents = @options.handleEvents ? true
|
||||
@gameUIState = @options.gameUIState or new GameUIState({
|
||||
canDragCamera: true
|
||||
})
|
||||
@initEasel()
|
||||
@initAudio()
|
||||
@onResize = _.debounce @onResize, resizeDelay
|
||||
|
@ -98,7 +103,7 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
@normalStage = new createjs.Stage(@normalCanvas[0])
|
||||
@webGLStage = new createjs.SpriteStage(@webGLCanvas[0])
|
||||
@normalStage.nextStage = @webGLStage
|
||||
@camera = new Camera @webGLCanvas
|
||||
@camera = new Camera(@webGLCanvas, { @gameUIState, @handleEvents })
|
||||
AudioPlayer.camera = @camera unless @options.choosing
|
||||
|
||||
@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
|
||||
@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
|
||||
@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.
|
||||
|
@ -121,7 +138,7 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
@webGLStage.enableMouseOver(10)
|
||||
@webGLStage.addEventListener 'stagemousemove', @onMouseMove
|
||||
@webGLStage.addEventListener 'stagemousedown', @onMouseDown
|
||||
@webGLCanvas[0].addEventListener 'mouseup', @onMouseUp
|
||||
@webGLStage.addEventListener 'stagemouseup', @onMouseUp
|
||||
@webGLCanvas.on 'mousewheel', @onMouseWheel
|
||||
@hookUpChooseControls() if @options.choosing # TODO: figure this stuff out
|
||||
createjs.Ticker.timingMode = createjs.Ticker.RAF_SYNCHED
|
||||
|
@ -222,8 +239,9 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
|
||||
updateState: (frameChanged) ->
|
||||
# world state must have been restored in @restoreWorldState
|
||||
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
|
||||
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
|
||||
@camera.zoomTo @heroLank.sprite, @camera.zoom, 750
|
||||
@lankBoss.update frameChanged
|
||||
@camera.updateZoom() # Make sure to do this right after the LankBoss updates, not before, so it can properly target sprite positions.
|
||||
@dimmer?.setSprites @lankBoss.lanks
|
||||
|
@ -371,7 +389,8 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
target = null
|
||||
@camera.setBounds e.bounds if e.bounds
|
||||
# @cameraBorder.updateBounds @camera.bounds
|
||||
@camera.zoomTo target, e.zoom, e.duration # TODO: SurfaceScriptModule perhaps shouldn't assign e.zoom if not set
|
||||
if @handleEvents
|
||||
@camera.zoomTo target, e.zoom, e.duration # TODO: SurfaceScriptModule perhaps shouldn't assign e.zoom if not set
|
||||
|
||||
onZoomUpdated: (e) ->
|
||||
if @ended
|
||||
|
@ -476,6 +495,7 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
@mouseScreenPos = {x: e.stageX, y: e.stageY}
|
||||
return if @disabled
|
||||
Backbone.Mediator.publish 'surface:mouse-moved', x: e.stageX, y: e.stageY
|
||||
@gameUIState.trigger('surface:stage-mouse-move', { originalEvent: e })
|
||||
|
||||
onMouseDown: (e) =>
|
||||
return if @disabled
|
||||
|
@ -484,16 +504,19 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
onBackground = not @webGLStage._getObjectsUnderPoint(e.stageX, e.stageY, null, true)
|
||||
|
||||
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 'tome:focus-editor', {}
|
||||
@gameUIState.trigger('surface:stage-mouse-down', event)
|
||||
@mouseIsDown = true
|
||||
|
||||
onMouseUp: (e) =>
|
||||
return if @disabled
|
||||
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', {}
|
||||
@gameUIState.trigger('surface:stage-mouse-up', event)
|
||||
@mouseIsDown = false
|
||||
|
||||
onMouseWheel: (e) =>
|
||||
|
@ -506,6 +529,7 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
canvas: @webGLCanvas
|
||||
event.screenPos = @mouseScreenPos if @mouseScreenPos
|
||||
Backbone.Mediator.publish 'surface:mouse-scrolled', event unless @disabled
|
||||
@gameUIState.trigger('surface:mouse-scrolled', event)
|
||||
|
||||
|
||||
#- Canvas callbacks
|
||||
|
@ -585,8 +609,9 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
@onResize()
|
||||
_.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'
|
||||
if @previousCameraZoom
|
||||
@camera.zoomTo @camera.newTarget or @camera.target, @previousCameraZoom, 3000
|
||||
if @handleEvents
|
||||
if @previousCameraZoom
|
||||
@camera.zoomTo @camera.newTarget or @camera.target, @previousCameraZoom, 3000
|
||||
|
||||
onFlagColorSelected: (e) ->
|
||||
@normalCanvas.add(@webGLCanvas).toggleClass 'flag-color-selected', Boolean(e.color)
|
||||
|
|
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_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_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."
|
||||
campaign: "Verhaallijn"
|
||||
for_beginners: "Voor Beginners"
|
||||
|
@ -16,7 +16,7 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
|
|||
|
||||
new_home:
|
||||
slogan: "Het meest uitdagende spel om mee te leren programmeren."
|
||||
classroom_edition: "Klas versie:"
|
||||
classroom_edition: "Klasversie:"
|
||||
learn_to_code: "Leer programmeren:"
|
||||
teacher: "Leraar"
|
||||
student: "Leerling"
|
||||
|
@ -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 "
|
||||
motivating: "motiveert"
|
||||
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"
|
||||
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."
|
||||
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."
|
||||
|
@ -1288,11 +1288,11 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
|
|||
avg_student_exp_varied: "Verschilt enorm per leerling"
|
||||
student_age_range_label: "Leeftijdscategorie leerlingen"
|
||||
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"
|
||||
create_class: "Maak klas aan"
|
||||
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_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"
|
||||
current_classes: "Huidige 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"
|
||||
archive_class: "archiveer klas"
|
||||
unarchive_class: "dearchiveer klas"
|
||||
|
@ -1372,9 +1372,9 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
|
|||
purchased: "Aanschaffing Voltooid!"
|
||||
purchase_now: "Nu Aanschaffen"
|
||||
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_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_3: "Zodra een leerling is ingeschreven zal deze toegang hebben tot alle inhoud van de cursus."
|
||||
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: "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 heeft hij/zij toegang tot alle inhoud."
|
||||
bulk_pricing_blurb: "Aanschaffen voor meer dan 25 leerlingen? Neem contact met ons op."
|
||||
total_unenrolled: "Totaal aantal niet ingeschreven"
|
||||
export_student_progress: "Exporteer Voortgang Leerlingen (CSV bestand)"
|
||||
|
|
|
@ -38,7 +38,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
|
|||
accessible_to: "Доступно для"
|
||||
everyone: "каждого"
|
||||
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."
|
||||
# why_games: "Why is learning through games important?"
|
||||
# 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]}
|
||||
totalFrames: {type: ['integer', 'undefined']}
|
||||
lastFrameHash: {type: ['number', 'undefined']}
|
||||
simulationFrameRate: {type: ['number', 'undefined']}
|
||||
|
||||
'god:world-load-progress-changed': c.object {required: ['progress', 'god']},
|
||||
god: {type: 'object'}
|
||||
|
|
|
@ -387,7 +387,7 @@ block content
|
|||
b CodeCombat Inc.
|
||||
p 301 Howard Street
|
||||
p Suite No. 830
|
||||
p San Francisco, California 94015
|
||||
p San Francisco, California 94105
|
||||
a(href="mailto:team@codecombat.com") team@codecombat.com
|
||||
.col-sm-4
|
||||
p
|
||||
|
|
|
@ -6,16 +6,30 @@ block content
|
|||
|
||||
if !me.isAdmin()
|
||||
div You must be logged in as an admin to view this page.
|
||||
|
||||
else if !view.countryGraphs || !view.countryGraphs['USA']
|
||||
h3 Loading...
|
||||
else
|
||||
p CodeCombat is now in #{view.totalSchools} schools with #{view.totalStudents} students [and #{view.totalTeachers} teachers] [in #{view.totalStates} states]
|
||||
p Students not attached to NCES data: #{view.untriagedStudents}
|
||||
p
|
||||
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 Student: member of a classroom or has schoolName set
|
||||
.small States, Districts, Schools are from NCES
|
||||
.small Student: member of a classroom or has schoolName set, not in HoC course instance
|
||||
.small +3 USA states are GU, PR, DC
|
||||
|
||||
h2 State Counts
|
||||
if view.stateCounts
|
||||
p
|
||||
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
|
||||
tr
|
||||
th State
|
||||
|
@ -23,7 +37,7 @@ block content
|
|||
th Schools
|
||||
th Teachers
|
||||
th Students
|
||||
each stateCount in view.stateCounts
|
||||
each stateCount in view.countryGraphs['USA'].stateCounts
|
||||
tr
|
||||
td= stateCount.state
|
||||
td= stateCount.districts
|
||||
|
@ -31,8 +45,9 @@ block content
|
|||
td= stateCount.teachers
|
||||
td= stateCount.students
|
||||
|
||||
h2 District Counts by State
|
||||
if view.districtCounts
|
||||
a(name="usaDistrictsByState")
|
||||
h2 USA Districts by State
|
||||
if view.countryGraphs['USA'].districtCounts
|
||||
table.table.table-striped.table-condensed
|
||||
tr
|
||||
th State
|
||||
|
@ -40,10 +55,26 @@ block content
|
|||
th Schools
|
||||
th Teachers
|
||||
th Students
|
||||
each districtCount in view.districtCounts
|
||||
each districtCount in view.countryGraphs['USA'].districtCounts
|
||||
tr
|
||||
td= districtCount.state
|
||||
td= districtCount.district
|
||||
td= districtCount.schools
|
||||
td= districtCount.teachers
|
||||
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
|
||||
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
|
||||
h1 Loading Level...
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
RootView = require 'views/core/RootView'
|
||||
CocoCollection = require 'collections/CocoCollection'
|
||||
Classroom = require 'models/Classroom'
|
||||
CourseInstance = require 'models/CourseInstance'
|
||||
TrialRequest = require 'models/TrialRequest'
|
||||
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
|
||||
|
||||
module.exports = class SchoolCountsView extends RootView
|
||||
|
@ -15,6 +16,8 @@ module.exports = class SchoolCountsView extends RootView
|
|||
return super() unless me.isAdmin()
|
||||
@classrooms = new CocoCollection([], { url: "/db/classroom/-/users", model: Classroom })
|
||||
@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 })
|
||||
@supermodel.loadCollection(@students, 'students', {cache: false})
|
||||
@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
|
||||
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
|
||||
orphanedSchoolStudentMap = {} # Used to link student schoolName to teacher Nces data
|
||||
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
|
||||
teacherID = classroom.get('ownerID')
|
||||
teacherMap[teacherID] ?= {}
|
||||
teacherMap[teacherID] = true
|
||||
teacherStudentMap[teacherID] ?= {}
|
||||
for studentID in classroom.get('members')
|
||||
continue unless studentNonHocMap[studentID]
|
||||
studentMap[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
|
||||
teacherMap[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]
|
||||
continue unless studentNonHocMap[student.id]
|
||||
schoolName = student.get('schoolName')
|
||||
studentMap[student.id] = true
|
||||
orphanedSchoolStudentMap[schoolName] ?= {}
|
||||
orphanedSchoolStudentMap[schoolName][student.id] = true
|
||||
|
||||
console.log(new Date().toISOString(), 'Processing trial requests...')
|
||||
# TODO: this step is crazy slow
|
||||
orphanSchoolsMatched = 0
|
||||
orphanStudentsMatched = 0
|
||||
console.log(new Date().toISOString(), "Processing trial #{@trialRequests.models.length} requests...")
|
||||
for trialRequest in @trialRequests.models
|
||||
teacherID = trialRequest.get('applicant')
|
||||
unless teacherMap[teacherID]
|
||||
# E.g. parents
|
||||
# console.log("Skipping non-teacher #{teacherID} trial request #{trialRequest.id}")
|
||||
continue
|
||||
props = trialRequest.get('properties')
|
||||
|
@ -78,27 +84,44 @@ module.exports = class SchoolCountsView extends RootView
|
|||
countryStateDistrictSchoolCountsMap[country][state][district][school].teachers[teacherID] = true
|
||||
for studentID, val of teacherStudentMap[teacherID]
|
||||
countryStateDistrictSchoolCountsMap[country][state][district][school].students[studentID] = true
|
||||
for orphanSchool, students of orphanedSchoolStudentMap
|
||||
if school is orphanSchool or school.replace(/unified|elementary|high|district|#\d+|isd|unified district|school district/ig, '').trim() is orphanSchool.trim()
|
||||
orphanSchoolsMatched++
|
||||
for studentID, val of students
|
||||
orphanStudentsMatched++
|
||||
countryStateDistrictSchoolCountsMap[country][state][district][school].students[studentID] = true
|
||||
delete orphanedSchoolStudentMap[school]
|
||||
console.log(new Date().toISOString(), "#{orphanSchoolsMatched} orphanSchoolsMatched #{orphanStudentsMatched} orphanStudentsMatched")
|
||||
else if not _.isEmpty(props.country)
|
||||
country = props.country?.trim()
|
||||
country = country[0].toUpperCase() + country.substring(1).toLowerCase()
|
||||
country = 'Taiwan' if /台灣/ig.test(country)
|
||||
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
|
||||
|
||||
console.log(new Date().toISOString(), 'Building graph...')
|
||||
@totalSchools = 0
|
||||
@totalStudents = 0
|
||||
@totalTeachers = 0
|
||||
@totalStates = 0
|
||||
@stateCounts = []
|
||||
stateCountsMap = {}
|
||||
@districtCounts = []
|
||||
console.log(new Date().toISOString(), 'Building country graphs...')
|
||||
@countryGraphs = {}
|
||||
@countryCounts = []
|
||||
totalStudents = 0
|
||||
totalTeachers = 0
|
||||
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
|
||||
@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}
|
||||
for district, schoolCountsMap of districtSchoolCountsMap
|
||||
stateData.districts++
|
||||
|
@ -106,39 +129,40 @@ module.exports = class SchoolCountsView extends RootView
|
|||
for school, counts of schoolCountsMap
|
||||
studentCount = Object.keys(counts.students).length
|
||||
teacherCount = Object.keys(counts.teachers).length
|
||||
@totalSchools++
|
||||
@totalStudents += studentCount
|
||||
@totalTeachers += teacherCount
|
||||
@countryGraphs[country].totalSchools++
|
||||
@countryGraphs[country].totalStudents += studentCount
|
||||
@countryGraphs[country].totalTeachers += teacherCount
|
||||
stateData.schools++
|
||||
stateData.students += studentCount
|
||||
stateData.teachers += teacherCount
|
||||
districtData.schools++
|
||||
districtData.students += studentCount
|
||||
districtData.teachers += teacherCount
|
||||
@districtCounts.push(districtData)
|
||||
@stateCounts.push(stateData)
|
||||
stateCountsMap[state] = stateData
|
||||
@untriagedStudents = Object.keys(studentMap).length - @totalStudents
|
||||
@countryGraphs[country].districtCounts.push(districtData)
|
||||
@countryGraphs[country].stateCounts.push(stateData)
|
||||
@countryGraphs[country].stateCountsMap[state] = stateData
|
||||
@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) ->
|
||||
return -1 if a.students > b.students
|
||||
return 1 if a.students < b.students
|
||||
return -1 if a.teachers > b.teachers
|
||||
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
|
||||
return -1 if stateCountsMap[a.state].students > stateCountsMap[b.state].students
|
||||
return 1 if stateCountsMap[a.state].students < stateCountsMap[b.state].students
|
||||
return -1 if stateCountsMap[a.state].teachers > stateCountsMap[b.state].teachers
|
||||
return 1 if stateCountsMap[a.state].teachers < stateCountsMap[b.state].teachers
|
||||
a.state.localeCompare(b.state)
|
||||
else
|
||||
return -1 if a.students > b.students
|
||||
return 1 if a.students < b.students
|
||||
return -1 if a.teachers > b.teachers
|
||||
return 1 if a.teachers < b.teachers
|
||||
a.district.localeCompare(b.district)
|
||||
for country, graph of @countryGraphs
|
||||
graph.stateCounts.sort (a, b) ->
|
||||
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)
|
||||
graph.districtCounts.sort (a, b) ->
|
||||
if a.state isnt b.state
|
||||
stateCountsA = graph.stateCountsMap[a.state]
|
||||
stateCountsB = graph.stateCountsMap[b.state]
|
||||
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)
|
||||
else
|
||||
b.students - a.students or b.teachers - a.teachers or b.schools - a.schools or b.district.localeCompare(a.district)
|
||||
@countryCounts.sort (a, b) ->
|
||||
b.students - a.students or b.teachers - a.teachers or b.schools - a.schools or b.country.localeCompare(a.country)
|
||||
|
||||
console.log(new Date().toISOString(), 'Done...')
|
||||
super()
|
||||
|
|
|
@ -11,6 +11,7 @@ Thang = require 'lib/world/thang'
|
|||
LevelThangEditView = require './LevelThangEditView'
|
||||
ComponentsCollection = require 'collections/ComponentsCollection'
|
||||
require 'vendor/treema'
|
||||
GameUIState = require 'models/GameUIState'
|
||||
|
||||
# Moving the screen while dragging thangs constants
|
||||
MOVE_MARGIN = 0.15
|
||||
|
@ -29,7 +30,6 @@ module.exports = class ThangsTabView extends CocoView
|
|||
template: thangs_template
|
||||
|
||||
subscriptions:
|
||||
'surface:sprite-selected': 'onExtantThangSelected'
|
||||
'surface:mouse-moved': 'onSurfaceMouseMoved'
|
||||
'surface:mouse-over': 'onSurfaceMouseOver'
|
||||
'surface:mouse-out': 'onSurfaceMouseOut'
|
||||
|
@ -39,7 +39,6 @@ module.exports = class ThangsTabView extends CocoView
|
|||
'editor:view-switched': 'onViewSwitched'
|
||||
'sprite:dragged': 'onSpriteDragged'
|
||||
'sprite:mouse-up': 'onSpriteMouseUp'
|
||||
'sprite:mouse-down': 'onSpriteMouseDown'
|
||||
'sprite:double-clicked': 'onSpriteDoubleClicked'
|
||||
'surface:stage-mouse-down': 'onStageMouseDown'
|
||||
'surface:stage-mouse-up': 'onStageMouseUp'
|
||||
|
@ -78,12 +77,17 @@ module.exports = class ThangsTabView extends CocoView
|
|||
constructor: (options) ->
|
||||
super options
|
||||
@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
|
||||
@thangTypes = @supermodel.loadCollection(new ThangTypeSearchCollection(), 'thangs').model
|
||||
# just loading all Components for now: https://github.com/codecombat/codecombat/issues/405
|
||||
@componentCollection = @supermodel.loadCollection(new ComponentsCollection(), 'components').load()
|
||||
@level = options.level
|
||||
@onThangsChanged = _.debounce(@onThangsChanged)
|
||||
|
||||
$(document).bind 'contextmenu', @preventDefaultContextMenu
|
||||
|
||||
|
@ -203,7 +207,7 @@ module.exports = class ThangsTabView extends CocoView
|
|||
initSurface: ->
|
||||
webGLCanvas = $('canvas#webgl-surface', @$el)
|
||||
normalCanvas = $('canvas#normal-surface', @$el)
|
||||
@surface = new Surface @world, normalCanvas, webGLCanvas, {
|
||||
@surface = new Surface(@world, normalCanvas, webGLCanvas, {
|
||||
paths: false
|
||||
coords: true
|
||||
grid: true
|
||||
|
@ -212,7 +216,9 @@ module.exports = class ThangsTabView extends CocoView
|
|||
showInvisible: true
|
||||
frameRate: 15
|
||||
levelType: @level.get 'type', true
|
||||
}
|
||||
@gameUIState
|
||||
handleEvents: false
|
||||
})
|
||||
@surface.playing = false
|
||||
@surface.setWorld @world
|
||||
@surface.lankBoss.suppressSelectionSounds = true
|
||||
|
@ -240,38 +246,79 @@ module.exports = class ThangsTabView extends CocoView
|
|||
@selectAddThang null, true
|
||||
@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) ->
|
||||
return unless @addThangLank?.thangType.get('kind') is 'Wall'
|
||||
@surface.camera.dragDisabled = true
|
||||
@paintingWalls = true
|
||||
# initial values for a mouse click lifecycle
|
||||
@dragged = 0
|
||||
@willUnselectSprite = false
|
||||
@gameUIState.set('canDragCamera', true)
|
||||
|
||||
if @addThangLank?.thangType.get('kind') is 'Wall'
|
||||
@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) ->
|
||||
if @paintingWalls
|
||||
# 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 {}
|
||||
@paintingWalls = false
|
||||
$('#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) ->
|
||||
return unless @selectedExtantThang and e.thang?.id is @selectedExtantThang?.id
|
||||
@dragged = true
|
||||
@surface.camera.dragDisabled = true
|
||||
selected = @gameUIState.get('selected')
|
||||
return unless _.any(selected) and @dragged > 10
|
||||
@willUnselectSprite = false
|
||||
{stageX, stageY} = e.originalEvent
|
||||
|
||||
# move the one under the mouse
|
||||
lastSelected = _.last(selected)
|
||||
cap = @surface.camera.screenToCanvas x: stageX, y: stageY
|
||||
wop = @surface.camera.canvasToWorld cap
|
||||
wop.z = @selectedExtantThang.depth / 2
|
||||
@adjustThangPos @selectedExtantLank, @selectedExtantThang, wop
|
||||
wop.z = lastSelected.thang.depth / 2
|
||||
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]
|
||||
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
|
||||
|
@ -279,24 +326,30 @@ module.exports = class ThangsTabView extends CocoView
|
|||
@calculateMovement(cap.x / w, cap.y / h, w / h)
|
||||
|
||||
onSpriteMouseUp: (e) ->
|
||||
clearTimeout @backgroundAddClickTimeout
|
||||
@surface.camera.unlock()
|
||||
if e.originalEvent.nativeEvent.button == 2 and @selectedExtantThang
|
||||
selected = @gameUIState.get('selected')
|
||||
if e.originalEvent.nativeEvent.button == 2 and _.any(selected)
|
||||
@onSpriteContextMenu e
|
||||
clearInterval(@movementInterval) if @movementInterval?
|
||||
@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})
|
||||
path = "#{@pathForThang(thang)}/components/original=#{LevelComponent.PhysicalID}"
|
||||
physical = @thangsTreema.get path
|
||||
return 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
|
||||
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}"
|
||||
physical = @thangsTreema.get path
|
||||
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
|
||||
|
||||
if @willUnselectSprite
|
||||
clickedSprite = _.find(selected, {sprite: e.sprite})
|
||||
@gameUIState.set('selected', _.without(selected, clickedSprite))
|
||||
|
||||
onSpriteDoubleClicked: (e) ->
|
||||
return unless e.thang and not @dragged
|
||||
return if @dragged > 10
|
||||
return unless e.thang
|
||||
@editThang thangID: e.thang.id
|
||||
|
||||
onRandomTerrainGenerated: (e) ->
|
||||
|
@ -320,35 +373,21 @@ module.exports = class ThangsTabView extends CocoView
|
|||
@onThangsChanged()
|
||||
@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
|
||||
onExtantThangSelected: (e) ->
|
||||
@selectedExtantLank?.setNameLabel? null unless @selectedExtantLank is e.sprite
|
||||
@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)
|
||||
previousSprite?.setNameLabel?(null) unless previousSprite is sprite
|
||||
|
||||
if thang and not (@addThangLank and @addThangType.get('name') in overlappableThangTypeNames)
|
||||
# We clicked on a Thang (or its Treema), so select the Thang
|
||||
@selectAddThang null, true
|
||||
@selectAddThang(null, true)
|
||||
@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
|
||||
@selectedExtantLank.setNameLabel @selectedExtantLank.thangType.get('name') + ': ' + @selectedExtantThang.id
|
||||
@selectedExtantLank.updateLabels()
|
||||
@selectedExtantLank.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()
|
||||
sprite.setNameLabel(sprite.thangType.get('name') + ': ' + thang.id)
|
||||
sprite.updateLabels()
|
||||
sprite.updateMarks()
|
||||
|
||||
justAdded: -> @lastAddTime and (new Date() - @lastAddTime) < 150
|
||||
|
||||
|
@ -482,11 +521,14 @@ module.exports = class ThangsTabView extends CocoView
|
|||
|
||||
deleteSelectedExtantThang: (e) =>
|
||||
return if $(e.target).hasClass 'treema-node'
|
||||
return unless @selectedExtantThang
|
||||
thang = @getThangByID(@selectedExtantThang.id)
|
||||
@thangsTreema.delete(@pathForThang(thang))
|
||||
@deleteEmptyTreema(thang)
|
||||
Thang.resetThangIDs() # TODO: find some way to do this when we delete from treema, too
|
||||
selected = @gameUIState.get('selected')
|
||||
return unless _.any(selected)
|
||||
|
||||
for singleSelected in selected
|
||||
thang = @getThangByID(singleSelected.thang.id)
|
||||
@thangsTreema.delete(@pathForThang(thang))
|
||||
@deleteEmptyTreema(thang)
|
||||
Thang.resetThangIDs() # TODO: find some way to do this when we delete from treema, too
|
||||
|
||||
deleteEmptyTreema: (thang)->
|
||||
thangType = @supermodel.getModelByOriginal ThangType, thang.thangType
|
||||
|
@ -564,17 +606,26 @@ module.exports = class ThangsTabView extends CocoView
|
|||
@selectAddThangType @addThangType, @cloneSourceThang if @addThangType # make another addThang sprite, since the World just refreshed
|
||||
|
||||
# update selection, since the thangs have been remade
|
||||
if @selectedExtantThang
|
||||
@selectedExtantLank = @surface.lankBoss.lanks[@selectedExtantThang.id]
|
||||
@selectedExtantThang = @selectedExtantLank?.thang
|
||||
selected = @gameUIState.get('selected')
|
||||
if _.any(selected)
|
||||
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
|
||||
|
||||
onTreemaThangSelected: (e, selectedTreemas) =>
|
||||
selectedThangID = _.last(selectedTreemas)?.data.id
|
||||
if selectedThangID isnt @selectedExtantThang?.id
|
||||
@surface.lankBoss.selectThang selectedThangID, null, true
|
||||
selectedThangTreemas = _.filter(selectedTreemas, (t) -> t instanceof ThangNode)
|
||||
thangIDs = (node.data.id for node in selectedThangTreemas)
|
||||
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) =>
|
||||
nativeEvent = e.originalEvent.nativeEvent
|
||||
return if nativeEvent and (nativeEvent.ctrlKey or nativeEvent.metaKey)
|
||||
id = treema?.data?.id
|
||||
@editThang thangID: id if id
|
||||
|
||||
|
@ -655,7 +706,8 @@ module.exports = class ThangsTabView extends CocoView
|
|||
|
||||
onDuplicateClicked: (e) ->
|
||||
$('#contextmenu').hide()
|
||||
@selectAddThangType @selectedExtantThang.spriteName, @selectedExtantThang
|
||||
selected = _.last(@gameUIState.get('selected'))
|
||||
@selectAddThangType(selected.thang.spriteName, selected.thang)
|
||||
|
||||
onClickRotationButton: (e) ->
|
||||
$('#contextmenu').hide()
|
||||
|
@ -667,7 +719,8 @@ module.exports = class ThangsTabView extends CocoView
|
|||
@hush = true
|
||||
thangData = @getThangByID thang.id
|
||||
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
|
||||
thangData.components.push component
|
||||
modificationFunction component
|
||||
|
@ -682,34 +735,44 @@ module.exports = class ThangsTabView extends CocoView
|
|||
lank.setDebug true
|
||||
|
||||
rotateSelectedThangTo: (radians) ->
|
||||
@modifySelectedThangComponentConfig @selectedExtantThang, LevelComponent.PhysicalID, (component) =>
|
||||
component.config.rotation = radians
|
||||
@selectedExtantThang.rotation = component.config.rotation
|
||||
for singleSelected in @gameUIState.get('selected')
|
||||
selectedThang = singleSelected.thang
|
||||
@modifySelectedThangComponentConfig selectedThang, LevelComponent.PhysicalID, (component) =>
|
||||
component.config.rotation = radians
|
||||
selectedThang.rotation = component.config.rotation
|
||||
|
||||
rotateSelectedThangBy: (radians) ->
|
||||
@modifySelectedThangComponentConfig @selectedExtantThang, LevelComponent.PhysicalID, (component) =>
|
||||
component.config.rotation = ((component.config.rotation ? 0) + radians) % (2 * Math.PI)
|
||||
@selectedExtantThang.rotation = component.config.rotation
|
||||
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)
|
||||
selectedThang.rotation = component.config.rotation
|
||||
|
||||
moveSelectedThangBy: (xDir, yDir) ->
|
||||
@modifySelectedThangComponentConfig @selectedExtantThang, LevelComponent.PhysicalID, (component) =>
|
||||
component.config.pos.x += 0.5 * xDir
|
||||
component.config.pos.y += 0.5 * yDir
|
||||
@selectedExtantThang.pos.x = component.config.pos.x
|
||||
@selectedExtantThang.pos.y = component.config.pos.y
|
||||
for singleSelected in @gameUIState.get('selected')
|
||||
selectedThang = singleSelected.thang
|
||||
@modifySelectedThangComponentConfig selectedThang, LevelComponent.PhysicalID, (component) =>
|
||||
component.config.pos.x += 0.5 * xDir
|
||||
component.config.pos.y += 0.5 * yDir
|
||||
selectedThang.pos.x = component.config.pos.x
|
||||
selectedThang.pos.y = component.config.pos.y
|
||||
|
||||
resizeSelectedThangBy: (xDir, yDir) ->
|
||||
@modifySelectedThangComponentConfig @selectedExtantThang, LevelComponent.PhysicalID, (component) =>
|
||||
component.config.width = (component.config.width ? 4) + 0.5 * xDir
|
||||
component.config.height = (component.config.height ? 4) + 0.5 * yDir
|
||||
@selectedExtantThang.width = component.config.width
|
||||
@selectedExtantThang.height = component.config.height
|
||||
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.height = (component.config.height ? 4) + 0.5 * yDir
|
||||
selectedThang.width = component.config.width
|
||||
selectedThang.height = component.config.height
|
||||
|
||||
toggleSelectedThangCollision: ->
|
||||
@modifySelectedThangComponentConfig @selectedExtantThang, LevelComponent.CollidesID, (component) =>
|
||||
component.config ?= {}
|
||||
component.config.collisionCategory = if component.config.collisionCategory is 'none' then 'ground' else 'none'
|
||||
@selectedExtantThang.collisionCategory = component.config.collisionCategory
|
||||
for singleSelected in @gameUIState.get('selected')
|
||||
selectedThang = singleSelected.thang
|
||||
@modifySelectedThangComponentConfig selectedThang, LevelComponent.CollidesID, (component) =>
|
||||
component.config ?= {}
|
||||
component.config.collisionCategory = if component.config.collisionCategory is 'none' then 'ground' else 'none'
|
||||
selectedThang.collisionCategory = component.config.collisionCategory
|
||||
|
||||
toggleThangsContainer: (e) ->
|
||||
$('#all-thangs').toggleClass('hide')
|
||||
|
|
|
@ -81,10 +81,10 @@ module.exports = class VerifierTest extends CocoClass
|
|||
@updateCallback? state: 'running'
|
||||
|
||||
processSingleGameResults: (e) ->
|
||||
console.log(e)
|
||||
@goals = e.goalStates
|
||||
@frames = e.totalFrames
|
||||
@lastFrameHash = e.lastFrameHash
|
||||
@simulationFrameRate = e.simulationFrameRate
|
||||
@state = 'complete'
|
||||
@updateCallback? state: @state
|
||||
@scheduleCleanup()
|
||||
|
@ -92,6 +92,7 @@ module.exports = class VerifierTest extends CocoClass
|
|||
isSuccessful: () ->
|
||||
return false unless @solution?
|
||||
return false unless @frames == @solution.frameCount or @options.dontCareAboutFrames
|
||||
return false if @simulationFrameRate < 30
|
||||
if @goals and @solution.goals
|
||||
for k of @goals
|
||||
continue if not @solution.goals[k]
|
||||
|
|
|
@ -72,6 +72,7 @@ module.exports =
|
|||
|
||||
|
||||
fetchNextLevel: wrap (req, res) ->
|
||||
unless req.user? then return res.status(200).send({})
|
||||
levelOriginal = req.params.levelOriginal
|
||||
unless database.isID(levelOriginal) then throw new errors.UnprocessableEntity('Invalid level original ObjectId')
|
||||
sessionID = req.params.sessionID
|
||||
|
@ -162,3 +163,9 @@ module.exports =
|
|||
students: (user.toObject({req: req}) for user in users)
|
||||
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/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.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)
|
||||
|
|
|
@ -96,24 +96,6 @@ describe 'Camera (Surface point of view)', ->
|
|||
checkConversionsFromWorldPos wop, cam
|
||||
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', ->
|
||||
cam = new Camera {attr: (attr) -> 100}, null
|
||||
angle = Math.asin(3 / 4)
|
|
@ -2,6 +2,7 @@ LankBoss = require 'lib/surface/LankBoss'
|
|||
Camera = require 'lib/surface/Camera'
|
||||
World = require 'lib/world/world'
|
||||
ThangType = require 'models/ThangType'
|
||||
GameUIState = require 'models/GameUIState'
|
||||
|
||||
treeData = require 'test/app/fixtures/tree1.thang.type'
|
||||
munchkinData = require 'test/app/fixtures/ogre-munchkin-m.thang.type'
|
||||
|
@ -53,6 +54,7 @@ describe 'LankBoss', ->
|
|||
surfaceTextLayer: new createjs.Container()
|
||||
world: world
|
||||
thangTypes: thangTypes
|
||||
gameUIState: new GameUIState()
|
||||
}
|
||||
|
||||
window.lankBoss = lankBoss = new LankBoss(options)
|
||||
|
|
Loading…
Reference in a new issue