mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-03-14 07:00:01 -04:00
Added support for diminished exp for repeatables. Needs tweaking though.
This commit is contained in:
parent
7096a07ce8
commit
a61d0e5569
9 changed files with 66 additions and 44 deletions
|
@ -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)))
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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 = {}
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -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) ->
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) ->
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue