mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-23 15:48:11 -05:00
Rechecked and added cool stuff for achievements
This commit is contained in:
parent
d4043ac3db
commit
871149b2bc
17 changed files with 201 additions and 112 deletions
|
@ -1,6 +1,9 @@
|
||||||
CocoCollection = require 'collections/CocoCollection'
|
CocoCollection = require 'collections/CocoCollection'
|
||||||
|
Achievement = require 'models/Achievement'
|
||||||
|
|
||||||
class NewAchievementCollection extends CocoCollection
|
class NewAchievementCollection extends CocoCollection
|
||||||
|
model: Achievement
|
||||||
|
|
||||||
initialize: (me = require('lib/auth').me) ->
|
initialize: (me = require('lib/auth').me) ->
|
||||||
@url = "/db/user/#{me.id}/achievements?notified=false"
|
@url = "/db/user/#{me.id}/achievements?notified=false"
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ module.exports = class Achievement extends CocoModel
|
||||||
urlRoot: '/db/achievement'
|
urlRoot: '/db/achievement'
|
||||||
|
|
||||||
isRepeatable: ->
|
isRepeatable: ->
|
||||||
@get('proportionalTo')?a
|
@get('proportionalTo')?
|
||||||
|
|
||||||
# TODO logic is duplicated in Mongoose Achievement schema
|
# TODO logic is duplicated in Mongoose Achievement schema
|
||||||
getExpFunction: ->
|
getExpFunction: ->
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
storage = require 'lib/storage'
|
storage = require 'lib/storage'
|
||||||
deltasLib = require 'lib/deltas'
|
deltasLib = require 'lib/deltas'
|
||||||
|
|
||||||
NewAchievementCollection = require '../collections/NewAchievementCollection'
|
|
||||||
|
|
||||||
class CocoModel extends Backbone.Model
|
class CocoModel extends Backbone.Model
|
||||||
idAttribute: '_id'
|
idAttribute: '_id'
|
||||||
loaded: false
|
loaded: false
|
||||||
|
@ -298,13 +296,15 @@ class CocoModel extends Backbone.Model
|
||||||
return if _.isString @url then @url else @url()
|
return if _.isString @url then @url else @url()
|
||||||
|
|
||||||
@pollAchievements: ->
|
@pollAchievements: ->
|
||||||
|
NewAchievementCollection = require '../collections/NewAchievementCollection' # Nasty mutual inclusion if put on top
|
||||||
|
console.debug 'Polling for new achievements'
|
||||||
achievements = new NewAchievementCollection
|
achievements = new NewAchievementCollection
|
||||||
achievements.fetch(
|
achievements.fetch
|
||||||
success: (collection) ->
|
success: (collection) ->
|
||||||
|
console.debug 'Polling for achievements success', collection
|
||||||
me.fetch (success: -> Backbone.Mediator.publish('achievements:new', collection)) unless _.isEmpty(collection.models)
|
me.fetch (success: -> Backbone.Mediator.publish('achievements:new', collection)) unless _.isEmpty(collection.models)
|
||||||
error: (collection, res, options) ->
|
error: (collection, res, options) ->
|
||||||
console.error 'Miserably failed to fetch unnotified achievements'
|
console.error 'Miserably failed to fetch unnotified achievements'
|
||||||
)
|
|
||||||
|
|
||||||
CocoModel.pollAchievements = _.debounce CocoModel.pollAchievements, 500
|
CocoModel.pollAchievements = _.debounce CocoModel.pollAchievements, 500
|
||||||
|
|
||||||
|
|
|
@ -58,9 +58,15 @@ module.exports = class SuperModel extends Backbone.Model
|
||||||
return res
|
return res
|
||||||
else
|
else
|
||||||
@addCollection collection
|
@addCollection collection
|
||||||
@listenToOnce collection, 'sync', (c) ->
|
onCollectionSynced = (c) ->
|
||||||
console.debug 'Registering collection', url
|
if collection.url is c.url
|
||||||
@registerCollection c
|
console.debug 'Registering collection', url, c
|
||||||
|
@registerCollection c
|
||||||
|
else
|
||||||
|
console.warn 'Sync triggered for collection', c
|
||||||
|
console.warn 'Yet got other object', c
|
||||||
|
@listenToOnce collection, 'sync', onCollectionSynced
|
||||||
|
@listenToOnce collection, 'sync', onCollectionSynced
|
||||||
res = @addModelResource(collection, name, fetchOptions, value)
|
res = @addModelResource(collection, name, fetchOptions, value)
|
||||||
res.load() if not (res.isLoading or res.isLoaded)
|
res.load() if not (res.isLoading or res.isLoaded)
|
||||||
return res
|
return res
|
||||||
|
|
|
@ -16,6 +16,10 @@ module.exports = class User extends CocoModel
|
||||||
super()
|
super()
|
||||||
@migrateEmails()
|
@migrateEmails()
|
||||||
|
|
||||||
|
onLoaded: ->
|
||||||
|
CocoModel.pollAchievements() # Check for achievements on login
|
||||||
|
super arguments...
|
||||||
|
|
||||||
isAdmin: ->
|
isAdmin: ->
|
||||||
permissions = @attributes['permissions'] or []
|
permissions = @attributes['permissions'] or []
|
||||||
return 'admin' in permissions
|
return 'admin' in permissions
|
||||||
|
@ -121,15 +125,16 @@ module.exports = class User extends CocoModel
|
||||||
isEmailSubscriptionEnabled: (name) -> (@get('emails') or {})[name]?.enabled
|
isEmailSubscriptionEnabled: (name) -> (@get('emails') or {})[name]?.enabled
|
||||||
|
|
||||||
a = 5
|
a = 5
|
||||||
b = 40
|
b = 100
|
||||||
|
c = b
|
||||||
|
|
||||||
# y = a * ln(1/b * (x + b)) + 1
|
# y = a * ln(1/b * (x + c)) + 1
|
||||||
@levelFromExp: (xp) ->
|
@levelFromExp: (xp) ->
|
||||||
if xp > 0 then Math.floor(a * Math.log((1/b) * (xp + b))) + 1 else 1
|
if xp > 0 then Math.floor(a * Math.log((1/b) * (xp + c))) + 1 else 1
|
||||||
|
|
||||||
# x = (e^((y-1)/a) - 1) * b
|
# x = b * e^((y-1)/a) - c
|
||||||
@expForLevel: (level) ->
|
@expForLevel: (level) ->
|
||||||
Math.ceil((Math.exp((level - 1)/ a) - 1) * b)
|
if level > 1 then Math.ceil Math.exp((level - 1)/ a) * b - c else 0
|
||||||
|
|
||||||
level: ->
|
level: ->
|
||||||
User.levelFromExp(@get('points'))
|
User.levelFromExp(@get('points'))
|
||||||
|
|
|
@ -229,25 +229,25 @@ _.extend UserSchema.properties,
|
||||||
levelSystemEdits: c.int()
|
levelSystemEdits: c.int()
|
||||||
levelComponentEdits: c.int()
|
levelComponentEdits: c.int()
|
||||||
thangTypeEdits: c.int()
|
thangTypeEdits: c.int()
|
||||||
'stats.patchesSubmitted': c.int
|
patchesSubmitted: c.int
|
||||||
description: 'Amount of patches submitted, not necessarily accepted'
|
description: 'Amount of patches submitted, not necessarily accepted'
|
||||||
'stats.patchesContributed': c.int
|
patchesContributed: c.int
|
||||||
description: 'Amount of patches submitted and accepted'
|
description: 'Amount of patches submitted and accepted'
|
||||||
'stats.patchesAccepted': c.int
|
patchesAccepted: c.int
|
||||||
description: 'Amount of patches accepted by the user as owner'
|
description: 'Amount of patches accepted by the user as owner'
|
||||||
# The below patches only apply to those that actually got accepted
|
# The below patches only apply to those that actually got accepted
|
||||||
'stats.totalTranslationPatches': c.int()
|
totalTranslationPatches: c.int()
|
||||||
'stats.totalMiscPatches': c.int()
|
totalMiscPatches: c.int()
|
||||||
'stats.articleTranslationPatches': c.int()
|
articleTranslationPatches: c.int()
|
||||||
'stats.articleMiscPatches': c.int()
|
articleMiscPatches: c.int()
|
||||||
'stats.levelTranslationPatches': c.int()
|
levelTranslationPatches: c.int()
|
||||||
'stats.levelMiscPatches': c.int()
|
levelMiscPatches: c.int()
|
||||||
'stats.levelComponentTranslationPatches': c.int()
|
levelComponentTranslationPatches: c.int()
|
||||||
'stats.levelComponentMiscPatches': c.int()
|
levelComponentMiscPatches: c.int()
|
||||||
'stats.levelSystemTranslationPatches': c.int()
|
levelSystemTranslationPatches: c.int()
|
||||||
'stats.levelSystemMiscPatches': c.int()
|
levelSystemMiscPatches: c.int()
|
||||||
'stats.thangTypeTranslationPatches': c.int()
|
thangTypeTranslationPatches: c.int()
|
||||||
'stats.thangTypeMiscPatches': c.int()
|
thangTypeMiscPatches: c.int()
|
||||||
|
|
||||||
|
|
||||||
c.extendBasicProperties UserSchema, 'user'
|
c.extendBasicProperties UserSchema, 'user'
|
||||||
|
|
|
@ -129,7 +129,8 @@
|
||||||
|
|
||||||
> .progress-bar-wrapper
|
> .progress-bar-wrapper
|
||||||
position: absolute
|
position: absolute
|
||||||
width: 331px
|
margin-left: 12px
|
||||||
|
width: 319px
|
||||||
height: 20px
|
height: 20px
|
||||||
z-index: 2
|
z-index: 2
|
||||||
|
|
||||||
|
@ -147,27 +148,27 @@
|
||||||
background-size: 100% 100%
|
background-size: 100% 100%
|
||||||
z-index: 1
|
z-index: 1
|
||||||
|
|
||||||
.notifyjs-achievement-wood-base
|
.notifyjs-achievement-wood-base, .achievement-wood
|
||||||
.achievement-icon
|
.achievement-icon
|
||||||
background: url("/images/achievements/border_wood.png") no-repeat
|
background: url("/images/achievements/border_wood.png") no-repeat
|
||||||
background-size: 100% 100%
|
background-size: 100% 100%
|
||||||
|
|
||||||
.notifyjs-achievement-stone-base
|
.notifyjs-achievement-stone-base, .achievement-stone
|
||||||
.achievement-icon
|
.achievement-icon
|
||||||
background: url("/images/achievements/border_stone.png") no-repeat
|
background: url("/images/achievements/border_stone.png") no-repeat
|
||||||
background-size: 100% 100%
|
background-size: 100% 100%
|
||||||
|
|
||||||
.notifyjs-achievement-silver-base
|
.notifyjs-achievement-silver-base, .achievement-silver
|
||||||
.achievement-icon
|
.achievement-icon
|
||||||
background: url("/images/achievements/border_silver.png") no-repeat
|
background: url("/images/achievements/border_silver.png") no-repeat
|
||||||
background-size: 100% 100%
|
background-size: 100% 100%
|
||||||
|
|
||||||
.notifyjs-achievement-gold-base
|
.notifyjs-achievement-gold-base, .achievement-gold
|
||||||
.achievement-icon
|
.achievement-icon
|
||||||
background: url("/images/achievements/border_gold.png") no-repeat
|
background: url("/images/achievements/border_gold.png") no-repeat
|
||||||
background-size: 100% 100%
|
background-size: 100% 100%
|
||||||
|
|
||||||
.notifyjs-achievement-diamond-base
|
.notifyjs-achievement-diamond-base, .achievement-diamond
|
||||||
.achievement-icon
|
.achievement-icon
|
||||||
background: url("/images/achievements/border_diamond.png") no-repeat
|
background: url("/images/achievements/border_diamond.png") no-repeat
|
||||||
background-size: 100% 100%
|
background-size: 100% 100%
|
||||||
|
@ -185,3 +186,11 @@
|
||||||
z-index: 1000
|
z-index: 1000
|
||||||
box-shadow: 0 0 0 1px black, 0 0 0 3px lightgrey, 0 0 0 4px black
|
box-shadow: 0 0 0 1px black, 0 0 0 3px lightgrey, 0 0 0 4px black
|
||||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif
|
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif
|
||||||
|
|
||||||
|
// Achievements page
|
||||||
|
h2.achievements-category
|
||||||
|
margin-left: 20px
|
||||||
|
|
||||||
|
.table-layout
|
||||||
|
#no-achievements
|
||||||
|
margin-top: 40px
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
div
|
div(class=notifyClass)
|
||||||
.clearfix.achievement-body
|
.clearfix.achievement-body(class=locked === true ? "locked" : "")
|
||||||
.achievement-icon(class=locked === true ? "locked" : "", class=border)
|
.achievement-icon
|
||||||
.achievement-image(data-notify-html="image")
|
.achievement-image(data-notify-html="image")
|
||||||
if imgURL
|
if imgURL
|
||||||
img(src=imgURL)
|
img(src=imgURL)
|
||||||
|
|
|
@ -51,7 +51,7 @@ body
|
||||||
.col-xs-4.text-center
|
.col-xs-4.text-center
|
||||||
a(href="/user/#{me.get('slug') || me.get('_id')}") Profile
|
a(href="/user/#{me.get('slug') || me.get('_id')}") Profile
|
||||||
.col-xs-4.text-center
|
.col-xs-4.text-center
|
||||||
a(href="#") Stats
|
a(href="/user/#{me.get('slug') || me.get('_id')}/stats") Stats
|
||||||
.col-xs-4.text-center
|
.col-xs-4.text-center
|
||||||
a.disabled() Code
|
a.disabled() Code
|
||||||
li.user-dropdown-footer
|
li.user-dropdown-footer
|
||||||
|
|
|
@ -3,14 +3,12 @@ extends /templates/base
|
||||||
// User pages might have some user page specific header, if not remove this
|
// User pages might have some user page specific header, if not remove this
|
||||||
block content
|
block content
|
||||||
.clearfix
|
.clearfix
|
||||||
//-
|
if user && viewName
|
||||||
if user && viewName
|
ol.breadcrumb
|
||||||
ol.breadcrumb
|
li
|
||||||
li
|
a(href="/user/#{user.id}") #{user.displayName()}
|
||||||
- var userName = user.get('name');
|
li.active
|
||||||
//_a(href="/user/#{user.id}") #{userName}
|
| #{viewName}
|
||||||
li.active
|
|
||||||
//-| #{viewName}
|
|
||||||
if !userLoaded
|
if !userLoaded
|
||||||
| LOADING
|
| LOADING
|
||||||
else if !user
|
else if !user
|
||||||
|
|
|
@ -1,12 +1,46 @@
|
||||||
extends /templates/kinds/user
|
extends /templates/kinds/user
|
||||||
|
|
||||||
block append content
|
block append content
|
||||||
if achievements
|
.btn-group.pull-right
|
||||||
.row
|
button#grid-layout-button.btn.btn-default(data-layout='grid', class=activeLayout==='grid' ? 'active' : '') Grid
|
||||||
each achievement, index in achievements
|
button#table-layout-button.btn.btn-default(data-layout='table', class=activeLayout==='table' ? 'active' : '') Table
|
||||||
- var title = achievement.get('name');
|
if achievementsByCategory
|
||||||
- var description = achievement.get('description');
|
if activeLayout === 'grid'
|
||||||
- var imgURL = achievement.get('icon');
|
.grid-layout
|
||||||
- var locked = ! achievement.get('unlocked');
|
each achievements, category in achievementsByCategory
|
||||||
.col-lg-4.col-xs-12
|
.row
|
||||||
include ../achievement_notify
|
h2.achievements-category=category
|
||||||
|
each achievement, index in achievements
|
||||||
|
- var title = achievement.get('name');
|
||||||
|
- var description = achievement.get('description');
|
||||||
|
- var imgURL = achievement.getImageURL();
|
||||||
|
- var locked = ! achievement.get('unlocked');
|
||||||
|
- var notifyClass = achievement.getNotifyStyle()
|
||||||
|
.col-lg-4.col-xs-12
|
||||||
|
include ../achievement_notify
|
||||||
|
else if activeLayout === 'table'
|
||||||
|
.table-layout
|
||||||
|
if earnedAchievements.length
|
||||||
|
table.table
|
||||||
|
tr
|
||||||
|
th Name
|
||||||
|
th Description
|
||||||
|
th Date
|
||||||
|
th Amount
|
||||||
|
th XP
|
||||||
|
each earnedAchievement in earnedAchievements
|
||||||
|
- var achievement = earnedAchievement.get('achievement');
|
||||||
|
tr
|
||||||
|
td= achievement.get('name')
|
||||||
|
td= achievement.get('description')
|
||||||
|
td= moment().format("MMMM Do YY", earnedAchievement.get('changed'))
|
||||||
|
if achievement.isRepeatable()
|
||||||
|
td= earnedAchievement.get('achievedAmount')
|
||||||
|
else
|
||||||
|
td
|
||||||
|
td= earnedAchievement.get('earnedPoints')
|
||||||
|
else
|
||||||
|
.panel#no-achievements
|
||||||
|
.panel-body No achievements earned yet.
|
||||||
|
else
|
||||||
|
div How did you even do that?
|
||||||
|
|
|
@ -8,6 +8,8 @@ locale = require 'locale/locale'
|
||||||
|
|
||||||
Achievement = require '../../models/Achievement'
|
Achievement = require '../../models/Achievement'
|
||||||
User = require '../../models/User'
|
User = require '../../models/User'
|
||||||
|
utils = require 'lib/utils'
|
||||||
|
|
||||||
# TODO remove
|
# TODO remove
|
||||||
|
|
||||||
filterKeyboardEvents = (allowedEvents, func) ->
|
filterKeyboardEvents = (allowedEvents, func) ->
|
||||||
|
@ -39,11 +41,15 @@ module.exports = class RootView extends CocoView
|
||||||
totalExpNeeded = nextLevelExp - currentLevelExp
|
totalExpNeeded = nextLevelExp - currentLevelExp
|
||||||
expFunction = achievement.getExpFunction()
|
expFunction = achievement.getExpFunction()
|
||||||
currentExp = me.get('points')
|
currentExp = me.get('points')
|
||||||
previousExp = currentExp - achievement.get('worth')
|
if achievement.isRepeatable()
|
||||||
previousExp = expFunction(earnedAchievement.get('previouslyAchievedAmount')) * achievement.get('worth') if achievement.isRepeatable()
|
achievedExp = expFunction(earnedAchievement.get('previouslyAchievedAmount')) * achievement.get('worth') if achievement.isRepeatable()
|
||||||
achievedExp = currentExp - previousExp
|
else
|
||||||
|
achievedExp = achievement.get 'worth'
|
||||||
|
previousExp = currentExp - achievedExp
|
||||||
leveledUp = currentExp - achievedExp < currentLevelExp
|
leveledUp = currentExp - achievedExp < currentLevelExp
|
||||||
|
console.debug 'Leveled up' if leveledUp
|
||||||
alreadyAchievedPercentage = 100 * (previousExp - currentLevelExp) / totalExpNeeded
|
alreadyAchievedPercentage = 100 * (previousExp - currentLevelExp) / totalExpNeeded
|
||||||
|
alreadyAchievedPercentage = 0 if alreadyAchievedPercentage < 0 # In case of level up
|
||||||
newlyAchievedPercentage = if leveledUp then 100 * (currentExp - currentLevelExp) / totalExpNeeded else 100 * achievedExp / 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 "Current level is #{currentLevel} (#{currentLevelExp} xp), next level is #{nextLevel} (#{nextLevelExp} xp)."
|
||||||
|
@ -92,25 +98,23 @@ module.exports = class RootView extends CocoView
|
||||||
data
|
data
|
||||||
|
|
||||||
showNewAchievement: (achievement, earnedAchievement) ->
|
showNewAchievement: (achievement, earnedAchievement) ->
|
||||||
data = createNotifyData achievement, earnedAchievement
|
data = @createNotifyData achievement, earnedAchievement
|
||||||
options =
|
options =
|
||||||
autoHideDelay: 10000
|
autoHideDelay: 1000000
|
||||||
globalPosition: 'bottom right'
|
globalPosition: 'bottom right'
|
||||||
showDuration: 400
|
showDuration: 400
|
||||||
style: achievement.getNotifyStyle()
|
style: achievement.getNotifyStyle()
|
||||||
autoHide: true
|
autoHide: false
|
||||||
clickToHide: true
|
clickToHide: true
|
||||||
|
|
||||||
|
console.debug 'showing achievement', achievement.get 'name'
|
||||||
$.notify( data, options )
|
$.notify( data, options )
|
||||||
|
|
||||||
handleNewAchievements: (earnedAchievements) ->
|
handleNewAchievements: (earnedAchievements) ->
|
||||||
_.each(earnedAchievements.models, (earnedAchievement) =>
|
_.each earnedAchievements.models, (earnedAchievement) =>
|
||||||
achievement = new Achievement(_id: earnedAchievement.get('achievement'))
|
achievement = new Achievement(_id: earnedAchievement.get('achievement'))
|
||||||
console.log achievement
|
achievement.fetch
|
||||||
achievement.fetch(
|
|
||||||
success: (achievement) => @showNewAchievement(achievement, earnedAchievement)
|
success: (achievement) => @showNewAchievement(achievement, earnedAchievement)
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
logoutAccount: ->
|
logoutAccount: ->
|
||||||
logoutUser($('#login-email').val())
|
logoutUser($('#login-email').val())
|
||||||
|
|
|
@ -9,6 +9,12 @@ EarnedAchievementCollection = require 'collections/EarnedAchievementCollection'
|
||||||
module.exports = class AchievementsView extends UserView
|
module.exports = class AchievementsView extends UserView
|
||||||
id: 'user-achievements-view'
|
id: 'user-achievements-view'
|
||||||
template: template
|
template: template
|
||||||
|
viewName: 'Stats'
|
||||||
|
activeLayout: 'grid'
|
||||||
|
|
||||||
|
events:
|
||||||
|
'click #grid-layout-button': 'layoutChanged'
|
||||||
|
'click #table-layout-button': 'layoutChanged'
|
||||||
|
|
||||||
constructor: (userID, options) ->
|
constructor: (userID, options) ->
|
||||||
super options, userID
|
super options, userID
|
||||||
|
@ -29,9 +35,20 @@ module.exports = class AchievementsView extends UserView
|
||||||
earned.set 'achievement', relatedAchievement
|
earned.set 'achievement', relatedAchievement
|
||||||
super()
|
super()
|
||||||
|
|
||||||
|
layoutChanged: (e) ->
|
||||||
|
@activeLayout = $(e.currentTarget).data 'layout'
|
||||||
|
@render()
|
||||||
|
|
||||||
getRenderData: ->
|
getRenderData: ->
|
||||||
context = super()
|
context = super()
|
||||||
|
context.activeLayout = @activeLayout
|
||||||
|
|
||||||
|
# After user is loaded
|
||||||
if @user and not @user.isAnonymous()
|
if @user and not @user.isAnonymous()
|
||||||
context.achievements = @achievements.models
|
|
||||||
context.earnedAchievements = @earnedAchievements.models
|
context.earnedAchievements = @earnedAchievements.models
|
||||||
|
context.achievements = @achievements.models
|
||||||
|
context.achievementsByCategory = {}
|
||||||
|
for achievement in @achievements.models
|
||||||
|
context.achievementsByCategory[achievement.get('category')] ?= []
|
||||||
|
context.achievementsByCategory[achievement.get('category')].push achievement
|
||||||
context
|
context
|
||||||
|
|
|
@ -19,7 +19,7 @@ achievements =
|
||||||
worth: 10
|
worth: 10
|
||||||
collection: 'users'
|
collection: 'users'
|
||||||
userField: '_id'
|
userField: '_id'
|
||||||
category: 'Miscellaneous'
|
category: 'miscellaneous'
|
||||||
difficulty: 1
|
difficulty: 1
|
||||||
|
|
||||||
completedFirstLevel:
|
completedFirstLevel:
|
||||||
|
@ -29,7 +29,17 @@ achievements =
|
||||||
worth: 50
|
worth: 50
|
||||||
collection: 'users'
|
collection: 'users'
|
||||||
userField: '_id'
|
userField: '_id'
|
||||||
category: 'Levels'
|
category: 'levels'
|
||||||
|
difficulty: 1
|
||||||
|
|
||||||
|
completedFiveLevels:
|
||||||
|
name: 'Completed one Level'
|
||||||
|
description: 'Completed your very first level.'
|
||||||
|
query: 'stats.gamesCompleted': $gte: 1
|
||||||
|
worth: 50
|
||||||
|
collection: 'users'
|
||||||
|
userField: '_id'
|
||||||
|
category: 'levels'
|
||||||
difficulty: 1
|
difficulty: 1
|
||||||
|
|
||||||
simulatedBy:
|
simulatedBy:
|
||||||
|
@ -39,7 +49,7 @@ achievements =
|
||||||
worth: 1
|
worth: 1
|
||||||
collection: 'users'
|
collection: 'users'
|
||||||
userField: '_id'
|
userField: '_id'
|
||||||
category: 'Miscellaneous'
|
category: 'miscellaneous'
|
||||||
difficulty: 1
|
difficulty: 1
|
||||||
proportionalTo: 'simulatedBy'
|
proportionalTo: 'simulatedBy'
|
||||||
function:
|
function:
|
||||||
|
|
|
@ -75,9 +75,9 @@ class EarnedAchievementHandler extends Handler
|
||||||
return doneWithAchievement()
|
return doneWithAchievement()
|
||||||
|
|
||||||
finalQuery = _.clone achievement.get 'query'
|
finalQuery = _.clone achievement.get 'query'
|
||||||
finalQuery.$or = [{}, {}] # Allow both ObjectIDs or hexa string IDs
|
finalQuery.$or = [{}, {}] # Allow both ObjectIDs or hex string IDs
|
||||||
finalQuery.$or[0][achievement.userField] = userID
|
finalQuery.$or[0][achievement.userField] = userID
|
||||||
finalQuery.$or[1][achievement.userField] = ObjectId userID
|
finalQuery.$or[1][achievement.userField] = mongoose.Types.ObjectId userID
|
||||||
|
|
||||||
model.findOne finalQuery, (err, something) ->
|
model.findOne finalQuery, (err, something) ->
|
||||||
return doneWithAchievement() if _.isEmpty something
|
return doneWithAchievement() if _.isEmpty something
|
||||||
|
|
|
@ -1,45 +1,48 @@
|
||||||
describe 'utils library', ->
|
describe 'Utility library', ->
|
||||||
util = require 'lib/utils'
|
util = require 'lib/utils'
|
||||||
|
|
||||||
beforeEach ->
|
describe 'i18n', ->
|
||||||
this.fixture1 =
|
beforeEach ->
|
||||||
'text': 'G\'day, Wizard! Come to practice? Well, let\'s get started...'
|
this.fixture1 =
|
||||||
'blurb': 'G\'day'
|
'text': 'G\'day, Wizard! Come to practice? Well, let\'s get started...'
|
||||||
'i18n':
|
'blurb': 'G\'day'
|
||||||
'es-419':
|
'i18n':
|
||||||
'text': '¡Buenas, Hechicero! ¿Vienes a practicar? Bueno, empecemos...'
|
'es-419':
|
||||||
'es-ES':
|
'text': '¡Buenas, Hechicero! ¿Vienes a practicar? Bueno, empecemos...'
|
||||||
'text': '¡Buenas Mago! ¿Vienes a practicar? Bien, empecemos...'
|
'es-ES':
|
||||||
'es':
|
'text': '¡Buenas Mago! ¿Vienes a practicar? Bien, empecemos...'
|
||||||
'text': '¡Buenas Mago! ¿Vienes a practicar? Muy bien, empecemos...'
|
'es':
|
||||||
'fr':
|
'text': '¡Buenas Mago! ¿Vienes a practicar? Muy bien, empecemos...'
|
||||||
'text': 'S\'lut, Magicien! Venu pratiquer? Ok, bien débutons...'
|
'fr':
|
||||||
'pt-BR':
|
'text': 'S\'lut, Magicien! Venu pratiquer? Ok, bien débutons...'
|
||||||
'text': 'Bom dia, feiticeiro! Veio praticar? Então vamos começar...'
|
'pt-BR':
|
||||||
'en':
|
'text': 'Bom dia, feiticeiro! Veio praticar? Então vamos começar...'
|
||||||
'text': 'Ohai Magician!'
|
'en':
|
||||||
'de':
|
'text': 'Ohai Magician!'
|
||||||
'text': '\'N Tach auch, Zauberer! Kommst Du zum Üben? Dann lass uns anfangen...'
|
'de':
|
||||||
'sv':
|
'text': '\'N Tach auch, Zauberer! Kommst Du zum Üben? Dann lass uns anfangen...'
|
||||||
'text': 'Godagens, trollkarl! Kommit för att öva? Nå, låt oss börja...'
|
'sv':
|
||||||
|
'text': 'Godagens, trollkarl! Kommit för att öva? Nå, låt oss börja...'
|
||||||
|
|
||||||
it 'i18n should find a valid target string', ->
|
it 'i18n should find a valid target string', ->
|
||||||
expect(util.i18n(this.fixture1, 'text', 'sv')).toEqual(this.fixture1.i18n['sv'].text)
|
expect(util.i18n(this.fixture1, 'text', 'sv')).toEqual(this.fixture1.i18n['sv'].text)
|
||||||
expect(util.i18n(this.fixture1, 'text', 'es-ES')).toEqual(this.fixture1.i18n['es-ES'].text)
|
expect(util.i18n(this.fixture1, 'text', 'es-ES')).toEqual(this.fixture1.i18n['es-ES'].text)
|
||||||
|
|
||||||
it 'i18n picks the correct fallback for a specific language', ->
|
it 'i18n picks the correct fallback for a specific language', ->
|
||||||
expect(util.i18n(this.fixture1, 'text', 'fr-be')).toEqual(this.fixture1.i18n['fr'].text)
|
expect(util.i18n(this.fixture1, 'text', 'fr-be')).toEqual(this.fixture1.i18n['fr'].text)
|
||||||
|
|
||||||
it 'i18n picks the correct fallback', ->
|
it 'i18n picks the correct fallback', ->
|
||||||
expect(util.i18n(this.fixture1, 'text', 'nl')).toEqual(this.fixture1.i18n['en'].text)
|
expect(util.i18n(this.fixture1, 'text', 'nl')).toEqual(this.fixture1.i18n['en'].text)
|
||||||
expect(util.i18n(this.fixture1, 'text', 'nl', 'de')).toEqual(this.fixture1.i18n['de'].text)
|
expect(util.i18n(this.fixture1, 'text', 'nl', 'de')).toEqual(this.fixture1.i18n['de'].text)
|
||||||
|
|
||||||
it 'i18n falls back to the default text, even for other targets (like blurb)', ->
|
it 'i18n falls back to the default text, even for other targets (like blurb)', ->
|
||||||
delete this.fixture1.i18n['en']
|
delete this.fixture1.i18n['en']
|
||||||
expect(util.i18n(this.fixture1, 'text', 'en')).toEqual(this.fixture1.text)
|
expect(util.i18n(this.fixture1, 'text', 'en')).toEqual(this.fixture1.text)
|
||||||
expect(util.i18n(this.fixture1, 'blurb', 'en')).toEqual(this.fixture1.blurb)
|
expect(util.i18n(this.fixture1, 'blurb', 'en')).toEqual(this.fixture1.blurb)
|
||||||
delete this.fixture1.blurb
|
delete this.fixture1.blurb
|
||||||
expect(util.i18n(this.fixture1, 'blurb', 'en')).toEqual(null)
|
expect(util.i18n(this.fixture1, 'blurb', 'en')).toEqual(null)
|
||||||
|
|
||||||
it 'i18n can fall forward if a general language is not found', ->
|
it 'i18n can fall forward if a general language is not found', ->
|
||||||
expect(util.i18n(this.fixture1, 'text', 'pt')).toEqual(this.fixture1.i18n['pt-BR'].text)
|
expect(util.i18n(this.fixture1, 'text', 'pt')).toEqual(this.fixture1.i18n['pt-BR'].text)
|
||||||
|
|
||||||
|
describe 'Miscellaneous utility', ->
|
||||||
|
|
|
@ -3,7 +3,8 @@ User = require 'models/User'
|
||||||
describe 'UserModel', ->
|
describe 'UserModel', ->
|
||||||
it 'experience functions are correct', ->
|
it 'experience functions are correct', ->
|
||||||
expect(User.expForLevel(User.levelFromExp 0)).toBe 0
|
expect(User.expForLevel(User.levelFromExp 0)).toBe 0
|
||||||
expect(User.expForLevel(User.levelFromExp 50)).toBe 50
|
expect(User.levelFromExp User.expForLevel 1).toBe 1
|
||||||
|
expect(User.levelFromExp User.expForLevel 10).toBe 10
|
||||||
expect(User.expForLevel 1).toBe 0
|
expect(User.expForLevel 1).toBe 0
|
||||||
expect(User.expForLevel 2).toBeGreaterThan User.expForLevel 1
|
expect(User.expForLevel 2).toBeGreaterThan User.expForLevel 1
|
||||||
|
|
||||||
|
@ -13,4 +14,3 @@ describe 'UserModel', ->
|
||||||
|
|
||||||
me.set 'points', 50
|
me.set 'points', 50
|
||||||
expect(me.level()).toBe User.levelFromExp 50
|
expect(me.level()).toBe User.levelFromExp 50
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue