From ac95d775e68f5b3aab31f7c57bbdf19550c4f3bb Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Wed, 9 Jul 2014 20:23:05 +0200 Subject: [PATCH 01/21] Users can now be gotten by slug --- app/lib/utils.coffee | 2 ++ app/models/User.coffee | 23 +++++++++++++++++++++++ app/schemas/models/user.coffee | 8 ++++++-- server/users/User.coffee | 3 +++ server/users/user_handler.coffee | 2 +- test/app/models/User.spec.coffee | 0 6 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 test/app/models/User.spec.coffee diff --git a/app/lib/utils.coffee b/app/lib/utils.coffee index a63a1274d..430a9d8fb 100644 --- a/app/lib/utils.coffee +++ b/app/lib/utils.coffee @@ -76,6 +76,8 @@ module.exports.getByPath = (target, path) -> obj = obj[piece] obj +module.exports.isID = (id) -> _.isString(id) and id.length is 24 and id.match(/[a-f0-9]/gi)?.length is 24 + module.exports.round = _.curry (digits, n) -> n = +n.toFixed(digits) diff --git a/app/models/User.coffee b/app/models/User.coffee index 1086e1373..cdaf1e86a 100644 --- a/app/models/User.coffee +++ b/app/models/User.coffee @@ -1,6 +1,7 @@ GRAVATAR_URL = 'https://www.gravatar.com/' cache = {} CocoModel = require './CocoModel' +util = require 'lib/utils' module.exports = class User extends CocoModel @className: 'User' @@ -45,6 +46,28 @@ module.exports = class User extends CocoModel cache[id] = user user + # callbacks can be either success or error + @getByIDOrSlug: (idOrSlug, force, callbacks={}) -> + {me} = require 'lib/auth' + isID = util.isID idOrSlug + if me.id is idOrSlug or me.slug is idOrSlug + callbacks.success me if callbacks.success? + return me + cached = cache[idOrSlug] + user = cached or new @ _id: idOrSlug + if force or not cached + user.loading = true + user.fetch + success: -> + user.loading = false + Backbone.Mediator.publish 'user:fetched' + callbacks.success user if callbacks.success? + error: -> + user.loading = false + callbacks.error user if callbacks.error? + cache[idOrSlug] = user + user + getEnabledEmails: -> @migrateEmails() emails = _.clone(@get('emails')) or {} diff --git a/app/schemas/models/user.coffee b/app/schemas/models/user.coffee index edb2279bb..b34ec1dea 100644 --- a/app/schemas/models/user.coffee +++ b/app/schemas/models/user.coffee @@ -1,8 +1,12 @@ c = require './../schemas' emailSubscriptions = ['announcement', 'tester', 'level_creator', 'developer', 'article_editor', 'translator', 'support', 'notification'] -UserSchema = c.object {}, - name: c.shortString({title: 'Display Name', default: ''}) +UserSchema = c.object + title: 'User' + +c.extendNamedProperties UserSchema # let's have the name be the first property + +_.extend UserSchema.properties, email: c.shortString({title: 'Email', format: 'email'}) firstName: c.shortString({title: 'First Name'}) lastName: c.shortString({title: 'Last Name'}) diff --git a/server/users/User.coffee b/server/users/User.coffee index 421335a21..fc7440ef3 100644 --- a/server/users/User.coffee +++ b/server/users/User.coffee @@ -4,6 +4,7 @@ crypto = require 'crypto' {salt, isProduction} = require '../../server_config' mail = require '../commons/mail' log = require 'winston' +plugins = require '../plugins/plugins' sendwithus = require '../sendwithus' @@ -136,6 +137,8 @@ UserSchema.statics.hashPassword = (password) -> shasum.update(salt + password) shasum.digest('hex') +UserSchema.plugin plugins.NamedPlugin + module.exports = User = mongoose.model('User', UserSchema) AchievablePlugin = require '../plugins/achievements' diff --git a/server/users/user_handler.coffee b/server/users/user_handler.coffee index f8b608255..45b0253f0 100644 --- a/server/users/user_handler.coffee +++ b/server/users/user_handler.coffee @@ -116,7 +116,7 @@ UserHandler = class UserHandler extends Handler ] getById: (req, res, id) -> - if req.user?._id.equals(id) + if Handler.isID(id) and req.user?._id.equals(id) return @sendSuccess(res, @formatEntity(req, req.user, 256)) super(req, res, id) diff --git a/test/app/models/User.spec.coffee b/test/app/models/User.spec.coffee new file mode 100644 index 000000000..e69de29bb From ea4523bf6a0c560ff7b013d4262fa03c9a00f27b Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Wed, 9 Jul 2014 21:29:57 +0200 Subject: [PATCH 02/21] Test coverage for GETting users by slug --- test/server/functional/user.spec.coffee | 70 ++++++++++++++++++------- 1 file changed, 52 insertions(+), 18 deletions(-) diff --git a/test/server/functional/user.spec.coffee b/test/server/functional/user.spec.coffee index 5395566a4..8f1bedbcd 100644 --- a/test/server/functional/user.spec.coffee +++ b/test/server/functional/user.spec.coffee @@ -44,20 +44,6 @@ describe 'User.updateMailChimp', -> describe 'POST /db/user', -> - createAnonNameUser = (done)-> - request.post getURL('/auth/logout'), -> - request.get getURL('/auth/whoami'), -> - req = request.post(getURL('/db/user'), (err, response) -> - expect(response.statusCode).toBe(200) - request.get getURL('/auth/whoami'), (request, response, body) -> - res = JSON.parse(response.body) - expect(res.anonymous).toBeTruthy() - expect(res.name).toEqual('Jim') - done() - ) - form = req.form() - form.append('name', 'Jim') - it 'preparing test : clears the db first', (done) -> clearModels [User], (err) -> throw err if err @@ -105,11 +91,32 @@ describe 'POST /db/user', -> done() it 'should allow setting anonymous user name', (done) -> - createAnonNameUser(done) - - it 'should allow multiple anonymous users with same name', (done) -> - createAnonNameUser(done) + request.post getURL('/auth/logout'), -> + request.get getURL('/auth/whoami'), -> + req = request.post(getURL('/db/user'), (err, response) -> + expect(response.statusCode).toBe(200) + request.get getURL('/auth/whoami'), (request, response, body) -> + res = JSON.parse(response.body) + expect(res.anonymous).toBeTruthy() + expect(res.name).toEqual('Jim') + done() + ) + form = req.form() + form.append('name', 'Jim') + it 'should not allow multiple anonymous users with same name anymore', (done) -> + request.post getURL('/auth/logout'), -> + request.get getURL('/auth/whoami'), -> + req = request.post(getURL('/db/user'), (err, response) -> + expect(response.statusCode).toBe(409) + request.get getURL('/auth/whoami'), (request, response, body) -> + res = JSON.parse(response.body) + expect(res.anonymous).toBeTruthy() + expect(res.name).not.toEqual('Jim') + done() + ) + form = req.form() + form.append('name', 'Jim') it 'should not allow setting existing user name to anonymous user', (done) -> @@ -267,3 +274,30 @@ describe 'GET /db/user', -> expect(response.statusCode).toBe(422) done() ) + + it 'can fetch myself by id completely', (done) -> + loginSam (sam) -> + request.get {url: getURL(urlUser + '/' + sam.id)}, (err, response) -> + expect(err).toBeNull() + expect(response.statusCode).toBe(200) + done() + + # TODO Ruben should be able to fetch other users but probably with restricted data access + # Add to the test case above an extra data check + + it 'can fetch myself by slug completely', (done) -> + loginSam (sam) -> + request.get {url: getURL(urlUser + '/sam')}, (err, response) -> + expect(err).toBeNull() + expect(response.statusCode).toBe(200) + guy = JSON.parse response.body + expect(guy._id).toBe sam.get('_id').toHexString() + expect(guy.name).toBe sam.get 'name' + done() + + + + + + + From a8353cb7ffa77a2386a6d3ad74892593cfb44ea0 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Wed, 9 Jul 2014 21:33:25 +0200 Subject: [PATCH 03/21] Anonymous users can have the same name --- server/plugins/plugins.coffee | 2 +- test/server/functional/user.spec.coffee | 40 ++++++++++--------------- 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/server/plugins/plugins.coffee b/server/plugins/plugins.coffee index e36bb16de..872df064a 100644 --- a/server/plugins/plugins.coffee +++ b/server/plugins/plugins.coffee @@ -41,7 +41,7 @@ module.exports.NamedPlugin = (schema) -> err.response = {message: ' is a reserved name', property: 'name'} err.code = 422 return next(err) - if newSlug isnt @get('slug') + if newSlug not in [@get('slug'), ""] and not @get 'anonymous' @set('slug', newSlug) @checkSlugConflicts(next) else diff --git a/test/server/functional/user.spec.coffee b/test/server/functional/user.spec.coffee index 8f1bedbcd..4b0e4ffca 100644 --- a/test/server/functional/user.spec.coffee +++ b/test/server/functional/user.spec.coffee @@ -44,6 +44,20 @@ describe 'User.updateMailChimp', -> describe 'POST /db/user', -> + createAnonNameUser = (name, done)-> + request.post getURL('/auth/logout'), -> + request.get getURL('/auth/whoami'), -> + req = request.post(getURL('/db/user'), (err, response) -> + expect(response.statusCode).toBe(200) + request.get getURL('/auth/whoami'), (request, response, body) -> + res = JSON.parse(response.body) + expect(res.anonymous).toBeTruthy() + expect(res.name).toEqual(name) + done() + ) + form = req.form() + form.append('name', name) + it 'preparing test : clears the db first', (done) -> clearModels [User], (err) -> throw err if err @@ -91,32 +105,10 @@ describe 'POST /db/user', -> done() it 'should allow setting anonymous user name', (done) -> - request.post getURL('/auth/logout'), -> - request.get getURL('/auth/whoami'), -> - req = request.post(getURL('/db/user'), (err, response) -> - expect(response.statusCode).toBe(200) - request.get getURL('/auth/whoami'), (request, response, body) -> - res = JSON.parse(response.body) - expect(res.anonymous).toBeTruthy() - expect(res.name).toEqual('Jim') - done() - ) - form = req.form() - form.append('name', 'Jim') + createAnonNameUser('Jim', done) it 'should not allow multiple anonymous users with same name anymore', (done) -> - request.post getURL('/auth/logout'), -> - request.get getURL('/auth/whoami'), -> - req = request.post(getURL('/db/user'), (err, response) -> - expect(response.statusCode).toBe(409) - request.get getURL('/auth/whoami'), (request, response, body) -> - res = JSON.parse(response.body) - expect(res.anonymous).toBeTruthy() - expect(res.name).not.toEqual('Jim') - done() - ) - form = req.form() - form.append('name', 'Jim') + createAnonNameUser('Jim', done) it 'should not allow setting existing user name to anonymous user', (done) -> From a310afc750216df2a0d5719db0ae701b8e76b6df Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Thu, 10 Jul 2014 10:07:36 +0200 Subject: [PATCH 04/21] Corrected sending errors in auth --- server/routes/auth.coffee | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/server/routes/auth.coffee b/server/routes/auth.coffee index 5c3298f44..25c67e778 100644 --- a/server/routes/auth.coffee +++ b/server/routes/auth.coffee @@ -159,15 +159,11 @@ module.exports.setup = (app) -> module.exports.loginUser = loginUser = (req, res, user, send=true, next=null) -> user.save((err) -> - if err - return @sendDatabaseError(res, err) + return errors.serverError res, err if err? req.logIn(user, (err) -> - if err - return @sendDatabaseError(res, err) - - if send - return @sendSuccess(res, user) + return errors.serverError res, err if err? + return res.send user if send next() if next ) ) From 6e593b2ec0efe7fc16460981e74ea645d0ccb53b Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Thu, 10 Jul 2014 10:46:34 +0200 Subject: [PATCH 05/21] User name slugs can now be unset by emptying their name --- server/plugins/plugins.coffee | 4 +++- test/server/functional/user.spec.coffee | 11 +++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/server/plugins/plugins.coffee b/server/plugins/plugins.coffee index 872df064a..7716f0d98 100644 --- a/server/plugins/plugins.coffee +++ b/server/plugins/plugins.coffee @@ -41,9 +41,11 @@ module.exports.NamedPlugin = (schema) -> err.response = {message: ' is a reserved name', property: 'name'} err.code = 422 return next(err) - if newSlug not in [@get('slug'), ""] and not @get 'anonymous' + if newSlug not in [@get('slug'), ''] and not @get 'anonymous' @set('slug', newSlug) @checkSlugConflicts(next) + else if newSlug is '' and @get 'slug' + @set 'slug', undefined else next() ) diff --git a/test/server/functional/user.spec.coffee b/test/server/functional/user.spec.coffee index 4b0e4ffca..b60b944f4 100644 --- a/test/server/functional/user.spec.coffee +++ b/test/server/functional/user.spec.coffee @@ -107,7 +107,7 @@ describe 'POST /db/user', -> it 'should allow setting anonymous user name', (done) -> createAnonNameUser('Jim', done) - it 'should not allow multiple anonymous users with same name anymore', (done) -> + it 'should allow multiple anonymous users with same name', (done) -> createAnonNameUser('Jim', done) it 'should not allow setting existing user name to anonymous user', (done) -> @@ -274,9 +274,6 @@ describe 'GET /db/user', -> expect(response.statusCode).toBe(200) done() - # TODO Ruben should be able to fetch other users but probably with restricted data access - # Add to the test case above an extra data check - it 'can fetch myself by slug completely', (done) -> loginSam (sam) -> request.get {url: getURL(urlUser + '/sam')}, (err, response) -> @@ -287,6 +284,12 @@ describe 'GET /db/user', -> expect(guy.name).toBe sam.get 'name' done() + # TODO Ruben should be able to fetch other users but probably with restricted data access + # Add to the test case above an extra data check + + xit 'can unset name and undefine slug' + + xit 'can fetch another user with restricted fields' From 94210fc461471379908655ae0b97997a3a26e446 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Thu, 10 Jul 2014 18:00:32 +0200 Subject: [PATCH 06/21] Anonymous users are now silently renamed upon signup in case of conflict --- server/users/User.coffee | 41 +++++++++++++++----- server/users/user_handler.coffee | 3 +- test/server/functional/user.spec.coffee | 51 +++++++++++++++++-------- 3 files changed, 68 insertions(+), 27 deletions(-) diff --git a/server/users/User.coffee b/server/users/User.coffee index fc7440ef3..13f4c11fb 100644 --- a/server/users/User.coffee +++ b/server/users/User.coffee @@ -30,6 +30,9 @@ UserSchema.methods.isAdmin = -> p = @get('permissions') return p and 'admin' in p +UserSchema.methods.isAnonymous = -> + @get 'anonymous' + UserSchema.methods.trackActivity = (activityName, increment) -> now = new Date() increment ?= parseInt increment or 1 @@ -109,6 +112,30 @@ UserSchema.statics.updateMailChimp = (doc, callback) -> mc?.lists.subscribe params, onSuccess, onFailure +UserSchema.statics.unconflictName = unconflictName = (name, done) -> + User.findOne {slug: _.str.slugify(name)}, (err, otherUser) -> + return done err if err? + return done null, name unless otherUser + suffix = _.random(0, 9) + '' + unconflictName name + suffix, done + +UserSchema.methods.register = (done) -> + @set('anonymous', false) + @set('permissions', ['admin']) if not isProduction + if (name = @get 'name')? and name isnt '' + unconflictName name, (err, uniqueName) => + return done err if err + @set 'name', uniqueName + done() + else done() + data = + email_id: sendwithus.templates.welcome_email + recipient: + address: @get 'email' + sendwithus.api.send data, (err, result) -> + log.error "sendwithus post-save error: #{err}, result: #{result}" if err + + UserSchema.pre('save', (next) -> @set('emailLower', @get('email')?.toLowerCase()) @set('nameLower', @get('name')?.toLowerCase()) @@ -116,16 +143,10 @@ UserSchema.pre('save', (next) -> if @get('password') @set('passwordHash', User.hashPassword(pwd)) @set('password', undefined) - if @get('email') and @get('anonymous') - @set('anonymous', false) - @set('permissions', ['admin']) if not isProduction - data = - email_id: sendwithus.templates.welcome_email - recipient: - address: @get 'email' - sendwithus.api.send data, (err, result) -> - log.error "sendwithus post-save error: #{err}, result: #{result}" if err - next() + if @get('email') and @get('anonymous') # a user registers + @register next + else + next() ) UserSchema.post 'save', (doc) -> diff --git a/server/users/user_handler.coffee b/server/users/user_handler.coffee index 45b0253f0..c145855b9 100644 --- a/server/users/user_handler.coffee +++ b/server/users/user_handler.coffee @@ -104,7 +104,8 @@ UserHandler = class UserHandler extends Handler return callback(null, req, user) unless req.body.name nameLower = req.body.name?.toLowerCase() return callback(null, req, user) unless nameLower - return callback(null, req, user) if nameLower is user.get('nameLower') and not user.get('anonymous') + return callback(null, req, user) if user.get 'anonymous' # anonymous users can have any name + return callback(null, req, user) if nameLower is user.get('nameLower') User.findOne({nameLower: nameLower, anonymous: false}).exec (err, otherUser) -> log.error "Database error setting user name: #{err}" if err return callback(res: 'Database error.', code: 500) if err diff --git a/test/server/functional/user.spec.coffee b/test/server/functional/user.spec.coffee index b60b944f4..e8bfb2b51 100644 --- a/test/server/functional/user.spec.coffee +++ b/test/server/functional/user.spec.coffee @@ -110,24 +110,13 @@ describe 'POST /db/user', -> it 'should allow multiple anonymous users with same name', (done) -> createAnonNameUser('Jim', done) - it 'should not allow setting existing user name to anonymous user', (done) -> - - createAnonUser = -> - request.post getURL('/auth/logout'), -> - request.get getURL('/auth/whoami'), -> - req = request.post(getURL('/db/user'), (err, response) -> - expect(response.statusCode).toBe(409) - done() - ) - form = req.form() - form.append('name', 'Jim') - + it 'should allow setting existing user name to anonymous user', (done) -> req = request.post(getURL('/db/user'), (err, response, body) -> expect(response.statusCode).toBe(200) request.get getURL('/auth/whoami'), (request, response, body) -> res = JSON.parse(response.body) expect(res.anonymous).toBeFalsy() - createAnonUser() + createAnonNameUser 'Jim', done ) form = req.form() form.append('email', 'new@user.com') @@ -212,6 +201,39 @@ ghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghl form.append('email', 'New@email.com') form.append('name', 'Wilhelm') + it 'should not allow two users with the same name slug', (done) -> + loginSam (sam) -> + samsName = sam.get 'name' + sam.set 'name', 'admin' + request.put {uri:getURL(urlUser + '/' + sam.id), json: sam.toObject()}, (err, response) -> + expect(err).toBeNull() + expect(response.statusCode).toBe 409 + + sam.set 'name', samsName + done() + + it 'should silently rename an anonymous user if their name conflicts upon signup', (done) -> + request.post getURL('/auth/logout'), -> + request.get getURL('/auth/whoami'), -> + req = request.post getURL('/db/user'), (err, response) -> + expect(response.statusCode).toBe(200) + request.get getURL('/auth/whoami'), (err, response) -> + expect(err).toBeNull() + guy = JSON.parse(response.body) + expect(guy.anonymous).toBeTruthy() + expect(guy.name).toEqual 'admin' + + guy.email = 'blub@blub' # Email means registration + req = request.post {url: getURL('/db/user'), json: guy}, (err, response) -> + expect(err).toBeNull() + finalGuy = response.body + expect(finalGuy.anonymous).toBeFalsy() + expect(finalGuy.name).not.toEqual guy.name + expect(finalGuy.name.length).toBe guy.name.length + 1 + done() + form = req.form() + form.append('name', 'admin') + describe 'GET /db/user', -> it 'logs in as admin', (done) -> @@ -293,6 +315,3 @@ describe 'GET /db/user', -> - - - From 9e296b7c3d6ca1b9efcf58e5aded43213dc2eab4 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Thu, 10 Jul 2014 18:24:02 +0200 Subject: [PATCH 07/21] Added a test case for unsetting slugs (and fixed related bug) --- server/plugins/plugins.coffee | 1 + test/server/functional/user.spec.coffee | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/server/plugins/plugins.coffee b/server/plugins/plugins.coffee index 7716f0d98..2fdce3bef 100644 --- a/server/plugins/plugins.coffee +++ b/server/plugins/plugins.coffee @@ -46,6 +46,7 @@ module.exports.NamedPlugin = (schema) -> @checkSlugConflicts(next) else if newSlug is '' and @get 'slug' @set 'slug', undefined + next() else next() ) diff --git a/test/server/functional/user.spec.coffee b/test/server/functional/user.spec.coffee index e8bfb2b51..2bbb3a1ab 100644 --- a/test/server/functional/user.spec.coffee +++ b/test/server/functional/user.spec.coffee @@ -209,6 +209,7 @@ ghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghl expect(err).toBeNull() expect(response.statusCode).toBe 409 + # Restore Sam sam.set 'name', samsName done() @@ -234,6 +235,21 @@ ghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghl form = req.form() form.append('name', 'admin') + it 'should be able to unset a slug by setting an empty name', (done) -> + loginSam (sam) -> + samsName = sam.get 'name' + sam.set 'name', '' + request.put {uri:getURL(urlUser + '/' + sam.id), json: sam.toObject()}, (err, response) -> + expect(err).toBeNull() + expect(response.statusCode).toBe 200 + newSam = response.body + + # Restore Sam + sam.set 'name', samsName + request.put {uri:getURL(urlUser + '/' + sam.id), json: sam.toObject()}, (err, response) -> + expect(err).toBeNull() + done() + describe 'GET /db/user', -> it 'logs in as admin', (done) -> @@ -309,8 +325,6 @@ describe 'GET /db/user', -> # TODO Ruben should be able to fetch other users but probably with restricted data access # Add to the test case above an extra data check - xit 'can unset name and undefine slug' - xit 'can fetch another user with restricted fields' From e18b906e043f455d0a201d663dd5112342f0d193 Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Thu, 10 Jul 2014 11:13:11 -0700 Subject: [PATCH 08/21] Name conflicts migration script. --- .../migrations/2014-07-09-name-conflicts.js | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 scripts/mongodb/migrations/2014-07-09-name-conflicts.js diff --git a/scripts/mongodb/migrations/2014-07-09-name-conflicts.js b/scripts/mongodb/migrations/2014-07-09-name-conflicts.js new file mode 100644 index 000000000..cf8c81f66 --- /dev/null +++ b/scripts/mongodb/migrations/2014-07-09-name-conflicts.js @@ -0,0 +1,52 @@ +load('bower_components/lodash/dist/lodash.js'); +load('bower_components/underscore.string/dist/underscore.string.min.js'); + +var slugs = {}; +var num = 0; + +var unconflictName; + +unconflictName = function(name) { + var otherUser, suffix; + otherUser = db.users.findOne({ + slug: _.string.slugify(name) + }); + if (!otherUser) { + return name; + } + suffix = _.random(0, 9) + ''; + return unconflictName(name + suffix); +}; + +var params = { + name:1, + emails:1, + email:1, + slug:1, + dateCreated:1 +}; + +db.users.find({anonymous:false}, params).sort({_id:1}).forEach(function (user) { + num += 1; + var slug = _.string.slugify(user.name); + if(!slug) return; + var update = {}; + if(slugs[slug]) { + originalName = slugs[slug]; + conflictingName = user.name; + availableName = unconflictName(conflictingName); + conflictingSlug = slug; + slug = _.string.slugify(availableName); + update.name = availableName; + update.nameLower = availableName.toLowerCase(); + if (!(user.emails && user.emails.anyNotes === false)) + db.changedEmails.insert({email:user.email, user:user._id, name:user.name}); + print(_.str.sprintf('\n\n\tConflict! Username "%s" conflicts with "%s" (both sluggify to "%s"). Changing to "%s"\n\n\n', + conflictingName, originalName, conflictingSlug, availableName)); + } + update.slug = slug; + slugs[slug] = user.name; + if(user.slug) return; + print(_.str.sprintf('Setting user %s (%s) to slug %s with update %s', user.name, user.dateCreated, slug, JSON.stringify({$set:update}))); + db.users.update({_id:user._id}, {$set:update}); +}); \ No newline at end of file From efa2bee08c01b1c33cafce8bbaccfed28b5f775c Mon Sep 17 00:00:00 2001 From: Jayant Jain Date: Sun, 22 Jun 2014 19:11:15 +0530 Subject: [PATCH 09/21] Adds shortcuts for undo/redo for thangs tab view --- app/views/editor/level/thangs_tab_view.coffee | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/views/editor/level/thangs_tab_view.coffee b/app/views/editor/level/thangs_tab_view.coffee index 8e905654a..e2d0d4ac8 100644 --- a/app/views/editor/level/thangs_tab_view.coffee +++ b/app/views/editor/level/thangs_tab_view.coffee @@ -57,6 +57,8 @@ module.exports = class ThangsTabView extends View 'delete, del, backspace': 'deleteSelectedExtantThang' 'left': -> @moveAddThangSelection -1 'right': -> @moveAddThangSelection 1 + 'ctrl+z': 'undoAction' + 'ctrl+shift+z': 'redoAction' constructor: (options) -> super options @@ -450,6 +452,12 @@ module.exports = class ThangsTabView extends View $('#add-thangs-column').toggle() @onWindowResize e + undoAction: (e) -> + @thangsTreema.undo() + + redoAction: (e) -> + @thangsTreema.redo() + class ThangsNode extends TreemaNode.nodeMap.array valueClass: 'treema-array-replacement' getChildren: -> From c40da260f04d68e4fc185404a3a42b1788addfe5 Mon Sep 17 00:00:00 2001 From: Jayant Jain Date: Tue, 24 Jun 2014 16:00:29 +0530 Subject: [PATCH 10/21] Adds initial modal and view for terrain generator --- app/styles/terrain_randomise.sass | 148 ++++++++++++++++++ app/templates/modal/terrain_randomise.jade | 14 ++ .../modal/terrain_randomise_modal.coffee | 29 ++++ 3 files changed, 191 insertions(+) create mode 100644 app/styles/terrain_randomise.sass create mode 100644 app/templates/modal/terrain_randomise.jade create mode 100644 app/views/modal/terrain_randomise_modal.coffee diff --git a/app/styles/terrain_randomise.sass b/app/styles/terrain_randomise.sass new file mode 100644 index 000000000..3022f10c1 --- /dev/null +++ b/app/styles/terrain_randomise.sass @@ -0,0 +1,148 @@ +#terrain-randomise-modal + #noob-view p + font-size: 30px + + #skip-tutorial-button + font-size: 16px + + .tutorial-suggestion + text-align: center + font-size: 18px + margin: 10px 0 30px + + .play-option + margin-bottom: 15px + width: 100% + height: 100px + overflow: hidden + background: white + border: 1px solid #333 + position: relative + + -webkit-transition: opacity 0.3s ease-in-out + -moz-transition: opacity 0.3s ease-in-out + -ms-transition: opacity 0.3s ease-in-out + -o-transition: opacity 0.3s ease-in-out + transition: opacity 0.3s ease-in-out + + opacity: 0.4 + + border-radius: 5px + .only-one + -webkit-transition: opacity 0.3s ease-in-out + -moz-transition: opacity 0.3s ease-in-out + -ms-transition: opacity 0.3s ease-in-out + -o-transition: opacity 0.3s ease-in-out + transition: opacity 0.3s ease-in-out + opacity: 0 + + .play-option:hover + opacity: 1 + .only-one + opacity: 1 + + .my-icon + position: relative + left: 0 + top: -10px + z-index: 1 + + .my-team-icon + height: 60px + position: relative + top: -10px + left: 10px + z-index: 0 + + .opponent-team-icon + height: 60px + position: relative + top: 10px + right: 10px + z-index: 0 + float: right + -moz-transform: scaleX(-1) + -o-transform: scaleX(-1) + -webkit-transform: scaleX(-1) + transform: scaleX(-1) + filter: FlipH + -ms-filter: "FlipH" + + .opponent-icon + position: relative + float: right + right: 0 + top: -10px + -moz-transform: scaleX(-1) + -o-transform: scaleX(-1) + -webkit-transform: scaleX(-1) + transform: scaleX(-1) + filter: FlipH + -ms-filter: "FlipH" + z-index: 1 + + .name-label + border-bottom: 20px solid lightslategray + height: 0 + width: 40% + position: absolute + bottom: 0 + color: black + font-weight: bold + text-align: center + z-index: 2 + + span + position: relative + top: 1px + + .my-name + border-right: 15px solid transparent + left: 0 + span + left: 3px + + .preset-size + border-left: 15px solid transparent + right: 0 + //text-align: right + span + right: 3px + + .preset-name + border-top: 25px solid darkgray + border-left: 20px solid transparent + border-right: 20px solid transparent + height: 0 + width: 30% + position: absolute + left: 35% + top: 0 + color: black + text-align: center + font-size: 18px + font-weight: bold + + span + position: relative + top: -25px + + .easy-option .preset-name + border-top: 25px solid limegreen + + .medium-option .preset-name + border-top: 25px solid darkorange + + .hard-option .preset-name + border-top: 25px solid black + color: white + + .vs + position: absolute + left: 40% + right: 40% + text-align: center + top: 35px + font-size: 40px + font-weight: bolder + color: black \ No newline at end of file diff --git a/app/templates/modal/terrain_randomise.jade b/app/templates/modal/terrain_randomise.jade new file mode 100644 index 000000000..e2bbd23f6 --- /dev/null +++ b/app/templates/modal/terrain_randomise.jade @@ -0,0 +1,14 @@ +extends /templates/modal/modal_base + +block modal-header-content + h3(data-i18n="editor.pick_a_terrain") Pick a Terrain + +block modal-body-content + div#normal-view + a(href="#") + div.play-option(data-preset-type="grassy", data-preset-size="small") + div.preset-size.name-label + span(data-i18n="ladder.small") Small + div.preset-name + span(data-i18n="ladder.grassy") Grassy + //- for model in models \ No newline at end of file diff --git a/app/views/modal/terrain_randomise_modal.coffee b/app/views/modal/terrain_randomise_modal.coffee new file mode 100644 index 000000000..6dba35042 --- /dev/null +++ b/app/views/modal/terrain_randomise_modal.coffee @@ -0,0 +1,29 @@ +ModalView = require 'views/kinds/ModalView' +template = require 'templates/modal/terrain_randomise' +CocoModel = require 'models/CocoModel' + +module.exports = class TerrainRandomiseModal extends ModalView + id: 'terrain-randomise-modal' + template: template + thangs = [] + + events: + 'click .play-option': 'onRandomise' + + onRevertModel: (e) -> + id = $(e.target).val() + CocoModel.backedUp[id].revert() + $(e.target).closest('tr').remove() + @reloadOnClose = true + + onRandomise: (e) -> + + getRenderData: -> + c = super() + models = _.values CocoModel.backedUp + models = (m for m in models when m.hasLocalChanges()) + c.models = models + c + + onHidden: -> + location.reload() if @reloadOnClose From 813ecaa59c1350f05b2dd350bbf920b9ba6ff1f7 Mon Sep 17 00:00:00 2001 From: Jayant Jain Date: Tue, 24 Jun 2014 16:05:14 +0530 Subject: [PATCH 11/21] Adds randomise option to dropdown on editor page --- app/templates/editor/level/edit.jade | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/templates/editor/level/edit.jade b/app/templates/editor/level/edit.jade index d04b8f4bd..e446f7613 100644 --- a/app/templates/editor/level/edit.jade +++ b/app/templates/editor/level/edit.jade @@ -79,6 +79,8 @@ block header a(data-i18n="common.fork")#fork-level-start-button Fork li(class=anonymous ? "disabled": "") a(data-toggle="coco-modal", data-target="modal/revert", data-i18n="editor.revert")#revert-button Revert + li(class=anonymous ? "disabled": "") + a(data-toggle="coco-modal", data-target="modal/terrain_randomise", data-i18n="editor.randomise")#randomise-button Randomise li(class=anonymous ? "disabled": "") a(data-i18n="editor.pop_i18n")#pop-level-i18n-button Populate i18n li.divider From 1436a4dac75b9157218e0bfab278248c7e017ae8 Mon Sep 17 00:00:00 2001 From: Jayant Jain Date: Tue, 24 Jun 2014 16:15:39 +0530 Subject: [PATCH 12/21] Cleans up some css for terrain generator modal --- app/styles/terrain_randomise.sass | 28 ++++------------------ app/templates/modal/terrain_randomise.jade | 2 +- 2 files changed, 5 insertions(+), 25 deletions(-) diff --git a/app/styles/terrain_randomise.sass b/app/styles/terrain_randomise.sass index 3022f10c1..e6cf29bec 100644 --- a/app/styles/terrain_randomise.sass +++ b/app/styles/terrain_randomise.sass @@ -1,16 +1,6 @@ #terrain-randomise-modal - #noob-view p - font-size: 30px - - #skip-tutorial-button - font-size: 16px - - .tutorial-suggestion - text-align: center - font-size: 18px - margin: 10px 0 30px - - .play-option + + .choose-option margin-bottom: 15px width: 100% height: 100px @@ -36,7 +26,7 @@ transition: opacity 0.3s ease-in-out opacity: 0 - .play-option:hover + .choose-option:hover opacity: 1 .only-one opacity: 1 @@ -135,14 +125,4 @@ .hard-option .preset-name border-top: 25px solid black - color: white - - .vs - position: absolute - left: 40% - right: 40% - text-align: center - top: 35px - font-size: 40px - font-weight: bolder - color: black \ No newline at end of file + color: white \ No newline at end of file diff --git a/app/templates/modal/terrain_randomise.jade b/app/templates/modal/terrain_randomise.jade index e2bbd23f6..680af187c 100644 --- a/app/templates/modal/terrain_randomise.jade +++ b/app/templates/modal/terrain_randomise.jade @@ -6,7 +6,7 @@ block modal-header-content block modal-body-content div#normal-view a(href="#") - div.play-option(data-preset-type="grassy", data-preset-size="small") + div.choose-option(data-preset-type="grassy", data-preset-size="small") div.preset-size.name-label span(data-i18n="ladder.small") Small div.preset-name From 74205b31d2ce69a3558798f9bf1e259600947e66 Mon Sep 17 00:00:00 2001 From: Jayant Jain Date: Thu, 26 Jun 2014 18:32:31 +0530 Subject: [PATCH 13/21] Adds code for passing randomised thangs to thang view --- app/views/editor/level/thangs_tab_view.coffee | 6 ++++++ app/views/modal/terrain_randomise_modal.coffee | 13 ++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/views/editor/level/thangs_tab_view.coffee b/app/views/editor/level/thangs_tab_view.coffee index e2d0d4ac8..f68965bc3 100644 --- a/app/views/editor/level/thangs_tab_view.coffee +++ b/app/views/editor/level/thangs_tab_view.coffee @@ -43,6 +43,7 @@ module.exports = class ThangsTabView extends View 'sprite:mouse-up': 'onSpriteMouseUp' 'sprite:double-clicked': 'onSpriteDoubleClicked' 'surface:stage-mouse-up': 'onStageMouseUp' + 'randomise:terrain-generated': 'onRandomiseTerrain' events: 'click #extant-thangs-filter button': 'onFilterExtantThangs' @@ -223,6 +224,11 @@ module.exports = class ThangsTabView extends View return unless e.thang @editThang thangID: e.thang.id + onRandomiseTerrain: (e) -> + console.log e + for thang in e.thangs + console.log thang.id, thang.pos + # 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) -> @selectedExtantSprite?.setNameLabel? null unless @selectedExtantSprite is e.sprite diff --git a/app/views/modal/terrain_randomise_modal.coffee b/app/views/modal/terrain_randomise_modal.coffee index 6dba35042..0b5f96426 100644 --- a/app/views/modal/terrain_randomise_modal.coffee +++ b/app/views/modal/terrain_randomise_modal.coffee @@ -8,7 +8,7 @@ module.exports = class TerrainRandomiseModal extends ModalView thangs = [] events: - 'click .play-option': 'onRandomise' + 'click .choose-option': 'onRandomise' onRevertModel: (e) -> id = $(e.target).val() @@ -17,6 +17,17 @@ module.exports = class TerrainRandomiseModal extends ModalView @reloadOnClose = true onRandomise: (e) -> + @thangs = [] + @thangs.push { + 'id':'Grass01' + 'pos':{ + 'x':20 + 'y':20 + } + } + Backbone.Mediator.publish('randomise:terrain-generated', + 'thangs': @thangs + ) getRenderData: -> c = super() From 38bed0ee93649b8e67cc65416c25fa57f857a30c Mon Sep 17 00:00:00 2001 From: Jayant Jain Date: Thu, 26 Jun 2014 21:40:11 +0530 Subject: [PATCH 14/21] Random thangs are now created and positioned in the thangs view --- app/views/editor/level/thangs_tab_view.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/views/editor/level/thangs_tab_view.coffee b/app/views/editor/level/thangs_tab_view.coffee index f68965bc3..bcc398bbc 100644 --- a/app/views/editor/level/thangs_tab_view.coffee +++ b/app/views/editor/level/thangs_tab_view.coffee @@ -228,6 +228,8 @@ module.exports = class ThangsTabView extends View console.log e for thang in e.thangs console.log thang.id, thang.pos + @selectAddThangType thang.id + @addThang @addThangType, thang.pos # 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) -> @@ -280,6 +282,7 @@ module.exports = class ThangsTabView extends View @selectAddThang {target: icons[nextSelectedIndex]} selectAddThangType: (type, @cloneSourceThang) -> + console.log type if _.isString type type = _.find @supermodel.getModels(ThangType), (m) -> m.get('name') is type pos = @addThangSprite?.thang.pos # Maintain old sprite's pos if we have it @@ -397,6 +400,7 @@ module.exports = class ThangsTabView extends View @editThang thangID: id if id addThang: (thangType, pos) -> + console.log thangType, pos thangID = Thang.nextID(thangType.get('name'), @world) until thangID and not @thangsTreema.get "id=#{thangID}" if @cloneSourceThang components = _.cloneDeep @thangsTreema.get "id=#{@cloneSourceThang.id}/components" From b401b0489378d7b82d96615280e12b6dff0d0e41 Mon Sep 17 00:00:00 2001 From: Jayant Jain Date: Sat, 28 Jun 2014 10:20:55 +0530 Subject: [PATCH 15/21] Adds randomiseFloor and randomiseBorder functions --- app/templates/modal/terrain_randomise.jade | 2 +- .../modal/terrain_randomise_modal.coffee | 94 +++++++++++++++++-- 2 files changed, 88 insertions(+), 8 deletions(-) diff --git a/app/templates/modal/terrain_randomise.jade b/app/templates/modal/terrain_randomise.jade index 680af187c..799b4312c 100644 --- a/app/templates/modal/terrain_randomise.jade +++ b/app/templates/modal/terrain_randomise.jade @@ -6,7 +6,7 @@ block modal-header-content block modal-body-content div#normal-view a(href="#") - div.choose-option(data-preset-type="grassy", data-preset-size="small") + div.choose-option(data-preset-type="dungeon", data-preset-size="small") div.preset-size.name-label span(data-i18n="ladder.small") Small div.preset-name diff --git a/app/views/modal/terrain_randomise_modal.coffee b/app/views/modal/terrain_randomise_modal.coffee index 0b5f96426..35562ef81 100644 --- a/app/views/modal/terrain_randomise_modal.coffee +++ b/app/views/modal/terrain_randomise_modal.coffee @@ -2,6 +2,34 @@ ModalView = require 'views/kinds/ModalView' template = require 'templates/modal/terrain_randomise' CocoModel = require 'models/CocoModel' +presets = { + 'dungeon': { + 'type':'dungeon' + 'borders':['Dungeon Wall'] + 'floors':['Dungeon Floor'] + 'decorations':[] + } +} + +sizes = { + 'small': { + 'x':80 + 'y':68 + } + 'large': { + 'x':160 + 'y':136 + } + 'floorSize': { + 'x':20 + 'y':20 + } + 'borderSize': { + 'x':4 + 'y':4 + } +} + module.exports = class TerrainRandomiseModal extends ModalView id: 'terrain-randomise-modal' template: template @@ -18,16 +46,68 @@ module.exports = class TerrainRandomiseModal extends ModalView onRandomise: (e) -> @thangs = [] - @thangs.push { - 'id':'Grass01' - 'pos':{ - 'x':20 - 'y':20 - } - } + target = $(e.target) + presetType = target.attr 'data-preset-type' + presetSize = target.attr 'data-preset-size' + @randomiseThangs presetType, presetSize + Backbone.Mediator.publish('randomise:terrain-generated', 'thangs': @thangs ) + + randomiseThangs: (presetName, presetSize) -> + preset = presets[presetName] + presetSize = sizes[presetSize] + @thangs = [] + @randomiseFloor preset, presetSize + @randomiseBorder preset, presetSize + + randomiseFloor: (preset, presetSize) -> + for i in _.range(0, presetSize.x, sizes.floorSize.x) + for j in _.range(0, presetSize.y, sizes.floorSize.y) + @thangs.push { + 'id': @getRandomThang(preset.floors) + 'pos': { + 'x': i + 'y': j + } + } + + randomiseBorder: (preset, presetSize) -> + for i in _.range(0-sizes.floorSize.x/2+sizes.borderSize.x, presetSize.x-sizes.floorSize.x/2, sizes.borderSize.x) + @thangs.push { + 'id': @getRandomThang(preset.borders) + 'pos': { + 'x': i + 'y': 0-sizes.floorSize.x/2 + } + } + @thangs.push { + 'id': @getRandomThang(preset.borders) + 'pos': { + 'x': i + 'y': presetSize.y - sizes.borderSize.y + } + } + + for i in _.range(0-sizes.floorSize.y/2, presetSize.y-sizes.borderSize.y, sizes.borderSize.y) + @thangs.push { + 'id': @getRandomThang(preset.borders) + 'pos': { + 'x': 0-sizes.floorSize.x/2+sizes.borderSize.x + 'y': i + } + } + @thangs.push { + 'id': @getRandomThang(preset.borders) + 'pos': { + 'x': presetSize.x - sizes.borderSize.x - sizes.floorSize.x/2 + 'y': i + } + } + + getRandomThang: (thangList) -> + return thangList[_.random(0, thangList.length-1)] getRenderData: -> c = super() From dbe464df1637b490a1933a3d1de700624c6d83ca Mon Sep 17 00:00:00 2001 From: Jayant Jain Date: Sat, 28 Jun 2014 10:22:15 +0530 Subject: [PATCH 16/21] Removes console logs --- app/views/editor/level/thangs_tab_view.coffee | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/views/editor/level/thangs_tab_view.coffee b/app/views/editor/level/thangs_tab_view.coffee index bcc398bbc..010b38b50 100644 --- a/app/views/editor/level/thangs_tab_view.coffee +++ b/app/views/editor/level/thangs_tab_view.coffee @@ -225,9 +225,7 @@ module.exports = class ThangsTabView extends View @editThang thangID: e.thang.id onRandomiseTerrain: (e) -> - console.log e for thang in e.thangs - console.log thang.id, thang.pos @selectAddThangType thang.id @addThang @addThangType, thang.pos @@ -282,7 +280,6 @@ module.exports = class ThangsTabView extends View @selectAddThang {target: icons[nextSelectedIndex]} selectAddThangType: (type, @cloneSourceThang) -> - console.log type if _.isString type type = _.find @supermodel.getModels(ThangType), (m) -> m.get('name') is type pos = @addThangSprite?.thang.pos # Maintain old sprite's pos if we have it @@ -400,7 +397,6 @@ module.exports = class ThangsTabView extends View @editThang thangID: id if id addThang: (thangType, pos) -> - console.log thangType, pos thangID = Thang.nextID(thangType.get('name'), @world) until thangID and not @thangsTreema.get "id=#{thangID}" if @cloneSourceThang components = _.cloneDeep @thangsTreema.get "id=#{@cloneSourceThang.id}/components" From da0b0d9830d1c748ac35271ce73877995d1fdd31 Mon Sep 17 00:00:00 2001 From: Jayant Jain Date: Fri, 4 Jul 2014 01:04:21 +0530 Subject: [PATCH 17/21] Big terrain generator commit - 1.Adds method for randomising decorations/doodads 2.Creates presets 3.Adds thang clusters which can be used to create presets easily --- app/templates/modal/terrain_randomise.jade | 2 +- .../modal/terrain_randomise_modal.coffee | 89 +++++++++++++++++-- 2 files changed, 82 insertions(+), 9 deletions(-) diff --git a/app/templates/modal/terrain_randomise.jade b/app/templates/modal/terrain_randomise.jade index 799b4312c..680af187c 100644 --- a/app/templates/modal/terrain_randomise.jade +++ b/app/templates/modal/terrain_randomise.jade @@ -6,7 +6,7 @@ block modal-header-content block modal-body-content div#normal-view a(href="#") - div.choose-option(data-preset-type="dungeon", data-preset-size="small") + div.choose-option(data-preset-type="grassy", data-preset-size="small") div.preset-size.name-label span(data-i18n="ladder.small") Small div.preset-name diff --git a/app/views/modal/terrain_randomise_modal.coffee b/app/views/modal/terrain_randomise_modal.coffee index 35562ef81..e7bdc4a73 100644 --- a/app/views/modal/terrain_randomise_modal.coffee +++ b/app/views/modal/terrain_randomise_modal.coffee @@ -2,12 +2,51 @@ ModalView = require 'views/kinds/ModalView' template = require 'templates/modal/terrain_randomise' CocoModel = require 'models/CocoModel' +clusters = { + 'rocks': ['Rock 1', 'Rock 2', 'Rock 3', 'Rock 4', 'Rock 5', 'Rock Cluster 1', 'Rock Cluster 2', 'Rock Cluster 3'] + 'trees': ['Tree 1', 'Tree 2', 'Tree 3', 'Tree 4'] + 'shrubs': ['Shrub 1', 'Shrub 2', 'Shrub 3'] + 'houses': ['House 1', 'House 2', 'House 3', 'House 4'] + 'animals': ['Cow', 'Horse'] + 'wood': ['Firewood 1', 'Firewood 2', 'Firewood 3', 'Barrel'] + 'farm': ['Farm'] +} + presets = { - 'dungeon': { - 'type':'dungeon' - 'borders':['Dungeon Wall'] - 'floors':['Dungeon Floor'] - 'decorations':[] + # 'dungeon': { + # 'type':'dungeon' + # 'borders':['Dungeon Wall'] + # 'floors':['Dungeon Floor'] + # 'decorations':[] + # } + 'grassy': { + 'type':'grassy' + 'borders':['Tree 1', 'Tree 2', 'Tree 3'] + 'floors':['Grass01', 'Grass02', 'Grass03'] + 'decorations': { + 'house': { + 'num':[1,2] #min-max + 'width': 20 + 'height': 20 + 'clusters': { + 'houses':[1,1] + 'trees':[1,2] + 'shrubs':[0,3] + 'rocks':[1,2] + } + } + 'farm': { + 'num':[1,2] #min-max + 'width': 20 + 'height': 20 + 'clusters': { + 'farm':[1,1] + 'shrubs':[2,3] + 'wood':[2,4] + 'animals':[2,3] + } + } + } } } @@ -45,12 +84,12 @@ module.exports = class TerrainRandomiseModal extends ModalView @reloadOnClose = true onRandomise: (e) -> - @thangs = [] target = $(e.target) presetType = target.attr 'data-preset-type' presetSize = target.attr 'data-preset-size' @randomiseThangs presetType, presetSize - + # console.log target, target.attr 'data-preset-type' + # console.log target.attr 'data-preset-size' Backbone.Mediator.publish('randomise:terrain-generated', 'thangs': @thangs ) @@ -61,6 +100,8 @@ module.exports = class TerrainRandomiseModal extends ModalView @thangs = [] @randomiseFloor preset, presetSize @randomiseBorder preset, presetSize + @randomiseDecorations preset, presetSize + # console.log _.range(0, presetSize.x, sizes.floorSize) randomiseFloor: (preset, presetSize) -> for i in _.range(0, presetSize.x, sizes.floorSize.x) @@ -106,9 +147,41 @@ module.exports = class TerrainRandomiseModal extends ModalView } } + randomiseDecorations: (preset, presetSize)-> + console.log preset.decorations + for name, decoration of preset.decorations + console.log 'here', decoration + for num in _.range(_.random(decoration.num[0], decoration.num[1])) + center = + { + 'x':_.random(decoration.width, presetSize.x - decoration.width), + 'y':_.random(decoration.height, presetSize.y - decoration.height) + } + min = + { + 'x':center.x - decoration.width/2 + 'y':center.y - decoration.height/2 + } + max = + { + 'x':center.x + decoration.width/2 + 'y':center.y + decoration.height/2 + } + console.log center, min, max + for cluster, range of decoration.clusters + for i in _.range(_.random(range[0], range[1])) + @thangs.push { + 'id':@getRandomThang(clusters[cluster]) + 'pos':{ + 'x':_.random(min.x, max.x) + 'y':_.random(min.y, max.y) + } + } + + getRandomThang: (thangList) -> return thangList[_.random(0, thangList.length-1)] - + getRenderData: -> c = super() models = _.values CocoModel.backedUp From 5fb4f161d0e99c950ad1d08222095a9a5e9b0913 Mon Sep 17 00:00:00 2001 From: Jayant Jain Date: Thu, 10 Jul 2014 21:18:51 +0530 Subject: [PATCH 18/21] Removes console logs --- app/views/modal/terrain_randomise_modal.coffee | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/views/modal/terrain_randomise_modal.coffee b/app/views/modal/terrain_randomise_modal.coffee index e7bdc4a73..94b4676fb 100644 --- a/app/views/modal/terrain_randomise_modal.coffee +++ b/app/views/modal/terrain_randomise_modal.coffee @@ -88,8 +88,6 @@ module.exports = class TerrainRandomiseModal extends ModalView presetType = target.attr 'data-preset-type' presetSize = target.attr 'data-preset-size' @randomiseThangs presetType, presetSize - # console.log target, target.attr 'data-preset-type' - # console.log target.attr 'data-preset-size' Backbone.Mediator.publish('randomise:terrain-generated', 'thangs': @thangs ) @@ -101,7 +99,6 @@ module.exports = class TerrainRandomiseModal extends ModalView @randomiseFloor preset, presetSize @randomiseBorder preset, presetSize @randomiseDecorations preset, presetSize - # console.log _.range(0, presetSize.x, sizes.floorSize) randomiseFloor: (preset, presetSize) -> for i in _.range(0, presetSize.x, sizes.floorSize.x) @@ -148,9 +145,7 @@ module.exports = class TerrainRandomiseModal extends ModalView } randomiseDecorations: (preset, presetSize)-> - console.log preset.decorations for name, decoration of preset.decorations - console.log 'here', decoration for num in _.range(_.random(decoration.num[0], decoration.num[1])) center = { @@ -167,7 +162,6 @@ module.exports = class TerrainRandomiseModal extends ModalView 'x':center.x + decoration.width/2 'y':center.y + decoration.height/2 } - console.log center, min, max for cluster, range of decoration.clusters for i in _.range(_.random(range[0], range[1])) @thangs.push { From a76a79bb9d33c160d51657abf44dd3a0aeb0bcf6 Mon Sep 17 00:00:00 2001 From: Jayant Jain Date: Fri, 11 Jul 2014 01:07:03 +0530 Subject: [PATCH 19/21] Fixes bug with placing addThang after randomise --- app/views/editor/level/thangs_tab_view.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/editor/level/thangs_tab_view.coffee b/app/views/editor/level/thangs_tab_view.coffee index 010b38b50..e594b00be 100644 --- a/app/views/editor/level/thangs_tab_view.coffee +++ b/app/views/editor/level/thangs_tab_view.coffee @@ -228,6 +228,7 @@ module.exports = class ThangsTabView extends View for thang in e.thangs @selectAddThangType thang.id @addThang @addThangType, thang.pos + @selectAddThangType null # 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) -> From d6928eb1c4223876b99c22e44c598f7e4e7366d3 Mon Sep 17 00:00:00 2001 From: Jayant Jain Date: Fri, 11 Jul 2014 01:43:51 +0530 Subject: [PATCH 20/21] Removes okay button, hides modal upon generation --- app/templates/modal/terrain_randomise.jade | 3 ++- app/views/modal/terrain_randomise_modal.coffee | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/templates/modal/terrain_randomise.jade b/app/templates/modal/terrain_randomise.jade index 680af187c..9ac509c68 100644 --- a/app/templates/modal/terrain_randomise.jade +++ b/app/templates/modal/terrain_randomise.jade @@ -11,4 +11,5 @@ block modal-body-content span(data-i18n="ladder.small") Small div.preset-name span(data-i18n="ladder.grassy") Grassy - //- for model in models \ No newline at end of file + //- for model in models +block modal-footer diff --git a/app/views/modal/terrain_randomise_modal.coffee b/app/views/modal/terrain_randomise_modal.coffee index 94b4676fb..d737f929f 100644 --- a/app/views/modal/terrain_randomise_modal.coffee +++ b/app/views/modal/terrain_randomise_modal.coffee @@ -91,6 +91,7 @@ module.exports = class TerrainRandomiseModal extends ModalView Backbone.Mediator.publish('randomise:terrain-generated', 'thangs': @thangs ) + @hide() randomiseThangs: (presetName, presetSize) -> preset = presets[presetName] From cf63989ee3778fc09d482d6d9727bffb3312b0d8 Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Thu, 10 Jul 2014 14:18:35 -0700 Subject: [PATCH 21/21] Tweak to make sure errors are noticed. --- scripts/mongodb/migrations/2014-07-09-name-conflicts.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/mongodb/migrations/2014-07-09-name-conflicts.js b/scripts/mongodb/migrations/2014-07-09-name-conflicts.js index cf8c81f66..bf15c1a9e 100644 --- a/scripts/mongodb/migrations/2014-07-09-name-conflicts.js +++ b/scripts/mongodb/migrations/2014-07-09-name-conflicts.js @@ -48,5 +48,9 @@ db.users.find({anonymous:false}, params).sort({_id:1}).forEach(function (user) { slugs[slug] = user.name; if(user.slug) return; print(_.str.sprintf('Setting user %s (%s) to slug %s with update %s', user.name, user.dateCreated, slug, JSON.stringify({$set:update}))); - db.users.update({_id:user._id}, {$set:update}); + var res = db.users.update({_id:user._id}, {$set:update}); + if(res.hasWriteError()) { + print("\n\n\n\n\n\n\n\n\n\nOH NOOOOOOOOO\n\n\n\n\n\n\n"); + db.changedEmails.insert({email:user.email, user:user._id, name:user.name, error:true}); + } }); \ No newline at end of file