mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-05-04 18:03:45 -04:00
Got repeatables working flawlessly and improved the achievement popup by a great deal
This commit is contained in:
parent
bb57756cd9
commit
8d2c62ba84
11 changed files with 80 additions and 39 deletions
app
models
schemas/models
styles
views
server
|
@ -273,7 +273,7 @@ class CocoModel extends Backbone.Model
|
||||||
achievements = new NewAchievementCollection
|
achievements = new NewAchievementCollection
|
||||||
achievements.fetch(
|
achievements.fetch(
|
||||||
success: (collection) ->
|
success: (collection) ->
|
||||||
Backbone.Mediator.publish('achievements:new', collection) unless _.isEmpty(collection.models)
|
me.fetch (success: -> Backbone.Mediator.publish('achievements:new', collection)) unless _.isEmpty(collection.models)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -78,13 +78,17 @@ module.exports = class User extends CocoModel
|
||||||
a = 5
|
a = 5
|
||||||
b = 40
|
b = 40
|
||||||
|
|
||||||
# y = a * ln(1/b * (x + b))
|
# y = a * ln(1/b * (x + b)) + 1
|
||||||
@levelFromExp: (xp) ->
|
@levelFromExp: (xp) ->
|
||||||
if xp > 0 then Math.floor(a * Math.log((1/b) * (xp + b))) else 0
|
if xp > 0 then Math.floor(a * Math.log((1/b) * (xp + b))) + 1 else 1
|
||||||
|
|
||||||
# x = (e^(y/a) - 1) * b
|
# x = (e^((y-1)/a) - 1) * b
|
||||||
@expForLevel: (level) ->
|
@expForLevel: (level) ->
|
||||||
Math.ceil((Math.exp(level / a) - 1) * b)
|
Math.ceil((Math.exp((level - 1)/ a) - 1) * b)
|
||||||
|
|
||||||
level: ->
|
level: ->
|
||||||
User.levelFromExp(@get('points'))
|
User.levelFromExp(@get('points'))
|
||||||
|
|
||||||
|
levelFromExp: (xp) -> User.levelFromExp(xp)
|
||||||
|
|
||||||
|
expForLevel: (level) -> User.expForLevel(level)
|
||||||
|
|
|
@ -25,6 +25,7 @@ MongoFindQuerySchema =
|
||||||
oneOf: [
|
oneOf: [
|
||||||
#{ $ref: '#/definitions/' + MongoQueryOperatorSchema.id},
|
#{ $ref: '#/definitions/' + MongoQueryOperatorSchema.id},
|
||||||
{ type: 'string' }
|
{ type: 'string' }
|
||||||
|
{ type: 'object' }
|
||||||
]
|
]
|
||||||
additionalProperties: true # TODO make Treema accept new pattern matched keys
|
additionalProperties: true # TODO make Treema accept new pattern matched keys
|
||||||
definitions: {}
|
definitions: {}
|
||||||
|
@ -49,6 +50,24 @@ _.extend(AchievementSchema.properties,
|
||||||
proportionalTo:
|
proportionalTo:
|
||||||
type: 'string'
|
type: 'string'
|
||||||
description: 'For repeatables only. Denotes the field a repeatable achievement needs for its calculations'
|
description: 'For repeatables only. Denotes the field a repeatable achievement needs for its calculations'
|
||||||
|
function:
|
||||||
|
type: 'object'
|
||||||
|
oneOf: [
|
||||||
|
linear:
|
||||||
|
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
|
||||||
)
|
)
|
||||||
|
|
||||||
AchievementSchema.definitions = {}
|
AchievementSchema.definitions = {}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
.notifyjs-achievement-base
|
.notifyjs-achievement-base
|
||||||
background: url("/images/pages/base/notify_mockup.png")
|
//background: url("/images/pages/base/notify_mockup.png")
|
||||||
|
background-image: url("/images/pages/base/modal_background.png")
|
||||||
background-size: 100% 100%
|
background-size: 100% 100%
|
||||||
width: 500px
|
width: 500px
|
||||||
height: 200px
|
height: 200px
|
||||||
padding: 35px 20px 15px 15px
|
padding: 35px 35px 15px 15px
|
||||||
text-align: center
|
text-align: center
|
||||||
|
cursor: auto
|
||||||
|
|
||||||
.achievement-body
|
.achievement-body
|
||||||
.achievement-image
|
.achievement-image
|
||||||
|
@ -13,7 +15,10 @@
|
||||||
width: 100px
|
width: 100px
|
||||||
height: 100px
|
height: 100px
|
||||||
border-radius: 50%
|
border-radius: 50%
|
||||||
margin: 30px 40px 20px 30px
|
margin: 20px 30px 20px 30px
|
||||||
|
-webkit-box-shadow: 0px 0px 36px 0px white
|
||||||
|
-moz-box-shadow: 0px 0px 36px 0px white
|
||||||
|
box-shadow: 0px 0px 36px 0px white
|
||||||
|
|
||||||
.achievement-title
|
.achievement-title
|
||||||
font-family: Bangers
|
font-family: Bangers
|
||||||
|
@ -23,13 +28,14 @@
|
||||||
margin-top: 10px
|
margin-top: 10px
|
||||||
font-size: 16px
|
font-size: 16px
|
||||||
|
|
||||||
.achievement-message
|
|
||||||
&:empty
|
|
||||||
display: none
|
|
||||||
|
|
||||||
|
|
||||||
.achievement-progress
|
.achievement-progress
|
||||||
padding: 25px 0px 0px 0px
|
padding: 15px 0px 0px 0px
|
||||||
|
|
||||||
|
.achievement-message
|
||||||
|
font-family: Bangers
|
||||||
|
font-size: 18px
|
||||||
|
&:empty
|
||||||
|
display: none
|
||||||
|
|
||||||
.progress-wrapper
|
.progress-wrapper
|
||||||
.progress-bar-wrapper
|
.progress-bar-wrapper
|
||||||
|
@ -38,4 +44,4 @@
|
||||||
padding-left: 5px
|
padding-left: 5px
|
||||||
font-family: Bangers
|
font-family: Bangers
|
||||||
font-size: 16px
|
font-size: 16px
|
||||||
float: right
|
float: right
|
||||||
|
|
|
@ -48,6 +48,7 @@ module.exports = class AchievementEditView extends View
|
||||||
@treema = @$el.find('#achievement-treema').treema(options)
|
@treema = @$el.find('#achievement-treema').treema(options)
|
||||||
|
|
||||||
@treema.build()
|
@treema.build()
|
||||||
|
console.log @treema
|
||||||
|
|
||||||
pushChangesToPreview: =>
|
pushChangesToPreview: =>
|
||||||
'TODO' # TODO might want some intrinsic preview thing
|
'TODO' # TODO might want some intrinsic preview thing
|
||||||
|
@ -73,4 +74,4 @@ module.exports = class AchievementEditView extends View
|
||||||
|
|
||||||
res.success =>
|
res.success =>
|
||||||
url = "/editor/achievement/#{@achievement.get('slug') or @achievement.id}"
|
url = "/editor/achievement/#{@achievement.get('slug') or @achievement.id}"
|
||||||
document.location.href = url
|
document.location.href = url
|
||||||
|
|
|
@ -8,11 +8,18 @@ module.exports = class AchievementSearchView extends SearchView
|
||||||
tableTemplate: require 'templates/editor/achievement/table'
|
tableTemplate: require 'templates/editor/achievement/table'
|
||||||
projection: ['name', 'description', 'collection', 'slug']
|
projection: ['name', 'description', 'collection', 'slug']
|
||||||
|
|
||||||
|
initialize: ->
|
||||||
|
console.log me.isAdmin()
|
||||||
|
unless me.isAdmin()
|
||||||
|
NotFoundView = require '../../not_found'
|
||||||
|
return new NotFoundView
|
||||||
|
else super()
|
||||||
|
|
||||||
getRenderData: ->
|
getRenderData: ->
|
||||||
context = super()
|
context = super()
|
||||||
context.currentEditor = 'editor.achievement_title'
|
context.currentEditor = 'editor.achievement_title'
|
||||||
context.currentNew = 'editor.new_achievement_title'
|
context.currentNew = 'editor.new_achievement_title'
|
||||||
context.currentNewSignup = 'editor.new_achievement_title_signup'
|
context.currentNewSignup = 'editor.new_achievement_title_login'
|
||||||
context.currentSearch = 'editor.achievement_search_title'
|
context.currentSearch = 'editor.achievement_search_title'
|
||||||
@$el.i18n()
|
@$el.i18n()
|
||||||
context
|
context
|
||||||
|
|
|
@ -39,17 +39,24 @@ module.exports = class RootView extends CocoView
|
||||||
totalExpNeeded = nextLevelExp - currentLevelExp
|
totalExpNeeded = nextLevelExp - currentLevelExp
|
||||||
currentExp = me.get('points')
|
currentExp = me.get('points')
|
||||||
worth = achievement.get('worth')
|
worth = achievement.get('worth')
|
||||||
alreadyAchievedPercentage = 100 * (currentExp - currentLevelExp - achievement.get('worth')) / totalExpNeeded
|
leveledUp = currentExp - worth < currentLevelExp
|
||||||
newlyAchievedPercentage = 100 * achievement.get('worth') / totalExpNeeded
|
alreadyAchievedPercentage = 100 * (currentExp - currentLevelExp - worth) / totalExpNeeded
|
||||||
|
newlyAchievedPercentage = if currentLevelExp is currentExp then 0 else 100 * worth / totalExpNeeded
|
||||||
|
|
||||||
console.debug "Current level is #{currentLevel} (#{currentLevelExp} xp), next level is #{nextLevel} (#{nextLevelExp} xp)."
|
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}"
|
console.debug "Need a total of #{nextLevelExp - currentLevelExp}, already had #{currentExp - currentLevelExp - worth} and just now earned #{worth} totalling on #{currentExp}"
|
||||||
|
|
||||||
alreadyAchievedBar = $("<div class='progress-bar progress-bar-warning' style='width:#{alreadyAchievedPercentage}%'></div>")
|
alreadyAchievedBar = $("<div class='progress-bar progress-bar-warning' style='width:#{alreadyAchievedPercentage}%'></div>")
|
||||||
newlyAchievedBar = $("<div class='progress-bar progress-bar-success' style='width:#{newlyAchievedPercentage}%'></div>")
|
newlyAchievedBar = $("<div data-toggle='tooltip' class='progress-bar progress-bar-success' style='width:#{newlyAchievedPercentage}%'></div>")
|
||||||
progressBar = $('<div class="progress"></div>').append(alreadyAchievedBar).append(newlyAchievedBar)
|
emptyBar = $("<div data-toggle='tooltip' class='progress-bar progress-bar-white' style='width:#{100 - newlyAchievedPercentage - alreadyAchievedPercentage}%'></div>")
|
||||||
message = "Reached level #{currentLevel}!" if currentExp - worth < currentLevelExp
|
progressBar = $('<div class="progress" data-toggle="tooltip"></div>').append(alreadyAchievedBar).append(newlyAchievedBar).append(emptyBar)
|
||||||
|
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")
|
||||||
|
emptyBar.tooltip(title: "#{nextLevelExp - currentExp} XP until level #{nextLevel}")
|
||||||
|
|
||||||
|
# TODO a default should be linked here
|
||||||
imageURL = '/file/' + achievement.get('icon')
|
imageURL = '/file/' + achievement.get('icon')
|
||||||
data =
|
data =
|
||||||
title: achievement.get('name')
|
title: achievement.get('name')
|
||||||
|
@ -60,11 +67,11 @@ module.exports = class RootView extends CocoView
|
||||||
message: message
|
message: message
|
||||||
|
|
||||||
options =
|
options =
|
||||||
autoHideDelay: 15000
|
autoHideDelay: 10000
|
||||||
globalPosition: 'bottom right'
|
globalPosition: 'bottom right'
|
||||||
showDuration: 400
|
showDuration: 400
|
||||||
style: 'achievement'
|
style: 'achievement'
|
||||||
autoHide: false
|
autoHide: true
|
||||||
clickToHide: true
|
clickToHide: true
|
||||||
|
|
||||||
$.notify( data, options )
|
$.notify( data, options )
|
||||||
|
|
|
@ -29,7 +29,7 @@ AchievementSchema.pre('save', (next) ->
|
||||||
next()
|
next()
|
||||||
)
|
)
|
||||||
|
|
||||||
AchievementSchema.plugin(plugins.SearchablePlugin, {searchable: ['name']})
|
|
||||||
AchievementSchema.plugin(plugins.NamedPlugin)
|
AchievementSchema.plugin(plugins.NamedPlugin)
|
||||||
|
AchievementSchema.plugin(plugins.SearchablePlugin, {searchable: ['name']})
|
||||||
|
|
||||||
module.exports = Achievement = mongoose.model('Achievement', AchievementSchema)
|
module.exports = Achievement = mongoose.model('Achievement', AchievementSchema)
|
||||||
|
|
|
@ -159,7 +159,7 @@ module.exports = class Handler
|
||||||
return @sendNotFoundError(res)
|
return @sendNotFoundError(res)
|
||||||
term = req.query.term
|
term = req.query.term
|
||||||
matchedObjects = []
|
matchedObjects = []
|
||||||
filters = [{filter: {index: true}}]
|
filters = if @modelClass.schema.uses_coco_versions or @modelClass.schema.uses_coco_permissions then [filter: {index: true}] else [filter: {}]
|
||||||
if @modelClass.schema.uses_coco_permissions and req.user
|
if @modelClass.schema.uses_coco_permissions and req.user
|
||||||
filters.push {filter: {index: req.user.get('id')}}
|
filters.push {filter: {index: req.user.get('id')}}
|
||||||
projection = null
|
projection = null
|
||||||
|
|
|
@ -15,7 +15,6 @@ loadAchievements = ->
|
||||||
category = achievement.get 'collection'
|
category = achievement.get 'collection'
|
||||||
achievements[category] = [] unless category of achievements
|
achievements[category] = [] unless category of achievements
|
||||||
achievements[category].push achievement
|
achievements[category].push achievement
|
||||||
|
|
||||||
loadAchievements()
|
loadAchievements()
|
||||||
|
|
||||||
module.exports = AchievablePlugin = (schema, options) ->
|
module.exports = AchievablePlugin = (schema, options) ->
|
||||||
|
@ -62,11 +61,11 @@ module.exports = AchievablePlugin = (schema, options) ->
|
||||||
console.error err if err?
|
console.error err if err?
|
||||||
)
|
)
|
||||||
|
|
||||||
if isRepeatable # TODO test more thoroughly
|
if isRepeatable
|
||||||
console.log 'Upserting repeatable achievement called \'' + (achievement.get 'name') + '\' for ' + userID
|
console.log 'Upserting repeatable achievement called \'' + (achievement.get 'name') + '\' for ' + userID
|
||||||
proportionalTo = achievement.get 'proportionalTo'
|
proportionalTo = achievement.get 'proportionalTo'
|
||||||
originalAmount = util.getByPath(originalDocObj, proportionalTo) or 0
|
originalAmount = util.getByPath(originalDocObj, proportionalTo) or 0
|
||||||
newAmount = docObj.get proportionalTo
|
newAmount = docObj[proportionalTo]
|
||||||
|
|
||||||
if originalAmount isnt newAmount
|
if originalAmount isnt newAmount
|
||||||
earned.notified = false
|
earned.notified = false
|
||||||
|
@ -87,10 +86,5 @@ module.exports = AchievablePlugin = (schema, options) ->
|
||||||
earnedPoints = achievement.get('worth')
|
earnedPoints = achievement.get('worth')
|
||||||
wrapUp()
|
wrapUp()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
delete before[doc.id] unless isNew # This assumes everything we patch has a _id
|
delete before[doc.id] unless isNew # This assumes everything we patch has a _id
|
||||||
return
|
return
|
||||||
|
|
|
@ -127,3 +127,6 @@ UserSchema.statics.hashPassword = (password) ->
|
||||||
shasum.digest('hex')
|
shasum.digest('hex')
|
||||||
|
|
||||||
module.exports = User = mongoose.model('User', UserSchema)
|
module.exports = User = mongoose.model('User', UserSchema)
|
||||||
|
|
||||||
|
AchievablePlugin = require '../plugins/achievements'
|
||||||
|
UserSchema.plugin(AchievablePlugin)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue