Merge pull request from codecombat/master

Merge master into production
This commit is contained in:
Michael Schmatz 2014-02-25 12:03:03 -08:00
commit d62f191db3
21 changed files with 104 additions and 54 deletions

View file

@ -87,6 +87,9 @@ module.exports = class LevelBus extends Bus
# LevelSession object. Either break this off into a separate class
# or have the LevelSession object listen for all these events itself.
setSpells: (spells) ->
@onSpellCreated spell: spell for spellKey, spell of spells
onSpellChanged: (e) ->
return unless @onPoint()
code = @session.get('code')
@ -102,6 +105,7 @@ module.exports = class LevelBus extends Bus
onSpellCreated: (e) ->
return unless @onPoint()
spellTeam = e.spell.team
@teamSpellMap ?= {}
@teamSpellMap[spellTeam] ?= []
unless e.spell.spellKey in @teamSpellMap[spellTeam]
@ -109,8 +113,7 @@ module.exports = class LevelBus extends Bus
@changedSessionProperties.teamSpells = true
@session.set({'teamSpells': @teamSpellMap})
@saveSession()
@onSpellChanged e # Save the new spell to the session, too.
onScriptStateChanged: (e) ->
return unless @onPoint()
@ -236,10 +239,3 @@ module.exports = class LevelBus extends Bus
destroy: ->
@session.off 'change:multiplayer', @onMultiplayerChanged, @
super()
setTeamSpellMap: (spellMap) ->
@teamSpellMap = spellMap
console.log @teamSpellMap
@changedSessionProperties.teamSpells = true
@session.set({'teamSpells': @teamSpellMap})
@saveSession()

View file

@ -132,6 +132,15 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
continue unless script.channel is channel
continue if alreadyTriggered and not script.repeats
continue if script.lastTriggered? and new Date().getTime() - script.lastTriggered < 1
continue if script.neverRun
if script.notAfter
for scriptID in script.notAfter
if scriptID in @triggered
script.neverRun = true
break
continue if script.neverRun
continue unless @scriptPrereqsSatisfied(script)
continue unless scriptMatchesEventPrereqs(script, event)
# everything passed!

View file

@ -40,6 +40,8 @@ module.exports = class Camera extends CocoClass
'camera-zoom-out': 'onZoomOut'
'surface:mouse-scrolled': 'onMouseScrolled'
'level:restarted': 'onLevelRestarted'
'sprite:mouse-down': 'onMouseDown'
'sprite:dragged': 'onMouseDragged'
# TODO: Fix tests to not use mainLayer
constructor: (@canvasWidth, @canvasHeight, angle=Math.asin(0.75), hFOV=d2r(30)) ->
@ -164,6 +166,21 @@ module.exports = class Camera extends CocoClass
target = @target
@zoomTo target, newZoom, 0
onMouseDown: (e) ->
return if @dragDisabled
@lastPos = {x: e.originalEvent.rawX, y: e.originalEvent.rawY}
onMouseDragged: (e) ->
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)

View file

@ -22,6 +22,7 @@ module.exports = IndieSprite = class IndieSprite extends CocoSprite
thang.width = thang.height = thang.depth = 4
thang.pos = pos ? @defaultPos()
thang.pos.z = thang.depth / 2
thang.shape = 'ellipsoid'
thang.rotation = 0
thang.action = 'idle'
thang.setAction = (action) -> thang.action = action

View file

