[#1977] APIv2 WIP - start of a framework where operations are shared

between client & server. If the op is called on the client, it updates
the user & then POSTs to the server with op of the same name. If called
on server, it updates the user and user.save()s
This commit is contained in:
Tyler Renelle
2013-12-11 09:30:39 -07:00
parent 842f0de838
commit ee1cd3c05d
39 changed files with 313 additions and 569 deletions

View File

@@ -17,7 +17,7 @@ window.env.t = function(string){
window.habitrpg = angular.module('habitrpg',
['ngResource', 'ngSanitize', 'userServices', 'groupServices', 'memberServices', 'challengeServices',
'sharedServices', 'authServices', 'notificationServices', 'guideServices',
'authServices', 'notificationServices', 'guideServices',
'ui.bootstrap', 'ui.keypress', 'ui.router', 'chieffancypants.loadingBar', 'At', 'pasvaz.bindonce'])
// @see https://github.com/angular-ui/ui-router/issues/110 and https://github.com/HabitRPG/habitrpg/issues/1705

View File

@@ -132,7 +132,7 @@ habitrpg.controller("ChallengesCtrl", ['$scope', 'User', 'Challenges', 'Notifica
//------------------------------------------------------------
$scope.addTask = function(addTo, listDef) {
var task = window.habitrpgShared.helpers.taskDefaults({text: listDef.newTask, type: listDef.type});
var task = $rootScope.Shared.taskDefaults({text: listDef.newTask, type: listDef.type});
addTo.unshift(task);
//User.log({op: "addTask", data: task}); //TODO persist
delete listDef.newTask;

View File

@@ -22,7 +22,7 @@ habitrpg.controller("FiltersCtrl", ['$scope', '$rootScope', 'User', 'API_URL', '
$scope.createTag = function(name) {
user.tags = user.tags || [];
user.tags.push({
id: window.habitrpgShared.helpers.uuid(),
id: $rootScope.Shared.uuid(),
name: name
});
User.log({op:'set',data:{'tags':user.tags}});

View File

@@ -47,17 +47,16 @@ habitrpg.controller("FooterCtrl", ['$scope', '$rootScope', 'User', '$http', 'Not
$scope.addMissedDay = function(){
if (!confirm("Are you sure you want to reset the day?")) return;
var dayBefore = moment(User.user.lastCron).subtract('days', 1).toDate();
User.set('lastCron', dayBefore);
User.set({'lastCron': dayBefore});
Notification.text('-1 day, remember to refresh');
}
$scope.addTenGems = function(){
console.log(API_URL);
$http.post(API_URL + '/api/v1/user/addTenGems').success(function(){
User.log({});
})
}
$scope.addLevelsAndGold = function(){
User.setMultiple({
User.set({
'stats.exp': User.user.stats.exp + 10000,
'stats.gp': User.user.stats.gp + 10000
});

View File

@@ -66,7 +66,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Groups', '$http', 'A
// We watch Members.selectedMember because it's asynchronously set, so would be a hassle to handle updates here
$scope.$watch( function() { return Members.selectedMember; }, function (member) {
if(member)
member.petCount = window.habitrpgShared.helpers.countPets(null, member.items.pets);
member.petCount = $rootScope.Shared.countPets(null, member.items.pets);
$scope.profile = member;
});
}

View File

@@ -2,13 +2,13 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', 'User', 'API_URL',
function($rootScope, $scope, User, API_URL, $http, Notification) {
var user = User.user;
var Items = window.habitrpgShared.items;
var Content = $rootScope.Shared.content;
// convenience vars since these are accessed frequently
$scope.selectedEgg = null; // {index: 1, name: "Tiger", value: 5}
$scope.selectedPotion = null; // {index: 5, name: "Red", value: 3}
$scope.totalPets = _.size($scope.Items.eggs) * _.size($scope.Items.hatchingPotions);
$scope.totalPets = _.size(Content.eggs) * _.size(Content.hatchingPotions);
// count egg, food, hatchingPotion stack totals
var countStacks = function(items) { return _.reduce(items,function(m,v){return m+v;},0);}
@@ -20,10 +20,10 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', 'User', 'API_URL',
$scope.$watch('user.items.gear', function(gear){
$scope.gear = {
base: _.where(Items.items.gear.flat, {klass: 'base'})
base: _.where(shared.content.gear.flat, {klass: 'base'})
};
_.each(gear.owned, function(bool,key){
var item = Items.items.gear.flat[key];
var item = shared.content.gear.flat[key];
if (!$scope.gear[item.klass]) $scope.gear[item.klass] = [];
$scope.gear[item.klass].push(item);
})
@@ -33,7 +33,7 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', 'User', 'API_URL',
if ($scope.selectedEgg && $scope.selectedEgg.name == egg) {
return $scope.selectedEgg = null; // clicked same egg, unselect
}
var eggData = _.findWhere(Items.items.eggs, {name:egg});
var eggData = _.findWhere(shared.content.eggs, {name:egg});
if (!$scope.selectedPotion) {
$scope.selectedEgg = eggData;
} else {
@@ -46,7 +46,7 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', 'User', 'API_URL',
return $scope.selectedPotion = null; // clicked same egg, unselect
}
// we really didn't think through the way these things are stored and getting passed around...
var potionData = _.findWhere(Items.items.hatchingPotions, {name:potion});
var potionData = _.findWhere(shared.content.hatchingPotions, {name:potion});
if (!$scope.selectedEgg) {
$scope.selectedPotion = potionData;
} else {
@@ -56,42 +56,18 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', 'User', 'API_URL',
$scope.chooseFood = function(food){
if ($scope.selectedFood && $scope.selectedFood.name == food) return $scope.selectedFood = null;
$scope.selectedFood = $scope.Items.food[food];
$scope.selectedFood = Content.food[food];
}
$scope.sellInventory = function() {
// TODO DRY this
if ($scope.selectedEgg) {
user.items.eggs[$scope.selectedEgg.name]--;
User.setMultiple({
'items.eggs': user.items.eggs,
'stats.gp': User.user.stats.gp + $scope.selectedEgg.value
});
if (user.items.eggs[$scope.selectedEgg.name] < 1) {
$scope.selectedEgg = null;
}
} else if ($scope.selectedPotion) {
user.items.hatchingPotions[$scope.selectedPotion.name]--;
User.setMultiple({
'items.hatchingPotions': user.items.hatchingPotions,
'stats.gp': User.user.stats.gp + $scope.selectedPotion.value
});
if (user.items.hatchingPotions[$scope.selectedPotion.name] < 1) {
$scope.selectedPotion = null;
}
} else if ($scope.selectedFood) {
user.items.food[$scope.selectedFood.name]--;
User.setMultiple({
'items.food': user.items.food,
'stats.gp': User.user.stats.gp + $scope.selectedFood.value
});
if (user.items.food[$scope.selectedFood.name] < 1) {
$scope.selectedFood = null;
var selected = $scope.selectedEgg ? 'selectedEgg' : $scope.selectedPotion ? 'selectedPotion' : $scope.selectedFood ? 'selectedFood' : undefined;
if (selected) {
var type = $scope.selectedEgg ? 'eggs' : $scope.selectedPotion ? 'hatchingPotions' : $scope.selectedFood ? 'food' : undefined;
user.ops.sell({query:{type:type, key: $scope[selected].name}});
if (user.items[type][$scope[selected].name] < 1) {
$scope[selected] = null;
}
}
}
$scope.ownedItems = function(inventory){
@@ -99,21 +75,15 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', 'User', 'API_URL',
}
$scope.hatch = function(egg, potion){
var pet = egg.name+"-"+potion.name;
if (user.items.pets[pet])
return alert("You already have that pet. Try hatching a different combination!");
var setObj = {};
setObj['items.pets.' + pet] = 5;
setObj['items.eggs.' + egg.name] = user.items.eggs[egg.name] - 1;
setObj['items.hatchingPotions.' + potion.name] = user.items.hatchingPotions[potion.name] - 1;
User.setMultiple(setObj);
alert("Your egg hatched! Visit your stable to equip your pet.");
user.ops.hatch({query:{egg:egg.name, hatchingPotion:potion.name}}, function(err, req){
// Bypassing the UserServices-injected callback so we can only show alert on success. It's safe, since this means
// UserServices callback will be 3rd param and never get called
if (err) return Notification.text(err);
User.log({op:'hatch', query:req.query});
Notification.text("Your egg hatched! Visit your stable to equip your pet.");
$scope.selectedEgg = null;
$scope.selectedPotion = null;
});
}
$scope.buy = function(type, item){
@@ -134,7 +104,7 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', 'User', 'API_URL',
// Feeding Pet
if ($scope.selectedFood) {
if (window.habitrpgShared.items.items.specialPets[pet]) return Notification.text("Can't feed this pet.");
if (window.habitrpgShared.shared.content.specialPets[pet]) return Notification.text("Can't feed this pet.");
var setObj = {};
var userPets = user.items.pets;
if (user.items.mounts[pet] && (userPets[pet] >= 50 || $scope.selectedFood.name == 'Saddle'))
@@ -163,7 +133,7 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', 'User', 'API_URL',
}
setObj['items.pets.' + pet] = userPets[pet];
setObj['items.food.' + $scope.selectedFood.name] = user.items.food[$scope.selectedFood.name] - 1;
User.setMultiple(setObj);
User.set(setObj);
$scope.selectedFood = null;
// Selecting Pet
@@ -182,19 +152,5 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', 'User', 'API_URL',
var mount = egg + '-' + potion;
User.set('items.currentMount', (user.items.currentMount == mount) ? '' : mount);
}
$scope.equip = function(user, item, costume) {
var equipTo = costume ? 'costume' : 'equipped';
if (item.type == 'shield') {
var weapon = Items.items.gear.flat[user.items.gear[equipTo].weapon];
if (weapon && weapon.twoHanded) return Notification.text(weapon.text + ' is two-handed');
}
var setVars = {};
setVars['items.gear.' + equipTo + '.' + item.type] = item.key;
if (item.twoHanded)
setVars['items.gear.' + equipTo + '.shield'] = 'warrior_shield_0';
User.setMultiple(setVars);
}
}
]);

View File

@@ -53,7 +53,7 @@ habitrpg.controller('NotificationCtrl',
$rootScope.$watch('user.items.pets', function(after, before){
if(_.size(after) === _.size(before) ||
window.habitrpgShared.helpers.countPets(null, after) < 90) return;
$rootScope.Shared.countPets(null, after) < 90) return;
User.user.achievements.beastMaster = true;
$rootScope.modals.achievements.beastMaster = true;
}, true);

View File

@@ -5,12 +5,15 @@
habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams', 'Notification', 'Groups',
function($scope, $rootScope, $location, User, $http, $state, $stateParams, Notification, Groups) {
var user = User.user;
$rootScope.modals = {};
$rootScope.modals.achievements = {};
$rootScope.User = User;
$rootScope.user = User.user;
$rootScope.user = user;
$rootScope.settings = User.settings;
$rootScope.Items = window.habitrpgShared.items.items;
$rootScope.Shared = window.habitrpgShared;
$rootScope.Content = window.habitrpgShared.content;
// Angular UI Router
$rootScope.$state = $state;
@@ -34,10 +37,10 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
// count pets, mounts collected totals, etc
$rootScope.countExists = function(items) {return _.reduce(items,function(m,v){return m+(v?1:0)},0)}
$rootScope.petCount = window.habitrpgShared.helpers.countPets(null, User.user.items.pets);
$rootScope.petCount = $rootScope.Shared.countPets(null, User.user.items.pets);
$rootScope.$watch('user.items.pets', function(pets){
$rootScope.petCount = window.habitrpgShared.helpers.countPets($rootScope.countExists(pets), User.user.items.pets);
$rootScope.petCount = $rootScope.Shared.countPets($rootScope.countExists(pets), User.user.items.pets);
}, true);
$scope.safeApply = function(fn) {
@@ -51,13 +54,6 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
}
};
/*
FIXME this is dangerous, organize helpers.coffee better, so we can group them by which controller needs them,
and then simply _.defaults($scope, Helpers.user) kinda thing
*/
_.defaults($rootScope, window.habitrpgShared.algos);
_.defaults($rootScope, window.habitrpgShared.helpers);
$rootScope.set = User.set;
$rootScope.authenticated = User.authenticated;
@@ -150,8 +146,6 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
$rootScope.applyingAction = true;
$scope.spell = spell;
if (spell.target == 'self') {
var tasks = User.user.habits.concat(User.user.dailys).concat(User.user.todos);
User.user.tasks = _.object(_.pluck(tasks,'id'), tasks);
$scope.castEnd(null, 'self');
} else if (spell.target == 'party') {
var party = Groups.party();

View File

@@ -16,13 +16,13 @@ habitrpg.controller('SettingsCtrl',
// }
$scope.showTour = function(){
User.set('flags.showTour',true);
User.set({'flags.showTour':true});
Guide.initTour();
$location.path('/tasks');
}
$scope.showBailey = function(){
User.set('flags.newStuff',true);
User.set({'flags.newStuff':true});
}
$scope.saveDayStart = function(){
@@ -31,7 +31,7 @@ habitrpg.controller('SettingsCtrl',
dayStart = 0;
return alert('Please enter a number between 0 and 24');
}
User.set('preferences.dayStart', dayStart);
User.set({'preferences.dayStart': dayStart});
}
$scope.language = window.env.language;
@@ -41,7 +41,7 @@ habitrpg.controller('SettingsCtrl',
$rootScope.$on('userSynced', function(){
location.reload();
});
User.set('preferences.language', $scope.language.code);
User.set({'preferences.language': $scope.language.code});
}
$scope.reroll = function(){
@@ -74,24 +74,18 @@ habitrpg.controller('SettingsCtrl',
$rootScope.$watch('modals.restore', function(value){
if(value === true){
$scope.restoreValues.stats = angular.copy(User.user.stats);
// $scope.restoreValues.items = angular.copy(User.user.items);
$scope.restoreValues.achievements = {streak: User.user.achievements.streak || 0};
}
})
$scope.restore = function(){
var stats = $scope.restoreValues.stats,
// items = $scope.restoreValues.items,
achievements = $scope.restoreValues.achievements;
User.setMultiple({
User.set({
"stats.hp": stats.hp,
"stats.exp": stats.exp,
"stats.gp": stats.gp,
"stats.lvl": stats.lvl,
// "items.weapon": items.weapon,
// "items.armor": items.armor,
// "items.head": items.head,
// "items.shield": items.shield,
"achievements.streak": achievements.streak
});
$rootScope.modals.restore = false;

View File

@@ -1,22 +1,22 @@
"use strict";
habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User', 'Algos', 'Helpers', 'Notification', '$http', 'API_URL',
function($scope, $rootScope, $location, User, Algos, Helpers, Notification, $http, API_URL) {
habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','Notification', '$http', 'API_URL',
function($scope, $rootScope, $location, User, Notification, $http, API_URL) {
$scope.obj = User.user; // used for task-lists
$scope.score = function(task, direction) {
if (task.type === "reward" && User.user.stats.gp < task.value){
return Notification.text('Not enough Gold!');
}
Algos.score(User.user, task, direction);
User.log({op: "score",data: task, dir: direction});
User.user.ops.score({params:{id: task.id, direction:direction}})
};
$scope.addTask = function(addTo, listDef) {
var task = window.habitrpgShared.helpers.taskDefaults({text: listDef.newTask, type: listDef.type}, User.user.filters);
addTo.unshift(task);
User.log({op: "addTask", data: task});
var newTask = {
text: listDef.newTask,
type: listDef.type,
tags: _.transform(User.user.filters, function(m,v,k){
if (v) m[k]=v;
})
}
User.user.ops.addTask({body:newTask});
delete listDef.newTask;
};
@@ -40,40 +40,12 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User', '
$scope.removeTask = function(list, $index) {
if (!confirm("Are you sure you want to delete this task?")) return;
User.log({ op: "delTask", data: list[$index] });
User.user.ops.deleteTask({params:{id:list[$index].id}})
list.splice($index, 1);
};
$scope.saveTask = function(task) {
var setVal = function(k, v) {
var op;
if (typeof v !== "undefined") {
op = { op: "set", data: {} };
op.data["tasks." + task.id + "." + k] = v;
return log.push(op);
}
};
var log = [];
setVal("text", task.text);
setVal("notes", task.notes);
setVal("priority", task.priority);
setVal("tags", task.tags);
if (task.type === "habit") {
setVal("up", task.up);
setVal("down", task.down);
} else if (task.type === "daily") {
setVal("repeat", task.repeat);
// TODO we'll remove this once rewrite's running for a while. This was a patch for derby issues
setVal("streak", task.streak);
} else if (task.type === "todo") {
setVal("date", task.date);
} else {
if (task.type === "reward") {
setVal("value", task.value);
}
}
User.log(log);
User.user.ops.updateTask({params:{id:task.id},body:task});
task._editing = false;
};
@@ -104,16 +76,18 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User', '
------------------------
*/
$scope.itemStore = window.habitrpgShared.items.updateStore(User.user);
$rootScope.$on('userSynced', function(){
$scope.itemStore = User.user.fns.updateStore();
})
$scope.buy = function(item) {
var hasEnough = window.habitrpgShared.items.buyItem(User.user, item);
var hasEnough = User.user.ops.buy({query:{key:item.key}});
if (hasEnough) {
User.log({op: "buy", key: item.key});
Notification.text("Item purchased.");
$scope.itemStore = window.habitrpgShared.items.updateStore(User.user);
$scope.itemStore = User.user.fns.updateStore();
} else {
Notification.text("Not enough Gold!");
// Notification.text("Not enough Gold!");
// handled by userServices interceptor
}
};

View File

@@ -12,56 +12,20 @@ habitrpg.controller("UserCtrl", ['$rootScope', '$scope', '$location', 'User', '$
});
$scope.allocate = function(stat){
var setObj = {}
setObj['stats.' + stat] = User.user.stats[stat] + 1;
setObj['stats.points'] = User.user.stats.points - 1;
User.setMultiple(setObj);
User.user.ops.allocate({query:{stat:stat}});
}
$scope.rerollClass = function(){
$scope.changeClass = function(klass){
if (!klass) {
if (!confirm("Are you sure you want to re-roll? This will reset your character's class and allocated points (you'll get them all back to re-allocate)"))
return;
User.setMultiple({
'flags.classSelected': false,
//'stats.points': this is handled on the server
'stats.str': 0,
'stats.def': 0,
'stats.per': 0,
'stats.int': 0
})
return User.user.ops.changeClass({});
}
$scope.rerollSubmit = function(){
var klass = $scope.selectedClass;
var setVars = {
"stats.class": klass,
"flags.classSelected": true
};
// Clear their gear and equip their new class's gear (can still equip old gear from inventory)
// If they've rolled this class before, restore their progress
_.each(['weapon', 'armor','shield','head'], function(type){
var foundKey = false;
_.findLast(User.user.items.gear.owned, function(v,k){
if (~k.indexOf(type + '_' + klass)) {
foundKey = k;
return true;
}
});
setVars['items.gear.equipped.' + type] =
foundKey ? foundKey : // restore progress from when they last rolled this class
(type == 'weapon') ? 'weapon_' + klass + '_0' : // weapon_0 is significant, don't reset to base_0
(type == 'shield' && klass == 'rogue') ? 'shield_rogue_0' : // rogues start with an off-hand weapon
type + '_base_0'; // naked for the rest!
// Grant them their new class's gear
if (type == 'weapon' || (type == 'shield' && klass == 'rogue'))
setVars['items.gear.owned.' + type + '_' + klass + '_0'] = true;
});
User.setMultiple(setVars);
User.user.ops.changeClass({query:{class:klass}});
$scope.selectedClass = undefined;
//FIXME run updateStore (we need to access a different scope)
User.user.fns.updateStore();
}
$scope.save = function(){
@@ -72,11 +36,28 @@ habitrpg.controller("UserCtrl", ['$rootScope', '$scope', '$location', 'User', '$
if(!curVal || $scope.editingProfile[key].toString() !== curVal.toString())
values['profile.' + key] = value;
});
User.setMultiple(values);
User.set(values);
$scope._editing.profile = false;
}
$scope.unlock = User.unlock;
/**
* For gem-unlockable preferences, (a) if owned, select preference (b) else, purchase
* @param path: User.preferences <-> User.purchased maps like User.preferences.skin=abc <-> User.purchased.skin.abc.
* Pass in this paramater as "skin.abc". Alternatively, pass as an array ["skin.abc", "skin.xyz"] to unlock sets
*/
$scope.unlock = function(path){
var fullSet = ~path.indexOf(',');
var cost = fullSet ? 1.25 : 0.5; // 5G per set, 2G per individual
if (fullSet) {
if (confirm("Purchase for 5 Gems?") !== true) return;
if (User.user.balance < cost) return $rootScope.modals.buyGems = true;
} else if (!User.user.fns.dotGet('purchased.' + path)) {
if (confirm("Purchase for 2 Gems?") !== true) return;
if (User.user.balance < cost) return $rootScope.modals.buyGems = true;
}
User.user.ops.unlock({query:{path:path}})
}
}
]);

View File

@@ -5,7 +5,7 @@
*/
angular.module('guideServices', []).
factory('Guide', ['$rootScope', 'User', 'Items', 'Helpers', function($rootScope, User, Items, Helpers) {
factory('Guide', ['$rootScope', 'User', function($rootScope, User) {
/**
* Init and show the welcome tour. Note we do it listening to a $rootScope broadcasted 'userLoaded' message,
@@ -79,7 +79,7 @@ angular.module('guideServices', []).
$('.main-herobox').popover('destroy');
var tour = new Tour({
onEnd: function(){
User.set('flags.showTour', false);
User.set({'flags.showTour': false});
}
});
tourSteps.forEach(function(step) {
@@ -134,7 +134,7 @@ angular.module('guideServices', []).
$rootScope.$watch('user.items.pets', function(after, before) {
if (User.user.achievements && User.user.achievements.beastMaster) return;
if (before >= 90) {
User.set('achievements.beastMaster', true);
User.set({'achievements.beastMaster': true});
$('#beastmaster-achievement-modal').modal('show'); // FIXME
}
});

View File

@@ -1,16 +0,0 @@
'use strict';
/**
* Services that persists and retrieves user from localStorage.
*/
angular.module('sharedServices', [] ).
factory("Items", ['$rootScope', function($rootScope){
return window.habitrpgShared.items;
}]).
factory("Algos", ['$rootScope', function($rootScope){
return window.habitrpgShared.algos;
}]).
factory("Helpers", ['$rootScope', function($rootScope){
return window.habitrpgShared.helpers;
}]);

View File

@@ -21,14 +21,13 @@ angular.module('userServices', []).
user = {}; // this is stored as a reference accessible to all controllers, that way updates propagate
//first we populate user with schema
_.extend(user, $window.habitrpgShared.helpers.newUser());
user.apiToken = user._id = ''; // we use id / apitoken to determine if registered
$window.habitrpgShared.algos.defineComputed(user);
//than we try to load localStorage
if (localStorage.getItem(STORAGE_USER_ID)) {
_.extend(user, JSON.parse(localStorage.getItem(STORAGE_USER_ID)));
}
user._wrapped = false;
var syncQueue = function (cb) {
if (!authenticated) {
@@ -71,6 +70,16 @@ angular.module('userServices', []).
// Update user
_.extend(user, data);
if (!user._wrapped){
$rootScope.Shared.wrap(user);
_.each(user.ops, function(op,k){
user.ops[k] = _.partialRight(op, function(err, req){
//if (err) return Notification.text(err); // FIXME Circular dependency found: Notification <- User
if (err) return alert(err);
userServices.log({op:k, params: req.params, query:req.query, body:req.body});
});
});
}
// Emit event when user is synced
$rootScope.$emit('userSynced');
@@ -108,6 +117,9 @@ angular.module('userServices', []).
};
var userServices = {
user: user,
set: function(updates) {
user.ops.update({body:updates});
},
online: function (status) {
if (status===true) {
settings.online = true;
@@ -130,7 +142,7 @@ angular.module('userServices', []).
// If they don't have timezone, set it
var offset = moment().zone(); // eg, 240 - this will be converted on server as -(offset/60)
if (user.preferences.timezoneOffset !== offset)
userServices.set('preferences.timezoneOffset', offset);
userServices.set({'preferences.timezoneOffset': offset});
cb && cb();
});
} else {
@@ -161,62 +173,6 @@ angular.module('userServices', []).
userServices.log({});
},
/*
Very simple path-set. `set('preferences.gender','m')` for example. We'll deprecate this once we have a complete API
*/
set: function(k, v) {
var log = { op: 'set', data: {} };
$window.habitrpgShared.helpers.dotSet(k, v, this.user);
log.data[k] = v;
userServices.log(log);
},
setMultiple: function(obj){
var log = { op: 'set', data: {} };
_.each(obj, function(v,k){
$window.habitrpgShared.helpers.dotSet(k, v, userServices.user);
log.data[k] = v;
});
userServices.log(log);
},
revive: function(){
$window.habitrpgShared.algos.revive(user);
userServices.log({ op: "revive" });
},
/**
* For gem-unlockable preferences, (a) if owned, select preference (b) else, purchase
* @param path: User.preferences <-> User.purchased maps like User.preferences.skin=abc <-> User.purchased.skin.abc.
* Pass in this paramater as "skin.abc". Alternatively, pass as an array ["skin.abc", "skin.xyz"] to unlock sets
*/
unlock: function(path){
var self = userServices; //this; // why isn't this working?
if (_.isArray(path)) {
if (confirm("Purchase for 5 Gems?") !== true) return;
if (user.balance < 1.25) return $rootScope.modals.buyGems = true;
path = path.join(',');
} else {
if ($window.habitrpgShared.helpers.dotGet('purchased.' + path, user)) {
var pref = path.split('.')[0],
val = path.split('.')[1];
return self.set('preferences.' + pref, val);
} else {
if (confirm("Purchase for 2 Gems?") !== true) return;
if (user.balance < 0.5) return $rootScope.modals.buyGems = true;
}
}
$http.post(API_URL + '/api/v1/user/unlock?path=' + path)
.success(function(data, status, headers, config){
self.log({}); // sync new unlocked & preferences
}).error(function(data, status, headers, config){
alert(status + ': ' + data);
//FIXME use method used elsewhere for handling this error, this is temp while developing
})
},
save: save,
settings: settings

View File

@@ -32,7 +32,6 @@
"js/app.js",
"js/services/authServices.js",
"js/services/notificationServices.js",
"js/services/sharedServices.js",
"js/services/userServices.js",
"js/services/groupServices.js",
"js/services/memberServices.js",

View File

@@ -1,9 +1,7 @@
var _ = require('lodash');
var nconf = require('nconf');
var async = require('async');
var algos = require('habitrpg-shared/script/algos');
var helpers = require('habitrpg-shared/script/helpers');
var items = require('habitrpg-shared/script/items');
var shared = require('habitrpg-shared');
var User = require('./../models/user').model;
var Group = require('./../models/group').model;
var api = module.exports;

View File

@@ -3,7 +3,7 @@ var validator = require('validator');
var check = validator.check;
var sanitize = validator.sanitize;
var passport = require('passport');
var helpers = require('habitrpg-shared/script/helpers');
var shared = require('habitrpg-shared');
var async = require('async');
var utils = require('../utils');
var nconf = require('nconf');
@@ -83,17 +83,18 @@ api.registerUser = function(req, res, next) {
if (found) {
return cb("Username already taken");
}
newUser = helpers.newUser(true);
salt = utils.makeSalt();
newUser.auth = {
var newUser = {
auth: {
local: {
username: username,
email: email,
salt: salt
salt: salt,
hashed_password: utils.encryptPassword(password, salt)
},
timestamps: {created: +new Date(), loggedIn: +new Date()}
}
};
newUser.auth.local.hashed_password = utils.encryptPassword(password, salt);
user = new User(newUser);
user.save(cb);
}
@@ -251,12 +252,12 @@ api.setupPassport = function(router) {
},
function(user, cb){
if (user) return cb(null, user);
var newUser = helpers.newUser(true);
newUser.auth = {
user = new User({
auth: {
facebook: req.user,
timestamps: {created: +new Date(), loggedIn: +new Date()}
};
user = new User(newUser);
}
});
user.save(cb);

View File

@@ -3,9 +3,7 @@
var _ = require('lodash');
var nconf = require('nconf');
var async = require('async');
var algos = require('habitrpg-shared/script/algos');
var helpers = require('habitrpg-shared/script/helpers');
var items = require('habitrpg-shared/script/items');
var shared = require('habitrpg-shared');
var User = require('./../models/user').model;
var Group = require('./../models/group').model;
var Challenge = require('./../models/challenge').model;

View File

@@ -31,7 +31,7 @@ var initDeprecated = function(req, res, next) {
return next();
};
router.post('/v1/users/:uid/tasks/:taskId/:direction', initDeprecated, auth.auth, api.scoreTask);
router.post('/v1/users/:uid/tasks/:taskId/:direction', initDeprecated, auth.auth, api.score);
router.get('/v1/users/:uid/calendar.ics', function(req, res, next) {
return next() //disable for now

View File

@@ -3,9 +3,7 @@
var _ = require('lodash');
var nconf = require('nconf');
var async = require('async');
var algos = require('habitrpg-shared/script/algos');
var helpers = require('habitrpg-shared/script/helpers');
var items = require('habitrpg-shared/script/items');
var shared = require('habitrpg-shared');
var User = require('./../models/user').model;
var Group = require('./../models/group').model;
var api = module.exports;
@@ -208,7 +206,7 @@ api.postChat = function(req, res, next) {
var user = res.locals.user
var group = res.locals.group;
var message = {
id: helpers.uuid(),
id: shared.uuid(),
uuid: user._id,
contributor: user.contributor && user.contributor.toObject(),
backer: user.backer && user.backer.toObject(),

View File

@@ -5,9 +5,7 @@ var ipn = require('paypal-ipn');
var _ = require('lodash');
var nconf = require('nconf');
var async = require('async');
var algos = require('habitrpg-shared/script/algos');
var helpers = require('habitrpg-shared/script/helpers');
var items = require('habitrpg-shared/script/items');
var shared = require('habitrpg-shared');
var validator = require('validator');
var check = validator.check;
var sanitize = validator.sanitize;
@@ -58,12 +56,6 @@ api.verifyTaskExists = function(req, res, next) {
return next();
};
function addTask(user, task) {
task = helpers.taskDefaults(task);
user[task.type+'s'].unshift(task);
return task;
}
/*
API Routes
---------------
@@ -73,7 +65,7 @@ function addTask(user, task) {
This is called form deprecated.coffee's score function, and the req.headers are setup properly to handle the login
Export it also so we can call it from deprecated.coffee
*/
api.scoreTask = function(req, res, next) {
api.score = function(req, res, next) {
var id = req.params.id,
direction = req.params.direction,
user = res.locals.user,
@@ -106,9 +98,9 @@ api.scoreTask = function(req, res, next) {
if (task.type === 'daily' || task.type === 'todo') {
task.completed = direction === 'up';
}
task = addTask(user, task);
task = user.ops.addTask({body:task});
}
var delta = algos.score(user, task, direction);
var delta = user.ops.score({params:{id:task.id, direction:direction}});
//user.markModified('flags');
user.save(function(err, saved) {
if (err) return res.json(500, {err: err});
@@ -146,38 +138,26 @@ api.getTask = function(req, res, next) {
* Delete Task
*/
api.deleteTask = function(req, res, next) {
api.verifyTaskExists(req, res, function(){
var user = res.locals.user;
user.deleteTask(res.locals.task.id);
user.save(function(err) {
if (err) return res.json(500, {err: err});
res.send(204);
});
})
};
/*
Update Task
*/
api.updateTask = function(req, res, next) {
var user = res.locals.user;
var tid = req.params.id;
var task = user.tasks[req.params.id];
_.merge(task, req.body);
user.save(function(err, saved) {
if (err) return res.json(500, {err: err})
return res.json(200, task);
});
};
api.createTask = function(req, res, next) {
var user = res.locals.user;
var task = addTask(user, req.body);
user.save(function(err, saved) {
if (err) return res.json(500, {err: err});
return res.json(201, task);
});
};
// api.updateTask // handled in Shared.ops
// api.addTask // handled in Shared.ops
api.sortTask = function(req, res, next) {
api.verifyTaskExists(req, res, function(){
var id = req.params.id;
var to = req.body.to, from = req.body.from, type = req.body.type;
var user = res.locals.user;
@@ -186,6 +166,7 @@ api.sortTask = function(req, res, next) {
if (err) return res.json(500, {err: err});
return res.json(200, saved.toJSON()[type+'s']);
});
})
};
api.clearCompleted = function(req, res, next) {
@@ -202,22 +183,7 @@ api.clearCompleted = function(req, res, next) {
Items
------------------------------------------------------------------------
*/
api.buy = function(req, res, next) {
var user = res.locals.user;
var key = req.params.key;
if (key !== 'potion' && !items.items.gear.flat[key]) {
return res.json(400, {err: ":item must be a supported key, see https://github.com/HabitRPG/habitrpg-shared/blob/master/script/items.coffee"});
}
var hasEnough = items.buyItem(user, items.items.gear.flat[key]);
if (hasEnough) {
return user.save(function(err, saved) {
if (err) return res.json(500, {err: err});
return res.json(200, saved.toJSON().items);
});
} else {
return res.json(200, {err: "Not enough GP"});
}
};
// api.buy // handled in Shard.ops
/*
------------------------------------------------------------------------
@@ -230,7 +196,7 @@ api.buy = function(req, res, next) {
*/
api.getUser = function(req, res, next) {
var user = res.locals.user.toJSON();
user.stats.toNextLevel = algos.tnl(user.stats.lvl);
user.stats.toNextLevel = shared.tnl(user.stats.lvl);
user.stats.maxHealth = 50;
delete user.apiToken;
if (user.auth) {
@@ -249,7 +215,7 @@ api.getUser = function(req, res, next) {
* Note: custom is for 3rd party apps
*/
acceptablePUTPaths = _.reduce(require('./../models/user').schema.paths, function(m,v,leaf){
var found= _.find('tasks achievements filters flags invitations items lastCron party preferences profile stats tags custom'.split(' '), function(root){
var found= _.find('tasks achievements filters flags invitations lastCron party preferences profile stats tags'.split(' '), function(root){
return leaf.indexOf(root) == 0;
});
if (found) m[leaf]=true;
@@ -263,14 +229,14 @@ acceptablePUTPaths['tasks']=true; // and for now, let them fully set tasks.* (fo
* PUT /user {'stats.hp':50, 'tasks.TASK_ID.repeat.m':false}
* See acceptablePUTPaths for which user paths are supported
*/
api.updateUser = function(req, res, next) {
api.update = function(req, res, next) {
var user = res.locals.user;
var errors = [];
if (_.isEmpty(req.body)) return res.json(200, user);
_.each(req.body, function(v, k) {
if (acceptablePUTPaths[k])
helpers.dotSet(k, v, user);
user.fns.dotSet(k, v);
else
errors.push("path `" + k + "` was not saved, as it's a protected path. Make sure to send `PUT /api/v1/user` request bodies as `{'set.this.path':value}` instead of `{set:{this:{path:value}}}`");
return true;
@@ -284,7 +250,7 @@ api.updateUser = function(req, res, next) {
api.cron = function(req, res, next) {
var user = res.locals.user;
algos.cron(user);
shared.cron(user);
if (user.isModified()) {
res.locals.wasModified = true;
user.auth.timestamps.loggedin = new Date();
@@ -292,15 +258,6 @@ api.cron = function(req, res, next) {
user.save(next);
};
api.revive = function(req, res, next) {
var user = res.locals.user;
algos.revive(user);
user.save(function(err, saved) {
if (err) return res.json(500, {err: err});
return res.json(200, saved);
});
};
api.reroll = function(req, res, next) {
var user = res.locals.user;
if (user.balance < 1) return res.json(401, {err: "Not enough tokens."});
@@ -353,36 +310,7 @@ api['delete'] = function(req, res) {
------------------------------------------------------------------------
*/
api.unlock = function(req, res) {
var user = res.locals.user;
var path = req.query.path;
var fullSet = ~path.indexOf(',');
// 5G per set, 2G per individual
cost = fullSet ? 1.25 : 0.5;
if (user.balance < cost)
return res.json(401, {err: 'Not enough gems'});
if (fullSet) {
var paths = path.split(',');
_.each(paths, function(p){
helpers.dotSet('purchased.' + p, true, user);
});
} else {
if (helpers.dotGet('purchased.' + path, user) === true)
return res.json(401, {err: 'User already purchased that'});
helpers.dotSet('purchased.' + path, true, user);
}
user.balance -= cost;
user._v++;
user.markModified('purchased');
user.save(function(err, saved){
if (err) res.json(500, {err:err});
res.send(200);
})
}
// api.unlock // see Shared.ops
/*
------------------------------------------------------------------------
@@ -492,7 +420,7 @@ api.deleteTag = function(req, res){
api.cast = function(req, res) {
var user = res.locals.user;
var type = req.body.type, target = req.body.target;
var spell = items.items.spells[user.stats.class][req.params.spell];
var spell = shared.content.spells[user.stats.class][req.params.spell];
var done = function(){
var err = arguments[0];
@@ -548,6 +476,28 @@ api.cast = function(req, res) {
}
}
/**
* All other user.ops which can easily be mapped to habitrpg-shared/index.coffee, not requiring custom API-wrapping
*/
_.each(shared.wrap({}).ops, function(op,k){
if (!api[k]) {
api[k] = function(req, res, next) {
var user = res.locals.user;
async.series([
function(cb){ user.ops[k](req, cb) },
function(cb){ user.save(cb) },
], function(err){
if (err) {
// If we want to send something other than 500, pass err as {code: 200, message: "Not enough GP"}
if (err.code) return res.json(err.code, err.message);
return res.json(500,{err:err});
}
return res.send(200);
})
}
}
})
/*
------------------------------------------------------------------------
Batch Update
@@ -558,78 +508,28 @@ api.batchUpdate = function(req, res, next) {
var user = res.locals.user;
var oldSend = res.send;
var oldJson = res.json;
var performAction = function(action, cb) {
// TODO come up with a more consistent approach here. like:
// req.body=action.data; delete action.data; _.defaults(req.params, action)
// Would require changing action.dir on mobile app
req.params.id = action.data && action.data.id;
req.params.direction = action.dir;
req.params.type = action.type;
req.params.key = action.key;
req.body = action.data;
var callOp = function(_req, cb) {
res.send = res.json = function(code, data) {
if (_.isNumber(code) && code >= 400) {
console.error({
code: code,
data: data
});
}
if (_.isNumber(code) && code >= 400)
console.error({code: code, data: data});
//FIXME send error messages down
return cb();
};
switch (action.op) {
case "score":
api.scoreTask(req, res);
break;
case "buy":
api.buy(req, res);
break;
case "sortTask":
api.verifyTaskExists(req, res, function() {
api.sortTask(req, res);
});
break;
case "addTask":
api.createTask(req, res);
break;
case "delTask":
api.verifyTaskExists(req, res, function() {
api.deleteTask(req, res);
});
break;
case "set":
api.updateUser(req, res);
break;
case "delTag":
api.deleteTag(req, res);
break;
case "revive":
api.revive(req, res);
break;
case "clear-completed":
api.clearCompleted(req, res);
break;
case "reroll":
api.reroll(req, res);
break;
default:
cb();
break;
}
api[_req.op](_req, res);
};
// Setup the array of functions we're going to call in parallel with async
var actions = _.transform(req.body || [], function(result, action) {
if (!_.isEmpty(action)) {
var ops = _.transform(req.body || [], function(result, _req) {
if (!_.isEmpty(_req)) {
result.push(function(cb) {
performAction(action, cb);
callOp(_req, cb);
});
}
});
// call all the operations, then return the user object to the requester
async.series(actions, function(err) {
async.series(ops, function(err) {
res.json = oldJson;
res.send = oldSend;
if (err) return res.json(500, {err: err});

View File

@@ -1,12 +1,12 @@
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var helpers = require('habitrpg-shared/script/helpers');
var shared = require('habitrpg-shared');
var _ = require('lodash');
var TaskSchemas = require('./task');
var Group = require('./group').model;
var ChallengeSchema = new Schema({
_id: {type: String, 'default': helpers.uuid},
_id: {type: String, 'default': shared.uuid},
name: String,
shortName: String,
description: String,

View File

@@ -1,10 +1,10 @@
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var helpers = require('habitrpg-shared/script/helpers');
var shared = require('habitrpg-shared');
var _ = require('lodash');
var GroupSchema = new Schema({
_id: {type: String, 'default': helpers.uuid},
_id: {type: String, 'default': shared.uuid},
name: String,
description: String,
leader: {type: String, ref: 'User'},

View File

@@ -6,7 +6,7 @@
// ------------
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var helpers = require('habitrpg-shared/script/helpers');
var shared = require('habitrpg-shared');
var _ = require('lodash');
// Task Schema
@@ -14,7 +14,7 @@ var _ = require('lodash');
var TaskSchema = {
//_id:{type: String,'default': helpers.uuid},
id: {type: String,'default': helpers.uuid},
id: {type: String,'default': shared.uuid},
text: String,
notes: {type: String, 'default': ''},
tags: {type: Schema.Types.Mixed, 'default': {}}, //{ "4ddf03d9-54bd-41a3-b011-ca1f1d2e9371" : true },

View File

@@ -6,8 +6,7 @@
// ------------
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var helpers = require('habitrpg-shared/script/helpers');
var items = require('habitrpg-shared/script/items');
var shared = require('habitrpg-shared');
var _ = require('lodash');
var TaskSchemas = require('./task');
var Challenge = require('./challenge').model;
@@ -15,23 +14,23 @@ var Challenge = require('./challenge').model;
// User Schema
// -----------
var eggPotionMapping = _.transform(items.items.eggs, function(m, egg){
_.defaults(m, _.transform(items.items.hatchingPotions, function(m2, pot){
var eggPotionMapping = _.transform(shared.content.eggs, function(m, egg){
_.defaults(m, _.transform(shared.content.hatchingPotions, function(m2, pot){
m2[egg.name + '-' + pot.name] = true;
}));
})
var specialPetsMapping = items.items.specialPets; // may need to revisit if we add additional information about the special pets
var specialPetsMapping = shared.content.specialPets; // may need to revisit if we add additional information about the special pets
var UserSchema = new Schema({
// ### UUID and API Token
_id: {
type: String,
'default': helpers.uuid
'default': shared.uuid
},
apiToken: {
type: String,
'default': helpers.uuid
'default': shared.uuid
},
// ### Mongoose Update Object
@@ -109,7 +108,7 @@ var UserSchema = new Schema({
},
items: {
gear: {
owned: _.transform(items.items.gear.flat, function(m,v,k){
owned: _.transform(shared.content.gear.flat, function(m,v,k){
m[v.key] = {type: Boolean};
if (v.key.match(/[weapon|armor|head|shield]_warrior_0/))
m[v.key]['default'] = true;
@@ -149,19 +148,19 @@ var UserSchema = new Schema({
// 'PandaCub': 0, // 0 indicates "doesn't own"
// 'Wolf': 5 // Number indicates "stacking"
// }
eggs: _.transform(items.items.eggs, function(m,v,k){ m[k] = Number; }),
eggs: _.transform(shared.content.eggs, function(m,v,k){ m[k] = Number; }),
// hatchingPotions: {
// 'Desert': 0, // 0 indicates "doesn't own"
// 'CottonCandyBlue': 5 // Number indicates "stacking"
// }
hatchingPotions: _.transform(items.items.hatchingPotions, function(m,v,k){ m[k] = Number; }),
hatchingPotions: _.transform(shared.content.hatchingPotions, function(m,v,k){ m[k] = Number; }),
// Food: {
// 'Watermelon': 0, // 0 indicates "doesn't own"
// 'RottenMeat': 5 // Number indicates "stacking"
// }
food: _.transform(items.items.food, function(m,v,k){ m[k] = Number; }),
food: _.transform(shared.content.food, function(m,v,k){ m[k] = Number; }),
// mounts: {
// 'Wolf-Desert': true,
@@ -281,16 +280,30 @@ UserSchema.methods.toJSON = function() {
return doc;
};
UserSchema.virtual('tasks').get(function () {
var tasks = this.habits.concat(this.dailys).concat(this.todos).concat(this.rewards);
var tasks = _.object(_.pluck(tasks,'id'), tasks);
return tasks;
});
//UserSchema.virtual('tasks').get(function () {
// var tasks = this.habits.concat(this.dailys).concat(this.todos).concat(this.rewards);
// var tasks = _.object(_.pluck(tasks,'id'), tasks);
// return tasks;
//});
// FIXME - since we're using special @post('init') above, we need to flag when the original path was modified.
// Custom setter/getter virtuals?
UserSchema.post('init', function(doc){
shared.wrap(doc);
})
UserSchema.pre('save', function(next) {
// Populate new users with default content
if (this.isNew){
//TODO for some reason this doesn't work here: `_.merge(this, shared.content.userDefaults);`
this.habits = shared.content.userDefaults.habits;
this.dailys = shared.content.userDefaults.dailys;
this.todos = shared.content.userDefaults.todos;
this.rewards = shared.content.userDefaults.rewards;
this.tags = shared.content.userDefaults.tags;
// tasks automatically get id=helpers.uuid() from TaskSchema id.default, but tags are Schema.Types.Mixed - so we need to manually invoke here
_.each(this.tags, function(tag){tag.id = shared.uuid();})
}
//this.markModified('tasks');
if (_.isNaN(this.preferences.dayStart) || this.preferences.dayStart < 0 || this.preferences.dayStart > 24) {
this.preferences.dayStart = 0;
@@ -308,7 +321,7 @@ UserSchema.pre('save', function(next) {
// Actually, can this be used as an attr default? (schema {type: ..., 'default': function(){}})
this.stats.points = this.stats.lvl - (this.stats.con + this.stats.str + this.stats.per + this.stats.int);
var petCount = helpers.countPets(_.reduce(this.items.pets,function(m,v){
var petCount = shared.countPets(_.reduce(this.items.pets,function(m,v){
//HOTFIX - Remove when solution is found, the first argument passed to reduce is a function
if(_.isFunction(v)) return m;
return m+(v?1:0)},0), this.items.pets);

View File

@@ -19,7 +19,6 @@ var middleware = require('../middleware');
$ mocha test/user.mocha.coffee
*/
var verifyTaskExists = user.verifyTaskExists
var cron = user.cron;
router.get('/status', function(req, res) {
@@ -32,16 +31,16 @@ router.get('/status', function(req, res) {
router.get('/export/history',auth.auth,dataexport.history); //[todo] encode data output options in the data controller and use these to build routes
/* Scoring*/
router.post('/user/task/:id/:direction', auth.auth, cron, user.scoreTask);
router.post('/user/tasks/:id/:direction', auth.auth, cron, user.scoreTask);
router.post('/user/task/:id/:direction', auth.auth, cron, user.score);
router.post('/user/tasks/:id/:direction', auth.auth, cron, user.score);
/* Tasks*/
router.get('/user/tasks', auth.auth, cron, user.getTasks);
router.get('/user/task/:id', auth.auth, cron, user.getTask);
router.put('/user/task/:id', auth.auth, cron, verifyTaskExists, user.updateTask);
router["delete"]('/user/task/:id', auth.auth, cron, verifyTaskExists, user.deleteTask);
router.post('/user/task', auth.auth, cron, user.createTask);
router.put('/user/task/:id/sort', auth.auth, cron, verifyTaskExists, user.sortTask);
router.put('/user/task/:id', auth.auth, cron, user.updateTask);
router["delete"]('/user/task/:id', auth.auth, cron, user.deleteTask);
router.post('/user/task', auth.auth, cron, user.addTask);
router.put('/user/task/:id/sort', auth.auth, cron, user.sortTask);
router.post('/user/clear-completed', auth.auth, cron, user.clearCompleted);
router.post('/user/task/:id/unlink', auth.auth, challenges.unlink); // removing cron since they may want to remove task first
if (nconf.get('NODE_ENV') == 'development') {
@@ -53,7 +52,7 @@ router.post('/user/buy/:key', auth.auth, cron, user.buy);
/* User*/
router.get('/user', auth.auth, cron, user.getUser);
router.put('/user', auth.auth, cron, user.updateUser);
router.put('/user', auth.auth, cron, user.update);
router.post('/user/revive', auth.auth, cron, user.revive);
router.post('/user/batch-update', middleware.forceRefresh, auth.auth, cron, user.batchUpdate);
router.post('/user/reroll', auth.auth, cron, user.reroll);

View File

@@ -9,7 +9,7 @@ script(type='text/ng-template', id='partials/options.inventory.inventory.html')
li.customize-menu
menu.pets-menu(label='{{label}}', ng-repeat='(klass,label) in {base:"Base", warrior:"Warrior", wizard:"Wizard", rogue:"Rogue", special:"Special"}', ng-show='gear[klass]')
div(ng-repeat='item in gear[klass]')
button.customize-option(popover='{{item.notes}}', popover-title='{{item.text}}', popover-trigger='mouseenter', popover-placement='right', ng-click='equip(user,item)', class='shop_{{item.key}}', ng-class='{selectableInventory: user.items.gear.equipped[item.type] == item.key}')
button.customize-option(popover='{{item.notes}}', popover-title='{{item.text}}', popover-trigger='mouseenter', popover-placement='right', ng-click='user.ops.equip({query:{key:item.key}})', class='shop_{{item.key}}', ng-class='{selectableInventory: user.items.gear.equipped[item.type] == item.key}')
label.checkbox.inline
input(type="checkbox", ng-model="user.preferences.costume")
| Use Costume&nbsp;
@@ -17,22 +17,22 @@ script(type='text/ng-template', id='partials/options.inventory.inventory.html')
li.customize-menu(ng-if='user.preferences.costume')
menu.pets-menu(label='{{label}}', ng-repeat='(klass,label) in {base:"Base", warrior:"Warrior", wizard:"Wizard", rogue:"Rogue", special:"Special"}', ng-show='gear[klass]')
div(ng-repeat='item in gear[klass]')
button.customize-option(popover='{{item.notes}}', popover-title='{{item.text}}', popover-trigger='mouseenter', popover-placement='right', ng-click='equip(user,item, true)', class='shop_{{item.key}}', ng-class='{selectableInventory: user.items.gear.costume[item.type] == item.key}')
button.customize-option(popover='{{item.notes}}', popover-title='{{item.text}}', popover-trigger='mouseenter', popover-placement='right', ng-click='user.ops.equip({query:{type:"costume", key:item.key}})', class='shop_{{item.key}}', ng-class='{selectableInventory: user.items.gear.costume[item.type] == item.key}')
li.customize-menu
menu.pets-menu(label='Eggs ({{eggCount}})')
p(ng-show='eggCount < 1') You don't have any eggs.
div(ng-repeat='(egg,points) in ownedItems(user.items.eggs)')
//TODO move positioning this styling to css
button.customize-option(popover='{{Items.eggs[egg].notes}}', popover-title='{{Items.eggs[egg].text}} Egg', popover-trigger='mouseenter', popover-placement='right', ng-click='chooseEgg(egg)', class='Pet_Egg_{{egg}}', ng-class='{selectableInventory: selectedPotion && !user.items.pets[egg+"-"+selectedPotion.name]}')
button.customize-option(popover='{{Content.eggs[egg].notes}}', popover-title='{{Content.eggs[egg].text}} Egg', popover-trigger='mouseenter', popover-placement='right', ng-click='chooseEgg(egg)', class='Pet_Egg_{{egg}}', ng-class='{selectableInventory: selectedPotion && !user.items.pets[egg+"-"+selectedPotion.name]}')
.badge.badge-info.stack-count {{points}}
//-p {{Items.eggs[egg].text}}
//-p {{Content.eggs[egg].text}}
li.customize-menu
menu.hatchingPotion-menu(label='Hatching Potions ({{potCount}})')
p(ng-show='potCount < 1') You don't have any hatching potions.
div(ng-repeat='(pot,points) in ownedItems(user.items.hatchingPotions)')
button.customize-option(popover='{{Items.hatchingPotions[pot].notes}}', popover-title='{{Items.hatchingPotions[pot].text}} Potion', popover-trigger='mouseenter', popover-placement='right', ng-click='choosePotion(pot)', class='Pet_HatchingPotion_{{pot}}', ng-class='{selectableInventory: selectedEgg && !user.items.pets[selectedEgg.name+"-"+pot]}')
button.customize-option(popover='{{Content.hatchingPotions[pot].notes}}', popover-title='{{Content.hatchingPotions[pot].text}} Potion', popover-trigger='mouseenter', popover-placement='right', ng-click='choosePotion(pot)', class='Pet_HatchingPotion_{{pot}}', ng-class='{selectableInventory: selectedEgg && !user.items.pets[selectedEgg.name+"-"+pot]}')
.badge.badge-info.stack-count {{points}}
//-p {{pot}}
@@ -40,7 +40,7 @@ script(type='text/ng-template', id='partials/options.inventory.inventory.html')
menu.pets-menu(label='Food ({{foodCount}})')
p(ng-show='foodCount < 1') You don't have any food.
div(ng-repeat='(food,points) in ownedItems(user.items.food)')
button.customize-option(popover='{{Items.food[food].notes}}', popover-title='{{Items.food[food].text}}', popover-trigger='mouseenter', popover-placement='right', ng-click='chooseFood(food)', class='Pet_Food_{{food}}')
button.customize-option(popover='{{Content.food[food].notes}}', popover-title='{{Content.food[food].text}}', popover-trigger='mouseenter', popover-placement='right', ng-click='chooseFood(food)', class='Pet_Food_{{food}}')
.badge.badge-info.stack-count {{points}}
//-p {{food}}
@@ -70,7 +70,7 @@ script(type='text/ng-template', id='partials/options.inventory.inventory.html')
menu.inventory-list(type='list')
li.customize-menu
menu.pets-menu(label='Eggs')
div(ng-repeat='egg in Items.eggs')
div(ng-repeat='egg in Content.eggs')
button.customize-option(popover='{{egg.notes}}', popover-title='{{egg.text}} Egg', popover-trigger='mouseenter', popover-placement='left', ng-click='buy("egg", egg)', class='Pet_Egg_{{egg.name}}')
p
| {{egg.value}}
@@ -78,7 +78,7 @@ script(type='text/ng-template', id='partials/options.inventory.inventory.html')
li.customize-menu
menu.pets-menu(label='Hatching Potions')
div(ng-repeat='pot in Items.hatchingPotions')
div(ng-repeat='pot in Content.hatchingPotions')
button.customize-option(popover='{{pot.notes}}', popover-title='{{pot.text}} Potion', popover-trigger='mouseenter', popover-placement='left', ng-click='buy("hatchingPotion", pot)', class='Pet_HatchingPotion_{{pot.name}}')
p
| {{pot.value}}
@@ -86,7 +86,7 @@ script(type='text/ng-template', id='partials/options.inventory.inventory.html')
li.customize-menu
menu.pets-menu(label='Food')
div(ng-repeat='food in Items.food')
div(ng-repeat='food in Content.food')
button.customize-option(popover='{{food.notes}}', popover-title='{{food.text}}', popover-trigger='mouseenter', popover-placement='left', ng-click='buy("food", food)', class='Pet_Food_{{food.name}}')
p
| {{food.value}}

View File

@@ -26,9 +26,9 @@ script(type='text/ng-template', id='partials/options.inventory.stable.mounts.htm
Shall I bring you your steed, {{user.profile.name}}? Click a mount to saddle up.
h4 {{mountCount}} / {{totalPets}} Mounts Tamed
menu.pets(type='list')
li.customize-menu(ng-repeat='egg in Items.eggs')
li.customize-menu(ng-repeat='egg in Content.eggs')
menu
div(ng-repeat='potion in Items.hatchingPotions', popover-trigger='mouseenter', popover='{{potion.text}} {{egg.mountText}}', popover-placement='bottom', ng-init='mount = egg.name+"-"+potion.name')
div(ng-repeat='potion in Content.hatchingPotions', popover-trigger='mouseenter', popover='{{potion.text}} {{egg.mountText}}', popover-placement='bottom', ng-init='mount = egg.name+"-"+potion.name')
button(class="pet-button Mount_Head_{{mount}}", ng-show='user.items.mounts[mount]', ng-class='{active: user.items.currentMount == mount}', ng-click='chooseMount(egg.name, potion.name)')
//div(class='Mount_Head_{{mount}}')
button(class="pet-button pet-not-owned", ng-hide='user.items.mounts[mount]')
@@ -59,9 +59,9 @@ script(type='text/ng-template', id='partials/options.inventory.stable.pets.html'
h4 {{petCount}} / {{totalPets}} Pets Found
menu.pets(type='list')
li.customize-menu(ng-repeat='egg in Items.eggs')
li.customize-menu(ng-repeat='egg in Content.eggs')
menu
div(ng-repeat='potion in Items.hatchingPotions', popover-trigger='mouseenter', popover='{{potion.text}} {{egg.text}}', popover-placement='bottom', ng-init='pet = egg.name+"-"+potion.name')
div(ng-repeat='potion in Content.hatchingPotions', popover-trigger='mouseenter', popover='{{potion.text}} {{egg.text}}', popover-placement='bottom', ng-init='pet = egg.name+"-"+potion.name')
button(class="pet-button Pet-{{pet}}", ng-if='user.items.pets[pet]>0', ng-class='{active: user.items.currentPet == pet, selectableInventory: selectedFood}', ng-click='choosePet(egg.name, potion.name)')
.progress(ng-class='{"progress-success": user.items.pets[pet]<50}')
.bar(style="width: {{user.items.pets[pet]/.5}}%;")
@@ -85,7 +85,7 @@ script(type='text/ng-template', id='partials/options.inventory.stable.pets.html'
li.customize-menu
menu.pets-menu(label='Food')
div(ng-repeat='(food,points) in ownedItems(user.items.food)')
button.customize-option(popover-append-to-body='true', popover='{{Items.food[food].notes}}', popover-title='{{Items.food[food].text}}', popover-trigger='mouseenter', popover-placement='left', ng-click='chooseFood(food)', class='Pet_Food_{{food}}')
button.customize-option(popover-append-to-body='true', popover='{{Content.food[food].notes}}', popover-title='{{Content.food[food].text}}', popover-trigger='mouseenter', popover-placement='left', ng-click='chooseFood(food)', class='Pet_Food_{{food}}')
.badge.badge-info.stack-count {{points}}
// Remove this once we have images in
p {{Items.food[food].text}}
p {{Content.food[food].text}}

View File

@@ -6,8 +6,8 @@ script(id='partials/options.profile.avatar.html', type='text/ng-template')
li.customize-menu
menu(label='Head')
menu
button.broad_armor_base_0.customize-option(type='button', ng-click='set("preferences.size","broad")')
button.slim_armor_base_0.customize-option(type='button', ng-click='set("preferences.size","slim")')
button.broad_armor_base_0.customize-option(type='button', ng-click='set({"preferences.size":"broad"})')
button.slim_armor_base_0.customize-option(type='button', ng-click='set({"preferences.size":"slim"})')
.span4
h3 Hair
@@ -15,60 +15,60 @@ script(id='partials/options.profile.avatar.html', type='text/ng-template')
// Color
li.customize-menu
menu(label='Color')
button(type='button', class='customize-option', style='width: 40px; height: 40px; background-color:#c8c8c8;', ng-click='set("preferences.hair.color", "white")')
button(type='button', class='customize-option', style='width: 40px; height: 40px; background-color:#903a00;', ng-click='set("preferences.hair.color", "brown")')
button(type='button', class='customize-option', style='width: 40px; height: 40px; background-color:#cfb853;', ng-click='set("preferences.hair.color", "blond")')
button(type='button', class='customize-option', style='width: 40px; height: 40px; background-color:#ec720f;', ng-click='set("preferences.hair.color", "red")')
button(type='button', class='customize-option', style='width: 40px; height: 40px; background-color:#2e2e2e;', ng-click='set("preferences.hair.color", "black")')
button(type='button', class='customize-option', style='width: 40px; height: 40px; background-color:#c8c8c8;', ng-click='set({"preferences.hair.color": "white"})')
button(type='button', class='customize-option', style='width: 40px; height: 40px; background-color:#903a00;', ng-click='set({"preferences.hair.color": "brown"})')
button(type='button', class='customize-option', style='width: 40px; height: 40px; background-color:#cfb853;', ng-click='set({"preferences.hair.color": "blond"})')
button(type='button', class='customize-option', style='width: 40px; height: 40px; background-color:#ec720f;', ng-click='set({"preferences.hair.color": "red" })')
button(type='button', class='customize-option', style='width: 40px; height: 40px; background-color:#2e2e2e;', ng-click='set({"preferences.hair.color": "black"})')
// Bangs
li.customize-menu
menu(label='Bangs')
button(class='head_base_0 customize-option', type='button', ng-click='set("preferences.hair.bangs",0)')
button(class='hair_bangs_1_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.bangs",1)')
button(class='hair_bangs_2_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.bangs",2)')
button(class='hair_bangs_3_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.bangs",3)')
button(class='hair_bangs_1_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set({"preferences.hair.bangs":1})')
button(class='hair_bangs_2_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set({"preferences.hair.bangs":2})')
button(class='hair_bangs_3_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set({"preferences.hair.bangs":3})')
// Beard
li.customize-menu
menu(label='Beard')
button(class='head_base_0 customize-option', type='button', ng-click='set("preferences.hair.beard",0)')
button(class='hair_beard_1_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.beard",1)')
button(class='hair_beard_2_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.beard",2)')
button(class='hair_beard_3_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.beard",3)')
button(class='head_base_0 customize-option', type='button', ng-click='set({"preferences.hair.beard":0})')
button(class='hair_beard_1_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set({"preferences.hair.beard":1})')
button(class='hair_beard_2_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set({"preferences.hair.beard":2})')
button(class='hair_beard_3_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set({"preferences.hair.beard":3})')
// Mustache
li.customize-menu
menu(label='Mustache')
button(class='head_base_0 customize-option', type='button', ng-click='set("preferences.hair.mustache",0)')
button(class='hair_mustache_1_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.mustache",1)')
button(class='hair_mustache_2_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.mustache",2)')
button(class='head_base_0 customize-option', type='button', ng-click='set({"preferences.hair.mustache":0})')
button(class='hair_mustache_1_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set({"preferences.hair.mustache":1})')
button(class='hair_mustache_2_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set({"preferences.hair.mustache":2})')
// Base
li.customize-menu
menu(label='Base')
button(class='head_base_0 customize-option', type='button', ng-click='set("preferences.hair.base",0)')
button(class='hair_base_1_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.base",1)')
button(class='hair_base_2_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.base",2)')
button(class='hair_base_3_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.base",3)')
button(class='hair_base_4_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.base",4)')
button(class='hair_base_5_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.base",5)')
button(class='hair_base_6_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.base",6)')
button(class='hair_base_7_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.base",7)')
button(class='hair_base_8_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.base",8)')
button(class='head_base_0 customize-option', type='button', ng-click='set({"preferences.hair.base":0})')
button(class='hair_base_1_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set({"preferences.hair.base":1})')
button(class='hair_base_2_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set({"preferences.hair.base":2})')
button(class='hair_base_3_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set({"preferences.hair.base":3})')
button(class='hair_base_4_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set({"preferences.hair.base":4})')
button(class='hair_base_5_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set({"preferences.hair.base":5})')
button(class='hair_base_6_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set({"preferences.hair.base":6})')
button(class='hair_base_7_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set({"preferences.hair.base":7})')
button(class='hair_base_8_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set({"preferences.hair.base":8})')
.span4
// skin
li.customize-menu
menu(label='Basic Skins')
button.customize-option(type='button', class='skin_asian', ng-click='set("preferences.skin","asian")')
button.customize-option(type='button', class='skin_white', ng-click='set("preferences.skin","white")')
button.customize-option(type='button', class='skin_ea8349', ng-click='set("preferences.skin","ea8349")')
button.customize-option(type='button', class='skin_c06534', ng-click='set("preferences.skin","c06534")')
button.customize-option(type='button', class='skin_98461a', ng-click='set("preferences.skin","98461a")')
button.customize-option(type='button', class='skin_black', ng-click='set("preferences.skin","black")')
button.customize-option(type='button', class='skin_dead', ng-click='set("preferences.skin","dead")')
button.customize-option(type='button', class='skin_orc', ng-click='set("preferences.skin","orc")')
button.customize-option(type='button', class='skin_asian', ng-click='set({"preferences.skin":"asian" })')
button.customize-option(type='button', class='skin_white', ng-click='set({"preferences.skin":"white" })')
button.customize-option(type='button', class='skin_ea8349', ng-click='set({"preferences.skin":"ea8349"})')
button.customize-option(type='button', class='skin_c06534', ng-click='set({"preferences.skin":"c06534"})')
button.customize-option(type='button', class='skin_98461a', ng-click='set({"preferences.skin":"98461a"})')
button.customize-option(type='button', class='skin_black', ng-click='set({"preferences.skin":"black" })')
button.customize-option(type='button', class='skin_dead', ng-click='set({"preferences.skin":"dead" })')
button.customize-option(type='button', class='skin_orc', ng-click='set({"preferences.skin":"orc" })')
// Rainbow Skin
h5.
@@ -83,7 +83,7 @@ script(id='partials/options.profile.avatar.html', type='text/ng-template')
button.customize-option(type='button', class='skin_d7a9f7', ng-class='{locked: !user.purchased.skin.d7a9f7}', ng-click='unlock("skin.d7a9f7")')
button.customize-option(type='button', class='skin_800ed0', ng-class='{locked: !user.purchased.skin.800ed0}', ng-click='unlock("skin.800ed0")')
button.customize-option(type='button', class='skin_rainbow', ng-class='{locked: !user.purchased.skin.rainbow}', ng-click='unlock("skin.rainbow")')
button.btn.btn-small.btn-primary(ng-hide='user.purchased.skin.eb052b && user.purchased.skin.f69922 && user.purchased.skin.f5d70f && user.purchased.skin.0ff591 && user.purchased.skin.2b43f6 && user.purchased.skin.d7a9f7 && user.purchased.skin.800ed0 && user.purchased.skin.rainbow', ng-click='unlock(["skin.eb052b", "skin.f69922", "skin.f5d70f", "skin.0ff591", "skin.2b43f6", "skin.d7a9f7", "skin.800ed0", "skin.rainbow"])') Unlock Set - 5<span class="Pet_Currency_Gem1x inline-gems"/>
button.btn.btn-small.btn-primary(ng-hide='user.purchased.skin.eb052b && user.purchased.skin.f69922 && user.purchased.skin.f5d70f && user.purchased.skin.0ff591 && user.purchased.skin.2b43f6 && user.purchased.skin.d7a9f7 && user.purchased.skin.800ed0 && user.purchased.skin.rainbow', ng-click='unlock("skin.eb052b,skin.f69922,skin.f5d70f,skin.0ff591,skin.2b43f6,skin.d7a9f7,skin.800ed0,skin.rainbow")') Unlock Set - 5<span class="Pet_Currency_Gem1x inline-gems"/>
// Special Events
// restore to d4df481 to see purchasing + "limited edition" code
@@ -104,11 +104,11 @@ script(id='partials/options.profile.stats.html', type='text/ng-template')
.span4.border-right(ng-show='user.stats.lvl >= 5')
h4
| {{user.stats.class}}&nbsp;
a.btn.btn-danger.btn-mini(ng-click='rerollClass()') Re-roll
a.btn.btn-danger.btn-mini(ng-click='changeClass(null)') Re-roll
h6 Points: {{user.stats.points}}
fieldset
label.checkbox
input(type='checkbox', ng-model='user.preferences.automaticAllocation', ng-change='set("preferences.automaticAllocation", user.preferences.automaticAllocation?true: false)')
input(type='checkbox', ng-model='user.preferences.automaticAllocation', ng-change='set({"preferences.automaticAllocation": user.preferences.automaticAllocation?true: false})')
| Automatic Allocation
i.icon-question-sign(popover-trigger='mouseenter', popover-placement='bottom', popover="When 'automatic' is checked, your points will be allocated to the stat representing your task focus (see Task > Edit). When unchecked, you'll have one point to allocate each level. The system makes a suggestion, but you can ignore the suggestion.")
table.table.table-striped

View File

@@ -30,8 +30,8 @@ script(type='text/ng-template', id='partials/options.settings.settings.html')
select(ng-model='language.code', ng-options='lang.code as lang.name for lang in avalaibleLanguages', ng-change='changeLanguage()')
hr
h4 Misc
button.btn(ng-hide='user.preferences.hideHeader', ng-click='set("preferences.hideHeader",true)', popover-trigger='mouseenter', popover-placement='right', popover='Hide your avatar, Health/Experience bars, and party.') Hide Header
button.btn(ng-show='user.preferences.hideHeader', ng-click='set("preferences.hideHeader",false)', popover-trigger='mouseenter', popover='Display your avatar, Health/Experience bars, and party.') Show Header
button.btn(ng-hide='user.preferences.hideHeader', ng-click='set({"preferences.hideHeader":true})', popover-trigger='mouseenter', popover-placement='right', popover='Hide your avatar, Health/Experience bars, and party.') Hide Header
button.btn(ng-show='user.preferences.hideHeader', ng-click='set({"preferences.hideHeader":false})', popover-trigger='mouseenter', popover='Display your avatar, Health/Experience bars, and party.') Show Header
button.btn(ng-click='showTour()', popover-trigger='mouseenter', popover='Restart the introductory tour from when you first joined HabitRPG.') Show Tour
button.btn(ng-click='showBailey()', popover-trigger='mouseenter', popover='Bring Bailey the Town Crier out of hiding so you can review past news.') Show Bailey
button.btn(ng-click='modals.restore = true', popover-trigger='mouseenter', popover='Manually change values like Health, Level, and Gold.') Fix Character Values

View File

@@ -46,7 +46,7 @@ a.pull-right.gem-wallet(popover-trigger='mouseenter', popover-title='Guild Bank'
ng-model='user.party.order',
ng-controller='ChatCtrl',
ng-options='k as v for (k , v) in partyOrderChoices',
ng-change='set("party.order", user.party.order)'
ng-change='set({"party.order": user.party.order})'
)
table.table.table-striped(bindonce='group')
tr(ng-repeat='member in group.members')

View File

@@ -11,15 +11,15 @@
// stat bars
.hero-stats
.meter.health(title='Health')
.bar(style='width: {{percent(user.stats.hp, 50)}}%;')
.bar(style='width: {{Shared.percent(user.stats.hp, 50)}}%;')
span.meter-text
i.icon-heart
| {{user.stats.hp | number:0}} / 50
.meter.experience(title='Experience')
.bar(style='width: {{percent(user.stats.exp,tnl(user.stats.lvl))}}%;')
.bar(style='width: {{Shared.percent(user.stats.exp,Shared.tnl(user.stats.lvl))}}%;')
span.meter-text
i.icon-star
| {{user.stats.exp | number:0}} / {{tnl(user.stats.lvl) | number:0}}
| {{user.stats.exp | number:0}} / {{Shared.tnl(user.stats.lvl) | number:0}}
// FIXME doesn't look great here, but the "Experience" CSS title rollover covers it where it was before
span(ng-show='user.history.exp')
a(ng-click='toggleChart("exp")', tooltip='Progress')

View File

@@ -40,7 +40,7 @@ div(modal='user.flags.contributor')
{{user.profile.name}}, you awesome person! You're now a level {{user.contributor.level}} contributor for helping HabitRPG. See <a href='http://habitrpg.wikia.com/wiki/Contributor_Rewards' target='_blank'>what prizes you've earned for your contribution!</a>
.modal-footer
button.btn.btn-default.cancel(ng-click='set("flags.contributor",false)') Ok
button.btn.btn-default.cancel(ng-click='set("{flags.contributor":false})') Ok

View File

@@ -66,4 +66,4 @@
span(class='weapon_healer_6')
.modal-footer
button.btn.btn-default.cancel(ng-click='rerollSubmit()') Select
button.btn.btn-default.cancel(ng-click='selectedClass && changeClass(selectedClass)') Select

View File

@@ -9,7 +9,7 @@ div(modal='user.stats.hp <= 0', options='{backdrop:true, keyboard:false, backdro
.row-fluid
.span4
p
a.btn.btn-danger.btn-large.notification-action(ng-click='User.revive()') Continue
a.btn.btn-danger.btn-large.notification-action(ng-click='user.ops.revive({})') Continue
.span8
p
| You've lost a Level, all your Gold, and a random piece of Equipment. Arise, Habiteer, and try again! Curb those negative Habits, be vigilant in completion of Dailies, and hold death at arm's length with a Health Potion if you falter!

View File

@@ -10,7 +10,7 @@ div(modal='modals.dropsEnabled')
p
span.item-drop-icon(class='Pet_Egg_Wolf', style='margin-left: 0px')
span.
You've unlocked the Drop System! Now when you complete tasks, you have a small chance of finding an item. You just found a <strong>{{Items.eggs.Wolf.text}}</strong> Egg! {{Items.eggs.Wolf.notes}}
You've unlocked the Drop System! Now when you complete tasks, you have a small chance of finding an item. You just found a <strong>{{Content.eggs.Wolf.text}}</strong> Egg! {{Content.eggs.Wolf.notes}}
br
p.
<span class='Pet_Currency_Gem item-drop-icon'></span> If you've got your eye on a pet, but can't wait any longer for it to drop, use Gems in <strong>Options > Inventory</strong> to buy one!

View File

@@ -23,7 +23,7 @@ p
| : {{profile.stats.lvl}}
p
strong Experience
| : {{profile.stats.exp | number:0}} / {{tnl(profile.stats.lvl)}}
| : {{profile.stats.exp | number:0}} / {{Shared.tnl(profile.stats.lvl)}}
p
strong Strength
| :&nbsp;

View File

@@ -18,10 +18,10 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template")
// Gold & Gems
span.option-box.pull-right.wallet(bo-if='main && list.type=="reward"')
.money
| {{gold(user.stats.gp)}}
| {{Shared.gold(user.stats.gp)}}
span.shop_gold(tooltip='Gold')
.money
| {{silver(user.stats.gp)}}
| {{Shared.silver(user.stats.gp)}}
span.shop_silver(tooltip='Silver')
// Header
@@ -62,7 +62,7 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template")
// Spells
ul.items(ng-if='main && list.type=="reward" && user.stats.class')
li.task.reward-item(ng-repeat='(k,spell) in Items.spells[user.stats.class]', ng-show='user.stats.lvl >= spell.lvl')
li.task.reward-item(ng-repeat='(k,spell) in Content.spells[user.stats.class]', ng-show='user.stats.lvl >= spell.lvl')
.task-meta-controls
span.task-notes(popover-trigger='mouseenter', popover-placement='left', popover='{{spell.notes}}', popover-title='{{spell.text}}')
i.icon-comment

View File

@@ -1,4 +1,4 @@
li(bindonce='list', ng-repeat='task in obj[list.type+"s"]', class='task {{taskClasses(task, user.filters, user.preferences.dayStart, user.lastCron, list.showCompleted, main)}}', ng-click='spell && castEnd(task, "task", $event)', ng-class='{"cast-target":spell}')
li(bindonce='list', ng-repeat='task in obj[list.type+"s"]', class='task {{Shared.taskClasses(task, user.filters, user.preferences.dayStart, user.lastCron, list.showCompleted, main)}}', ng-click='spell && castEnd(task, "task", $event)', ng-class='{"cast-target":spell}')
// right-hand side control buttons
.task-meta-controls
@@ -118,13 +118,13 @@ li(bindonce='list', ng-repeat='task in obj[list.type+"s"]', class='task {{taskCl
legend.option-title Repeat
.task-controls.tile-group.repeat-days(bindonce)
// note, does not use data-toggle="buttons-checkbox" - it would interfere with our own click binding
button.task-action-btn.tile(ng-class='{active: task.repeat.su}', type='button', ng-click='task.challenge.id || (task.repeat.su = !task.repeat.su)' bo-text='moment.weekdaysMin(0)')
button.task-action-btn.tile(ng-class='{active: task.repeat.m}', type='button', ng-click='task.challenge.id || (task.repeat.m = !task.repeat.m)' bo-text='moment.weekdaysMin(1)')
button.task-action-btn.tile(ng-class='{active: task.repeat.t}', type='button', ng-click='task.challenge.id || (task.repeat.t = !task.repeat.t)' bo-text='moment.weekdaysMin(2)')
button.task-action-btn.tile(ng-class='{active: task.repeat.w}', type='button', ng-click='task.challenge.id || (task.repeat.w = !task.repeat.w)' bo-text='moment.weekdaysMin(3)')
button.task-action-btn.tile(ng-class='{active: task.repeat.th}', type='button', ng-click='task.challenge.id || (task.repeat.th = !task.repeat.th)' bo-text='moment.weekdaysMin(4)')
button.task-action-btn.tile(ng-class='{active: task.repeat.f}', type='button', ng-click='task.challenge.id || (task.repeat.f= !task.repeat.f)' bo-text='moment.weekdaysMin(5)')
button.task-action-btn.tile(ng-class='{active: task.repeat.s}', type='button', ng-click='task.challenge.id || (task.repeat.s = !task.repeat.s)' bo-text='moment.weekdaysMin(6)')
button.task-action-btn.tile(ng-class='{active: task.repeat.su}', type='button', ng-click='task.challenge.id || (task.repeat.su = !task.repeat.su)', bo-text='moment.weekdaysMin(0)')
button.task-action-btn.tile(ng-class='{active: task.repeat.m}', type='button', ng-click='task.challenge.id || (task.repeat.m = !task.repeat.m)', bo-text='moment.weekdaysMin(1)')
button.task-action-btn.tile(ng-class='{active: task.repeat.t}', type='button', ng-click='task.challenge.id || (task.repeat.t = !task.repeat.t)', bo-text='moment.weekdaysMin(2)')
button.task-action-btn.tile(ng-class='{active: task.repeat.w}', type='button', ng-click='task.challenge.id || (task.repeat.w = !task.repeat.w)', bo-text='moment.weekdaysMin(3)')
button.task-action-btn.tile(ng-class='{active: task.repeat.th}', type='button', ng-click='task.challenge.id || (task.repeat.th = !task.repeat.th)', bo-text='moment.weekdaysMin(4)')
button.task-action-btn.tile(ng-class='{active: task.repeat.f}', type='button', ng-click='task.challenge.id || (task.repeat.f= !task.repeat.f)', bo-text='moment.weekdaysMin(5)')
button.task-action-btn.tile(ng-class='{active: task.repeat.s}', type='button', ng-click='task.challenge.id || (task.repeat.s = !task.repeat.s)', bo-text='moment.weekdaysMin(6)')
// if Reward, pricing
fieldset.option-group.option-short(ng-if='task.type=="reward" && !task.challenge.id')