mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-24 16:17:57 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
22e7bb8643
9 changed files with 121 additions and 22 deletions
|
@ -391,7 +391,7 @@
|
||||||
prompt_body: "Do you want to get more?"
|
prompt_body: "Do you want to get more?"
|
||||||
prompt_button: "Enter Shop"
|
prompt_button: "Enter Shop"
|
||||||
recovered: "Previous gems purchase recovered. Please refresh the page."
|
recovered: "Previous gems purchase recovered. Please refresh the page."
|
||||||
or_subscribe: "Or 3500 gems/mo if you..."
|
price: "x3500 / mo"
|
||||||
|
|
||||||
subscribe:
|
subscribe:
|
||||||
comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
|
comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
|
||||||
|
@ -455,6 +455,14 @@
|
||||||
blocks: "Blocks" # As in "this shield blocks this much damage"
|
blocks: "Blocks" # As in "this shield blocks this much damage"
|
||||||
backstab: "Backstab" # As in "this dagger does this much backstab damage"
|
backstab: "Backstab" # As in "this dagger does this much backstab damage"
|
||||||
skills: "Skills"
|
skills: "Skills"
|
||||||
|
attack_1: "Deals"
|
||||||
|
attack_2: "of listed"
|
||||||
|
attack_3: "weapon damage."
|
||||||
|
health_1: "Gains"
|
||||||
|
health_2: "of listed"
|
||||||
|
health_3: "armor health."
|
||||||
|
speed_1: "Moves at"
|
||||||
|
speed_2: "meters per second."
|
||||||
available_for_purchase: "Available for Purchase" # Shows up when you have unlocked, but not purchased, a hero in the hero store
|
available_for_purchase: "Available for Purchase" # Shows up when you have unlocked, but not purchased, a hero in the hero store
|
||||||
level_to_unlock: "Level to unlock:" # Label for which level you have to beat to unlock a particular hero (click a locked hero in the store to see)
|
level_to_unlock: "Level to unlock:" # Label for which level you have to beat to unlock a particular hero (click a locked hero in the store to see)
|
||||||
restricted_to_certain_heroes: "Only certain heroes can play this level."
|
restricted_to_certain_heroes: "Only certain heroes can play this level."
|
||||||
|
@ -534,10 +542,10 @@
|
||||||
cat_blurb: "Airbender"
|
cat_blurb: "Airbender"
|
||||||
josh_title: "Game Designer"
|
josh_title: "Game Designer"
|
||||||
josh_blurb: "Floor Is Lava"
|
josh_blurb: "Floor Is Lava"
|
||||||
retrostyle_title: "Illustration"
|
|
||||||
retrostyle_blurb: "RetroStyle"
|
|
||||||
jose_title: "Music"
|
jose_title: "Music"
|
||||||
jose_blurb: "Taking Off"
|
jose_blurb: "Taking Off"
|
||||||
|
retrostyle_title: "Illustration"
|
||||||
|
retrostyle_blurb: "RetroStyle Games"
|
||||||
|
|
||||||
teachers:
|
teachers:
|
||||||
title: "CodeCombat for Teachers"
|
title: "CodeCombat for Teachers"
|
||||||
|
|
|
@ -356,13 +356,22 @@ module.exports = class ThangType extends CocoModel
|
||||||
else
|
else
|
||||||
classSpecificScore = stat * 5
|
classSpecificScore = stat * 5
|
||||||
classAverage = @classStatAverages[prop][@get('heroClass')]
|
classAverage = @classStatAverages[prop][@get('heroClass')]
|
||||||
stats[prop] = Math.round(2 * ((classAverage - 2.5) + classSpecificScore / 2)) / 2 / 10
|
stats[prop] =
|
||||||
|
relative: Math.round(2 * ((classAverage - 2.5) + classSpecificScore / 2)) / 2 / 10
|
||||||
|
absolute: stat
|
||||||
|
pieces = ($.i18n.t "choose_hero.#{prop}_#{num}" for num in [1 .. 3])
|
||||||
|
percent = Math.round(stat * 100) + '%'
|
||||||
|
className = $.i18n.t "general.#{_.string.slugify @get('heroClass')}"
|
||||||
|
stats[prop].description = [pieces[0], percent, pieces[1], className, pieces[2]].join ' '
|
||||||
|
|
||||||
minSpeed = 4
|
minSpeed = 4
|
||||||
maxSpeed = 16
|
maxSpeed = 16
|
||||||
speedRange = maxSpeed - minSpeed
|
speedRange = maxSpeed - minSpeed
|
||||||
speedPoints = rawNumbers.speed - minSpeed
|
speedPoints = rawNumbers.speed - minSpeed
|
||||||
stats.speed = Math.round(20 * speedPoints / speedRange) / 2 / 10
|
stats.speed =
|
||||||
|
relative: Math.round(20 * speedPoints / speedRange) / 2 / 10
|
||||||
|
absolute: rawNumbers.speed
|
||||||
|
description: "#{$.i18n.t 'choose_hero.speed_1'} #{rawNumbers.speed} #{$.i18n.t 'choose_hero.speed_2'}"
|
||||||
|
|
||||||
stats.skills = (_.string.titleize(_.string.humanize(skill)) for skill in programmableConfig.programmableProperties when skill isnt 'say')
|
stats.skills = (_.string.titleize(_.string.humanize(skill)) for skill in programmableConfig.programmableProperties when skill isnt 'say')
|
||||||
|
|
||||||
|
|
|
@ -3,19 +3,19 @@ c = require './../schemas'
|
||||||
PaymentSchema = c.object({title: 'Payment', required: []}, {
|
PaymentSchema = c.object({title: 'Payment', required: []}, {
|
||||||
purchaser: c.objectId(links: [ {rel: 'extra', href: '/db/user/{($)}'} ]) # in case of gifts
|
purchaser: c.objectId(links: [ {rel: 'extra', href: '/db/user/{($)}'} ]) # in case of gifts
|
||||||
recipient: c.objectId(links: [ {rel: 'extra', href: '/db/user/{($)}'} ])
|
recipient: c.objectId(links: [ {rel: 'extra', href: '/db/user/{($)}'} ])
|
||||||
|
|
||||||
service: { enum: ['stripe', 'ios' ]}
|
service: { enum: ['stripe', 'ios', 'invoice']}
|
||||||
amount: { type: 'integer', description: 'Payment in cents.' }
|
amount: { type: 'integer', description: 'Payment in cents.' }
|
||||||
created: c.date({title: 'Created', readOnly: true})
|
created: c.date({title: 'Created', readOnly: true})
|
||||||
gems: { type: 'integer', description: 'The number of gems acquired.' }
|
gems: { type: 'integer', description: 'The number of gems acquired.' }
|
||||||
productID: { enum: ['gems_5', 'gems_10', 'gems_20']}
|
productID: { enum: ['gems_5', 'gems_10', 'gems_20']}
|
||||||
|
|
||||||
ios: c.object({title: 'iOS IAP Data'}, {
|
ios: c.object({title: 'iOS IAP Data'}, {
|
||||||
transactionID: { type: 'string' }
|
transactionID: { type: 'string' }
|
||||||
rawReceipt: { type: 'string' }
|
rawReceipt: { type: 'string' }
|
||||||
localPrice: { type: 'string' }
|
localPrice: { type: 'string' }
|
||||||
})
|
})
|
||||||
|
|
||||||
stripe: c.object({title: 'Stripe Data'}, {
|
stripe: c.object({title: 'Stripe Data'}, {
|
||||||
timestamp: { type: 'integer', description: 'Unique identifier provided by the client, to guard against duplicate payments.' }
|
timestamp: { type: 'integer', description: 'Unique identifier provided by the client, to guard against duplicate payments.' }
|
||||||
chargeID: { type: 'string' }
|
chargeID: { type: 'string' }
|
||||||
|
|
|
@ -146,9 +146,9 @@ block content
|
||||||
.team_bio
|
.team_bio
|
||||||
h4.team_name
|
h4.team_name
|
||||||
a(href="http://retrostylegames.com/") Pavel Konstantinov
|
a(href="http://retrostylegames.com/") Pavel Konstantinov
|
||||||
p(data-i18n="about.pavel_title")
|
p(data-i18n="about.retrostyle_title")
|
||||||
| Illustration
|
| Illustration
|
||||||
p(data-i18n="about.pavel_blurb")
|
p(data-i18n="about.retrostyle_blurb")
|
||||||
| RetroStyle Games
|
| RetroStyle Games
|
||||||
|
|
||||||
a(href="http://retrostylegames.com/")
|
a(href="http://retrostylegames.com/")
|
||||||
|
@ -156,7 +156,7 @@ block content
|
||||||
.team_bio
|
.team_bio
|
||||||
h4.team_name
|
h4.team_name
|
||||||
a(href="http://retrostylegames.com/") Oleg Ulyanickiy
|
a(href="http://retrostylegames.com/") Oleg Ulyanickiy
|
||||||
p(data-i18n="about.oleg_title")
|
p(data-i18n="about.retrostyle_title")
|
||||||
| Illustration
|
| Illustration
|
||||||
p(data-i18n="about.oleg_blurb")
|
p(data-i18n="about.retrostyle_blurb")
|
||||||
| RetroStyle Games
|
| RetroStyle Games
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
span= product.price
|
span= product.price
|
||||||
|
|
||||||
.product
|
.product
|
||||||
h4 x3500 / mo
|
h4(data-i18n="buy_gems.price") x3500 / mo
|
||||||
h3(data-i18n="account.subscription")
|
h3(data-i18n="account.subscription")
|
||||||
button.start-subscription-button.btn.btn-lg.btn-illustrated.btn-success(data-i18n="subscribe.subscribe_title") Subscribe
|
button.start-subscription-button.btn.btn-lg.btn-illustrated.btn-success(data-i18n="subscribe.subscribe_title") Subscribe
|
||||||
|
|
||||||
|
@ -45,4 +45,3 @@
|
||||||
|
|
||||||
div#close-modal
|
div#close-modal
|
||||||
span.glyphicon.glyphicon-remove
|
span.glyphicon.glyphicon-remove
|
||||||
|
|
||||||
|
|
|
@ -52,11 +52,11 @@
|
||||||
.stat-label(data-i18n='choose_hero.skills')
|
.stat-label(data-i18n='choose_hero.skills')
|
||||||
.stat-value= hero.stats.skills.join(', ')
|
.stat-value= hero.stats.skills.join(', ')
|
||||||
for stat in ['attack', 'health', 'speed']
|
for stat in ['attack', 'health', 'speed']
|
||||||
.hero-stat-row(class=stat)
|
.hero-stat-row(class=stat, title=hero.stats[stat].description)
|
||||||
.stat-label(data-i18n='choose_hero.'+stat)
|
.stat-label(data-i18n='choose_hero.'+stat)
|
||||||
.stat-value
|
.stat-value
|
||||||
.stat-progress
|
.stat-progress
|
||||||
.stat-progress-bar(style="width: " + (parseInt(hero.stats[stat]*100)) + "%")
|
.stat-progress-bar(style="width: " + (parseInt(hero.stats[stat].relative * 100)) + "%")
|
||||||
|
|
||||||
a.left(role="button", data-slide="prev", href="#hero-carousel")
|
a.left(role="button", data-slide="prev", href="#hero-carousel")
|
||||||
span.glyphicon.glyphicon-play
|
span.glyphicon.glyphicon-play
|
||||||
|
|
|
@ -266,6 +266,7 @@ module.exports = class CampaignEditorView extends RootView
|
||||||
class LevelsNode extends TreemaObjectNode
|
class LevelsNode extends TreemaObjectNode
|
||||||
valueClass: 'treema-levels'
|
valueClass: 'treema-levels'
|
||||||
@levels: {}
|
@levels: {}
|
||||||
|
ordered: true
|
||||||
|
|
||||||
buildValueForDisplay: (valEl, data) ->
|
buildValueForDisplay: (valEl, data) ->
|
||||||
@buildValueForDisplaySimply valEl, ''+_.size(data)
|
@buildValueForDisplaySimply valEl, ''+_.size(data)
|
||||||
|
|
|
@ -6,13 +6,13 @@ module.exports = class SaveCampaignModal extends ModalView
|
||||||
id: 'save-campaign-modal'
|
id: 'save-campaign-modal'
|
||||||
template: template
|
template: template
|
||||||
plain: true
|
plain: true
|
||||||
|
|
||||||
events:
|
events:
|
||||||
'click #save-button': 'onClickSaveButton'
|
'click #save-button': 'onClickSaveButton'
|
||||||
|
|
||||||
constructor: (options, @modelsToSave) ->
|
constructor: (options, @modelsToSave) ->
|
||||||
super(options)
|
super(options)
|
||||||
|
|
||||||
getRenderData: ->
|
getRenderData: ->
|
||||||
c = super()
|
c = super()
|
||||||
c.modelsToSave = @modelsToSave
|
c.modelsToSave = @modelsToSave
|
||||||
|
@ -26,9 +26,21 @@ module.exports = class SaveCampaignModal extends ModalView
|
||||||
@insertSubView(deltaView, $el)
|
@insertSubView(deltaView, $el)
|
||||||
)
|
)
|
||||||
super()
|
super()
|
||||||
|
|
||||||
onClickSaveButton: ->
|
onClickSaveButton: ->
|
||||||
@showLoading()
|
@showLoading()
|
||||||
|
@reverseLevelsBeforeSave()
|
||||||
modelsBeingSaved = (model.patch() for model in @modelsToSave.models)
|
modelsBeingSaved = (model.patch() for model in @modelsToSave.models)
|
||||||
modelsBeingSaved = modelsBeingSaved
|
$.when(_.compact(modelsBeingSaved)...).done(-> document.location.reload())
|
||||||
$.when(_.compact(modelsBeingSaved)...).done(-> document.location.reload())
|
|
||||||
|
reverseLevelsBeforeSave: ->
|
||||||
|
# For some unfathomable reason, not in our code anywhere, the levels get reversed during the save somehow.
|
||||||
|
# Since we want to maintain their orders, we reverse them first, so that when they're reversed again, it's right.
|
||||||
|
# Yaaaay!
|
||||||
|
return unless campaign = _.find @modelsToSave.models, (m) -> m.constructor.className is 'Campaign'
|
||||||
|
levelsReversed = {}
|
||||||
|
levels = campaign.get 'levels'
|
||||||
|
levelIDs = _.keys(levels).reverse()
|
||||||
|
for levelID in levelIDs
|
||||||
|
levelsReversed[levelID] = levels[levelID]
|
||||||
|
campaign.set 'levels', levelsReversed
|
||||||
|
|
70
scripts/mongodb/addExternalSubs.js
Normal file
70
scripts/mongodb/addExternalSubs.js
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
// Add externally purchased subscriptions
|
||||||
|
|
||||||
|
// Usage:
|
||||||
|
// Edit hard coded data below.
|
||||||
|
// mongo <address>:<port>/<database> <script file> -u <username> -p <password>
|
||||||
|
|
||||||
|
// Skips duplicate payments, but this might be incorrect depending on the scenario
|
||||||
|
|
||||||
|
// TODO: output emails not found
|
||||||
|
|
||||||
|
var emails = ['pam@fred.com', 'Bob@fred.com'];
|
||||||
|
var purchaserID = '54ed0ac0ca7f1c421c025b3d';
|
||||||
|
var endDate = '2015-06-01';
|
||||||
|
var gems = 10500;
|
||||||
|
var amount = 1750;
|
||||||
|
var service = 'invoice';
|
||||||
|
|
||||||
|
emails = emails.map(function(e) { return e.toLowerCase();});
|
||||||
|
|
||||||
|
log("Input Data");
|
||||||
|
log("service\t" + service);
|
||||||
|
log("purchaserID\t" + purchaserID);
|
||||||
|
log("end date\t" + endDate);
|
||||||
|
log("gems\t" + gems);
|
||||||
|
log("amount\t" + amount);
|
||||||
|
log("emails");
|
||||||
|
log(emails);
|
||||||
|
|
||||||
|
// 1. Set free = endDate, updated purchased.gems
|
||||||
|
|
||||||
|
db.users.update(
|
||||||
|
{emailLower: {$in: emails}, "stripe.free": {$ne: endDate}},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
"stripe.free": endDate
|
||||||
|
},
|
||||||
|
$inc: {
|
||||||
|
"purchased.gems": gems
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
multi: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. create Payment objects
|
||||||
|
|
||||||
|
var cursor = db.users.find({emailLower: {$in: emails}});
|
||||||
|
while (cursor.hasNext()) {
|
||||||
|
var doc = cursor.next();
|
||||||
|
|
||||||
|
var criteria = {
|
||||||
|
purchaser: ObjectId(purchaserID),
|
||||||
|
recipient: doc._id,
|
||||||
|
service: service,
|
||||||
|
gems: gems,
|
||||||
|
amount: amount
|
||||||
|
}
|
||||||
|
if (db.payments.findOne(criteria)) {
|
||||||
|
log("Already have a payment for " + doc.email);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
db.payments.insert(criteria);
|
||||||
|
log("Added payment for " + doc.email);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function log(str) {
|
||||||
|
print(new Date().toISOString() + " " + str);
|
||||||
|
}
|
Loading…
Reference in a new issue