@ -13,7 +13,7 @@ module.exports = class SpriteBoss extends CocoClass
'bus:player-left': 'onPlayerLeft'
'level-set-debug': 'onSetDebug'
'level-highlight-sprites': 'onHighlightSprites'
'sprite:mouse-down': 'onSpriteMouseDown'
'sprite:mouse-up': 'onSpriteMouseUp'
'surface:stage-mouse-down': 'onStageMouseDown'
'level-select-sprite': 'onSelectSprite'
'level-suppress-selection-sounds': 'onSuppressSelectionSounds'
@ -21,6 +21,7 @@ module.exports = class SpriteBoss extends CocoClass
'level:restarted': 'onLevelRestarted'
'god:new-world-created': 'onNewWorld'
'tome:cast-spells': 'onCastSpells'
'camera:dragged': 'onCameraDragged'
constructor: (@options) ->
super()
@ -183,6 +184,11 @@ module.exports = class SpriteBoss extends CocoClass
sprite.hasMoved = false
@removeSprite sprite if missing
@cache true if updateCache and @cached
# mainly for handling selecting thangs from session when the thang is not always in existence
if @willSelectThang and @sprites[@willSelectThang[0]]
@selectThang @willSelectThang...
@willSelectThang = null
cache: (update=false) ->
return if @cached and not update
@ -226,8 +232,12 @@ module.exports = class SpriteBoss extends CocoClass
onSelectSprite: (e) ->
@selectThang e.thangID, e.spellName
onSpriteMouseDown: (e) ->
onCameraDragged: ->
@dragged = true
onSpriteMouseUp: (e) ->
return if key.shift and @options.choosing
return @dragged = false if @dragged
sprite = if e.sprite?.thang?.isSelectable then e.sprite else null
@selectSprite e, sprite
@ -236,6 +246,7 @@ module.exports = class SpriteBoss extends CocoClass
@selectSprite e if e.onBackground
selectThang: (thangID, spellName=null) ->
return @willSelectThang = [thangID, spellName] unless @sprites[thangID]
@selectSprite null, @sprites[thangID], spellName
selectSprite: (e, sprite=null, spellName=null) ->

View file

@ -311,7 +311,10 @@ module.exports = Surface = class Surface extends CocoClass
onNewWorld: (event) ->
return unless event.world.name is @world.name
@casting = false
Backbone.Mediator.publish 'level-set-playing', { playing: @wasPlayingWhenCastingBegan }
# This has a tendency to break scripts that are waiting for playback to change when the level is loaded
# so only run it after the first world is created.
Backbone.Mediator.publish 'level-set-playing', { playing: @wasPlayingWhenCastingBegan } unless event.firstWorld
fastForwardTo = null
if @playing

View file

@ -37,7 +37,7 @@ module.exports = class WizardSprite extends IndieSprite
makeIndieThang: (thangType, thangID, pos) ->
thang = super thangType, thangID, pos
thang.isSelectable = false
thang.bobHeight = 1.5
thang.bobHeight = 0.75
thang.bobTime = 2
thang.pos.z += thang.bobHeight
thang

View file

@ -7,7 +7,7 @@ block content
!{description}
if !me.get('anonymous')
a(href="http://www.youtube.com/watch?v=IFvfZiJGDsw&list=HL1392928835&feature=mh_lolz").intro-button.btn.btn-primary.btn-lg Watch the Video
//a(href="http://www.youtube.com/watch?v=IFvfZiJGDsw&list=HL1392928835&feature=mh_lolz").intro-button.btn.btn-primary.btn-lg Watch the Video
a(href="/play/level/ladder-tutorial").intro-button.btn.btn-primary.btn-lg Play the Tutorial

View file

@ -144,6 +144,7 @@ module.exports = class ThangsTabView extends View
@surface.playing = false
@surface.setWorld @world
@surface.camera.zoomTo({x:262, y:-164}, 1.66, 0)
@surface.camera.dragDisabled = true
destroy: ->
@selectAddThangType null

View file

@ -393,7 +393,7 @@ module.exports = class PlayLevelView extends View
register: ->
@bus = LevelBus.get(@levelID, @session.id)
@bus.setSession(@session)
@bus.setTeamSpellMap @tome.teamSpellMap
@bus.setSpells @tome.spells
@bus.connect() if @session.get('multiplayer')
onSessionWillSave: (e) ->

View file

@ -62,7 +62,6 @@
"sendwithus": "2.0.x",
"aws-sdk":"~2.0.0",
"bayesian-battle":"0.0.x",
"hiredis":"",
"redis": ""
},
"devDependencies": {
@ -70,14 +69,14 @@
"javascript-brunch": "> 1.0 < 1.8",
"coffee-script-brunch": "https://github.com/brunch/coffee-script-brunch/tarball/master",
"coffeelint-brunch": "> 1.0 < 1.8",
"sass-brunch": "1.7.0",
"sass-brunch": "~1.8.0",
"css-brunch": "> 1.0 < 1.8",
"jade-brunch": "> 1.0 < 1.8",
"uglify-js-brunch": "~1.7.4",
"clean-css-brunch": "> 1.0 < 1.8",
"auto-reload-brunch": "> 1.0 < 1.8",
"brunch": "~1.7.4",
"jasmine-node": "1.12.x",
"jasmine-node": "1.13.x",
"nodemon": "0.7.5",
"marked": "0.2.x",
"telepath-brunch": "https://github.com/nwinter/telepath-brunch/tarball/master",

View file

@ -16,7 +16,7 @@ module.exports = class Handler
# subclasses should override these methods
hasAccess: (req) -> true
hasAccessToDocument: (req, document, method=null) ->
return true if req.user.isAdmin()
return true if req.user?.isAdmin()
if @modelClass.schema.uses_coco_permissions
return document.hasPermissionsForMethod(req.user, method or req.method)
return true
@ -32,7 +32,7 @@ module.exports = class Handler
# can only edit permissions if this is a brand new property,
# or you are an owner of the old one
isOwner = document.getAccessForUserObjectId(req.user._id) is 'owner'
if isBrandNew or isOwner or req.user.isAdmin()
if isBrandNew or isOwner or req.user?.isAdmin()
props.push 'permissions'
if @modelClass.schema.uses_coco_versions
@ -57,7 +57,7 @@ module.exports = class Handler
# generic handlers
get: (req, res) ->
# by default, ordinary users never get unfettered access to the database
return @sendUnauthorizedError(res) unless req.user.isAdmin()
return @sendUnauthorizedError(res) unless req.user?.isAdmin()
# admins can send any sort of query down the wire, though
conditions = JSON.parse(req.query.conditions || '[]')
@ -97,7 +97,7 @@ module.exports = class Handler
term = req.query.term
matchedObjects = []
filters = [{filter: {index: true}}]
if @modelClass.schema.uses_coco_permissions
if @modelClass.schema.uses_coco_permissions and req.user
filters.push {filter: {index: req.user.get('id')}}
for filter in filters
callback = (err, results) =>

View file

@ -39,6 +39,7 @@ LevelHandler = class LevelHandler extends Handler
callback err, level
getSession: (req, res, id) ->
return @sendNotFoundError(res) unless req.user
@fetchLevelByIDAndHandleErrors id, req, res, (err, level) =>
sessionQuery =
level:
@ -150,6 +151,7 @@ LevelHandler = class LevelHandler extends Handler
req.query.limit = parseInt(req.query.limit) ? 20
getFeedback: (req, res, id) ->
return @sendNotFoundError(res) unless req.user
@fetchLevelByIDAndHandleErrors id, req, res, (err, level) =>
feedbackQuery =
creator: mongoose.Types.ObjectId(req.user.id.toString())

View file

@ -147,7 +147,9 @@ ScriptSchema = c.object {
eventPrereqs: c.array {title: "Event Checks", description: "Logical checks on the event for this script to trigger.", format:'event-prereqs'}, EventPrereqSchema
repeats: {title: "Repeats", description: "Whether this script can trigger more than once during a level.", type: 'boolean', "default": false}
scriptPrereqs: c.array {title: "Happens After", description: "Scripts that need to fire first."},
c.shortString(title: "ID", description: "A unique ID of a script that must have triggered before the parent script can trigger.")
c.shortString(title: "ID", description: "A unique ID of a script.")
notAfter: c.array {title: "Not After", description: "Do not run this script if any of these scripts have run."},
c.shortString(title: "ID", description: "A unique ID of a script.")
noteChain: c.array {title: "Actions", description: "A list of things that happen when this script triggers."}, NoteGroupSchema
LevelThangSchema = c.object {

View file

@ -4,6 +4,7 @@ mail = require '../commons/mail'
module.exports.setup = (app) ->
app.post '/contact', (req, res) ->
return res.end() unless req.user
log.info "Sending mail from #{req.body.email} saying #{req.body.message}"
if config.isProduction
options = createMailOptions req.body.email, req.body.message, req.user

View file

@ -11,6 +11,7 @@ module.exports.setup = (app) ->
parts = module.split('/')
module = parts[0]
return getSchema(req, res, module) if parts[1] is 'schema'
return errors.unauthorized(res, 'Must have an identity to do anything with the db.') unless req.user
try
moduleName = module.replace '.', '_'

View file

@ -69,7 +69,7 @@ postFileSchema =
required: ['filename', 'mimetype', 'path']
filePost = (req, res) ->
return errors.forbidden(res) unless req.user.isAdmin()
return errors.forbidden(res) unless req.user?.isAdmin()
options = req.body
tv4 = require('tv4').tv4
valid = tv4.validate(options, postFileSchema)

View file

@ -31,7 +31,7 @@ UserHandler = class UserHandler extends Handler
return null unless document?
obj = document.toObject()
delete obj[prop] for prop in serverProperties
includePrivates = req.user and (req.user.isAdmin() or req.user._id.equals(document._id))
includePrivates = req.user and (req.user?.isAdmin() or req.user?._id.equals(document._id))
delete obj[prop] for prop in privateProperties unless includePrivates
# emailHash is used by gravatar
@ -105,7 +105,7 @@ UserHandler = class UserHandler extends Handler
]
getById: (req, res, id) ->
if req.user and req.user._id.equals(id)
if req.user?._id.equals(id)
return @sendSuccess(res, @formatEntity(req, req.user))
super(req, res, id)
@ -132,14 +132,15 @@ UserHandler = class UserHandler extends Handler
post: (req, res) ->
return @sendBadInputError(res, 'No input.') if _.isEmpty(req.body)
return @sendBadInputError(res, 'Must have an anonymous user to post with.') unless req.user
return @sendBadInputError(res, 'Existing users cannot create new ones.') unless req.user.get('anonymous')
req.body._id = req.user._id if req.user.get('anonymous')
@put(req, res)
hasAccessToDocument: (req, document) ->
if req.route.method in ['put', 'post', 'patch']
return true if req.user.isAdmin()
return req.user._id.equals(document._id)
return true if req.user?.isAdmin()
return req.user?._id.equals(document._id)
return true
getByRelationship: (req, res, args...) ->
@ -149,6 +150,7 @@ UserHandler = class UserHandler extends Handler
return @sendNotFoundError(res)
agreeToCLA: (req, res) ->
return @sendUnauthorizedError(res) unless req.user
doc =
user: req.user._id+''
email: req.user.get 'email'

View file

@ -1,6 +1,9 @@
# import this at the top of every file so we're not juggling connections
# and common libraries are available
console.log 'IT BEGINS'
GLOBAL._ = require('lodash')
_.str = require('underscore.string')
_.mixin(_.str.exports())
@ -71,20 +74,22 @@ unittest.getUser = (email, password, done, force) ->
return done(unittest.users[email]) if unittest.users[email] and not force
request = require 'request'
request.post getURL('/auth/logout'), ->
req = request.post(getURL('/db/user'), (err, response, body) ->
throw err if err
User.findOne({email:email}).exec((err, user) ->
if password is '80yqxpb38j'
user.set('permissions', [ 'admin' ])
user.save (err) ->
request.get getURL('/auth/whoami'), ->
req = request.post(getURL('/db/user'), (err, response, body) ->
throw err if err
User.findOne({email:email}).exec((err, user) ->
if password is '80yqxpb38j'
user.set('permissions', [ 'admin' ])
user.save (err) ->
wrapUpGetUser(email, user, done)
else
wrapUpGetUser(email, user, done)
else
wrapUpGetUser(email, user, done)
)
)
)
form = req.form()
form.append('email', email)
form.append('password', password)
form = req.form()
form.append('email', email)
form.append('password', password)
wrapUpGetUser = (email, user, done) ->
unittest.users[email] = user

View file

@ -17,8 +17,9 @@ describe '/auth/login', ->
it 'clears Users first', (done) ->
User.remove {}, (err) ->
throw err if err
done()
request.get getURL('/auth/whoami'), ->
throw err if err
done()
it 'finds no user', (done) ->
req = request.post(urlLogin, (error, response) ->
@ -92,9 +93,10 @@ describe '/auth/reset', ->
form = req.form()
form.append('email', 'unknow')
it 'reset user password', (done) ->
it 'resets user password', (done) ->
req = request.post(urlReset, (error, response) ->
expect(response).toBeDefined()
console.log 'status code is', response.statusCode
expect(response.statusCode).toBe(200)
expect(response.body).toBeDefined()
passwordReset = response.body

View file

@ -54,18 +54,16 @@ describe 'POST /db/user', ->
describe 'PUT /db/user', ->
it 'denies requests without any data', (done) ->
req = request.post getURL('/auth/logout'),
(err, res) ->
expect(res.statusCode).toBe(200)
req = request.put getURL(urlUser),
(err, res) ->
expect(res.statusCode).toBe(422)
expect(res.body).toBe('No input.')
done()
it 'logs in as normal joe', (done) ->
loginJoe -> done()
request.post getURL('/auth/logout'),
loginJoe -> done()
it 'denies requests without any data', (done) ->
request.put getURL(urlUser),
(err, res) ->
expect(res.statusCode).toBe(422)
expect(res.body).toBe('No input.')
done()
it 'denies requests to edit someone who is not joe', (done) ->
unittest.getAdmin (admin) ->