Merge branch 'develop' of github.com:HabitRPG/habitrpg into Wrong-max-level-messages
2
common/dist/sprites/habitrpg-shared.css
vendored
496
common/dist/sprites/spritesmith-main-6.css
vendored
BIN
common/dist/sprites/spritesmith-main-6.png
vendored
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 156 KiB |
490
common/dist/sprites/spritesmith-main-7.css
vendored
BIN
common/dist/sprites/spritesmith-main-7.png
vendored
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 144 KiB |
1140
common/dist/sprites/spritesmith-main-8.css
vendored
BIN
common/dist/sprites/spritesmith-main-8.png
vendored
|
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 145 KiB |
1276
common/dist/sprites/spritesmith-main-9.css
vendored
BIN
common/dist/sprites/spritesmith-main-9.png
vendored
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
@@ -16,6 +16,7 @@
|
||||
"justin": "Justin",
|
||||
"ian": "Ian",
|
||||
"ianText": "Welcome to the Quest Shop! Here you can use Quest Scrolls to battle monsters with your friends. Be sure to check out our fine array of Quest Scrolls for purchase on the right!",
|
||||
"ianBrokenText": "Welcome to the Quest Shop... Here you can use Quest Scrolls to battle monsters with your friends... Be sure to check out our fine array of Quest Scrolls for purchase on the right...",
|
||||
"USD": "USD",
|
||||
"newStuff": "New Stuff",
|
||||
"cool": "Tell Me Later",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"youReceived": "You've Received",
|
||||
"dropQuestCongrats": "Congratulations on earning this quest scroll! You can invite your party to begin the quest now, or come back to it any time in your Inventory > Quests.",
|
||||
"questSend": "Clicking \"Invite\" will send an invitation to your party members. When all members have accepted or denied, the quest begins. See status under Social > Party.",
|
||||
"questSendBroken": "Clicking \"Invite\" will send an invitation to your party members... When all members have accepted or denied, the quest begins... See status under Social > Party...",
|
||||
"inviteParty": "Invite Party to Quest",
|
||||
"questInvitation": "Quest Invitation: ",
|
||||
"questInvitationTitle": "Quest Invitation",
|
||||
@@ -24,6 +25,7 @@
|
||||
"rejected": "Rejected",
|
||||
"pending": "Pending",
|
||||
"questStart": "Once all members have either accepted or rejected, the quest begins. Only those that clicked \"accept\" will be able to participate in the quest and receive the drops. If members are pending too long (inactive?), the quest owner can start the quest without them by clicking \"Begin\". The quest owner can also cancel the quest and regain the quest scroll by clicking \"Cancel\".",
|
||||
"questStartBroken": "Once all members have either accepted or rejected, the quest begins... Only those that clicked \"accept\" will be able to participate in the quest and receive the drops... If members are pending too long (inactive?), the quest owner can start the quest without them by clicking \"Begin\"... The quest owner can also cancel the quest and regain the quest scroll by clicking \"Cancel\"...",
|
||||
"begin": "Begin",
|
||||
"bossHP": "Boss Health",
|
||||
"bossStrength": "Boss Strength",
|
||||
@@ -34,9 +36,14 @@
|
||||
"itemsToCollect": "Items to Collect",
|
||||
"bossDmg1": "Each completed Daily and To-Do and each positive Habit hurts the boss. Hurt it more with redder tasks or Brutal Smash and Burst of Flames. The boss will deal damage to every quest participant for every Daily you've missed (multiplied by the boss's Strength) in addition to your regular damage, so keep your party healthy by completing your Dailies! <strong>All damage to and from a boss is tallied on cron (your day roll-over).</strong>",
|
||||
"bossDmg2": "Only participants will fight the boss and share in the quest loot.",
|
||||
"bossDmg1Broken": "Each completed Daily and To-Do and each positive Habit hurts the boss... Hurt it more with redder tasks or Brutal Smash and Burst of Flames... The boss will deal damage to every quest participant for every Daily you've missed (multiplied by the boss's Strength) in addition to your regular damage, so keep your party healthy by completing your Dailies... <strong>All damage to and from a boss is tallied on cron (your day roll-over)...</strong>",
|
||||
"bossDmg2Broken": "Only participants will fight the boss and share in the quest loot...",
|
||||
"tavernBossInfo": "Complete Dailies and To-Dos and score positive Habits to damage the World Boss! Incomplete Dailies fill the Exhaust Strike Bar. When the Exhaust Strike bar is full, the World Boss will attack an NPC. A World Boss will never damage individual players or accounts in any way. Only active accounts not resting in the Inn will have their tasks tallied.",
|
||||
"tavernBossInfoBroken": "Complete Dailies and To-Dos and score positive Habits to damage the World Boss... Incomplete Dailies fill the Exhaust Strike Bar... When the Exhaust Strike bar is full, the World Boss will attack an NPC... A World Boss will never damage individual players or accounts in any way... Only active accounts not resting in the Inn will have their tasks tallied...",
|
||||
"bossColl1": "To collect items, do your positive tasks. Quest items drop just like normal items; however, you won't see the drops until the next day, then everything you've found will be tallied up and contributed to the pile.",
|
||||
"bossColl2": "Only participants can collect items and share in the quest loot.",
|
||||
"bossColl1Broken": "To collect items, do your positive tasks... Quest items drop just like normal items; however, you won't see the drops until the next day, then everything you've found will be tallied up and contributed to the pile...",
|
||||
"bossColl2Broken": "Only participants can collect items and share in the quest loot...",
|
||||
"abort": "Abort",
|
||||
"leaveQuest": "Leave Quest",
|
||||
"sureLeave": "Are you sure you want to leave the active quest? All your quest progress will be lost.",
|
||||
@@ -63,6 +70,7 @@
|
||||
"sureAbort": "Are you sure you want to abort this mission? It will abort it for everyone in your party and all progress will be lost. The quest scroll will be returned to the quest owner.",
|
||||
"doubleSureAbort": "Are you double sure? Make sure they won't hate you forever!",
|
||||
"questWarning": "If new players join the party before the quest starts, they will also receive an invitation. However once the quest has started, no new party members can join the quest.",
|
||||
"questWarningBroken": "If new players join the party before the quest starts, they will also receive an invitation... However once the quest has started, no new party members can join the quest...",
|
||||
"bossRageTitle": "Rage",
|
||||
"bossRageDescription": "When this bar fills, the boss will unleash a special attack!",
|
||||
"startAQuest": "START A QUEST",
|
||||
|
||||
@@ -119,5 +119,26 @@
|
||||
"promoCodeApplied": "Promo Code Applied! Check your inventory",
|
||||
"promoPlaceholder": "Enter Promotion Code",
|
||||
"couponText": "We sometimes have events and give out coupon codes for special gear. (eg, those who stop by our Wondercon booth)",
|
||||
"displayInviteToPartyWhenPartyIs1": "Display Invite To Party button when party has 1 member."
|
||||
"displayInviteToPartyWhenPartyIs1": "Display Invite To Party button when party has 1 member.",
|
||||
"saveCustomDayStart": "Save Custom Day Start",
|
||||
"registration": "Registration",
|
||||
"addLocalAuth": "Add local authentication:",
|
||||
"generateCodes": "Generate Codes",
|
||||
"generate": "Generate",
|
||||
"getCodes": "Get Codes",
|
||||
"webhooks": "Webhooks",
|
||||
"enabled": "Enabled",
|
||||
"webhookURL": "Webhook URL",
|
||||
"add": "Add",
|
||||
"buyGemsGoldCap": "Cap raised to <%= amount %>",
|
||||
"mysticHourglass": "<%= amount %> Mystic Hourglass",
|
||||
"mysticHourglassText": "Mystic Hourglasses allow purchasing a previous month's Mystery Item set.",
|
||||
"purchasedPlanId": "Recurring $<%= price %> each <%= months %> Month(s) (<%= plan %>)",
|
||||
"purchasedPlanExtraMonths": "You have <%= months %> months of subscription credit.",
|
||||
"consecutiveSubscription": "Consecutive Subscription",
|
||||
"consecutiveMonths": "Consecutive Months:",
|
||||
"gemCapExtra": "Gem Cap Extra:",
|
||||
"mysticHourglasses": "Mystic Hourglasses:",
|
||||
"paypal": "PayPal",
|
||||
"amazonPayments": "Amazon Payments"
|
||||
}
|
||||
|
||||
@@ -1154,6 +1154,7 @@ api.specialMounts =
|
||||
'Orca-Base': 'orca'
|
||||
'Gryphon-RoyalPurple': 'royalPurpleGryphon'
|
||||
'Phoenix-Base': 'phoenix'
|
||||
'JackOLantern-Base': 'jackolantern'
|
||||
|
||||
api.timeTravelStable =
|
||||
pets:
|
||||
|
||||
@@ -27,7 +27,7 @@ import moment from 'moment';
|
||||
// mystery: <mystery_set_key>
|
||||
// }
|
||||
//
|
||||
// <gear_type> - What type of euqipment it is (armor, head, weapon, etc)
|
||||
// <gear_type> - What type of equipment it is (armor, head, weapon, etc)
|
||||
// <set_name> - What set this gear is a part of (special, mystery, warrior, etc)
|
||||
// <index> - The order in this particular set
|
||||
// <formatted_equipment_key> - CamelCased version of key
|
||||
|
||||
@@ -6,7 +6,8 @@ let specialMounts = {
|
||||
'Mammoth-Base': 'mammoth',
|
||||
'Orca-Base': 'orca',
|
||||
'Gryphon-RoyalPurple': 'royalPurpleGryphon',
|
||||
'Phoenix-Base': 'phoenix'
|
||||
'Phoenix-Base': 'phoenix',
|
||||
'JackOLantern-Base': 'jackolantern'
|
||||
}
|
||||
|
||||
export default specialMounts;
|
||||
|
||||
67
migrations/20151013_jackolanterns.js
Normal file
@@ -0,0 +1,67 @@
|
||||
var migrationName = 'new_stuff.js';
|
||||
var authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
|
||||
|
||||
/*
|
||||
* Award Jack-O'-Lantern mounts to users who already have the pet version, award pet if they don't
|
||||
*/
|
||||
|
||||
var dbserver = 'localhost:27017'; // FOR TEST DATABASE
|
||||
// var dbserver = 'username:password@ds031379-a0.mongolab.com:31379'; // FOR PRODUCTION DATABASE
|
||||
var dbname = 'habitrpg';
|
||||
|
||||
var mongo = require('mongoskin');
|
||||
var _ = require('lodash');
|
||||
|
||||
var dbUsers = mongo.db(dbserver + '/' + dbname + '?auto_reconnect').collection('users');
|
||||
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
var query = {
|
||||
};
|
||||
|
||||
// specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
var fields = {
|
||||
'items.pets.JackOLantern-Base':1
|
||||
};
|
||||
|
||||
console.warn('Updating users...');
|
||||
var progressCount = 1000;
|
||||
var count = 0;
|
||||
dbUsers.findEach(query, fields, {batchSize:250}, function(err, user) {
|
||||
if (err) { return exiting(1, 'ERROR! ' + err); }
|
||||
if (!user) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
return displayData();
|
||||
}
|
||||
count++;
|
||||
|
||||
// specify user data to change:
|
||||
var set = {};
|
||||
if (user.items.pets['JackOLantern-Base']) {
|
||||
set = {'migration':migrationName, 'items.mounts.JackOLantern-Base':true};
|
||||
} else {
|
||||
set = {'migration':migrationName, 'items.pets.JackOLantern-Base':5};
|
||||
}
|
||||
|
||||
dbUsers.update({_id:user._id}, {$set:set});
|
||||
|
||||
if (count%progressCount == 0) console.warn(count + ' ' + user._id);
|
||||
if (user._id == authorUuid) console.warn(authorName + ' processed');
|
||||
});
|
||||
|
||||
|
||||
function displayData() {
|
||||
console.warn('\n' + count + ' users processed\n');
|
||||
return exiting(0);
|
||||
}
|
||||
|
||||
|
||||
function exiting(code, msg) {
|
||||
code = code || 0; // 0 = success
|
||||
if (code && !msg) { msg = 'ERROR!'; }
|
||||
if (msg) {
|
||||
if (code) { console.error(msg); }
|
||||
else { console.log( msg); }
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
@@ -4,38 +4,10 @@ app = require("../../website/src/server")
|
||||
|
||||
describe "Site Status", ->
|
||||
|
||||
describe "Without token or user id", ->
|
||||
describe "GET /status", ->
|
||||
|
||||
it "/api/v2/status", (done) ->
|
||||
request.get(baseURL + "/status").set("Accept", "application/json").end (res) ->
|
||||
expect(res.statusCode).to.equal 200
|
||||
expect(res.body.status).to.equal "up"
|
||||
done()
|
||||
|
||||
it "/api/v2/user", (done) ->
|
||||
request
|
||||
.get(baseURL + "/user")
|
||||
.set("Accept", "application/json")
|
||||
.set("X-API-User", '')
|
||||
.set("X-API-Key", '')
|
||||
.end (res) ->
|
||||
expect(res.statusCode).to.equal 401
|
||||
expect(res.body.err).to.equal "You must include a token and uid (user id) in your request"
|
||||
done()
|
||||
|
||||
describe "With token or user id", ->
|
||||
|
||||
before (done) ->
|
||||
registerNewUser(done, true)
|
||||
|
||||
it "/api/v2/status", (done) ->
|
||||
request.get(baseURL + "/status").set("Accept", "application/json").end (res) ->
|
||||
expect(res.statusCode).to.equal 200
|
||||
expect(res.body.status).to.equal "up"
|
||||
done()
|
||||
|
||||
it "/api/v2/user", (done) ->
|
||||
request.get(baseURL + "/user").set("Accept", "application/json").end (res) ->
|
||||
expect(res.statusCode).to.equal 200
|
||||
expect(res.body._id).to.equal user._id
|
||||
done()
|
||||
|
||||
@@ -5,6 +5,30 @@ app = require("../../website/src/server")
|
||||
|
||||
describe "Users", ->
|
||||
|
||||
describe "GET /user without token or user id", ->
|
||||
|
||||
it "/api/v2/user", (done) ->
|
||||
request
|
||||
.get(baseURL + "/user")
|
||||
.set("Accept", "application/json")
|
||||
.set("X-API-User", '')
|
||||
.set("X-API-Key", '')
|
||||
.end (res) ->
|
||||
expect(res.statusCode).to.equal 401
|
||||
expect(res.body.err).to.equal "You must include a token and uid (user id) in your request"
|
||||
done()
|
||||
|
||||
describe "GET /user with token or user id", ->
|
||||
|
||||
before (done) ->
|
||||
registerNewUser(done, true)
|
||||
|
||||
it "/api/v2/user", (done) ->
|
||||
request.get(baseURL + "/user").set("Accept", "application/json").end (res) ->
|
||||
expect(res.statusCode).to.equal 200
|
||||
expect(res.body._id).to.equal user._id
|
||||
done()
|
||||
|
||||
describe "Deleting Users", ->
|
||||
|
||||
before (done) ->
|
||||
|
||||
@@ -135,7 +135,7 @@ describe('Stats Service', function() {
|
||||
});
|
||||
|
||||
describe('equipmentStatBonus', function() {
|
||||
it('tallies up stats from euqipment that is equipped', function() {
|
||||
it('tallies up stats from equipment that is equipped', function() {
|
||||
var equippedGear = {
|
||||
"weapon" : "weapon_special_1",
|
||||
"shield" : "shield_special_1",
|
||||
|
||||
@@ -68,6 +68,8 @@ echo Updating repositories...
|
||||
apt-get update -qq
|
||||
echo Installing node.js
|
||||
apt-get install -qq nodejs
|
||||
echo Updating npm...
|
||||
npm install -g npm
|
||||
|
||||
cd /vagrant
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
// Make user and settings available for everyone through root scope.
|
||||
habitrpg.controller('SettingsCtrl',
|
||||
['$scope', 'User', '$rootScope', '$http', 'ApiUrl', 'Guide', '$location', '$timeout', 'Notification', 'Shared',
|
||||
function($scope, User, $rootScope, $http, ApiUrl, Guide, $location, $timeout, Notification, Shared) {
|
||||
['$scope', 'User', '$rootScope', '$http', 'ApiUrl', 'Guide', '$location', '$timeout', 'Content', 'Notification', 'Shared',
|
||||
function($scope, User, $rootScope, $http, ApiUrl, Guide, $location, $timeout, Content, Notification, Shared) {
|
||||
|
||||
// FIXME we have this re-declared everywhere, figure which is the canonical version and delete the rest
|
||||
// $scope.auth = function (id, token) {
|
||||
@@ -150,6 +150,7 @@ habitrpg.controller('SettingsCtrl',
|
||||
Notification.text(env.t('promoCodeApplied'));
|
||||
});
|
||||
}
|
||||
|
||||
$scope.generateCodes = function(codes){
|
||||
$http.post(ApiUrl.get() + '/api/v2/coupons/generate/'+codes.event+'?count='+(codes.count || 1))
|
||||
.success(function(res,code){
|
||||
@@ -158,6 +159,7 @@ habitrpg.controller('SettingsCtrl',
|
||||
window.location.href = '/api/v2/coupons?limit='+codes.count+'&_id='+User.user._id+'&apiToken='+User.user.apiToken;
|
||||
})
|
||||
}
|
||||
|
||||
$scope.releasePets = function() {
|
||||
User.user.ops.releasePets({});
|
||||
$rootScope.$state.go('tasks');
|
||||
@@ -194,12 +196,30 @@ habitrpg.controller('SettingsCtrl',
|
||||
$http.get(ApiUrl.get() + '/api/v2/coupons/valid-discount/'+coupon)
|
||||
.success(function(){
|
||||
Notification.text("Coupon applied!");
|
||||
var subs = $scope.Content.subscriptionBlocks;
|
||||
var subs = Content.subscriptionBlocks;
|
||||
subs["basic_6mo"].discount = true;
|
||||
subs["google_6mo"].discount = false;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.gemGoldCap = function(subscription) {
|
||||
var baseCap = 25;
|
||||
var gemCapExtra = User.user.purchased.plan.consecutive.gemCapExtra;
|
||||
// @TODO: What are these magic numbers? 3? 5?
|
||||
var blocks = Content.subscriptionBlocks[subscription.key].months / 3 * 5;
|
||||
var flooredBlocks = Math.floor(blocks);
|
||||
|
||||
var userTotalDropCap = baseCap + gemCapExtra + flooredBlocks;
|
||||
var maxDropCap = 50;
|
||||
|
||||
return [userTotalDropCap, maxDropCap];
|
||||
};
|
||||
|
||||
$scope.numberOfMysticHourglasses = function(subscription) {
|
||||
var numberOfHourglasses = Content.subscriptionBlocks[subscription.key].months / 3;
|
||||
return Math.floor(numberOfHourglasses);
|
||||
};
|
||||
|
||||
function _calculateNextCron() {
|
||||
$scope.dayStart;
|
||||
|
||||
|
||||
@@ -67,8 +67,8 @@ api.authWithUrl = function(req, res, next) {
|
||||
}
|
||||
|
||||
api.registerUser = function(req, res, next) {
|
||||
var regEmail = RegexEscape(req.body.email),
|
||||
regUname = RegexEscape(req.body.username);
|
||||
var regUname = RegexEscape(req.body.username);
|
||||
var email = req.body.email && req.body.email.toLowerCase();
|
||||
async.auto({
|
||||
validate: function(cb) {
|
||||
if (!(req.body.username && req.body.password && req.body.email))
|
||||
@@ -80,14 +80,14 @@ api.registerUser = function(req, res, next) {
|
||||
cb();
|
||||
},
|
||||
findReg: function(cb) {
|
||||
User.findOne({$or:[{'auth.local.email': regEmail}, {'auth.local.username': regUname}]}, {'auth.local':1}, cb);
|
||||
User.findOne({$or:[{'auth.local.email': email}, {'auth.local.username': regUname}]}, {'auth.local':1}, cb);
|
||||
},
|
||||
findFacebook: function(cb){
|
||||
User.findOne({_id: req.headers['x-api-user'], apiToken: req.headers['x-api-key']}, {auth:1}, cb);
|
||||
},
|
||||
register: ['validate', 'findReg', 'findFacebook', function(cb, data) {
|
||||
if (data.findReg) {
|
||||
if (regEmail.test(data.findReg.auth.local.email)) return cb({code:401, err:"Email already taken"});
|
||||
if (email === data.findReg.auth.local.email) return cb({code:401, err:"Email already taken"});
|
||||
if (regUname.test(data.findReg.auth.local.username)) return cb({code:401, err:"Username already taken"});
|
||||
}
|
||||
var salt = utils.makeSalt();
|
||||
@@ -95,7 +95,7 @@ api.registerUser = function(req, res, next) {
|
||||
auth: {
|
||||
local: {
|
||||
username: req.body.username,
|
||||
email: req.body.email,
|
||||
email: email,
|
||||
salt: salt,
|
||||
hashed_password: utils.encryptPassword(req.body.password, salt)
|
||||
},
|
||||
@@ -143,7 +143,7 @@ api.loginLocal = function(req, res, next) {
|
||||
var username = req.body.username;
|
||||
var password = req.body.password;
|
||||
if (!(username && password)) return res.json(401, {err:'Missing :username or :password in request body, please provide both'});
|
||||
var login = validator.isEmail(username) ? {'auth.local.email':username} : {'auth.local.username':username};
|
||||
var login = validator.isEmail(username) ? {'auth.local.email':username.toLowerCase()} : {'auth.local.username':username};
|
||||
User.findOne(login, {auth:1}, function(err, user){
|
||||
if (err) return next(err);
|
||||
if (!user) return res.json(401, {err:"Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\"."});
|
||||
@@ -233,12 +233,12 @@ api.deleteSocial = function(req,res,next){
|
||||
}
|
||||
|
||||
api.resetPassword = function(req, res, next){
|
||||
var email = req.body.email,
|
||||
var email = req.body.email && req.body.email.toLowerCase(),
|
||||
salt = utils.makeSalt(),
|
||||
newPassword = utils.makeSalt(), // use a salt as the new password too (they'll change it later)
|
||||
hashed_password = utils.encryptPassword(newPassword, salt);
|
||||
|
||||
User.findOne({'auth.local.email': RegexEscape(email)}, function(err, user){
|
||||
User.findOne({'auth.local.email': email}, function(err, user){
|
||||
if (err) return next(err);
|
||||
if (!user) return res.send(401, {err:"Sorry, we can't find a user registered with email " + email + "\n- Make sure your email address is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login."});
|
||||
user.auth.local.salt = salt;
|
||||
@@ -283,14 +283,15 @@ api.changeUsername = function(req, res, next) {
|
||||
}
|
||||
|
||||
api.changeEmail = function(req, res, next){
|
||||
var email = req.body.email && req.body.email.toLowerCase()
|
||||
async.waterfall([
|
||||
function(cb){
|
||||
User.findOne({'auth.local.email': RegexEscape(req.body.email)}, {auth:1}, cb);
|
||||
User.findOne({'auth.local.email': email}, {auth:1}, cb);
|
||||
},
|
||||
function(found, cb){
|
||||
if(found) return cb({code:401, err: "Email already taken"});
|
||||
if (invalidPassword(res.locals.user, req.body.password)) return cb(invalidPassword(res.locals.user, req.body.password));
|
||||
res.locals.user.auth.local.email = req.body.email;
|
||||
res.locals.user.auth.local.email = email;
|
||||
res.locals.user.save(cb);
|
||||
}
|
||||
], function(err){
|
||||
|
||||
@@ -9,7 +9,8 @@ include ../../shared/mixins
|
||||
.arrow.hidden-xs
|
||||
h3.popover-title=env.t('ian')
|
||||
.popover-content
|
||||
p=env.t('ianText')
|
||||
p(ng-if='env.worldDmg.quests')=env.t('ianBrokenText')
|
||||
p(ng-if='!env.worldDmg.quests')=env.t('ianText')
|
||||
|
||||
h3.equipment-title=env.t('yourQuests')
|
||||
+ownedQuests(true,'right')
|
||||
|
||||
@@ -103,19 +103,19 @@ script(type='text/ng-template', id='partials/options.settings.settings.html')
|
||||
br.visible-xs
|
||||
button.btn.btn-block.btn-primary(ng-click='openDayStartModal(dayStart)',
|
||||
ng-disabled='dayStart == user.preferences.dayStart')
|
||||
| Save Custom Day Start
|
||||
=env.t('saveCustomDayStart')
|
||||
|
||||
.personal-options.col-md-6
|
||||
.panel.panel-default
|
||||
.panel-heading
|
||||
span Registration
|
||||
span=env.t('registration')
|
||||
.panel-body
|
||||
div(ng-if='user.auth.facebook.id')
|
||||
button.btn.btn-primary(disabled='disabled', ng-if='!user.auth.local.username')=env.t('registeredWithFb')
|
||||
button.btn.btn-danger(ng-click='http("delete","/api/v2/user/auth/social",null,"detachedFacebook")', ng-if='user.auth.local.username')=env.t('detachFacebook')
|
||||
hr
|
||||
div(ng-if='!user.auth.local.username')
|
||||
p Add local authentication:
|
||||
p=env.t('addLocalAuth')
|
||||
form(ng-submit='http("post","/api/v2/register",localAuth,"addedLocalAuth")', ng-init='localAuth={}', name='localAuth', novalidate)
|
||||
//-.alert.alert-danger(ng-messages='changeUsername.$error && changeUsername.submitted')=env.t('fillAll')
|
||||
.form-group
|
||||
@@ -189,15 +189,15 @@ script(type='text/ng-template', id='partials/options.settings.promo.html')
|
||||
small= env.t('couponText')
|
||||
div(ng-if='user.contributor.sudo')
|
||||
hr
|
||||
h4 Generate Codes
|
||||
h4=env.t('generateCodes')
|
||||
form.form(role='form',ng-submit='generateCodes(_codes)',ng-init='_codes={}')
|
||||
.form-group
|
||||
input.form-control(type='text',ng-model='_codes.event',placeholder="Event code (eg, 'wondercon')")
|
||||
.form-group
|
||||
input.form-control(type='number',ng-model='_codes.count',placeholder="Number of codes to generate (eg, 250)")
|
||||
.form-group
|
||||
button.btn.btn-primary(type='submit') Generate
|
||||
a.btn.btn-default(href='/api/v2/coupons?_id={{user._id}}&apiToken={{user.apiToken}}') Get Codes
|
||||
button.btn.btn-primary(type='submit')=env.t('generate')
|
||||
a.btn.btn-default(href='/api/v2/coupons?_id={{user._id}}&apiToken={{user.apiToken}}')=env.t('getCodes')
|
||||
|
||||
script(type='text/ng-template', id='partials/options.settings.api.html')
|
||||
.container-fluid
|
||||
@@ -214,12 +214,12 @@ script(type='text/ng-template', id='partials/options.settings.api.html')
|
||||
|
||||
hr
|
||||
|
||||
h2 Webhooks
|
||||
h2=env.t('webhooks')
|
||||
table.table.table-striped
|
||||
thead(ng-if='hasWebhooks')
|
||||
tr
|
||||
th Enabled
|
||||
th Webhook URL
|
||||
th=env.t('enabled')
|
||||
th=env.t('webhookURL')
|
||||
th
|
||||
tbody
|
||||
tr(ng-repeat="webhook in user.preferences.webhooks | toArray:true | orderBy:'sort'")
|
||||
@@ -235,9 +235,9 @@ script(type='text/ng-template', id='partials/options.settings.api.html')
|
||||
td(colspan=2)
|
||||
form.form-horizontal(ng-submit='addWebhook(_newWebhook.url)')
|
||||
.form-group.col-sm-10
|
||||
input.form-control(type='url', ng-model='_newWebhook.url', placeholder='Webhook URL')
|
||||
input.form-control(type='url', ng-model='_newWebhook.url', placeholder=env.t('webhookURL'))
|
||||
.col-sm-2
|
||||
button.btn.btn-sm.btn-primary(type='submit') Add
|
||||
button.btn.btn-sm.btn-primary(type='submit')=env.t('add')
|
||||
|
||||
script(id='partials/options.settings.export.html', type="text/ng-template")
|
||||
.container-fluid
|
||||
@@ -258,7 +258,7 @@ mixin subPerks()
|
||||
tr
|
||||
td
|
||||
span.hint(popover=env.t('buyGemsGoldText', {gemCost: "{{Shared.planGemLimits.convRate}}", gemLimit: "{{Shared.planGemLimits.convCap}}"}),popover-trigger='mouseenter',popover-placement='right') #{env.t('buyGemsGold')}
|
||||
span.badge.badge-success(ng-show='_subscription.key!="basic_earned"') Cap raised to {{ [25 + user.purchased.plan.consecutive.gemCapExtra + Math.floor(Content.subscriptionBlocks[_subscription.key].months/3*5), 50] | min }}
|
||||
span.badge.badge-success(ng-show='_subscription.key!="basic_earned"')=env.t('buyGemsGoldCap', {amount: '{{:: gemGoldCap(_subscription) | min }}'})
|
||||
tr
|
||||
td
|
||||
span.hint(popover=env.t('retainHistoryText'),popover-trigger='mouseenter',popover-placement='right')=env.t('retainHistory')
|
||||
@@ -269,8 +269,8 @@ mixin subPerks()
|
||||
td
|
||||
span.hint(popover=env.t('mysteryItemText'),popover-trigger='mouseenter',popover-placement='right') #{env.t('mysteryItem')}
|
||||
div(ng-show='_subscription.key!="basic_earned"')
|
||||
.badge.badge-success +{{Math.floor(Content.subscriptionBlocks[_subscription.key].months/3)}} Mystic Hourglass
|
||||
.small.muted Mystic Hourglasses allow purchasing a previous month's Mystery Item set.
|
||||
.badge.badge-success=env.t('mysticHourglass', {amount: '+{{:: numberOfMysticHourglasses(_subscription)}}'})
|
||||
.small.muted=env.t('mysticHourglassText')
|
||||
tr
|
||||
td
|
||||
span.hint(popover=env.t('supportDevsText'),popover-trigger='mouseenter',popover-placement='right')=env.t('supportDevs')
|
||||
@@ -324,17 +324,17 @@ script(id='partials/options.settings.subscription.html',type='text/ng-template')
|
||||
| #{env.t('subCanceled')} <strong>{{moment(user.purchased.plan.dateTerminated).format('MM/DD/YYYY')}}</strong>
|
||||
tr(ng-if='!user.purchased.plan.dateTerminated'): td
|
||||
h4=env.t('subscribed')
|
||||
p(ng-if='user.purchased.plan.planId') Recurring ${{Content.subscriptionBlocks[user.purchased.plan.planId].price}} each {{Content.subscriptionBlocks[user.purchased.plan.planId].months}} Month(s) ({{user.purchased.plan.paymentMethod}})
|
||||
p(ng-if='user.purchased.plan.planId')=env.t('purchasedPlanId', {price: '{{Content.subscriptionBlocks[user.purchased.plan.planId].price}}', months: '{{Content.subscriptionBlocks[user.purchased.plan.planId].months}}', plan: '{{user.purchased.plan.paymentMethod}}'})
|
||||
tr(ng-if='user.purchased.plan.extraMonths'): td
|
||||
span.glyphicon.glyphicon-credit-card
|
||||
| You have {{user.purchased.plan.extraMonths | number:2}} months of subscription credit.
|
||||
| #{env.t('purchasedPlanExtraMonths', {months: '{{user.purchased.plan.extraMonths | number:2}}'})}
|
||||
tr(ng-if='user.purchased.plan.consecutive.count || user.purchased.plan.consecutive.offset'): td
|
||||
span.glyphicon.glyphicon-forward
|
||||
| Consecutive Subscription
|
||||
| #{env.t('consecutiveSubscription')}
|
||||
ul.list-unstyled
|
||||
li Consecutive Months: {{user.purchased.plan.consecutive.count + user.purchased.plan.consecutive.offset}}
|
||||
li Gem Cap Extra: {{user.purchased.plan.consecutive.gemCapExtra}}
|
||||
li Mystic Hourglasses: {{user.purchased.plan.consecutive.trinkets}}
|
||||
li #{env.t('consecutiveMonths')} {{user.purchased.plan.consecutive.count + user.purchased.plan.consecutive.offset}}
|
||||
li #{env.t('gemCapExtra')} {{user.purchased.plan.consecutive.gemCapExtra}}
|
||||
li #{env.t('mysticHourglasses')} {{user.purchased.plan.consecutive.trinkets}}
|
||||
div(ng-if='!user.purchased.plan.customerId || (user.purchased.plan.customerId && user.purchased.plan.dateTerminated)')
|
||||
.form-group
|
||||
.radio(ng-repeat='block in Content.subscriptionBlocks | toArray | omit: "discount==true" | orderBy:"months"')
|
||||
@@ -355,8 +355,8 @@ script(id='partials/options.settings.subscription.html',type='text/ng-template')
|
||||
|
||||
h3(ng-if='(user.purchased.plan.customerId && user.purchased.plan.dateTerminated)')= env.t("resubscribe")
|
||||
a.btn.btn-primary(ng-click='Payments.showStripe({subscription:_subscription.key, coupon:_subscription.coupon})', ng-disabled='!_subscription.key')= env.t('card')
|
||||
a.btn.btn-success(href='/paypal/subscribe?_id={{user._id}}&apiToken={{user.apiToken}}&sub={{_subscription.key}}{{_subscription.coupon ? "&coupon="+_subscription.coupon : ""}}', ng-disabled='!_subscription.key') PayPal
|
||||
a.btn.btn-warning(ng-click="Payments.amazonPayments.init({type: 'subscription', subscription:_subscription.key, coupon:_subscription.coupon})") Amazon Payments
|
||||
a.btn.btn-success(href='/paypal/subscribe?_id={{user._id}}&apiToken={{user.apiToken}}&sub={{_subscription.key}}{{_subscription.coupon ? "&coupon="+_subscription.coupon : ""}}', ng-disabled='!_subscription.key')=env.t('paypal')
|
||||
a.btn.btn-warning(ng-click="Payments.amazonPayments.init({type: 'subscription', subscription:_subscription.key, coupon:_subscription.coupon})")=env.t('amazonPayments')
|
||||
div(ng-if='user.purchased.plan.customerId')
|
||||
.btn.btn-primary(ng-if='!user.purchased.plan.dateTerminated && user.purchased.plan.paymentMethod=="Stripe"', ng-click='Payments.showStripeEdit()')=env.t('subUpdateCard')
|
||||
.btn.btn-sm.btn-danger(ng-if='!user.purchased.plan.dateTerminated', ng-click='Payments.cancelSubscription()')=env.t('cancelSub')
|
||||
|
||||
@@ -2,13 +2,18 @@ div(class="#{env.worldDmg.quests ? 'npc_ian_broken' : 'npc_ian'}").pull-left
|
||||
|
||||
div(ng-if='::Content.quests[group.quest.key].boss')
|
||||
if tavern
|
||||
p!=env.t('tavernBossInfo')
|
||||
p(ng-if='!env.worldDmg.quests')!=env.t('tavernBossInfo')
|
||||
p(ng-if='env.worldDmg.quests')!=env.t('tavernBossInfoBroken')
|
||||
else
|
||||
p!=env.t('bossDmg1')
|
||||
p(ng-if='!env.worldDmg.quests')!=env.t('bossDmg1')
|
||||
p(ng-if='env.worldDmg.quests')!=env.t('bossDmg1Broken')
|
||||
br
|
||||
p=env.t('bossDmg2')
|
||||
p(ng-if='!env.worldDmg.quests')=env.t('bossDmg2')
|
||||
p(ng-if='env.worldDmg.quests')=env.t('bossDmg2Broken')
|
||||
|
||||
div(ng-if='::Content.quests[group.quest.key].collect')
|
||||
p=env.t('bossColl1')
|
||||
p(ng-if='!env.worldDmg.quests')=env.t('bossColl1')
|
||||
p(ng-if='env.worldDmg.quests')=env.t('bossColl1Broken')
|
||||
br
|
||||
p=env.t('bossColl2')
|
||||
p(ng-if='!env.worldDmg.quests')=env.t('bossColl2')
|
||||
p(ng-if='env.worldDmg.quests')=env.t('bossColl2Broken')
|
||||
|
||||
@@ -19,7 +19,8 @@ div(ng-if='group.quest.active===false')
|
||||
hr
|
||||
|
||||
div(class="#{env.worldDmg.quests ? 'npc_ian_broken' : 'npc_ian'}").pull-left
|
||||
p=env.t('questStart')
|
||||
p(ng-if='!env.worldDmg.quests')=env.t('questStart')
|
||||
p(ng-if='env.worldDmg.quests')=env.t('questStartBroken')
|
||||
|
||||
span(ng-if='user.party.quest.RSVPNeeded')
|
||||
button.btn.btn-sm.btn-success(ng-click='questAccept()')=env.t('accept')
|
||||
|
||||
@@ -39,8 +39,10 @@ script(type='text/ng-template', id='modals/showQuest.html')
|
||||
+questInfo
|
||||
hr
|
||||
div(class="#{env.worldDmg.quests ? 'npc_ian_broken' : 'npc_ian'}").pull-left
|
||||
p=env.t('questSend')
|
||||
p=env.t('questWarning')
|
||||
p(ng-if='!env.worldDmg.quests')=env.t('questSend')
|
||||
p(ng-if='!env.worldDmg.quests')=env.t('questWarning')
|
||||
p(ng-if='env.worldDmg.quests')=env.t('questSendBroken')
|
||||
p(ng-if='env.worldDmg.quests')=env.t('questWarningBroken')
|
||||
.modal-footer
|
||||
button.btn.btn-default(ng-click='closeQuest(); $close()')=env.t('cancel')
|
||||
button.btn.btn-primary(ng-click='questInit()')
|
||||
|
||||
@@ -1,34 +1,53 @@
|
||||
h2 10/8/2015 - WORLD BOSS REVEALED: BURNOUT!
|
||||
h2 10/14/2015 - JACK O'LANTERN PETS AND MOUNTS! WORLD BOSS EXHAUST STRIKE!
|
||||
hr
|
||||
tr
|
||||
td
|
||||
.promo_burnout.pull-right
|
||||
h3 Burnout, Scourge of the Flourishing Fields
|
||||
p It is well past midnight, still and stiflingly hot, when Redphoenix and scout captain Kiwibot abruptly burst through the city gates. "We need to evacuate all the wooden buildings!" Redphoenix shouts. "Hurry!"
|
||||
.Pet-JackOLantern-Base.pull-right
|
||||
h3 Jack O'Lantern Pets and Mounts
|
||||
p The Flourishing Fields are full of cute carved pumpkins - and it looks like one has followed you home! It must be frightened of Burnout and looking for an adventurer to protect it.
|
||||
br
|
||||
p Kiwibot grips the wall as she catches her breath."It's draining people and turning them into Exhaust Spirits! That's why everything was delayed. That's where the missing people have gone. It's been stealing their energy!"
|
||||
p Those of you who weren't around last Fall Festival have received a pet Jack-O-Lantern, and those of you who got one last year have received a Jack-O-Lantern Mount! Happy Fall Festival.
|
||||
p.small.muted by Lemoness
|
||||
tr
|
||||
td
|
||||
.npc_ian_broken.pull-right
|
||||
h3 World Boss: Burnout Uses Exhaust Strike!
|
||||
p Oh no! Despite our best efforts, we've let some Dailies get away from us, and now Burnout is inflamed with energy! With a crackling snarl, it engulfs Ian the Quest Master in a surge of spectral fire. As fallen quest scrolls smolder, the smoke clears, and you see that Ian has been drained of energy and turned into a drifting Exhaust Spirit!
|
||||
br
|
||||
p "'It'?'" asks Lemoness.
|
||||
p Only defeating Burnout can break the spell and save our beloved Quest Master. Let's keep our Dailies in check and defeat this monster before it attacks again!
|
||||
br
|
||||
p And then the heat takes form.
|
||||
br
|
||||
p It rises from the earth in a billowing, twisting mass, and the air chokes with the scent of smoke and sulphur. Flames lick across the molten ground and contort into limbs, writhing to horrific heights. Smoldering eyes snap open, and the creature lets out a deep and crackling cackle.
|
||||
br
|
||||
p Kiwibot whispers a single word.
|
||||
br
|
||||
p "Burnout."
|
||||
hr
|
||||
p Quickly, Habiticans, we have to save the Flourishing Fields from incineration! All of us will battle this World Boss together by completing tasks, but it won't attack us individually. However, the more Dailies we skip, the closer we get to triggering its fearsome Exhaust Strike, which will target our NPCs!
|
||||
br
|
||||
p Knowing your enemy is the first step to defeating it. <a href='http://habitica.wikia.com/wiki/Boss#World_Bosses' target='_blank'>Read more about World Bosses here</a>.
|
||||
br
|
||||
p.small.muted by Lemoness, SabreCat, and Kiwibot
|
||||
p Late to the fight? Learn about Burnout and how to defeat World Bosses <a href='http://habitica.wikia.com/wiki/Boss#World_Bosses' target='_blank'>here</a>.
|
||||
|
||||
if menuItem !== 'oldNews'
|
||||
hr
|
||||
a(href='/static/old-news', target='_blank') Read older news
|
||||
|
||||
mixin oldNews
|
||||
h2 10/8/2015 - WORLD BOSS REVEALED: BURNOUT!
|
||||
tr
|
||||
td
|
||||
.promo_burnout.pull-right
|
||||
h3 Burnout, Scourge of the Flourishing Fields
|
||||
p It is well past midnight, still and stiflingly hot, when Redphoenix and scout captain Kiwibot abruptly burst through the city gates. "We need to evacuate all the wooden buildings!" Redphoenix shouts. "Hurry!"
|
||||
br
|
||||
p Kiwibot grips the wall as she catches her breath."It's draining people and turning them into Exhaust Spirits! That's why everything was delayed. That's where the missing people have gone. It's been stealing their energy!"
|
||||
br
|
||||
p "'It'?'" asks Lemoness.
|
||||
br
|
||||
p And then the heat takes form.
|
||||
br
|
||||
p It rises from the earth in a billowing, twisting mass, and the air chokes with the scent of smoke and sulphur. Flames lick across the molten ground and contort into limbs, writhing to horrific heights. Smoldering eyes snap open, and the creature lets out a deep and crackling cackle.
|
||||
br
|
||||
p Kiwibot whispers a single word.
|
||||
br
|
||||
p "Burnout."
|
||||
hr
|
||||
p Quickly, Habiticans, we have to save the Flourishing Fields from incineration! All of us will battle this World Boss together by completing tasks, but it won't attack us individually. However, the more Dailies we skip, the closer we get to triggering its fearsome Exhaust Strike, which will target our NPCs!
|
||||
br
|
||||
p Knowing your enemy is the first step to defeating it. <a href='http://habitica.wikia.com/wiki/Boss#World_Bosses' target='_blank'>Read more about World Bosses here</a>.
|
||||
br
|
||||
p.small.muted by Lemoness, SabreCat, and Kiwibot
|
||||
|
||||
h2 10/5/2015 - OCTOBER BACKGROUNDS REVEALED, COSTUME CHALLENGE BEGINS, AND FALL PLOT-LINE CONTINUES
|
||||
tr
|
||||
td
|
||||
|
||||