codecombat/server/models/EarnedAchievement.coffee
Scott Erickson 2fe28852b4 More achievement tweaks
* Clients check updated achievements as well as new ones
* Clients do not wait to keep checking
* Update achievement points along with everything else in EarnedAchievement.upsertFor
* Fix various bugs
2016-09-06 09:37:02 -07:00

128 lines
5 KiB
CoffeeScript

mongoose = require 'mongoose'
jsonschema = require '../../app/schemas/models/earned_achievement'
util = require '../../app/core/utils'
log = require 'winston'
co = require 'co'
errors = require '../commons/errors'
EarnedAchievementSchema = new mongoose.Schema({
notified:
type: Boolean
default: false
}, {strict:false})
EarnedAchievementSchema.pre 'save', (next) ->
@set('changed', new Date())
next()
EarnedAchievementSchema.index({user: 1, achievement: 1}, {unique: true, name: 'earned achievement index'})
EarnedAchievementSchema.index({user: 1, changed: -1}, {name: 'latest '})
EarnedAchievementSchema.statics.upsertFor = (achievement, trigger, earned, user) ->
if achievement.get('proportionalTo') and earned
earnedAchievementDoc = yield @createForAchievement(achievement, trigger, {previouslyEarnedAchievement: earned})
return earnedAchievementDoc or earned
else if earned
achievementEarned = achievement.get('rewards')
actuallyEarned = earned.get('earnedRewards')
oldPoints = earned.get('earnedPoints') ? 0
newPoints = achievement.get('worth') ? 10
if (not _.isEqual(achievementEarned, actuallyEarned)) or (oldPoints isnt newPoints)
earned.set('earnedRewards', achievementEarned)
earned.set('earnedPoints', newPoints)
yield earned.save()
# make sure user has all the levels, heroes, gems, items and points they should have
update = {$inc: { points: newPoints - oldPoints }}
for rewardType, rewards of achievement.get('rewards') ? {}
if rewardType is 'gems'
update.$inc['earned.gems'] = rewards - (actuallyEarned.gems ? 0)
else if rewards.length
update.$addToSet ?= {}
update.$addToSet["earned.#{rewardType}"] = { $each: rewards }
yield user.update(update)
return earned
else
earned = yield @createForAchievement(achievement, trigger)
if not earned
console.error "Couldn't create achievement", achievement, trigger
throw new errors.NotFound("Couldn't create achievement")
return earned
EarnedAchievementSchema.statics.createForAchievement = co.wrap (achievement, doc, options={}) ->
{ previouslyEarnedAchievement, originalDocObj } = options
User = require('./User')
userObjectID = doc.get(achievement.get('userField'))
userID = if _.isObject userObjectID then userObjectID.toHexString() else userObjectID # TODO: Migrate to ObjectIds
earnedAttrs = {
user: userID
achievement: achievement._id.toHexString()
achievementName: achievement.get 'name'
earnedRewards: achievement.get 'rewards'
}
pointWorth = achievement.get('worth') ? 10
gemWorth = achievement.get('rewards')?.gems ? 0
earnedPoints = 0
earnedGems = 0
earnedDoc = null
isRepeatable = achievement.get('proportionalTo')?
if isRepeatable
proportionalTo = achievement.get('proportionalTo')
docObj = doc.toObject()
newAmount = util.getByPath(docObj, proportionalTo) or 0
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.
previouslyEarnedAchievement = yield EarnedAchievement.findOne({user: earnedAttrs.user, achievement: earnedAttrs.achievement})
if previouslyEarnedAchievement
originalAmount = 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
else
originalAmount = 0
if originalAmount isnt newAmount
expFunction = achievement.getExpFunction()
earnedAttrs.notified = false
earnedAttrs.achievedAmount = newAmount
earnedPoints = earnedAttrs.earnedPoints = (expFunction(newAmount) - expFunction(originalAmount)) * pointWorth
earnedGems = earnedAttrs.earnedGems = (expFunction(newAmount) - expFunction(originalAmount)) * gemWorth ? 0
earnedAttrs.previouslyAchievedAmount = originalAmount
yield EarnedAchievement.update({achievement: earnedAttrs.achievement, user: earnedAttrs.user}, earnedAttrs, {upsert: true})
earnedDoc = new EarnedAchievement(earnedAttrs)
else # not alreadyAchieved
earnedAttrs.earnedPoints = pointWorth
earnedAttrs.earnedGems = gemWorth
earnedDoc = new EarnedAchievement(earnedAttrs)
yield earnedDoc.save()
earnedPoints = pointWorth
earnedGems = gemWorth
User.saveActiveUser(userID, "achievement")
if earnedDoc
update = {$inc: {points: earnedPoints, 'earned.gems': earnedGems}}
for rewardType, rewards of achievement.get('rewards') ? {}
continue if rewardType is 'gems'
if rewards.length
update.$addToSet ?= {}
update.$addToSet["earned.#{rewardType}"] = $each: rewards
yield User.update({_id: mongoose.Types.ObjectId(userID)}, update, {})
return earnedDoc
module.exports = EarnedAchievement = mongoose.model('EarnedAchievement', EarnedAchievementSchema)