Added a bunch of achievements to the script. Restyled big parts.

This commit is contained in:
Ruben Vereecken 2014-08-03 23:58:51 +02:00
parent 871149b2bc
commit 26085f9f3e
15 changed files with 243 additions and 80 deletions

View file

@ -51,7 +51,7 @@ module.exports = class CocoRouter extends Backbone.Router
'editor': go('editor/MainEditorView')
'editor/achievement': go('editor/achievement/AchievementSearchView')
'editor/achievement': go('editor/achievement/AchievementEditView')
'editor/achievement/:articleID': go('editor/achievement/AchievementEditView')
'editor/article': go('editor/article/ArticleSearchView')
'editor/article/preview': go('editor/article/ArticlePreviewView')
'editor/article/:articleID': go('editor/article/ArticleEditView')

View file

@ -37,7 +37,7 @@ module.exports = class LevelSession extends CocoModel
return true if c1[thang][spell] isnt c2[thang]?[spell]
false
isMultiPlayer: ->
isMultiplayer: ->
console.log @get 'levelName'
console.log @
console.log @get 'team'

View file

@ -56,6 +56,9 @@ _.extend AchievementSchema.properties,
proportionalTo:
type: 'string'
description: 'For repeatables only. Denotes the field a repeatable achievement needs for its calculations'
recalculable:
type: 'boolean'
description: 'Needs to be set to true before it is elligible for recalculation.'
function:
type: 'object'
description: 'Function that gives total experience for X amount achieved'

View file

@ -1,7 +1,7 @@
.locked
filter: grayscale(100%)
-moz-filter: grayscale(100%)
-webkit-filter: grayscale(100%)
filter: desaturate(gray, 50)
-moz-filter: desaturate(gray, 50)
-webkit-filter: desaturate(gray, 50)
.achievement-body
.achievement-icon
@ -37,8 +37,10 @@
max-height: 2.6em
margin-top: auto
margin-bottom: 0px !important
padding-left: 8px
padding-left: 5px
text-overflow: ellipsis
// Specific to the user stats page
#user-achievements-view
.row
//.col-lg-4, .col-xs-12
@ -65,7 +67,7 @@
margin-right: 5px
width: 260px
height: 100px
padding: 20px 10px 20px 60px
padding: 15px 10px 20px 60px
.achievement-title
font-size: 20px

View file

@ -288,3 +288,9 @@ body[lang='ja']
a[data-toggle="coco-modal"]
cursor: pointer
.achievement-corner
position: fixed
bottom: 0px
right: 0px
z-index: 1001

View file

@ -106,13 +106,20 @@ block content
th Last Played
th Status
each session in recentlyPlayed
if session.get('levelName')
tr
td
a(href="/play/level/#{session.get('levelID')}")= session.get('levelName')
- var posturl = ''
- if (session.get('team')) posturl = '?team=' + session.get('team')
a(href="/play/level/#{session.get('levelID') + posturl}")= session.get('levelName') + (session.get('team') ? ' (' + session.get('team') + ')' : '')
td= moment(session.get('changed')).fromNow()
td
if session.get('state').complete === true
| Completed
td Completed
else if ! session.isMultiplayer()
td Unfinished
else
td
else
.panel.panel-default
.panel-body

View file

@ -73,6 +73,7 @@ body
.main-content-area
block content
p If this is showing, you dun goofed
.achievement-corner
block footer
.footer.clearfix

View file

@ -69,9 +69,9 @@ block append content
th Last Played
th Status
each session in singlePlayerSessions
if session.get('levelName')
tr
td
if session.get('levelName')
a(href="/play/level/#{session.get('levelID')}")= session.get('levelName')
td= moment(session.get('changed')).fromNow()
if session.get('state').complete === true

View file

