mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-03-13 22:49:51 -04:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
00242f5767
13 changed files with 169 additions and 47 deletions
|
@ -110,7 +110,6 @@
|
|||
forgot_password: "Forgot your password?"
|
||||
authenticate_gplus: "Authenticate G+"
|
||||
load_profile: "Load G+ Profile"
|
||||
load_email: "Load G+ Email"
|
||||
finishing: "Finishing"
|
||||
sign_in_with_facebook: "Sign in with Facebook"
|
||||
sign_in_with_gplus: "Sign in with G+"
|
||||
|
|
|
@ -8,7 +8,7 @@ block modal-body-content
|
|||
.panel(class=achievement.earned ? 'earned' : '')
|
||||
.panel-body
|
||||
img.icon(src=achievement.getImageURL(), draggable="false")
|
||||
h3= achievement.name
|
||||
h3= achievement.name + (achievement.earned && achievement.earned.get('achievedAmount') ? (' - ' + achievement.earned.get('achievedAmount') + 'x') : '')
|
||||
p= achievement.description
|
||||
|
||||
if achievement.earnedDate
|
||||
|
@ -17,16 +17,15 @@ block modal-body-content
|
|||
.created(data-i18n="user.status_unfinished")
|
||||
|
||||
.rewards
|
||||
- rewards = achievement.get('rewards')
|
||||
- rewards = { gems: 100 }
|
||||
- rewards = achievement.get('rewards');
|
||||
if rewards && rewards.gems
|
||||
span.gems.label.label-default
|
||||
span= rewards.gems
|
||||
span= achievement.earnedGems || rewards.gems
|
||||
img.gem(src="/images/common/gem.png", draggable="false")
|
||||
|
||||
- worth = achievement.get('worth')
|
||||
- worth = achievement.get('worth');
|
||||
if worth
|
||||
span.worth.label.label-default
|
||||
span #{worth}xp
|
||||
span #{achievement.earnedPoints || worth}xp
|
||||
// maybe add more icons/numbers for items, heroes, levels, xp?
|
||||
block modal-footer
|
|
@ -268,6 +268,7 @@ module.exports = class HeroVictoryModal extends ModalView
|
|||
|
||||
updateXPBars: (achievedXP) ->
|
||||
previousXP = @previousXP
|
||||
previousXP = previousXP + 1000000 if me.isInGodMode()
|
||||
previousLevel = @previousLevel
|
||||
|
||||
currentXP = previousXP + achievedXP
|
||||
|
|
|
@ -443,6 +443,7 @@ module.exports = class SpellView extends CocoView
|
|||
# window.snippetEntries = snippetEntries
|
||||
lang = SpellView.editModes[e.language].substr 'ace/mode/'.length
|
||||
@zatanna.addSnippets snippetEntries, lang
|
||||
@editorLang = lang
|
||||
|
||||
onMultiplayerChanged: ->
|
||||
if @session.get('multiplayer')
|
||||
|
@ -1091,6 +1092,7 @@ module.exports = class SpellView extends CocoView
|
|||
@destroyAceEditor(@ace)
|
||||
@debugView?.destroy()
|
||||
@toolbarView?.destroy()
|
||||
@zatanna.addSnippets [], @editorLang if @editorLang?
|
||||
$(window).off 'resize', @onWindowResize
|
||||
super()
|
||||
|
||||
|
|
|
@ -30,10 +30,11 @@ module.exports = class PlayAchievementsModal extends ModalView
|
|||
'i18n'
|
||||
'rewards'
|
||||
'collection'
|
||||
'function'
|
||||
])
|
||||
|
||||
earnedAchievementsFetcher = new CocoCollection([], {url: '/db/earned_achievement', model: EarnedAchievement})
|
||||
earnedAchievementsFetcher.setProjection([ 'achievement' ])
|
||||
earnedAchievementsFetcher.setProjection ['achievement', 'achievedAmount']
|
||||
|
||||
achievementsFetcher.skip = 0
|
||||
achievementsFetcher.fetch cache: false, data: {skip: 0, limit: PAGE_SIZE}
|
||||
|
@ -75,6 +76,9 @@ module.exports = class PlayAchievementsModal extends ModalView
|
|||
if earned = @earnedMap[achievement.id]
|
||||
achievement.earned = earned
|
||||
achievement.earnedDate = earned.getCreationDate()
|
||||
expFunction = achievement.getExpFunction()
|
||||
achievement.earnedGems = Math.round (achievement.get('rewards')?.gems or 0) * expFunction earned.get('achievedAmount')
|
||||
achievement.earnedPoints = Math.round (achievement.get('worth', true) or 0) * expFunction earned.get('achievedAmount')
|
||||
achievement.earnedDate ?= ''
|
||||
@achievements.comparator = (m) -> m.earnedDate
|
||||
@achievements.sort()
|
||||
|
|
85
scripts/recreateEarnedAchievements.coffee
Normal file
85
scripts/recreateEarnedAchievements.coffee
Normal file
|
@ -0,0 +1,85 @@
|
|||
database = require '../server/commons/database'
|
||||
mongoose = require 'mongoose'
|
||||
log = require 'winston'
|
||||
async = require 'async'
|
||||
|
||||
### SET UP ###
|
||||
do (setupLodash = this) ->
|
||||
GLOBAL._ = require 'lodash'
|
||||
_.str = require 'underscore.string'
|
||||
_.mixin _.str.exports()
|
||||
GLOBAL.tv4 = require('tv4').tv4
|
||||
|
||||
database.connect()
|
||||
|
||||
LocalMongo = require '../app/lib/LocalMongo'
|
||||
User = require '../server/users/User'
|
||||
EarnedAchievement = require '../server/achievements/EarnedAchievement'
|
||||
Achievement = require '../server/achievements/Achievement'
|
||||
Achievement.loadAchievements (achievementCategories) ->
|
||||
# Really, it's just the 'users' category, since we don't keep all the LevelSession achievements in memory, rather letting the clients make those.
|
||||
userAchievements = achievementCategories.users
|
||||
console.log 'There are', userAchievements.length, 'user achievements.'
|
||||
|
||||
t0 = new Date().getTime()
|
||||
total = 100000
|
||||
#testUsers = ['livelily+test31@gmail.com', 'livelily+test37@gmail.com']
|
||||
if testUsers?
|
||||
userQuery = emailLower: {$in: testUsers}
|
||||
else
|
||||
userQuery = anonymous: false
|
||||
User.count userQuery, (err, count) -> total = count
|
||||
|
||||
onFinished = ->
|
||||
t1 = new Date().getTime()
|
||||
runningTime = ((t1-t0)/1000/60/60).toFixed(2)
|
||||
console.log "we finished in #{runningTime} hours"
|
||||
process.exit()
|
||||
|
||||
# Fetch every single user. This tends to get big so do it in a streaming fashion.
|
||||
userStream = User.find(userQuery).sort('_id').stream()
|
||||
streamFinished = false
|
||||
usersTotal = 0
|
||||
usersFinished = 0
|
||||
totalAchievementsExisting = 0
|
||||
totalAchievementsCreated = 0
|
||||
numberRunning = 0
|
||||
doneWithUser = ->
|
||||
++usersFinished
|
||||
numberRunning -= 1
|
||||
userStream.resume()
|
||||
onFinished?() if streamFinished and usersFinished is usersTotal
|
||||
|
||||
userStream.on 'error', (err) -> log.error err
|
||||
userStream.on 'close', -> streamFinished = true
|
||||
userStream.on 'data', (user) ->
|
||||
++usersTotal
|
||||
numberRunning += 1
|
||||
userStream.pause() if numberRunning > 20
|
||||
userID = user.get('_id').toHexString()
|
||||
userObject = user.toObject()
|
||||
|
||||
# Fetch all of a user's earned achievements
|
||||
EarnedAchievement.find {user: userID}, (err, alreadyEarnedAchievements) ->
|
||||
log.error err if err
|
||||
|
||||
achievementsExisting = 0
|
||||
achievementsCreated = 0
|
||||
for achievement in userAchievements
|
||||
#console.log "Testing", achievement.get('name'), achievement.get('_id') if testUsers?
|
||||
shouldBeAchieved = LocalMongo.matchesQuery userObject, achievement.get('query')
|
||||
continue unless shouldBeAchieved # Could delete existing ones that shouldn't be achieved if we wanted.
|
||||
earnedAchievement = _.find(alreadyEarnedAchievements, (ea) -> ea.get('user') is userID and ea.get('achievement') is achievement.get('_id').toHexString())
|
||||
if earnedAchievement
|
||||
#console.log "... already earned #{achievement.get('name')} #{achievement.get('_id')} for user: #{user.get('name')} #{user.get('_id')}" if testUsers?
|
||||
++achievementsExisting
|
||||
continue
|
||||
#console.log "Making an achievement: #{achievement.get('name')} #{achievement.get('_id')} for user: #{user.get('name')} #{user.get('_id')}" if testUsers?
|
||||
++achievementsCreated
|
||||
EarnedAchievement.createForAchievement achievement, user
|
||||
|
||||
totalAchievementsExisting += achievementsExisting
|
||||
totalAchievementsCreated += achievementsCreated
|
||||
pctDone = (100 * usersFinished / total).toFixed(2)
|
||||
console.log "Created #{achievementsCreated}, existing #{achievementsExisting} EarnedAchievements for #{user.get('name') or '???'} (#{user.get('_id')}) (#{pctDone}%, totals #{totalAchievementsExisting} existing, #{totalAchievementsCreated} created)"
|
||||
doneWithUser()
|
|
@ -47,7 +47,7 @@ AchievementSchema.methods.getExpFunction = ->
|
|||
return utils.functionCreators[func.kind](func.parameters) if func.kind of utils.functionCreators
|
||||
|
||||
AchievementSchema.statics.jsonschema = jsonschema
|
||||
AchievementSchema.statics.earnedAchievements = {}
|
||||
AchievementSchema.statics.achievementCollections = {}
|
||||
|
||||
# Reloads all achievements into memory.
|
||||
# TODO might want to tweak this to only load new achievements
|
||||
|
@ -57,16 +57,21 @@ AchievementSchema.statics.loadAchievements = (done) ->
|
|||
query = Achievement.find({collection: {$ne: 'level.sessions'}})
|
||||
query.exec (err, docs) ->
|
||||
_.each docs, (achievement) ->
|
||||
category = achievement.get 'collection'
|
||||
AchievementSchema.statics.earnedAchievements[category] = [] unless category of AchievementSchema.statics.earnedAchievements
|
||||
AchievementSchema.statics.earnedAchievements[category].push achievement
|
||||
done?(AchievementSchema.statics.earnedAchievements)
|
||||
collection = achievement.get 'collection'
|
||||
AchievementSchema.statics.achievementCollections[collection] ?= []
|
||||
if _.find AchievementSchema.statics.achievementCollections[collection], ((a) -> a.get('_id').toHexString() is achievement.get('_id').toHexString())
|
||||
log.warn "Uh oh, we tried to add another copy of the same achievement #{achievement.get('_id')} #{achievement.get('name')} to the #{collection} achievement list..."
|
||||
else
|
||||
AchievementSchema.statics.achievementCollections[collection].push achievement
|
||||
unless achievement.get('query')
|
||||
log.error "Uh oh, there is an achievement with an empty query: #{achievement}"
|
||||
done?(AchievementSchema.statics.achievementCollections)
|
||||
|
||||
AchievementSchema.statics.getLoadedAchievements = ->
|
||||
AchievementSchema.statics.earnedAchievements
|
||||
AchievementSchema.statics.achievementCollections
|
||||
|
||||
AchievementSchema.statics.resetAchievements = ->
|
||||
delete AchievementSchema.statics.earnedAchievements[category] for category of AchievementSchema.statics.earnedAchievements
|
||||
delete AchievementSchema.statics.achievementCollections[collection] for collection of AchievementSchema.statics.achievementCollections
|
||||
|
||||
# Queries are stored as JSON strings, objectify them upon loading
|
||||
AchievementSchema.post 'init', (doc) -> doc.objectifyQuery()
|
||||
|
@ -76,6 +81,7 @@ AchievementSchema.pre 'save', (next) ->
|
|||
next()
|
||||
|
||||
# Reload achievements upon save
|
||||
# This is going to basically not work when there is more than one application server, right?
|
||||
AchievementSchema.post 'save', -> @constructor.loadAchievements()
|
||||
|
||||
AchievementSchema.plugin(plugins.NamedPlugin)
|
||||
|
|
|
@ -50,29 +50,36 @@ EarnedAchievementSchema.statics.createForAchievement = (achievement, doc, origin
|
|||
proportionalTo = achievement.get 'proportionalTo'
|
||||
docObj = doc.toObject()
|
||||
newAmount = util.getByPath(docObj, proportionalTo) or 0
|
||||
if previouslyEarnedAchievement
|
||||
originalAmount = previouslyEarnedAchievement.get('achievedAmount') or 0
|
||||
updateEarnedAchievement = (originalAmount) ->
|
||||
#console.log 'original amount is', originalAmount, 'and new amount is', newAmount, 'for', proportionalTo, 'with doc', docObj, 'and previously earned achievement amount', previouslyEarnedAchievement?.get('achievedAmount'), 'because we had originalDocObj', originalDocObj
|
||||
|
||||
if originalAmount isnt newAmount
|
||||
expFunction = achievement.getExpFunction()
|
||||
earned.notified = false
|
||||
earned.achievedAmount = newAmount
|
||||
#console.log 'earnedPoints is', (expFunction(newAmount) - expFunction(originalAmount)) * pointWorth, 'was', earned.earnedPoints, earned.previouslyAchievedAmount, 'got exp function for new amount', newAmount, expFunction(newAmount), 'for original amount', originalAmount, expFunction(originalAmount), 'with point worth', pointWorth
|
||||
earnedPoints = earned.earnedPoints = (expFunction(newAmount) - expFunction(originalAmount)) * pointWorth
|
||||
earnedGems = earned.earnedGems = (expFunction(newAmount) - expFunction(originalAmount)) * gemWorth
|
||||
earned.previouslyAchievedAmount = originalAmount
|
||||
EarnedAchievement.update {achievement: earned.achievement, user: earned.user}, earned, {upsert: true}, (err) ->
|
||||
return log.error err if err?
|
||||
|
||||
wrapUp(new EarnedAchievement(earned))
|
||||
else
|
||||
done?()
|
||||
|
||||
if proportionalTo is 'simulatedBy' and newAmount > 0 and not previouslyEarnedAchievement and Math.random() < 0.1
|
||||
# Because things like simulatedBy get updated with $inc and not the post-save plugin hook,
|
||||
# we (infrequently) fetch the previously earned achievement so we can really update.
|
||||
EarnedAchievement.findOne {user: earned.user, achievement: earned.achievement}, (err, previouslyEarnedAchievement) ->
|
||||
log.error err if err?
|
||||
updateEarnedAchievement previouslyEarnedAchievement?.get('achievedAmount') or 0
|
||||
else if previouslyEarnedAchievement
|
||||
updateEarnedAchievement previouslyEarnedAchievement.get('achievedAmount') or 0
|
||||
else if originalDocObj # This branch could get buggy if unchangedCopy tracking isn't working.
|
||||
originalAmount = util.getByPath(originalDocObj, proportionalTo) or 0
|
||||
updateEarnedAchievement util.getByPath(originalDocObj, proportionalTo) or 0
|
||||
else
|
||||
originalAmount = 0
|
||||
#console.log 'original amount is', originalAmount, 'and new amount is', newAmount, 'for', proportionalTo, 'with doc', docObj, 'and previously earned achievement amount', previouslyEarnedAchievement?.get('achievedAmount'), 'because we had originalDocObj', originalDocObj
|
||||
|
||||
if originalAmount isnt newAmount
|
||||
expFunction = achievement.getExpFunction()
|
||||
earned.notified = false
|
||||
earned.achievedAmount = newAmount
|
||||
#console.log 'earnedPoints is', (expFunction(newAmount) - expFunction(originalAmount)) * pointWorth, 'was', earned.earnedPoints, earned.previouslyAchievedAmount, 'got exp function for new amount', newAmount, expFunction(newAmount), 'for original amount', originalAmount, expFunction(originalAmount), 'with point worth', pointWorth
|
||||
earnedPoints = earned.earnedPoints = (expFunction(newAmount) - expFunction(originalAmount)) * pointWorth
|
||||
earnedGems = earned.earnedGems = (expFunction(newAmount) - expFunction(originalAmount)) * gemWorth
|
||||
earned.previouslyAchievedAmount = originalAmount
|
||||
EarnedAchievement.update {achievement: earned.achievement, user: earned.user}, earned, {upsert: true}, (err) ->
|
||||
return log.debug err if err?
|
||||
|
||||
#log.debug earnedPoints
|
||||
wrapUp(new EarnedAchievement(earned))
|
||||
else
|
||||
done?()
|
||||
updateEarnedAchievement 0
|
||||
|
||||
else # not alreadyAchieved
|
||||
#log.debug 'Creating a new earned achievement called \'' + (achievement.get 'name') + '\' for ' + userID
|
||||
|
|
|
@ -10,7 +10,7 @@ util = require '../../app/core/utils'
|
|||
|
||||
class EarnedAchievementHandler extends Handler
|
||||
modelClass: EarnedAchievement
|
||||
|
||||
|
||||
editableProperties: ['notified']
|
||||
|
||||
# Don't allow POSTs or anything yet
|
||||
|
@ -141,7 +141,12 @@ class EarnedAchievementHandler extends Handler
|
|||
recalculatingAll = true
|
||||
t0 = new Date().getTime()
|
||||
total = 100000
|
||||
User.count {anonymous:false}, (err, count) -> total = count
|
||||
#testUsers = ['livelily+test37@gmail.com']
|
||||
if testUsers?
|
||||
userQuery = emailLower: {$in: testUsers}
|
||||
else
|
||||
userQuery = anonymous: false
|
||||
User.count userQuery, (err, count) -> total = count
|
||||
|
||||
onFinished = ->
|
||||
t1 = new Date().getTime()
|
||||
|
@ -159,10 +164,10 @@ class EarnedAchievementHandler extends Handler
|
|||
Achievement.find filter, (err, achievements) ->
|
||||
callback?(err) if err?
|
||||
callback?(new Error 'No achievements to recalculate') unless achievements.length
|
||||
#log.info "Recalculating a total of #{achievements.length} achievements..."
|
||||
log.info "Recalculating a total of #{achievements.length} achievements..."
|
||||
|
||||
# Fetch every single user. This tends to get big so do it in a streaming fashion.
|
||||
userStream = User.find().sort('_id').stream()
|
||||
userStream = User.find(userQuery).sort('_id').stream()
|
||||
streamFinished = false
|
||||
usersTotal = 0
|
||||
usersFinished = 0
|
||||
|
@ -253,11 +258,11 @@ class EarnedAchievementHandler extends Handler
|
|||
log.error err if err
|
||||
#console.log 'User', user.get('name'), 'had newTotalPoints', newTotalPoints, 'and newTotalRewards', newTotalRewards, 'previousRewards', previousRewards
|
||||
return doneWithUser(user) unless newTotalPoints or newTotalRewards.gems or _.some(newTotalRewards, (r) -> r.length)
|
||||
# log.debug "Matched a total of #{newTotalPoints} new points"
|
||||
# log.debug "Incrementing score for these achievements with #{newTotalPoints - previousPoints}"
|
||||
#log.debug "Matched a total of #{newTotalPoints} new points"
|
||||
#log.debug "Incrementing score for these achievements with #{newTotalPoints - previousPoints}"
|
||||
pointDelta = newTotalPoints - previousPoints
|
||||
pctDone = (100 * usersFinished / total).toFixed(2)
|
||||
console.log "Updated points to #{newTotalPoints} (#{if pointDelta < 0 then '' else '+'}#{pointDelta}) for #{user.get('name') or '???'} (#{user.get('_id')}) (#{pctDone}%)"
|
||||
#console.log "Updated points to #{newTotalPoints} (#{if pointDelta < 0 then '' else '+'}#{pointDelta}) for #{user.get('name') or '???'} (#{user.get('_id')}) (#{pctDone}%)"
|
||||
if recalculatingAll
|
||||
update = {$set: {points: newTotalPoints, 'earned.gems': 0, 'earned.heroes': [], 'earned.items': [], 'earned.levels': []}}
|
||||
else
|
||||
|
|
|
@ -22,7 +22,18 @@ CampaignHandler = class CampaignHandler extends Handler
|
|||
jsonSchema: require '../../app/schemas/models/campaign.schema'
|
||||
|
||||
hasAccess: (req) ->
|
||||
req.method is 'GET' or req.user?.isAdmin()
|
||||
req.method in ['GET', 'PUT'] or req.user?.isAdmin()
|
||||
|
||||
hasAccessToDocument: (req, document, method=null) ->
|
||||
return true if req.user?.isAdmin()
|
||||
|
||||
if @modelClass.schema.uses_coco_translation_coverage and (method or req.method).toLowerCase() in ['post', 'put']
|
||||
return true if @isJustFillingTranslations(req, document)
|
||||
|
||||
if req.method is 'GET'
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
get: (req, res) ->
|
||||
return @sendForbiddenError(res) if not @hasAccess(req)
|
||||
|
|
|
@ -37,13 +37,13 @@ ThangTypeHandler = class ThangTypeHandler extends Handler
|
|||
]
|
||||
|
||||
hasAccess: (req) ->
|
||||
req.method in ['GET', 'PUT'] or req.user?.isAdmin()
|
||||
req.method in ['GET', 'POST'] or req.user?.isAdmin()
|
||||
|
||||
hasAccessToDocument: (req, document, method=null) ->
|
||||
method = (method or req.method).toLowerCase()
|
||||
return true if method is 'get'
|
||||
return true if req.user?.isAdmin()
|
||||
return true if method is 'put' and @isJustFillingTranslations(req, document)
|
||||
return true if method is 'post' and @isJustFillingTranslations(req, document)
|
||||
return
|
||||
|
||||
get: (req, res) ->
|
||||
|
|
|
@ -33,11 +33,12 @@ AchievablePlugin = (schema, options) ->
|
|||
for achievement in loadedAchievements[category]
|
||||
do (achievement) ->
|
||||
query = achievement.get('query')
|
||||
return log.warn("Empty achievement query for #{achievement.get('name')}.") if _.isEmpty query
|
||||
return log.error("Empty achievement query for #{achievement.get('name')}.") if _.isEmpty query
|
||||
isRepeatable = achievement.get('proportionalTo')?
|
||||
alreadyAchieved = if isNew then false else LocalMongo.matchesQuery doc.unchangedCopy, query
|
||||
newlyAchieved = LocalMongo.matchesQuery(docObj, query)
|
||||
return unless newlyAchieved and (not alreadyAchieved or isRepeatable)
|
||||
#log.info "Making an achievement: #{achievement.get('name')} #{achievement.get('_id')} for doc: #{doc.get('name')} #{doc.get('_id')}"
|
||||
EarnedAchievement.createForAchievement(achievement, doc, doc.unchangedCopy)
|
||||
|
||||
module.exports = AchievablePlugin
|
||||
|
|
|
@ -282,6 +282,8 @@ UserHandler = class UserHandler extends Handler
|
|||
if codeLanguage = req.user.get('aceConfig.language')
|
||||
codeLanguage = codeLanguage[0].toUpperCase() + codeLanguage.slice(1)
|
||||
emailParams['email_data']['codeLanguage'] = codeLanguage
|
||||
if senderEmail = req.user.get('email')
|
||||
emailParams['email_data']['senderEmail'] = senderEmail
|
||||
|
||||
# Type-specific email data
|
||||
if type is 'subscribe modal parent'
|
||||
|
|
Loading…
Reference in a new issue