mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-29 10:35:51 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
6d0c30d69d
23 changed files with 171 additions and 33 deletions
BIN
app/assets/images/common/gem.png
Normal file
BIN
app/assets/images/common/gem.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.5 KiB |
|
@ -95,7 +95,7 @@
|
||||||
|
|
||||||
home:
|
home:
|
||||||
slogan: "Learn to Code by Playing a Game"
|
slogan: "Learn to Code by Playing a Game"
|
||||||
no_ie: "CodeCombat does not run in Internet Explorer 9 or older. Sorry!"
|
no_ie: "CodeCombat does not run in Internet Explorer 8 or older. Sorry!"
|
||||||
no_mobile: "CodeCombat wasn't designed for mobile devices and may not work!"
|
no_mobile: "CodeCombat wasn't designed for mobile devices and may not work!"
|
||||||
play: "Play" # The big play button that just starts playing a level
|
play: "Play" # The big play button that just starts playing a level
|
||||||
old_browser: "Uh oh, your browser is too old to run CodeCombat. Sorry!"
|
old_browser: "Uh oh, your browser is too old to run CodeCombat. Sorry!"
|
||||||
|
|
|
@ -64,3 +64,15 @@ module.exports = class User extends CocoModel
|
||||||
|
|
||||||
level: ->
|
level: ->
|
||||||
User.levelFromExp(@get('points'))
|
User.levelFromExp(@get('points'))
|
||||||
|
|
||||||
|
gems: ->
|
||||||
|
gemsEarned = @get('earned')?.gems ? 0
|
||||||
|
purchased = @get('purchased') ? {}
|
||||||
|
gemsPurchased = purchased.gems ? 0
|
||||||
|
sum = (arr) -> arr?.reduce((a, b) -> a + b) ? 0
|
||||||
|
gemsSpent = sum(purchased.heroes) + sum(purchased.items) + sum(purchased.levels)
|
||||||
|
gemsEarned + gemsPurchased - gemsSpent
|
||||||
|
|
||||||
|
earnedHero: (heroOriginal) -> heroOriginal in me.get('earned')?.heroes ? []
|
||||||
|
earnedItem: (itemOriginal) -> itemOriginal in me.get('earned')?.items ? []
|
||||||
|
earnedLevel: (levelOriginal) -> levelOriginal in me.get('earned')?.levels ? []
|
||||||
|
|
|
@ -45,7 +45,7 @@ _.extend AchievementSchema.properties,
|
||||||
query:
|
query:
|
||||||
#type:'object'
|
#type:'object'
|
||||||
$ref: '#/definitions/mongoFindQuery'
|
$ref: '#/definitions/mongoFindQuery'
|
||||||
worth: c.float
|
worth: c.float()
|
||||||
collection: {type: 'string'}
|
collection: {type: 'string'}
|
||||||
description: c.shortString()
|
description: c.shortString()
|
||||||
userField: c.shortString()
|
userField: c.shortString()
|
||||||
|
@ -61,7 +61,7 @@ _.extend AchievementSchema.properties,
|
||||||
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'
|
||||||
recalculable:
|
recalculable:
|
||||||
type: 'boolean'
|
type: 'boolean'
|
||||||
description: 'Needs to be set to true before it is elligible for recalculation.'
|
description: 'Needs to be set to true before it is eligible for recalculation.'
|
||||||
function:
|
function:
|
||||||
type: 'object'
|
type: 'object'
|
||||||
description: 'Function that gives total experience for X amount achieved'
|
description: 'Function that gives total experience for X amount achieved'
|
||||||
|
@ -82,6 +82,8 @@ _.extend AchievementSchema.properties,
|
||||||
format: 'i18n'
|
format: 'i18n'
|
||||||
props: ['name', 'description']
|
props: ['name', 'description']
|
||||||
description: 'Help translate this achievement'
|
description: 'Help translate this achievement'
|
||||||
|
rewards: c.RewardSchema 'awarded by this achievement'
|
||||||
|
|
||||||
|
|
||||||
_.extend AchievementSchema, # Let's have these on the bottom
|
_.extend AchievementSchema, # Let's have these on the bottom
|
||||||
# TODO We really need some required properties in my opinion but this makes creating new achievements impossible as it is now
|
# TODO We really need some required properties in my opinion but this makes creating new achievements impossible as it is now
|
||||||
|
|
|
@ -30,4 +30,5 @@ module.exports =
|
||||||
achievedAmount: type: 'number'
|
achievedAmount: type: 'number'
|
||||||
earnedPoints: type: 'number'
|
earnedPoints: type: 'number'
|
||||||
previouslyAchievedAmount: {type: 'number'}
|
previouslyAchievedAmount: {type: 'number'}
|
||||||
|
earnedRewards: c.RewardSchema 'awarded by this achievement to this user'
|
||||||
notified: type: 'boolean'
|
notified: type: 'boolean'
|
||||||
|
|
|
@ -17,6 +17,8 @@ UserSchema = c.object
|
||||||
simulatedBy: 0
|
simulatedBy: 0
|
||||||
simulatedFor: 0
|
simulatedFor: 0
|
||||||
jobProfile: {}
|
jobProfile: {}
|
||||||
|
earned: {heroes: [], items: [], levels: [], gems: 0}
|
||||||
|
purchased: {heroes: [], items: [], levels: [], gems: 0}
|
||||||
|
|
||||||
c.extendNamedProperties UserSchema # let's have the name be the first property
|
c.extendNamedProperties UserSchema # let's have the name be the first property
|
||||||
|
|
||||||
|
@ -265,6 +267,8 @@ _.extend UserSchema.properties,
|
||||||
thangTypeTranslationPatches: c.int()
|
thangTypeTranslationPatches: c.int()
|
||||||
thangTypeMiscPatches: c.int()
|
thangTypeMiscPatches: c.int()
|
||||||
|
|
||||||
|
earned: c.RewardSchema 'earned by achievements'
|
||||||
|
purchased: c.RewardSchema 'purchased with gems'
|
||||||
|
|
||||||
c.extendBasicProperties UserSchema, 'user'
|
c.extendBasicProperties UserSchema, 'user'
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ me.pct = (ext) -> combine({type: 'number', maximum: 1.0, minimum: 0.0}, ext)
|
||||||
me.date = (ext) -> combine({type: ['object', 'string'], format: 'date-time'}, ext)
|
me.date = (ext) -> combine({type: ['object', 'string'], format: 'date-time'}, ext)
|
||||||
# should just be string (Mongo ID), but sometimes mongoose turns them into objects representing those, so we are lenient
|
# should just be string (Mongo ID), but sometimes mongoose turns them into objects representing those, so we are lenient
|
||||||
me.objectId = (ext) -> schema = combine({type: ['object', 'string']}, ext)
|
me.objectId = (ext) -> schema = combine({type: ['object', 'string']}, ext)
|
||||||
|
me.stringID = (ext) -> schema = combine({type: 'string', minLength: 24, maxLength: 24}, ext)
|
||||||
me.url = (ext) -> combine({type: 'string', format: 'url', pattern: urlPattern}, ext)
|
me.url = (ext) -> combine({type: 'string', format: 'url', pattern: urlPattern}, ext)
|
||||||
me.int = (ext) -> combine {type: 'integer'}, ext
|
me.int = (ext) -> combine {type: 'integer'}, ext
|
||||||
me.float = (ext) -> combine {type: 'number'}, ext
|
me.float = (ext) -> combine {type: 'number'}, ext
|
||||||
|
@ -209,3 +210,16 @@ me.HeroConfigSchema = me.object {description: 'Which hero the player is using, e
|
||||||
description: 'The inventory of the hero: slots to item ThangTypes.'
|
description: 'The inventory of the hero: slots to item ThangTypes.'
|
||||||
additionalProperties: me.objectId(description: 'An item ThangType.')
|
additionalProperties: me.objectId(description: 'An item ThangType.')
|
||||||
thangType: me.objectId(links: [{rel: 'db', href: '/db/thang.type/{($)}/version'}], title: 'Thang Type', description: 'The ThangType of the hero.', format: 'thang-type')
|
thangType: me.objectId(links: [{rel: 'db', href: '/db/thang.type/{($)}/version'}], title: 'Thang Type', description: 'The ThangType of the hero.', format: 'thang-type')
|
||||||
|
|
||||||
|
me.RewardSchema = (descriptionFragment='earned by achievements') ->
|
||||||
|
type: 'object'
|
||||||
|
additionalProperties: false
|
||||||
|
description: "Rewards #{descriptionFragment}."
|
||||||
|
properties:
|
||||||
|
heroes: me.array {uniqueItems: true, description: "Heroes #{descriptionFragment}."},
|
||||||
|
me.stringID(links: [{rel: 'db', href: '/db/thang.type/{($)}/version'}], title: 'Hero ThangType', description: 'A reference to the earned hero ThangType.', format: 'thang-type')
|
||||||
|
items: me.array {uniqueItems: true, description: "Items #{descriptionFragment}."},
|
||||||
|
me.stringID(links: [{rel: 'db', href: '/db/thang.type/{($)}/version'}], title: 'Item ThangType', description: 'A reference to the earned item ThangType.', format: 'thang-type')
|
||||||
|
levels: me.array {uniqueItems: true, description: "Levels #{descriptionFragment}."},
|
||||||
|
me.stringID(links: [{rel: 'db', href: '/db/level/{($)}/version'}], title: 'Level', description: 'A reference to the earned Level.', format: 'latest-version-original-reference')
|
||||||
|
gems: me.int {description: "Gems #{descriptionFragment}."}
|
||||||
|
|
|
@ -314,3 +314,27 @@ kbd
|
||||||
background-color: #333
|
background-color: #333
|
||||||
border-radius: 3px
|
border-radius: 3px
|
||||||
@include box-shadow(inset 0 -1px 0 rgba(0, 0, 0, .25))
|
@include box-shadow(inset 0 -1px 0 rgba(0, 0, 0, .25))
|
||||||
|
|
||||||
|
.gem
|
||||||
|
display: inline-block
|
||||||
|
background: transparent url(/images/common/gem.png) no-repeat center
|
||||||
|
background-size: contain
|
||||||
|
width: 80px
|
||||||
|
height: 80px
|
||||||
|
margin: 0px 2px
|
||||||
|
|
||||||
|
&.gem-20
|
||||||
|
width: 20px
|
||||||
|
height: 20px
|
||||||
|
|
||||||
|
&.gem-25
|
||||||
|
width: 25px
|
||||||
|
height: 25px
|
||||||
|
|
||||||
|
&.gem-40
|
||||||
|
width: 40px
|
||||||
|
height: 40px
|
||||||
|
|
||||||
|
&.gem-60
|
||||||
|
width: 60px
|
||||||
|
height: 60px
|
||||||
|
|
|
@ -52,9 +52,16 @@ $gameControlMargin: 30px
|
||||||
border: 2px groove white
|
border: 2px groove white
|
||||||
@include transition(margin-bottom 0.5s ease)
|
@include transition(margin-bottom 0.5s ease)
|
||||||
|
|
||||||
&.disabled
|
&.disabled, &.locked
|
||||||
|
background-image: url(/images/pages/game-menu/lock.png)
|
||||||
|
background-size: 75%
|
||||||
|
background-repeat: no-repeat
|
||||||
|
background-position: 50% 50%
|
||||||
opacity: 0.7
|
opacity: 0.7
|
||||||
|
|
||||||
|
a
|
||||||
|
cursor: default
|
||||||
|
|
||||||
&.next
|
&.next
|
||||||
width: 2 * $levelDotWidth
|
width: 2 * $levelDotWidth
|
||||||
height: 2 * $levelDotHeight
|
height: 2 * $levelDotHeight
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
@import "bootstrap/variables"
|
@import "bootstrap/variables"
|
||||||
@import "bootstrap/mixins"
|
@import "bootstrap/mixins"
|
||||||
@import "base"
|
|
||||||
|
|
||||||
#employers-wrapper
|
#employers-wrapper
|
||||||
background-color: #B4B4B4
|
background-color: #B4B4B4
|
||||||
|
@ -51,5 +50,3 @@
|
||||||
#login-button
|
#login-button
|
||||||
margin-left: 40%
|
margin-left: 40%
|
||||||
width: 20%
|
width: 20%
|
||||||
|
|
||||||
|
|
|
@ -2,23 +2,25 @@
|
||||||
.carousel-indicator-container
|
.carousel-indicator-container
|
||||||
ol.carousel-indicators
|
ol.carousel-indicators
|
||||||
for hero, index in heroes
|
for hero, index in heroes
|
||||||
- var info = heroInfo[hero.get('slug')]
|
li(data-hero-id=hero.get('original'), title=hero.get('name'), data-slide-to=index, data-target="#hero-carousel", class="hero-indicator" + (hero.locked ? " locked" : ""))
|
||||||
li(data-hero-id=hero.get('original'), title=hero.get('name'), data-slide-to=index, data-target="#hero-carousel", class="hero-indicator" + (info.status == "Locked" ? " locked" : ""))
|
|
||||||
.hero-avatar
|
.hero-avatar
|
||||||
if info.status == "Locked"
|
if hero.locked
|
||||||
img.lock-indicator(src="/images/pages/game-menu/lock.png")
|
img.lock-indicator(src="/images/pages/game-menu/lock.png")
|
||||||
|
|
||||||
.carousel-inner
|
.carousel-inner
|
||||||
for hero in heroes
|
for hero in heroes
|
||||||
- var info = heroInfo[hero.get('slug')]
|
- var info = heroInfo[hero.get('slug')]
|
||||||
div(class="item hero-item" + (info.status == "Locked" ? " locked" : ""), data-hero-id=hero.get('original'))
|
div(class="item hero-item" + (hero.locked ? " locked" : ""), data-hero-id=hero.get('original'))
|
||||||
canvas.hero-canvas
|
canvas.hero-canvas
|
||||||
.hero-stats
|
.hero-stats
|
||||||
h2= info.fullName
|
h2= info.fullName
|
||||||
p
|
p
|
||||||
span(data-i18n="choose_hero.status") Status
|
span(data-i18n="choose_hero.status") Status
|
||||||
span.spr :
|
span.spr :
|
||||||
|
if hero.locked
|
||||||
| #{info.status}
|
| #{info.status}
|
||||||
|
else
|
||||||
|
| Available
|
||||||
p
|
p
|
||||||
span(data-i18n="choose_hero.weapons") Weapons
|
span(data-i18n="choose_hero.weapons") Weapons
|
||||||
span.spr :
|
span.spr :
|
||||||
|
|
|
@ -4,8 +4,8 @@ block content
|
||||||
|
|
||||||
h1#site-slogan(data-i18n="home.slogan") Learn to Code by Playing a Game
|
h1#site-slogan(data-i18n="home.slogan") Learn to Code by Playing a Game
|
||||||
|
|
||||||
.alert.alert-danger.lt-ie10
|
.alert.alert-danger.lt-ie9
|
||||||
strong(data-i18n="home.no_ie") CodeCombat does not run in Internet Explorer 9 or older. Sorry!
|
strong(data-i18n="home.no_ie") CodeCombat does not run in Internet Explorer 8 or older. Sorry!
|
||||||
|
|
||||||
if isMobile
|
if isMobile
|
||||||
.alert.alert-danger.mobile
|
.alert.alert-danger.mobile
|
||||||
|
|
|
@ -6,12 +6,12 @@
|
||||||
each level in campaign.levels
|
each level in campaign.levels
|
||||||
- var next = !seenNext && levelStatusMap[level.id] != "complete";
|
- var next = !seenNext && levelStatusMap[level.id] != "complete";
|
||||||
- seenNext = seenNext || next;
|
- seenNext = seenNext || next;
|
||||||
div(style="left: #{level.x}%; bottom: #{level.y}%; background-color: #{campaign.color}", class="level" + (next ? " next" : "") + (level.disabled ? " disabled" : "") + " " + levelStatusMap[level.id] || "", data-level-id=level.id, title=level.name)
|
div(style="left: #{level.x}%; bottom: #{level.y}%; background-color: #{campaign.color}", class="level" + (next ? " next" : "") + (level.disabled ? " disabled" : "") + (level.locked ? " locked" : "") + " " + levelStatusMap[level.id] || "", data-level-id=level.id, title=level.name)
|
||||||
a(href=level.type == 'hero' ? '#' : level.disabled ? "/play" : "/play/#{level.levelPath || 'level'}/#{level.id}", disabled=level.disabled, data-level-id=level.id, data-level-path=level.levelPath || 'level', data-level-name=level.name)
|
a(href=level.type == 'hero' ? '#' : level.disabled ? "/play" : "/play/#{level.levelPath || 'level'}/#{level.id}", disabled=level.disabled, data-level-id=level.id, data-level-path=level.levelPath || 'level', data-level-name=level.name)
|
||||||
div(style="left: #{level.x}%; bottom: #{level.y}%", class="level-shadow" + (next ? " next" : "") + " " + levelStatusMap[level.id] || "")
|
div(style="left: #{level.x}%; bottom: #{level.y}%", class="level-shadow" + (next ? " next" : "") + " " + levelStatusMap[level.id] || "")
|
||||||
.level-info-container(data-level-id=level.id, data-level-path=level.levelPath || 'level', data-level-name=level.name)
|
.level-info-container(data-level-id=level.id, data-level-path=level.levelPath || 'level', data-level-name=level.name)
|
||||||
div(class="level-info " + (levelStatusMap[level.id] || ""))
|
div(class="level-info " + (levelStatusMap[level.id] || ""))
|
||||||
h3= level.name + (level.disabled ? " (Coming soon!)" : "")
|
h3= level.name + (level.disabled ? " (Coming soon!)" : (level.locked ? " (Locked)" : ""))
|
||||||
.level-description= level.description
|
.level-description= level.description
|
||||||
span(data-i18n="play.level_difficulty") Difficulty:
|
span(data-i18n="play.level_difficulty") Difficulty:
|
||||||
each i in Array(level.difficulty)
|
each i in Array(level.difficulty)
|
||||||
|
@ -46,6 +46,8 @@
|
||||||
a(href="/play-old", data-i18n="play.older_campaigns").header-font Older Campaigns
|
a(href="/play-old", data-i18n="play.older_campaigns").header-font Older Campaigns
|
||||||
|
|
||||||
.user-status.header-font
|
.user-status.header-font
|
||||||
|
span.gem.gem-20
|
||||||
|
span.spr= me.gems()
|
||||||
if me.get('anonymous')
|
if me.get('anonymous')
|
||||||
span.spr(data-i18n="play.anonymous_player") Anonymous Player
|
span.spr(data-i18n="play.anonymous_player") Anonymous Player
|
||||||
button.btn.btn-default.btn-flat.btn-sm(data-toggle='coco-modal', data-target='modal/AuthModal', data-i18n="login.log_in")
|
button.btn.btn-default.btn-flat.btn-sm(data-toggle='coco-modal', data-target='modal/AuthModal', data-i18n="login.log_in")
|
||||||
|
|
|
@ -360,11 +360,14 @@ class LatestVersionReferenceNode extends TreemaNode
|
||||||
return 'Unknown' unless @settings.supermodel?
|
return 'Unknown' unless @settings.supermodel?
|
||||||
m = CocoModel.getReferencedModel(@getData(), @workingSchema)
|
m = CocoModel.getReferencedModel(@getData(), @workingSchema)
|
||||||
data = @getData()
|
data = @getData()
|
||||||
|
if _.isString data # LatestVersionOriginalReferenceNode just uses original
|
||||||
|
m = @settings.supermodel.getModelByOriginal(m.constructor, data)
|
||||||
|
else
|
||||||
m = @settings.supermodel.getModelByOriginalAndMajorVersion(m.constructor, data.original, data.majorVersion)
|
m = @settings.supermodel.getModelByOriginalAndMajorVersion(m.constructor, data.original, data.majorVersion)
|
||||||
if @instance and not m
|
if @instance and not m
|
||||||
m = @instance
|
m = @instance
|
||||||
@settings.supermodel.registerModel(m)
|
@settings.supermodel.registerModel(m)
|
||||||
return 'Unknown' unless m
|
return 'Unknown - ' + (data.original ? data) unless m
|
||||||
return @modelToString(m)
|
return @modelToString(m)
|
||||||
|
|
||||||
saveChanges: ->
|
saveChanges: ->
|
||||||
|
@ -409,6 +412,15 @@ class LatestVersionReferenceNode extends TreemaNode
|
||||||
selected = @getSelectedResultEl()
|
selected = @getSelectedResultEl()
|
||||||
return not selected.length
|
return not selected.length
|
||||||
|
|
||||||
|
class LatestVersionOriginalReferenceNode extends LatestVersionReferenceNode
|
||||||
|
# Just for saving the original, not the major version.
|
||||||
|
saveChanges: ->
|
||||||
|
selected = @getSelectedResultEl()
|
||||||
|
return unless selected.length
|
||||||
|
fullValue = selected.data('value')
|
||||||
|
@data = fullValue.attributes.original
|
||||||
|
@instance = fullValue
|
||||||
|
|
||||||
class LevelComponentReferenceNode extends LatestVersionReferenceNode
|
class LevelComponentReferenceNode extends LatestVersionReferenceNode
|
||||||
# HACK: this list of properties is needed by the thang components edit view and config views.
|
# HACK: this list of properties is needed by the thang components edit view and config views.
|
||||||
# need a better way to specify this, or keep the search models from bleeding into those
|
# need a better way to specify this, or keep the search models from bleeding into those
|
||||||
|
@ -436,6 +448,7 @@ module.exports.setup = ->
|
||||||
TreemaNode.setNodeSubclass('javascript', JavaScriptTreema)
|
TreemaNode.setNodeSubclass('javascript', JavaScriptTreema)
|
||||||
TreemaNode.setNodeSubclass('image-file', ImageFileTreema)
|
TreemaNode.setNodeSubclass('image-file', ImageFileTreema)
|
||||||
TreemaNode.setNodeSubclass('latest-version-reference', LatestVersionReferenceNode)
|
TreemaNode.setNodeSubclass('latest-version-reference', LatestVersionReferenceNode)
|
||||||
|
TreemaNode.setNodeSubclass('latest-version-original-reference', LatestVersionOriginalReferenceNode)
|
||||||
TreemaNode.setNodeSubclass('component-reference', LevelComponentReferenceNode)
|
TreemaNode.setNodeSubclass('component-reference', LevelComponentReferenceNode)
|
||||||
TreemaNode.setNodeSubclass('i18n', InternationalizationNode)
|
TreemaNode.setNodeSubclass('i18n', InternationalizationNode)
|
||||||
TreemaNode.setNodeSubclass('sound-file', SoundFileTreema)
|
TreemaNode.setNodeSubclass('sound-file', SoundFileTreema)
|
||||||
|
|
|
@ -15,8 +15,6 @@ module.exports = class AchievementPopup extends CocoView
|
||||||
@popup ?= true
|
@popup ?= true
|
||||||
@className += ' popup' if @popup
|
@className += ' popup' if @popup
|
||||||
super options
|
super options
|
||||||
console.debug 'Created an AchievementPopup', @$el
|
|
||||||
|
|
||||||
@render()
|
@render()
|
||||||
|
|
||||||
calculateData: ->
|
calculateData: ->
|
||||||
|
@ -62,7 +60,6 @@ module.exports = class AchievementPopup extends CocoView
|
||||||
c
|
c
|
||||||
|
|
||||||
render: ->
|
render: ->
|
||||||
console.debug 'render achievement popup'
|
|
||||||
super()
|
super()
|
||||||
@container.prepend @$el
|
@container.prepend @$el
|
||||||
if @popup
|
if @popup
|
||||||
|
|
|
@ -5,6 +5,7 @@ AchievementPopup = require 'views/achievements/AchievementPopup'
|
||||||
ConfirmModal = require 'views/modal/ConfirmModal'
|
ConfirmModal = require 'views/modal/ConfirmModal'
|
||||||
errors = require 'lib/errors'
|
errors = require 'lib/errors'
|
||||||
app = require 'application'
|
app = require 'application'
|
||||||
|
nodes = require 'views/editor/level/treema_nodes'
|
||||||
|
|
||||||
module.exports = class AchievementEditView extends RootView
|
module.exports = class AchievementEditView extends RootView
|
||||||
id: 'editor-achievement-edit-view'
|
id: 'editor-achievement-edit-view'
|
||||||
|
@ -36,8 +37,13 @@ module.exports = class AchievementEditView extends RootView
|
||||||
readOnly: me.get('anonymous')
|
readOnly: me.get('anonymous')
|
||||||
callbacks:
|
callbacks:
|
||||||
change: @pushChangesToPreview
|
change: @pushChangesToPreview
|
||||||
|
nodeClasses:
|
||||||
|
'thang-type': nodes.ThangTypeNode
|
||||||
|
'item-thang-type': nodes.ItemThangTypeNode
|
||||||
|
supermodel: @supermodel
|
||||||
@treema = @$el.find('#achievement-treema').treema(options)
|
@treema = @$el.find('#achievement-treema').treema(options)
|
||||||
@treema.build()
|
@treema.build()
|
||||||
|
@treema.childrenTreemas.rewards?.open(3)
|
||||||
@pushChangesToPreview()
|
@pushChangesToPreview()
|
||||||
|
|
||||||
getRenderData: (context={}) ->
|
getRenderData: (context={}) ->
|
||||||
|
|
|
@ -36,6 +36,7 @@ module.exports = class ChooseHeroView extends CocoView
|
||||||
getRenderData: (context={}) ->
|
getRenderData: (context={}) ->
|
||||||
context = super(context)
|
context = super(context)
|
||||||
context.heroes = @heroes.models
|
context.heroes = @heroes.models
|
||||||
|
hero.locked = temporaryHeroInfo[hero.get('slug')].status is 'Locked' and not me.earnedHero hero.get('original') for hero in context.heroes
|
||||||
context.level = @options.level
|
context.level = @options.level
|
||||||
context.codeLanguages = [
|
context.codeLanguages = [
|
||||||
{id: 'python', name: 'Python'}
|
{id: 'python', name: 'Python'}
|
||||||
|
@ -76,7 +77,7 @@ module.exports = class ChooseHeroView extends CocoView
|
||||||
size = 100 - (50 / 3) * distance
|
size = 100 - (50 / 3) * distance
|
||||||
$(@).css width: size, height: size, top: -(100 - size) / 2
|
$(@).css width: size, height: size, top: -(100 - size) / 2
|
||||||
heroInfo = temporaryHeroInfo[hero.get('slug')]
|
heroInfo = temporaryHeroInfo[hero.get('slug')]
|
||||||
locked = heroInfo.status is 'Locked'
|
locked = heroInfo.status is 'Locked' and not me.earnedHero ThangType.heroes[hero.get('slug')]
|
||||||
hero = @loadHero hero, heroIndex
|
hero = @loadHero hero, heroIndex
|
||||||
@preloadHero heroIndex + 1
|
@preloadHero heroIndex + 1
|
||||||
@preloadHero heroIndex - 1
|
@preloadHero heroIndex - 1
|
||||||
|
|
|
@ -321,6 +321,8 @@ module.exports = class InventoryView extends CocoView
|
||||||
for slot, item of items
|
for slot, item of items
|
||||||
@allowedItems.push gear[item] unless gear[item] in @allowedItems
|
@allowedItems.push gear[item] unless gear[item] in @allowedItems
|
||||||
break if level is @options.levelID
|
break if level is @options.levelID
|
||||||
|
for item in me.get('earned')?.items ? [] when not (item in @allowedItems)
|
||||||
|
@allowedItems.push item
|
||||||
|
|
||||||
onHeroSelectionUpdated: (e) ->
|
onHeroSelectionUpdated: (e) ->
|
||||||
@selectedHero = e.hero
|
@selectedHero = e.hero
|
||||||
|
|
|
@ -65,9 +65,10 @@ module.exports = class WorldMapView extends RootView
|
||||||
context = super(context)
|
context = super(context)
|
||||||
context.campaigns = campaigns
|
context.campaigns = campaigns
|
||||||
for campaign in context.campaigns
|
for campaign in context.campaigns
|
||||||
for level in campaign.levels
|
for level, index in campaign.levels
|
||||||
level.x ?= 10 + 80 * Math.random()
|
level.x ?= 10 + 80 * Math.random()
|
||||||
level.y ?= 10 + 80 * Math.random()
|
level.y ?= 10 + 80 * Math.random()
|
||||||
|
level.locked = index > 0 and not me.earnedLevel level.original
|
||||||
context.levelStatusMap = @levelStatusMap
|
context.levelStatusMap = @levelStatusMap
|
||||||
context.levelPlayCountMap = @levelPlayCountMap
|
context.levelPlayCountMap = @levelPlayCountMap
|
||||||
context.isIPadApp = application.isIPadApp
|
context.isIPadApp = application.isIPadApp
|
||||||
|
@ -96,7 +97,7 @@ module.exports = class WorldMapView extends RootView
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
@$levelInfo?.hide()
|
@$levelInfo?.hide()
|
||||||
return if $(e.target).attr('disabled')
|
return if $(e.target).attr('disabled') or $(e.target).parent().hasClass 'locked'
|
||||||
if application.isIPadApp
|
if application.isIPadApp
|
||||||
levelID = $(e.target).parents('.level').data('level-id')
|
levelID = $(e.target).parents('.level').data('level-id')
|
||||||
@$levelInfo = @$el.find(".level-info-container[data-level-id=#{levelID}]").show()
|
@$levelInfo = @$el.find(".level-info-container[data-level-id=#{levelID}]").show()
|
||||||
|
@ -515,6 +516,7 @@ hero = [
|
||||||
type: 'hero'
|
type: 'hero'
|
||||||
difficulty: 1
|
difficulty: 1
|
||||||
id: 'dungeons-of-kithgard'
|
id: 'dungeons-of-kithgard'
|
||||||
|
original: '528110f30268d018e3000001'
|
||||||
description: 'Grab the gem, but touch nothing else. Start here.'
|
description: 'Grab the gem, but touch nothing else. Start here.'
|
||||||
x: 17.23
|
x: 17.23
|
||||||
y: 36.94
|
y: 36.94
|
||||||
|
@ -524,6 +526,7 @@ hero = [
|
||||||
type: 'hero'
|
type: 'hero'
|
||||||
difficulty: 1
|
difficulty: 1
|
||||||
id: 'gems-in-the-deep'
|
id: 'gems-in-the-deep'
|
||||||
|
original: '54173c90844506ae0195a0b4'
|
||||||
description: 'Quickly collect the gems; you will need them.'
|
description: 'Quickly collect the gems; you will need them.'
|
||||||
x: 22.6
|
x: 22.6
|
||||||
y: 35.1
|
y: 35.1
|
||||||
|
@ -533,6 +536,7 @@ hero = [
|
||||||
type: 'hero'
|
type: 'hero'
|
||||||
difficulty: 1
|
difficulty: 1
|
||||||
id: 'shadow-guard'
|
id: 'shadow-guard'
|
||||||
|
original: '54174347844506ae0195a0b8'
|
||||||
description: 'Evade the Kithgard minion.'
|
description: 'Evade the Kithgard minion.'
|
||||||
x: 27.74
|
x: 27.74
|
||||||
y: 35.17
|
y: 35.17
|
||||||
|
@ -542,6 +546,7 @@ hero = [
|
||||||
type: 'hero'
|
type: 'hero'
|
||||||
difficulty: 1
|
difficulty: 1
|
||||||
id: 'true-names'
|
id: 'true-names'
|
||||||
|
original: '541875da4c16460000ab990f'
|
||||||
description: 'Learn an enemy\'s true name to defeat it.'
|
description: 'Learn an enemy\'s true name to defeat it.'
|
||||||
x: 32.7
|
x: 32.7
|
||||||
y: 36.7
|
y: 36.7
|
||||||
|
@ -551,6 +556,7 @@ hero = [
|
||||||
type: 'hero'
|
type: 'hero'
|
||||||
difficulty: 1
|
difficulty: 1
|
||||||
id: 'the-raised-sword'
|
id: 'the-raised-sword'
|
||||||
|
original: '5418aec24c16460000ab9aa6'
|
||||||
description: 'Learn to equip yourself for combat.'
|
description: 'Learn to equip yourself for combat.'
|
||||||
x: 36.6
|
x: 36.6
|
||||||
y: 39.5
|
y: 39.5
|
||||||
|
@ -560,6 +566,7 @@ hero = [
|
||||||
type: 'hero'
|
type: 'hero'
|
||||||
difficulty: 1
|
difficulty: 1
|
||||||
id: 'the-first-kithmaze'
|
id: 'the-first-kithmaze'
|
||||||
|
original: '5418b9d64c16460000ab9ab4'
|
||||||
description: 'The builders of Kith constructed many mazes to confuse travelers.'
|
description: 'The builders of Kith constructed many mazes to confuse travelers.'
|
||||||
x: 38.4
|
x: 38.4
|
||||||
y: 43.5
|
y: 43.5
|
||||||
|
@ -569,6 +576,7 @@ hero = [
|
||||||
type: 'hero'
|
type: 'hero'
|
||||||
difficulty: 1
|
difficulty: 1
|
||||||
id: 'the-second-kithmaze'
|
id: 'the-second-kithmaze'
|
||||||
|
original: '5418cf256bae62f707c7e1c3'
|
||||||
description: 'Many have tried, few have found their way through this maze.'
|
description: 'Many have tried, few have found their way through this maze.'
|
||||||
x: 38.9
|
x: 38.9
|
||||||
y: 48.1
|
y: 48.1
|
||||||
|
@ -578,6 +586,7 @@ hero = [
|
||||||
type: 'hero'
|
type: 'hero'
|
||||||
difficulty: 1
|
difficulty: 1
|
||||||
id: 'new-sight'
|
id: 'new-sight'
|
||||||
|
original: '5418d40f4c16460000ab9ac2'
|
||||||
description: 'A true name can only be seen with the correct lenses.'
|
description: 'A true name can only be seen with the correct lenses.'
|
||||||
x: 39.3
|
x: 39.3
|
||||||
y: 53.1
|
y: 53.1
|
||||||
|
@ -587,6 +596,7 @@ hero = [
|
||||||
type: 'hero'
|
type: 'hero'
|
||||||
difficulty: 1
|
difficulty: 1
|
||||||
id: 'lowly-kithmen'
|
id: 'lowly-kithmen'
|
||||||
|
original: '541b24511ccc8eaae19f3c1f'
|
||||||
description: 'Use your glasses to seek out and attack the Kithmen.'
|
description: 'Use your glasses to seek out and attack the Kithmen.'
|
||||||
x: 39.4
|
x: 39.4
|
||||||
y: 57.7
|
y: 57.7
|
||||||
|
@ -596,6 +606,7 @@ hero = [
|
||||||
type: 'hero'
|
type: 'hero'
|
||||||
difficulty: 1
|
difficulty: 1
|
||||||
id: 'a-bolt-in-the-dark'
|
id: 'a-bolt-in-the-dark'
|
||||||
|
original: '541b288e1ccc8eaae19f3c25'
|
||||||
description: 'Kithmen are not the only ones to stand in your way.'
|
description: 'Kithmen are not the only ones to stand in your way.'
|
||||||
x: 40.0
|
x: 40.0
|
||||||
y: 63.2
|
y: 63.2
|
||||||
|
@ -605,6 +616,7 @@ hero = [
|
||||||
type: 'hero'
|
type: 'hero'
|
||||||
difficulty: 1
|
difficulty: 1
|
||||||
id: 'the-final-kithmaze'
|
id: 'the-final-kithmaze'
|
||||||
|
original: '541b434e1ccc8eaae19f3c33'
|
||||||
description: 'To escape you must find your way through an Elder Kithman\'s maze.'
|
description: 'To escape you must find your way through an Elder Kithman\'s maze.'
|
||||||
x: 42.67
|
x: 42.67
|
||||||
y: 67.98
|
y: 67.98
|
||||||
|
@ -614,6 +626,7 @@ hero = [
|
||||||
type: 'hero'
|
type: 'hero'
|
||||||
difficulty: 1
|
difficulty: 1
|
||||||
id: 'kithgard-gates'
|
id: 'kithgard-gates'
|
||||||
|
original: '541c9a30c6362edfb0f34479'
|
||||||
description: 'Escape the Kithgard dungeons and don\'t let the guardians get you.'
|
description: 'Escape the Kithgard dungeons and don\'t let the guardians get you.'
|
||||||
x: 47.38
|
x: 47.38
|
||||||
y: 70.55
|
y: 70.55
|
||||||
|
@ -624,6 +637,7 @@ hero = [
|
||||||
type: 'hero'
|
type: 'hero'
|
||||||
difficulty: 1
|
difficulty: 1
|
||||||
id: 'defence-of-plainswood'
|
id: 'defence-of-plainswood'
|
||||||
|
original: '541b67f71ccc8eaae19f3c62'
|
||||||
description: 'Protect the peasants from the pursuing ogres.'
|
description: 'Protect the peasants from the pursuing ogres.'
|
||||||
x: 52.66
|
x: 52.66
|
||||||
y: 69.66
|
y: 69.66
|
||||||
|
|
|
@ -75,7 +75,7 @@
|
||||||
"javascript-brunch": "> 1.0 < 1.8",
|
"javascript-brunch": "> 1.0 < 1.8",
|
||||||
"coffee-script-brunch": "https://github.com/brunch/coffee-script-brunch/tarball/master",
|
"coffee-script-brunch": "https://github.com/brunch/coffee-script-brunch/tarball/master",
|
||||||
"coffeelint-brunch": "> 1.0 < 1.8",
|
"coffeelint-brunch": "> 1.0 < 1.8",
|
||||||
"sass-brunch": "1.7.0",
|
"sass-brunch": "1.8.3",
|
||||||
"css-brunch": "> 1.0 < 1.8",
|
"css-brunch": "> 1.0 < 1.8",
|
||||||
"jade-brunch": "> 1.0 < 1.8",
|
"jade-brunch": "> 1.0 < 1.8",
|
||||||
"uglify-js-brunch": "~1.7.4",
|
"uglify-js-brunch": "~1.7.4",
|
||||||
|
@ -86,7 +86,7 @@
|
||||||
"marked": "0.2.x",
|
"marked": "0.2.x",
|
||||||
"telepath-brunch": "https://github.com/nwinter/telepath-brunch/tarball/master",
|
"telepath-brunch": "https://github.com/nwinter/telepath-brunch/tarball/master",
|
||||||
"bower": "~1.3.8",
|
"bower": "~1.3.8",
|
||||||
"bless-brunch": "~1.6.1",
|
"bless-brunch": "https://github.com/ThomasConner/bless-brunch/tarball/master",
|
||||||
"karma-script-launcher": "~0.1.0",
|
"karma-script-launcher": "~0.1.0",
|
||||||
"karma-chrome-launcher": "~0.1.2",
|
"karma-chrome-launcher": "~0.1.2",
|
||||||
"karma-firefox-launcher": "~0.1.3",
|
"karma-firefox-launcher": "~0.1.3",
|
||||||
|
|
|
@ -5,7 +5,7 @@ class AchievementHandler extends Handler
|
||||||
modelClass: Achievement
|
modelClass: Achievement
|
||||||
|
|
||||||
# Used to determine which properties requests may edit
|
# Used to determine which properties requests may edit
|
||||||
editableProperties: ['name', 'query', 'worth', 'collection', 'description', 'userField', 'proportionalTo', 'icon', 'function', 'related', 'difficulty', 'category', 'recalculable']
|
editableProperties: ['name', 'query', 'worth', 'collection', 'description', 'userField', 'proportionalTo', 'icon', 'function', 'related', 'difficulty', 'category', 'recalculable', 'rewards']
|
||||||
allowedMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']
|
allowedMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']
|
||||||
jsonSchema = require '../../app/schemas/models/achievement.coffee'
|
jsonSchema = require '../../app/schemas/models/achievement.coffee'
|
||||||
|
|
||||||
|
|
|
@ -77,15 +77,21 @@ class EarnedAchievementHandler extends Handler
|
||||||
EarnedAchievement.find {user: userID}, (err, alreadyEarned) ->
|
EarnedAchievement.find {user: userID}, (err, alreadyEarned) ->
|
||||||
alreadyEarnedIDs = []
|
alreadyEarnedIDs = []
|
||||||
previousPoints = 0
|
previousPoints = 0
|
||||||
|
previousRewards = heroes: [], items: [], levels: [], gems: 0
|
||||||
async.each alreadyEarned, ((earned, doneWithEarned) ->
|
async.each alreadyEarned, ((earned, doneWithEarned) ->
|
||||||
if (_.find achievements, (single) -> earned.get('achievement') is single.get('_id').toHexString()) # if already earned
|
if (_.find achievements, (single) -> earned.get('achievement') is single.get('_id').toHexString()) # if already earned
|
||||||
alreadyEarnedIDs.push earned.get('achievement')
|
alreadyEarnedIDs.push earned.get('achievement')
|
||||||
previousPoints += earned.get 'earnedPoints'
|
previousPoints += earned.get 'earnedPoints'
|
||||||
|
for rewardType in ['heroes', 'items', 'levels']
|
||||||
|
previousRewards[rewardType] = previousRewards[rewardType].concat(earned.get('earnedRewards')?[rewardType] ? [])
|
||||||
|
previousRewards.gems += earned.get('earnedRewards')?.gems ? 0
|
||||||
doneWithEarned()
|
doneWithEarned()
|
||||||
), -> # After checking already achieved
|
), (err) -> # After checking already achieved
|
||||||
|
log.error err if err
|
||||||
# TODO maybe also delete earned? Make sure you don't delete too many
|
# TODO maybe also delete earned? Make sure you don't delete too many
|
||||||
|
|
||||||
newTotalPoints = 0
|
newTotalPoints = 0
|
||||||
|
newTotalRewards = heroes: [], items: [], levels: [], gems: 0
|
||||||
|
|
||||||
async.each achievements, ((achievement, doneWithAchievement) ->
|
async.each achievements, ((achievement, doneWithAchievement) ->
|
||||||
return doneWithAchievement() unless achievement.isRecalculable()
|
return doneWithAchievement() unless achievement.isRecalculable()
|
||||||
|
@ -122,17 +128,43 @@ class EarnedAchievementHandler extends Handler
|
||||||
earned.earnedPoints = newPoints
|
earned.earnedPoints = newPoints
|
||||||
newTotalPoints += newPoints
|
newTotalPoints += newPoints
|
||||||
|
|
||||||
|
earned.earnedRewards = achievement.get('rewards')
|
||||||
|
for rewardType in ['heroes', 'items', 'levels']
|
||||||
|
newTotalRewards[rewardType] = newTotalRewards[rewardType].concat(achievement.get('rewards')?[rewardType] ? [])
|
||||||
|
newTotalRewards.gems += achievement.get('rewards')?.gems ? 0
|
||||||
|
|
||||||
EarnedAchievement.update {achievement:earned.achievement, user:earned.user}, earned, {upsert: true}, (err) ->
|
EarnedAchievement.update {achievement:earned.achievement, user:earned.user}, earned, {upsert: true}, (err) ->
|
||||||
doneWithAchievement err
|
doneWithAchievement err
|
||||||
), -> # Wrap up a user, save points
|
), (err) -> # Wrap up a user, save points
|
||||||
|
log.error err if err
|
||||||
# Since some achievements cannot be recalculated it's important to deduct the old amount of exp
|
# 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
|
# and add the new amount, instead of just setting to the new amount
|
||||||
return doneWithUser(user) unless newTotalPoints
|
#console.log 'User', user.get('name'), 'had newTotalPoints', newTotalPoints, 'and newTotalRewards', newTotalRewards
|
||||||
|
return doneWithUser(user) unless newTotalPoints or newTotalRewards.gems or _.some(newTotalRewards, (r) -> r.length)
|
||||||
# log.debug "Matched a total of #{newTotalPoints} new points"
|
# log.debug "Matched a total of #{newTotalPoints} new points"
|
||||||
# log.debug "Incrementing score for these achievements with #{newTotalPoints - previousPoints}"
|
# log.debug "Incrementing score for these achievements with #{newTotalPoints - previousPoints}"
|
||||||
pctDone = (100 * usersFinished / total).toFixed(2)
|
pctDone = (100 * usersFinished / total).toFixed(2)
|
||||||
console.log "Updated points to #{newTotalPoints}(+#{newTotalPoints - previousPoints}) for #{user.get('name') or '???'} (#{user.get('_id')}) (#{pctDone}%)"
|
console.log "Updated points to #{newTotalPoints}(+#{newTotalPoints - previousPoints}) for #{user.get('name') or '???'} (#{user.get('_id')}) (#{pctDone}%)"
|
||||||
User.update {_id: userID}, {$inc: points: newTotalPoints - previousPoints}, {}, (err) ->
|
update = {$inc: {points: newTotalPoints - previousPoints}}
|
||||||
|
for rewardType, rewards of newTotalRewards
|
||||||
|
if rewardType is 'gems'
|
||||||
|
update.$inc['earned.gems'] = rewards - previousRewards.gems
|
||||||
|
else
|
||||||
|
previousCounts = _.countBy previousRewards[rewardType]
|
||||||
|
newCounts = _.countBy rewards
|
||||||
|
relevantRewards = _.union _.keys(previousCounts), _.keys(newCounts)
|
||||||
|
for reward in relevantRewards
|
||||||
|
[previousCount, newCount] = [previousCounts[reward], newCounts[reward]]
|
||||||
|
if newCount and not previousCount
|
||||||
|
update.$addToSet ?= {}
|
||||||
|
update.$addToSet["earned.#{rewardType}"] ?= {$each: []}
|
||||||
|
update.$addToSet["earned.#{rewardType}"].$each.push reward
|
||||||
|
else if previousCount and not newCount
|
||||||
|
# Might $pull $each also work here?
|
||||||
|
update.$pullAll ?= {}
|
||||||
|
update.$pullAll["earned.#{rewardType}"] ?= []
|
||||||
|
update.$pullAll["earned.#{rewardType}"].push reward
|
||||||
|
User.update {_id: userID}, update, {}, (err) ->
|
||||||
log.error err if err?
|
log.error err if err?
|
||||||
doneWithUser(user)
|
doneWithUser(user)
|
||||||
|
|
||||||
|
|
|
@ -50,12 +50,20 @@ AchievablePlugin = (schema, options) ->
|
||||||
user: userID
|
user: userID
|
||||||
achievement: achievement._id.toHexString()
|
achievement: achievement._id.toHexString()
|
||||||
achievementName: achievement.get 'name'
|
achievementName: achievement.get 'name'
|
||||||
|
earnedRewarsd: achievement.get 'rewards'
|
||||||
|
|
||||||
worth = achievement.get('worth') ? 10
|
worth = achievement.get('worth') ? 10
|
||||||
earnedPoints = 0
|
earnedPoints = 0
|
||||||
wrapUp = ->
|
wrapUp = ->
|
||||||
# Update user's experience points
|
# Update user's experience points
|
||||||
User.update {_id: userID}, {$inc: {points: earnedPoints}}, {}, (err, count) ->
|
update = {$inc: {points: earnedPoints}}
|
||||||
|
for rewardType, rewards of achievement.get('rewards') ? {}
|
||||||
|
if rewardType is 'gems'
|
||||||
|
update.$inc['earned.gems'] = rewards if rewards
|
||||||
|
else if rewards.length
|
||||||
|
update.$addToSet ?= {}
|
||||||
|
update.$addToSet["earned.#{rewardType}"] = $each: rewards
|
||||||
|
User.update {_id: userID}, update, {}, (err, count) ->
|
||||||
log.error err if err?
|
log.error err if err?
|
||||||
|
|
||||||
if isRepeatable
|
if isRepeatable
|
||||||
|
|
Loading…
Reference in a new issue