@ -66,26 +66,6 @@ module.exports = class RootView extends CocoView
barBorder = $('<img src="/images/achievements/bar_border.png" />')
###
barBorder.hover (e) ->
#console.debug e
x = e.pageX
y = e.pageY
$actualHover = _.find [$('.progress-bar-warning'), $('.progress-bar-success'), $('.progress-bar-white')], (el) ->
offset = el.offset()
l = offset.left
t = offset.top
h = el.height() + 10
w = el.width() + 10
maxx = l + w
maxy = t + h
return (y <= maxy && y >= t) && (x <= maxx && x >= l) ? true : null
#console.debug $actualHover
$actualHover.trigger e if $actualHover
###
data =
title: achievement.get('name')
image: $("<img src='#{achievement.getImageURL()}' />")
@ -99,16 +79,25 @@ module.exports = class RootView extends CocoView
showNewAchievement: (achievement, earnedAchievement) ->
data = @createNotifyData achievement, earnedAchievement
console.debug data
options =
autoHideDelay: 1000000
autoHideDelay: 5000
elementPosition: 'top left'
globalPosition: 'bottom right'
showDuration: 400
showDuration: 0
style: achievement.getNotifyStyle()
autoHide: false
clickToHide: true
console.debug 'showing achievement', achievement.get 'name'
$.notify( data, options )
unless @timeout?
$.notify data, options
@timeout = 2000
else
setTimeout ->
$.notify data, options
@timeout += 2000
, @timeout
handleNewAchievements: (earnedAchievements) ->
_.each earnedAchievements.models, (earnedAchievement) =>

View file

@ -26,9 +26,8 @@ module.exports = class AchievementsView extends UserView
onLoaded: ->
console.log @earnedAchievements
console.log 'onLoaded'
console.log @achievements
_.each @earnedAchievements.models, (earned) =>
console.log earned
return unless relatedAchievement = _.find @achievements.models, (achievement) ->
achievement.get('_id') is earned.get 'achievement'
relatedAchievement.set 'unlocked', true

View file

@ -25,7 +25,7 @@ module.exports = class MainUserView extends UserView
multiPlayerSessions = []
languageCounts = {}
for levelSession in @levelSessions.models
if levelSession.isMultiPlayer()
if levelSession.isMultiplayer()
multiPlayerSessions.push levelSession
else
singlePlayerSessions.push levelSession

View file

@ -11,9 +11,21 @@ do (setupLodash = this) ->
database.connect()
achievements =
## Util
## Types
contributor = (obj) ->
_.extend obj, # This way we get the name etc on top
collection: 'users'
userField: '_id'
category: 'contributor'
### UNLOCKABLES ###
# Generally ordered according to user.stats schema
unlockableAchievements =
signup:
name: 'Signed up'
name: 'Signed Up'
description: 'Signed up to the most awesome coding game around.'
query: 'anonymous': false
worth: 10
@ -21,27 +33,155 @@ achievements =
userField: '_id'
category: 'miscellaneous'
difficulty: 1
recalculable: true
completedFirstLevel:
name: 'Completed one Level'
name: 'Completed 1 Level'
description: 'Completed your very first level.'
query: 'stats.gamesCompleted': $gte: 1
worth: 50
worth: 20
collection: 'users'
userField: '_id'
category: 'levels'
difficulty: 1
recalculable: true
completedFiveLevels:
name: 'Completed one Level'
description: 'Completed your very first level.'
query: 'stats.gamesCompleted': $gte: 1
name: 'Completed 5 Levels'
description: 'Completed 5 Levels.'
query: 'stats.gamesCompleted': $gte: 5
worth: 50
collection: 'users'
userField: '_id'
category: 'levels'
difficulty: 2
recalculable: true
completedTwentyLevels:
name: 'Completed 20 Levels'
description: 'Completed 20 Levels.'
query: 'stats.gamesCompleted': $gte: 20
worth: 500
collection: 'users'
userField: '_id'
category: 'levels'
difficulty: 3
recalculable: true
editedOneArticle: contributor
name: 'Edited an Article'
description: 'Edited your first Article.'
query: 'stats.articleEdits': $gte: 1
worth: 50
difficulty: 1
editedOneLevel: contributor
name: 'Edited a Level'
description: 'Edited your first Level.'
query: 'stats.levelEdits': $gte: 1
worth: 50
difficulty: 1
recalculable: true
editedOneLevelSystem: contributor
name: 'Edited a Level System'
description: 'Edited your first Level System.'
query: 'stats.levelSystemEdits': $gte: 1
worth: 50
difficulty: 1
recalculable: true
editedOneLevelComponent: contributor
name: 'Edited a Level Component'
description: 'Edited your first Level Component.'
query: 'stats.levelComponentEdits': $gte: 1
worth: 50
difficulty: 1
recalculable: true
editedOneThangType: contributor
name: 'Edited a Thang Type'
description: 'Edited your first Thang Type.'
query: 'stats.thangTypeEdits': $gte: 1
worth: 50
difficulty: 1
recalculable: true
submittedOnePatch: contributor
name: 'Submitted a Patch'
description: 'Submitted your very first patch.'
query: 'stats.patchesSubmitted': $gte: 1
worth: 50
difficulty: 1
recalculable: true
contributedOnePatch: contributor
name: 'Contributed a Patch'
description: 'Got your very first accepted Patch.'
query: 'stats.patchesContributed': $gte: 1
worth: 50
difficulty: 1
recalculable: true
acceptedOnePatch: contributor
name: 'Accepted a Patch'
description: 'Accepted your very first patch.'
query: 'stats.patchesAccepted': $gte: 1
worth: 50
difficulty: 1
recalculable: false
oneTranslationPatch: contributor
name: 'First Translation'
description: 'Did your very first translation.'
query: 'stats.totalTranslationPatches': $gte: 1
worth: 50
difficulty: 1
recalculable: true
oneMiscPatch: contributor
name: 'First Miscellaneous Patch'
description: 'Did your first miscellaneous patch.'
query: 'stats.totalMiscPatches': $gte: 1
worth: 50
difficulty: 1
recalculable: true
oneArticleTranslationPatch: contributor
name: 'First Article Translation'
description: 'Did your very first Article translation.'
query: 'stats.articleTranslationPatches': $gte: 1
worth: 50
difficulty: 1
recalculable: true
oneArticleMiscPatch: contributor
name: 'First Misc Article Patch'
description: 'Did your first miscellaneous Article patch.'
query: 'stats.totalMiscPatches': $gte: 1
worth: 50
difficulty: 1
recalculable: true
oneLevelTranslationPatch: contributor
name: 'First Level Translation'
description: 'Did your very first Level translation.'
query: 'stats.levelTranslationPatches': $gte: 1
worth: 50
difficulty: 1
recalculable: true
oneLevelMiscPatch: contributor
name: 'First Misc Level Patch'
description: 'Did your first misc Level patch.'
query: 'stats.levelMiscPatches': $gte: 1
worth: 50
difficulty: 1
recalculable: true
### REPEATABLES ###
repeatableAchievements =
simulatedBy:
name: 'Simulated ladder game'
description: 'Simulated a ladder game.'
@ -60,16 +200,30 @@ achievements =
c: 0
Achievement = require '../server/achievements/Achievement'
EarnedAchievement = require '../server/achievements/EarnedAchievement'
Achievement.remove {}, (err) ->
Achievement.find {}, (err, achievements) ->
achievementIDs = (achievement.get('_id') + '' for achievement in achievements)
EarnedAchievement.remove {achievement: $in: achievementIDs}, (err, count) ->
return log.error err if err?
log.info "Removed #{count} earned achievements that were related"
Achievement.remove {}, (err) ->
log.error err if err?
log.info 'Removed all achievements.'
log.info "Got #{Object.keys(unlockableAchievements).length} unlockable achievements"
log.info "and #{Object.keys(repeatableAchievements).length} repeatable achievements"
achievements = _.extend unlockableAchievements, repeatableAchievements
async.each Object.keys(achievements), (key, callback) ->
achievement = achievements[key]
log.info "Setting up '#{achievement.name}'..."
achievement = new Achievement achievement
achievement.save (err) ->
achievementM = new Achievement achievement
# What the actual * Mongoose? It automatically converts 'stats.edits' to a nested object
achievementM.set 'query', achievement.query
log.debug JSON.stringify achievementM.get 'query'
achievementM.save (err) ->
log.error err if err?
callback()
, (err) ->

