Added support for diminished exp for repeatables. Needs tweaking though.

This commit is contained in:
Ruben Vereecken 2014-06-03 12:40:47 +02:00
parent 7096a07ce8
commit a61d0e5569
9 changed files with 66 additions and 44 deletions

View file

@ -75,3 +75,12 @@ module.exports.getByPath = (target, path) ->
return undefined unless piece of obj
obj = obj[piece]
obj
module.exports.round = _.curry (digits, n) ->
n = +n.toFixed(digits)
module.exports.createLinearFunc = (params) ->
(x) -> (params.a or 1) * x + (params.b or 0)
module.exports.createLogFunc = (params) ->
(x) -> (params.a or 1) * Math.log((params.b or 1) * (x + (params.c or 0)))

View file

@ -1,4 +1,5 @@
CocoModel = require './CocoModel'
util = require '../lib/utils'
module.exports = class Achievement extends CocoModel
@className: 'Achievement'
@ -6,4 +7,11 @@ module.exports = class Achievement extends CocoModel
urlRoot: '/db/achievement'
isRepeatable: ->
@get('proportionalTo')?
@get('proportionalTo')?
# TODO logic is duplicated in Mongoose Achievement schema
getExpFunction: ->
kind = @get('function')?.kind or @schema.function.default.kind
parameters = @get('function')?.parameters or @schema.function.default.parameters
funcCreator = if kind is 'linear' then util.createLinearFunc else if kind is 'logarithmic' then utils.createLogFunc
return funcCreator(parameters) if funcCreator?

View file

@ -52,22 +52,17 @@ _.extend(AchievementSchema.properties,
description: 'For repeatables only. Denotes the field a repeatable achievement needs for its calculations'
function:
type: 'object'
oneOf: [
linear:
properties:
kind: {enum: ['linear', 'logarithmic'], default: 'linear'}
parameters:
type: 'object'
properties:
a: {type: 'number', default: 1},
required: ['a']
description: 'f(x) = a * x'
logarithmic:
type:'object'
properties:
a: {type: 'number', default: 1}
b: {type: 'number', default: 1}
required: ['a', 'b']
description: 'f(x) = a * ln(1/b * (x + b))'
]
default: linear: a: 1
c: {type: 'number', default: 1}
default: {kind: 'linear', parameters: a: 1}
required: ['kind', 'parameters']
additionalProperties: false
)
AchievementSchema.definitions = {}

View file

@ -20,15 +20,10 @@ module.exports =
href: '/db/achievement/{($)}'
}
]
collection:
type: 'string'
achievementName:
type: 'string'
created:
type: 'date'
changed:
type: 'date'
achievedAmount:
type: 'number'
notified:
type: 'boolean'
collection: type: 'string'
achievementName: type: 'string'
created: type: 'date'
changed: type: 'date'
achievedAmount: type: 'number'
previouslyAchievedAmount: {type: 'number', default: 0}
notified: type: 'boolean'

View file

@ -41,20 +41,23 @@ module.exports = class RootView extends CocoView
#test = new Achievement(_id:'537ce4855c91b8d1dda7fda8')
#test.fetch(success:@showNewAchievement)
showNewAchievement: (achievement) ->
showNewAchievement: (achievement, earnedAchievement) ->
currentLevel = me.level()
nextLevel = currentLevel + 1
currentLevelExp = User.expForLevel(currentLevel)
nextLevelExp = User.expForLevel(nextLevel)
totalExpNeeded = nextLevelExp - currentLevelExp
expFunction = achievement.getExpFunction()
currentExp = me.get('points')
worth = achievement.get('worth')
leveledUp = currentExp - worth < currentLevelExp
alreadyAchievedPercentage = 100 * (currentExp - currentLevelExp - worth) / totalExpNeeded
newlyAchievedPercentage = if currentLevelExp is currentExp then 0 else 100 * worth / totalExpNeeded
previousExp = currentExp - achievement.get('worth')
previousExp = expFunction(earnedAchievement.get('previouslyAchievedAmount')) * achievement.get('worth') if achievement.isRepeatable()
achievedExp = currentExp - previousExp
leveledUp = currentExp - achievedExp < currentLevelExp
alreadyAchievedPercentage = 100 * (previousExp - currentLevelExp) / totalExpNeeded
newlyAchievedPercentage = if leveledUp then 100 * (currentExp - currentLevelExp) / totalExpNeeded else 100 * achievedExp / totalExpNeeded
console.debug "Current level is #{currentLevel} (#{currentLevelExp} xp), next level is #{nextLevel} (#{nextLevelExp} xp)."
console.debug "Need a total of #{nextLevelExp - currentLevelExp}, already had #{currentExp - currentLevelExp - worth} and just now earned #{worth} totalling on #{currentExp}"
console.debug "Need a total of #{nextLevelExp - currentLevelExp}, already had #{previousExp} and just now earned #{achievedExp} totalling on #{currentExp}"
alreadyAchievedBar = $("<div class='progress-bar progress-bar-warning' style='width:#{alreadyAchievedPercentage}%'></div>")
newlyAchievedBar = $("<div data-toggle='tooltip' class='progress-bar progress-bar-success' style='width:#{newlyAchievedPercentage}%'></div>")
@ -63,7 +66,7 @@ module.exports = class RootView extends CocoView
message = if (currentLevel isnt 1) and leveledUp then "Reached level #{currentLevel}!" else null
alreadyAchievedBar.tooltip(title: "#{currentExp} XP in total")
newlyAchievedBar.tooltip(title: "#{worth} XP earned")
newlyAchievedBar.tooltip(title: "#{achievedExp} XP earned")
emptyBar.tooltip(title: "#{nextLevelExp - currentExp} XP until level #{nextLevel}")
# TODO a default should be linked here
@ -73,7 +76,7 @@ module.exports = class RootView extends CocoView
image: $("<img src='#{imageURL}' />")
description: achievement.get('description')
progressBar: progressBar
earnedExp: "+ #{worth} XP"
earnedExp: "+ #{achievedExp} XP"
message: message
options =
@ -87,13 +90,11 @@ module.exports = class RootView extends CocoView
$.notify( data, options )
handleNewAchievements: (earnedAchievements) ->
console.debug 'Got new earned achievements'
# TODO performance?
_.each(earnedAchievements.models, (earnedAchievement) =>
achievement = new Achievement(_id: earnedAchievement.get('achievement'))
console.log achievement
achievement.fetch(
success: @showNewAchievement
success: (achievement) => @showNewAchievement(achievement, earnedAchievement)
)
)

View file

@ -1,6 +1,7 @@
mongoose = require('mongoose')
jsonschema = require('../../app/schemas/models/achievement')
log = require 'winston'
util = require '../../app/lib/utils'
# `pre` and `post` are not called for update operations executed directly on the database,
# including `Model.update`,`.findByIdAndUpdate`,`.findOneAndUpdate`, `.findOneAndRemove`,and `.findByIdAndRemove`.order
@ -11,16 +12,22 @@ AchievementSchema = new mongoose.Schema({
userField: String
}, {strict: false})
AchievementSchema.methods.objectifyQuery = () ->
AchievementSchema.methods.objectifyQuery = ->
try
@set('query', JSON.parse(@get('query'))) if typeof @get('query') == "string"
catch error
log.error "Couldn't convert query string to object because of #{error}"
@set('query', {})
AchievementSchema.methods.stringifyQuery = () ->
AchievementSchema.methods.stringifyQuery = ->
@set('query', JSON.stringify(@get('query'))) if typeof @get('query') != "string"
AchievementSchema.methods.getExpFunction = ->
kind = @get('function.kind') or jsonschema.function.default.kind
parameters = @get('function.parameters') or jsonschema.function.default.parameters
funcCreator = if kind is 'linear' then util.createLinearFunc else if kind is 'logarithmic' then util.createLogFunc
return funcCreator(parameters) if funcCreator?
AchievementSchema.post('init', (doc) -> doc.objectifyQuery())
AchievementSchema.pre('save', (next) ->

View file

@ -13,7 +13,11 @@ EarnedAchievementSchema = new mongoose.Schema({
default: false
}, {strict:false})
EarnedAchievementSchema.pre 'save', (next) ->
@set('changed', Date.now())
next()
EarnedAchievementSchema.index({user: 1, achievement: 1}, {unique: true, name: 'earned achievement index'})
EarnedAchievementSchema.index({user: 1, changed: -1}, {name: 'latest '})
module.exports = EarnedAchievement = mongoose.model('EarnedAchievement', EarnedAchievementSchema)
module.exports = EarnedAchievement = mongoose.model('EarnedAchievement', EarnedAchievementSchema)

View file

@ -5,7 +5,7 @@ class AchievementHandler extends Handler
modelClass: Achievement
# Used to determine which properties requests may edit
editableProperties: ['name', 'query', 'worth', 'collection', 'description', 'userField', 'proportionalTo', 'icon']
editableProperties: ['name', 'query', 'worth', 'collection', 'description', 'userField', 'proportionalTo', 'icon', 'function']
jsonSchema = require '../../app/schemas/models/achievement.coffee'
hasAccess: (req) ->

View file

@ -54,6 +54,8 @@ module.exports = AchievablePlugin = (schema, options) ->
achievement: achievement._id.toHexString()
achievementName: achievement.get 'name'
}
worth = achievement.get('worth')
earnedPoints = 0
wrapUp = ->
# Update user's experience points
@ -68,22 +70,23 @@ module.exports = AchievablePlugin = (schema, options) ->
newAmount = docObj[proportionalTo]
if originalAmount isnt newAmount
expFunction = achievement.getExpFunction()
earned.notified = false
earned.achievedAmount = newAmount
earned.changed = Date.now()
earned.previouslyAchievedAmount = originalAmount
EarnedAchievement.findOneAndUpdate({achievement:earned.achievement, user:earned.user}, earned, upsert:true, (err, docs) ->
return log.debug err if err?
)
earnedPoints = achievement.get('worth') * (newAmount - originalAmount)
earnedPoints = (expFunction(newAmount) - expFunction(originalAmount)) * worth
log.debug earnedPoints
wrapUp()
else # not alreadyAchieved
log.debug 'Creating a new earned achievement called \'' + (achievement.get 'name') + '\' for ' + userID
(new EarnedAchievement(earned)).save (err, doc) ->
return log.debug err if err?
earnedPoints = achievement.get('worth')
earnedPoints = worth
wrapUp()
delete before[doc.id] unless isNew # This assumes everything we patch has a _id