View file

@ -29,6 +29,8 @@ AchievementSchema.methods.getExpFunction = ->
parameters = @get('function')?.parameters or jsonschema.properties.function.default.parameters
return utils.functionCreators[kind](parameters) if kind of utils.functionCreators
AchievementSchema.methods.isRecalculable = -> @get('recalculable') is true
AchievementSchema.statics.jsonschema = jsonschema
AchievementSchema.statics.earnedAchievements = {}

View file

@ -47,7 +47,7 @@ class EarnedAchievementHandler extends Handler
# Fetch every single user
User.find {}, (err, users) ->
callback err if err?
log.info "... for a total of #{users.length} users."
log.info "for a total of #{users.length} users."
async.each users, ((user, doneWithUser) ->
# Keep track of a user's already achieved in order to set the notified values correctly
@ -68,6 +68,8 @@ class EarnedAchievementHandler extends Handler
newTotalPoints = 0
async.each achievements, ((achievement, doneWithAchievement) ->
return doneWithAchievement() unless achievement.isRecalculable()
isRepeatable = achievement.get('proportionalTo')?
model = mongoose.modelNameByCollection(achievement.get('collection'))
if not model?
@ -79,6 +81,8 @@ class EarnedAchievementHandler extends Handler
finalQuery.$or[0][achievement.userField] = userID
finalQuery.$or[1][achievement.userField] = mongoose.Types.ObjectId userID
log.debug JSON.stringify finalQuery
model.findOne finalQuery, (err, something) ->
return doneWithAchievement() if _.isEmpty something
@ -105,14 +109,10 @@ class EarnedAchievementHandler extends Handler
EarnedAchievement.update {achievement:earned.achievement, user:earned.user}, earned, {upsert: true}, (err) ->
doneWithAchievement err
), saveUserPoints = ->
# In principle it is enough to deduct the old amount of points and add the new amount,
# but just to be entirely safe let's start from 0 in case we're updating all of a user's achievements
# Since some achievements cannot be recalculated it's important to deduct the old amount of exp
# and add the new amount, instead of just setting to the new amount
return doneWithUser() unless newTotalPoints
log.debug "Matched a total of #{newTotalPoints} new points"
if _.isEmpty filter # Completely clean
log.debug "Setting this user's score to #{newTotalPoints}"
User.update {_id: userID}, {$set: points: newTotalPoints}, {}, doneWithUser
else
log.debug "Incrementing score for these achievements with #{newTotalPoints - previousPoints}"
User.update {_id: userID}, {$inc: points: newTotalPoints - previousPoints}, {}, doneWithUser
), onFinished

View file

@ -38,9 +38,9 @@ AchievablePlugin = (schema, options) ->
isRepeatable = achievement.get('proportionalTo')?
alreadyAchieved = if isNew then false else LocalMongo.matchesQuery originalDocObj, query
newlyAchieved = LocalMongo.matchesQuery(docObj, query)
#log.debug 'isRepeatable: ' + isRepeatable
#log.debug 'alreadyAchieved: ' + alreadyAchieved
#log.debug 'newlyAchieved: ' + newlyAchieved
log.debug 'isRepeatable: ' + isRepeatable
log.debug 'alreadyAchieved: ' + alreadyAchieved
log.debug 'newlyAchieved: ' + newlyAchieved
userObjectID = doc.get(achievement.get('userField'))
userID = if _.isObject userObjectID then userObjectID.toHexString() else userObjectID # Standardize! Use strings, not ObjectId's