[#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', window.habitrpg = angular.module('habitrpg',
['ngResource', 'ngSanitize', 'userServices', 'groupServices', 'memberServices', 'challengeServices', ['ngResource', 'ngSanitize', 'userServices', 'groupServices', 'memberServices', 'challengeServices',
'sharedServices', 'authServices', 'notificationServices', 'guideServices', 'authServices', 'notificationServices', 'guideServices',
'ui.bootstrap', 'ui.keypress', 'ui.router', 'chieffancypants.loadingBar', 'At', 'pasvaz.bindonce']) '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 // @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) { $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); addTo.unshift(task);
//User.log({op: "addTask", data: task}); //TODO persist //User.log({op: "addTask", data: task}); //TODO persist
delete listDef.newTask; delete listDef.newTask;

View File

@@ -22,7 +22,7 @@ habitrpg.controller("FiltersCtrl", ['$scope', '$rootScope', 'User', 'API_URL', '
$scope.createTag = function(name) { $scope.createTag = function(name) {
user.tags = user.tags || []; user.tags = user.tags || [];
user.tags.push({ user.tags.push({
id: window.habitrpgShared.helpers.uuid(), id: $rootScope.Shared.uuid(),
name: name name: name
}); });
User.log({op:'set',data:{'tags':user.tags}}); 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(){ $scope.addMissedDay = function(){
if (!confirm("Are you sure you want to reset the day?")) return; if (!confirm("Are you sure you want to reset the day?")) return;
var dayBefore = moment(User.user.lastCron).subtract('days', 1).toDate(); 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'); Notification.text('-1 day, remember to refresh');
} }
$scope.addTenGems = function(){ $scope.addTenGems = function(){
console.log(API_URL);
$http.post(API_URL + '/api/v1/user/addTenGems').success(function(){ $http.post(API_URL + '/api/v1/user/addTenGems').success(function(){
User.log({}); User.log({});
}) })
} }
$scope.addLevelsAndGold = function(){ $scope.addLevelsAndGold = function(){
User.setMultiple({ User.set({
'stats.exp': User.user.stats.exp + 10000, 'stats.exp': User.user.stats.exp + 10000,
'stats.gp': User.user.stats.gp + 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 // 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) { $scope.$watch( function() { return Members.selectedMember; }, function (member) {
if(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; $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) { function($rootScope, $scope, User, API_URL, $http, Notification) {
var user = User.user; var user = User.user;
var Items = window.habitrpgShared.items; var Content = $rootScope.Shared.content;
// convenience vars since these are accessed frequently // convenience vars since these are accessed frequently
$scope.selectedEgg = null; // {index: 1, name: "Tiger", value: 5} $scope.selectedEgg = null; // {index: 1, name: "Tiger", value: 5}
$scope.selectedPotion = null; // {index: 5, name: "Red", value: 3} $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 // count egg, food, hatchingPotion stack totals
var countStacks = function(items) { return _.reduce(items,function(m,v){return m+v;},0);} 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.$watch('user.items.gear', function(gear){
$scope.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){ _.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] = []; if (!$scope.gear[item.klass]) $scope.gear[item.klass] = [];
$scope.gear[item.klass].push(item); $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) { if ($scope.selectedEgg && $scope.selectedEgg.name == egg) {
return $scope.selectedEgg = null; // clicked same egg, unselect 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) { if (!$scope.selectedPotion) {
$scope.selectedEgg = eggData; $scope.selectedEgg = eggData;
} else { } else {
@@ -46,7 +46,7 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', 'User', 'API_URL',
return $scope.selectedPotion = null; // clicked same egg, unselect return $scope.selectedPotion = null; // clicked same egg, unselect
} }
// we really didn't think through the way these things are stored and getting passed around... // 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) { if (!$scope.selectedEgg) {
$scope.selectedPotion = potionData; $scope.selectedPotion = potionData;
} else { } else {
@@ -56,42 +56,18 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', 'User', 'API_URL',
$scope.chooseFood = function(food){ $scope.chooseFood = function(food){
if ($scope.selectedFood && $scope.selectedFood.name == food) return $scope.selectedFood = null; 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() { $scope.sellInventory = function() {
// TODO DRY this var selected = $scope.selectedEgg ? 'selectedEgg' : $scope.selectedPotion ? 'selectedPotion' : $scope.selectedFood ? 'selectedFood' : undefined;
if ($scope.selectedEgg) { if (selected) {
user.items.eggs[$scope.selectedEgg.name]--; var type = $scope.selectedEgg ? 'eggs' : $scope.selectedPotion ? 'hatchingPotions' : $scope.selectedFood ? 'food' : undefined;
User.setMultiple({ user.ops.sell({query:{type:type, key: $scope[selected].name}});
'items.eggs': user.items.eggs, if (user.items[type][$scope[selected].name] < 1) {
'stats.gp': User.user.stats.gp + $scope.selectedEgg.value $scope[selected] = null;
});
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;
} }
} }
} }
$scope.ownedItems = function(inventory){ $scope.ownedItems = function(inventory){
@@ -99,21 +75,15 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', 'User', 'API_URL',
} }
$scope.hatch = function(egg, potion){ $scope.hatch = function(egg, potion){
var pet = egg.name+"-"+potion.name; user.ops.hatch({query:{egg:egg.name, hatchingPotion:potion.name}}, function(err, req){
if (user.items.pets[pet]) // Bypassing the UserServices-injected callback so we can only show alert on success. It's safe, since this means
return alert("You already have that pet. Try hatching a different combination!"); // UserServices callback will be 3rd param and never get called
if (err) return Notification.text(err);
var setObj = {}; User.log({op:'hatch', query:req.query});
setObj['items.pets.' + pet] = 5; Notification.text("Your egg hatched! Visit your stable to equip your pet.");
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.");
$scope.selectedEgg = null; $scope.selectedEgg = null;
$scope.selectedPotion = null; $scope.selectedPotion = null;
});
} }
$scope.buy = function(type, item){ $scope.buy = function(type, item){
@@ -134,7 +104,7 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', 'User', 'API_URL',
// Feeding Pet // Feeding Pet
if ($scope.selectedFood) { 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 setObj = {};
var userPets = user.items.pets; var userPets = user.items.pets;
if (user.items.mounts[pet] && (userPets[pet] >= 50 || $scope.selectedFood.name == 'Saddle')) 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.pets.' + pet] = userPets[pet];
setObj['items.food.' + $scope.selectedFood.name] = user.items.food[$scope.selectedFood.name] - 1; setObj['items.food.' + $scope.selectedFood.name] = user.items.food[$scope.selectedFood.name] - 1;
User.setMultiple(setObj); User.set(setObj);
$scope.selectedFood = null; $scope.selectedFood = null;
// Selecting Pet // Selecting Pet
@@ -182,19 +152,5 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', 'User', 'API_URL',
var mount = egg + '-' + potion; var mount = egg + '-' + potion;
User.set('items.currentMount', (user.items.currentMount == mount) ? '' : mount); 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){ $rootScope.$watch('user.items.pets', function(after, before){
if(_.size(after) === _.size(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; User.user.achievements.beastMaster = true;
$rootScope.modals.achievements.beastMaster = true; $rootScope.modals.achievements.beastMaster = true;
}, true); }, true);

View File

@@ -5,12 +5,15 @@
habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams', 'Notification', 'Groups', habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams', 'Notification', 'Groups',
function($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 = {};
$rootScope.modals.achievements = {}; $rootScope.modals.achievements = {};
$rootScope.User = User; $rootScope.User = User;
$rootScope.user = User.user; $rootScope.user = user;
$rootScope.settings = User.settings; $rootScope.settings = User.settings;
$rootScope.Items = window.habitrpgShared.items.items; $rootScope.Shared = window.habitrpgShared;
$rootScope.Content = window.habitrpgShared.content;
// Angular UI Router // Angular UI Router
$rootScope.$state = $state; $rootScope.$state = $state;
@@ -34,10 +37,10 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
// count pets, mounts collected totals, etc // count pets, mounts collected totals, etc
$rootScope.countExists = function(items) {return _.reduce(items,function(m,v){return m+(v?1:0)},0)} $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.$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); }, true);
$scope.safeApply = function(fn) { $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.set = User.set;
$rootScope.authenticated = User.authenticated; $rootScope.authenticated = User.authenticated;
@@ -150,8 +146,6 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
$rootScope.applyingAction = true; $rootScope.applyingAction = true;
$scope.spell = spell; $scope.spell = spell;
if (spell.target == 'self') { 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'); $scope.castEnd(null, 'self');
} else if (spell.target == 'party') { } else if (spell.target == 'party') {
var party = Groups.party(); var party = Groups.party();

View File

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

View File

@@ -1,22 +1,22 @@
"use strict"; "use strict";
habitrpg.controller("TasksCtrl", ['$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, Algos, Helpers, Notification, $http, API_URL) { function($scope, $rootScope, $location, User, Notification, $http, API_URL) {
$scope.obj = User.user; // used for task-lists $scope.obj = User.user; // used for task-lists
$scope.score = function(task, direction) { $scope.score = function(task, direction) {
if (task.type === "reward" && User.user.stats.gp < task.value){ User.user.ops.score({params:{id: task.id, direction:direction}})
return Notification.text('Not enough Gold!');
}
Algos.score(User.user, task, direction);
User.log({op: "score",data: task, dir: direction});
}; };
$scope.addTask = function(addTo, listDef) { $scope.addTask = function(addTo, listDef) {
var task = window.habitrpgShared.helpers.taskDefaults({text: listDef.newTask, type: listDef.type}, User.user.filters); var newTask = {
addTo.unshift(task); text: listDef.newTask,
User.log({op: "addTask", data: task}); 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; delete listDef.newTask;
}; };
@@ -40,40 +40,12 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User', '
$scope.removeTask = function(list, $index) { $scope.removeTask = function(list, $index) {
if (!confirm("Are you sure you want to delete this task?")) return; 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); list.splice($index, 1);
}; };
$scope.saveTask = function(task) { $scope.saveTask = function(task) {
var setVal = function(k, v) { User.user.ops.updateTask({params:{id:task.id},body:task});
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);
task._editing = false; 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) { $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) { if (hasEnough) {
User.log({op: "buy", key: item.key});
Notification.text("Item purchased."); Notification.text("Item purchased.");
$scope.itemStore = window.habitrpgShared.items.updateStore(User.user); $scope.itemStore = User.user.fns.updateStore();
} else { } 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){ $scope.allocate = function(stat){
var setObj = {} User.user.ops.allocate({query:{stat:stat}});
setObj['stats.' + stat] = User.user.stats[stat] + 1;
setObj['stats.points'] = User.user.stats.points - 1;
User.setMultiple(setObj);
} }
$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)")) 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; return;
User.setMultiple({ return User.user.ops.changeClass({});
'flags.classSelected': false,
//'stats.points': this is handled on the server
'stats.str': 0,
'stats.def': 0,
'stats.per': 0,
'stats.int': 0
})
} }
$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) User.user.ops.changeClass({query:{class:klass}});
// 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);
$scope.selectedClass = undefined; $scope.selectedClass = undefined;
//FIXME run updateStore (we need to access a different scope) User.user.fns.updateStore();
} }
$scope.save = function(){ $scope.save = function(){
@@ -72,11 +36,28 @@ habitrpg.controller("UserCtrl", ['$rootScope', '$scope', '$location', 'User', '$
if(!curVal || $scope.editingProfile[key].toString() !== curVal.toString()) if(!curVal || $scope.editingProfile[key].toString() !== curVal.toString())
values['profile.' + key] = value; values['profile.' + key] = value;
}); });
User.setMultiple(values); User.set(values);
$scope._editing.profile = false; $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', []). 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, * 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'); $('.main-herobox').popover('destroy');
var tour = new Tour({ var tour = new Tour({
onEnd: function(){ onEnd: function(){
User.set('flags.showTour', false); User.set({'flags.showTour': false});
} }
}); });
tourSteps.forEach(function(step) { tourSteps.forEach(function(step) {
@@ -134,7 +134,7 @@ angular.module('guideServices', []).
$rootScope.$watch('user.items.pets', function(after, before) { $rootScope.$watch('user.items.pets', function(after, before) {
if (User.user.achievements && User.user.achievements.beastMaster) return; if (User.user.achievements && User.user.achievements.beastMaster) return;
if (before >= 90) { if (before >= 90) {
User.set('achievements.beastMaster', true); User.set({'achievements.beastMaster': true});
$('#beastmaster-achievement-modal').modal('show'); // FIXME $('#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 user = {}; // this is stored as a reference accessible to all controllers, that way updates propagate
//first we populate user with schema //first we populate user with schema
_.extend(user, $window.habitrpgShared.helpers.newUser());
user.apiToken = user._id = ''; // we use id / apitoken to determine if registered user.apiToken = user._id = ''; // we use id / apitoken to determine if registered
$window.habitrpgShared.algos.defineComputed(user);
//than we try to load localStorage //than we try to load localStorage
if (localStorage.getItem(STORAGE_USER_ID)) { if (localStorage.getItem(STORAGE_USER_ID)) {
_.extend(user, JSON.parse(localStorage.getItem(STORAGE_USER_ID))); _.extend(user, JSON.parse(localStorage.getItem(STORAGE_USER_ID)));
} }
user._wrapped = false;
var syncQueue = function (cb) { var syncQueue = function (cb) {
if (!authenticated) { if (!authenticated) {
@@ -71,6 +70,16 @@ angular.module('userServices', []).
// Update user // Update user
_.extend(user, data); _.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 // Emit event when user is synced
$rootScope.$emit('userSynced'); $rootScope.$emit('userSynced');
@@ -108,6 +117,9 @@ angular.module('userServices', []).
}; };
var userServices = { var userServices = {
user: user, user: user,
set: function(updates) {
user.ops.update({body:updates});
},
online: function (status) { online: function (status) {
if (status===true) { if (status===true) {
settings.online = true; settings.online = true;
@@ -130,7 +142,7 @@ angular.module('userServices', []).
// If they don't have timezone, set it // If they don't have timezone, set it
var offset = moment().zone(); // eg, 240 - this will be converted on server as -(offset/60) var offset = moment().zone(); // eg, 240 - this will be converted on server as -(offset/60)
if (user.preferences.timezoneOffset !== offset) if (user.preferences.timezoneOffset !== offset)
userServices.set('preferences.timezoneOffset', offset); userServices.set({'preferences.timezoneOffset': offset});
cb && cb(); cb && cb();
}); });
} else { } else {
@@ -161,62 +173,6 @@ angular.module('userServices', []).
userServices.log({}); 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, save: save,
settings: settings settings: settings

View File

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

View File

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

View File

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

View File

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

View File

@@ -31,7 +31,7 @@ var initDeprecated = function(req, res, next) {
return 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) { router.get('/v1/users/:uid/calendar.ics', function(req, res, next) {
return next() //disable for now return next() //disable for now

View File

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

View File

@@ -5,9 +5,7 @@ var ipn = require('paypal-ipn');
var _ = require('lodash'); var _ = require('lodash');
var nconf = require('nconf'); var nconf = require('nconf');
var async = require('async'); var async = require('async');
var algos = require('habitrpg-shared/script/algos'); var shared = require('habitrpg-shared');
var helpers = require('habitrpg-shared/script/helpers');
var items = require('habitrpg-shared/script/items');
var validator = require('validator'); var validator = require('validator');
var check = validator.check; var check = validator.check;
var sanitize = validator.sanitize; var sanitize = validator.sanitize;
@@ -58,12 +56,6 @@ api.verifyTaskExists = function(req, res, next) {
return next(); return next();
}; };
function addTask(user, task) {
task = helpers.taskDefaults(task);
user[task.type+'s'].unshift(task);
return task;
}
/* /*
API Routes 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 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 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, var id = req.params.id,
direction = req.params.direction, direction = req.params.direction,
user = res.locals.user, user = res.locals.user,
@@ -106,9 +98,9 @@ api.scoreTask = function(req, res, next) {
if (task.type === 'daily' || task.type === 'todo') { if (task.type === 'daily' || task.type === 'todo') {
task.completed = direction === 'up'; 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.markModified('flags');
user.save(function(err, saved) { user.save(function(err, saved) {
if (err) return res.json(500, {err: err}); if (err) return res.json(500, {err: err});
@@ -146,38 +138,26 @@ api.getTask = function(req, res, next) {
* Delete Task * Delete Task
*/ */
api.deleteTask = function(req, res, next) { api.deleteTask = function(req, res, next) {
api.verifyTaskExists(req, res, function(){
var user = res.locals.user; var user = res.locals.user;
user.deleteTask(res.locals.task.id); user.deleteTask(res.locals.task.id);
user.save(function(err) { user.save(function(err) {
if (err) return res.json(500, {err: err}); if (err) return res.json(500, {err: err});
res.send(204); res.send(204);
}); });
})
}; };
/* /*
Update Task 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) { // api.updateTask // handled in Shared.ops
var user = res.locals.user;
var task = addTask(user, req.body); // api.addTask // handled in Shared.ops
user.save(function(err, saved) {
if (err) return res.json(500, {err: err});
return res.json(201, task);
});
};
api.sortTask = function(req, res, next) { api.sortTask = function(req, res, next) {
api.verifyTaskExists(req, res, function(){
var id = req.params.id; var id = req.params.id;
var to = req.body.to, from = req.body.from, type = req.body.type; var to = req.body.to, from = req.body.from, type = req.body.type;
var user = res.locals.user; var user = res.locals.user;
@@ -186,6 +166,7 @@ api.sortTask = function(req, res, next) {
if (err) return res.json(500, {err: err}); if (err) return res.json(500, {err: err});
return res.json(200, saved.toJSON()[type+'s']); return res.json(200, saved.toJSON()[type+'s']);
}); });
})
}; };
api.clearCompleted = function(req, res, next) { api.clearCompleted = function(req, res, next) {
@@ -202,22 +183,7 @@ api.clearCompleted = function(req, res, next) {
Items Items
------------------------------------------------------------------------ ------------------------------------------------------------------------
*/ */
api.buy = function(req, res, next) { // api.buy // handled in Shard.ops
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"});
}
};
/* /*
------------------------------------------------------------------------ ------------------------------------------------------------------------
@@ -230,7 +196,7 @@ api.buy = function(req, res, next) {
*/ */
api.getUser = function(req, res, next) { api.getUser = function(req, res, next) {
var user = res.locals.user.toJSON(); 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; user.stats.maxHealth = 50;
delete user.apiToken; delete user.apiToken;
if (user.auth) { if (user.auth) {
@@ -249,7 +215,7 @@ api.getUser = function(req, res, next) {
* Note: custom is for 3rd party apps * Note: custom is for 3rd party apps
*/ */
acceptablePUTPaths = _.reduce(require('./../models/user').schema.paths, function(m,v,leaf){ 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; return leaf.indexOf(root) == 0;
}); });
if (found) m[leaf]=true; 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} * PUT /user {'stats.hp':50, 'tasks.TASK_ID.repeat.m':false}
* See acceptablePUTPaths for which user paths are supported * 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 user = res.locals.user;
var errors = []; var errors = [];
if (_.isEmpty(req.body)) return res.json(200, user); if (_.isEmpty(req.body)) return res.json(200, user);
_.each(req.body, function(v, k) { _.each(req.body, function(v, k) {
if (acceptablePUTPaths[k]) if (acceptablePUTPaths[k])
helpers.dotSet(k, v, user); user.fns.dotSet(k, v);
else 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}}}`"); 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; return true;
@@ -284,7 +250,7 @@ api.updateUser = function(req, res, next) {
api.cron = function(req, res, next) { api.cron = function(req, res, next) {
var user = res.locals.user; var user = res.locals.user;
algos.cron(user); shared.cron(user);
if (user.isModified()) { if (user.isModified()) {
res.locals.wasModified = true; res.locals.wasModified = true;
user.auth.timestamps.loggedin = new Date(); user.auth.timestamps.loggedin = new Date();
@@ -292,15 +258,6 @@ api.cron = function(req, res, next) {
user.save(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) { api.reroll = function(req, res, next) {
var user = res.locals.user; var user = res.locals.user;
if (user.balance < 1) return res.json(401, {err: "Not enough tokens."}); 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) { // api.unlock // see Shared.ops
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);
})
}
/* /*
------------------------------------------------------------------------ ------------------------------------------------------------------------
@@ -492,7 +420,7 @@ api.deleteTag = function(req, res){
api.cast = function(req, res) { api.cast = function(req, res) {
var user = res.locals.user; var user = res.locals.user;
var type = req.body.type, target = req.body.target; 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 done = function(){
var err = arguments[0]; 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 Batch Update
@@ -558,78 +508,28 @@ api.batchUpdate = function(req, res, next) {
var user = res.locals.user; var user = res.locals.user;
var oldSend = res.send; var oldSend = res.send;
var oldJson = res.json; var oldJson = res.json;
var performAction = function(action, cb) {
// TODO come up with a more consistent approach here. like: var callOp = function(_req, cb) {
// 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;
res.send = res.json = function(code, data) { res.send = res.json = function(code, data) {
if (_.isNumber(code) && code >= 400) { if (_.isNumber(code) && code >= 400)
console.error({ console.error({code: code, data: data});
code: code,
data: data
});
}
//FIXME send error messages down //FIXME send error messages down
return cb(); return cb();
}; };
switch (action.op) { api[_req.op](_req, res);
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;
}
}; };
// Setup the array of functions we're going to call in parallel with async // Setup the array of functions we're going to call in parallel with async
var actions = _.transform(req.body || [], function(result, action) { var ops = _.transform(req.body || [], function(result, _req) {
if (!_.isEmpty(action)) { if (!_.isEmpty(_req)) {
result.push(function(cb) { result.push(function(cb) {
performAction(action, cb); callOp(_req, cb);
}); });
} }
}); });
// call all the operations, then return the user object to the requester // 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.json = oldJson;
res.send = oldSend; res.send = oldSend;
if (err) return res.json(500, {err: err}); if (err) return res.json(500, {err: err});

View File

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

View File

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

View File

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

View File

@@ -6,8 +6,7 @@
// ------------ // ------------
var mongoose = require("mongoose"); var mongoose = require("mongoose");
var Schema = mongoose.Schema; var Schema = mongoose.Schema;
var helpers = require('habitrpg-shared/script/helpers'); var shared = require('habitrpg-shared');
var items = require('habitrpg-shared/script/items');
var _ = require('lodash'); var _ = require('lodash');
var TaskSchemas = require('./task'); var TaskSchemas = require('./task');
var Challenge = require('./challenge').model; var Challenge = require('./challenge').model;
@@ -15,23 +14,23 @@ var Challenge = require('./challenge').model;
// User Schema // User Schema
// ----------- // -----------
var eggPotionMapping = _.transform(items.items.eggs, function(m, egg){ var eggPotionMapping = _.transform(shared.content.eggs, function(m, egg){
_.defaults(m, _.transform(items.items.hatchingPotions, function(m2, pot){ _.defaults(m, _.transform(shared.content.hatchingPotions, function(m2, pot){
m2[egg.name + '-' + pot.name] = true; 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({ var UserSchema = new Schema({
// ### UUID and API Token // ### UUID and API Token
_id: { _id: {
type: String, type: String,
'default': helpers.uuid 'default': shared.uuid
}, },
apiToken: { apiToken: {
type: String, type: String,
'default': helpers.uuid 'default': shared.uuid
}, },
// ### Mongoose Update Object // ### Mongoose Update Object
@@ -109,7 +108,7 @@ var UserSchema = new Schema({
}, },
items: { items: {
gear: { 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}; m[v.key] = {type: Boolean};
if (v.key.match(/[weapon|armor|head|shield]_warrior_0/)) if (v.key.match(/[weapon|armor|head|shield]_warrior_0/))
m[v.key]['default'] = true; m[v.key]['default'] = true;
@@ -149,19 +148,19 @@ var UserSchema = new Schema({
// 'PandaCub': 0, // 0 indicates "doesn't own" // 'PandaCub': 0, // 0 indicates "doesn't own"
// 'Wolf': 5 // Number indicates "stacking" // '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: { // hatchingPotions: {
// 'Desert': 0, // 0 indicates "doesn't own" // 'Desert': 0, // 0 indicates "doesn't own"
// 'CottonCandyBlue': 5 // Number indicates "stacking" // '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: { // Food: {
// 'Watermelon': 0, // 0 indicates "doesn't own" // 'Watermelon': 0, // 0 indicates "doesn't own"
// 'RottenMeat': 5 // Number indicates "stacking" // '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: { // mounts: {
// 'Wolf-Desert': true, // 'Wolf-Desert': true,
@@ -281,16 +280,30 @@ UserSchema.methods.toJSON = function() {
return doc; return doc;
}; };
UserSchema.virtual('tasks').get(function () { //UserSchema.virtual('tasks').get(function () {
var tasks = this.habits.concat(this.dailys).concat(this.todos).concat(this.rewards); // var tasks = this.habits.concat(this.dailys).concat(this.todos).concat(this.rewards);
var tasks = _.object(_.pluck(tasks,'id'), tasks); // var tasks = _.object(_.pluck(tasks,'id'), tasks);
return tasks; // return tasks;
}); //});
// FIXME - since we're using special @post('init') above, we need to flag when the original path was modified. UserSchema.post('init', function(doc){
// Custom setter/getter virtuals? shared.wrap(doc);
})
UserSchema.pre('save', function(next) { 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'); //this.markModified('tasks');
if (_.isNaN(this.preferences.dayStart) || this.preferences.dayStart < 0 || this.preferences.dayStart > 24) { if (_.isNaN(this.preferences.dayStart) || this.preferences.dayStart < 0 || this.preferences.dayStart > 24) {
this.preferences.dayStart = 0; 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(){}}) // 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); 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 //HOTFIX - Remove when solution is found, the first argument passed to reduce is a function
if(_.isFunction(v)) return m; if(_.isFunction(v)) return m;
return m+(v?1:0)},0), this.items.pets); return m+(v?1:0)},0), this.items.pets);

View File

@@ -19,7 +19,6 @@ var middleware = require('../middleware');
$ mocha test/user.mocha.coffee $ mocha test/user.mocha.coffee
*/ */
var verifyTaskExists = user.verifyTaskExists
var cron = user.cron; var cron = user.cron;
router.get('/status', function(req, res) { 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 router.get('/export/history',auth.auth,dataexport.history); //[todo] encode data output options in the data controller and use these to build routes
/* Scoring*/ /* Scoring*/
router.post('/user/task/: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.scoreTask); router.post('/user/tasks/:id/:direction', auth.auth, cron, user.score);
/* Tasks*/ /* Tasks*/
router.get('/user/tasks', auth.auth, cron, user.getTasks); router.get('/user/tasks', auth.auth, cron, user.getTasks);
router.get('/user/task/:id', auth.auth, cron, user.getTask); router.get('/user/task/:id', auth.auth, cron, user.getTask);
router.put('/user/task/:id', auth.auth, cron, verifyTaskExists, user.updateTask); router.put('/user/task/:id', auth.auth, cron, user.updateTask);
router["delete"]('/user/task/:id', auth.auth, cron, verifyTaskExists, user.deleteTask); router["delete"]('/user/task/:id', auth.auth, cron, user.deleteTask);
router.post('/user/task', auth.auth, cron, user.createTask); router.post('/user/task', auth.auth, cron, user.addTask);
router.put('/user/task/:id/sort', auth.auth, cron, verifyTaskExists, user.sortTask); router.put('/user/task/:id/sort', auth.auth, cron, user.sortTask);
router.post('/user/clear-completed', auth.auth, cron, user.clearCompleted); 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 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') { if (nconf.get('NODE_ENV') == 'development') {
@@ -53,7 +52,7 @@ router.post('/user/buy/:key', auth.auth, cron, user.buy);
/* User*/ /* User*/
router.get('/user', auth.auth, cron, user.getUser); 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/revive', auth.auth, cron, user.revive);
router.post('/user/batch-update', middleware.forceRefresh, auth.auth, cron, user.batchUpdate); router.post('/user/batch-update', middleware.forceRefresh, auth.auth, cron, user.batchUpdate);
router.post('/user/reroll', auth.auth, cron, user.reroll); 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 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]') 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]') 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 label.checkbox.inline
input(type="checkbox", ng-model="user.preferences.costume") input(type="checkbox", ng-model="user.preferences.costume")
| Use Costume&nbsp; | 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') 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]') 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]') 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 li.customize-menu
menu.pets-menu(label='Eggs ({{eggCount}})') menu.pets-menu(label='Eggs ({{eggCount}})')
p(ng-show='eggCount < 1') You don't have any eggs. p(ng-show='eggCount < 1') You don't have any eggs.
div(ng-repeat='(egg,points) in ownedItems(user.items.eggs)') div(ng-repeat='(egg,points) in ownedItems(user.items.eggs)')
//TODO move positioning this styling to css //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}} .badge.badge-info.stack-count {{points}}
//-p {{Items.eggs[egg].text}} //-p {{Content.eggs[egg].text}}
li.customize-menu li.customize-menu
menu.hatchingPotion-menu(label='Hatching Potions ({{potCount}})') menu.hatchingPotion-menu(label='Hatching Potions ({{potCount}})')
p(ng-show='potCount < 1') You don't have any hatching potions. p(ng-show='potCount < 1') You don't have any hatching potions.
div(ng-repeat='(pot,points) in ownedItems(user.items.hatchingPotions)') 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}} .badge.badge-info.stack-count {{points}}
//-p {{pot}} //-p {{pot}}
@@ -40,7 +40,7 @@ script(type='text/ng-template', id='partials/options.inventory.inventory.html')
menu.pets-menu(label='Food ({{foodCount}})') menu.pets-menu(label='Food ({{foodCount}})')
p(ng-show='foodCount < 1') You don't have any food. p(ng-show='foodCount < 1') You don't have any food.
div(ng-repeat='(food,points) in ownedItems(user.items.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}} .badge.badge-info.stack-count {{points}}
//-p {{food}} //-p {{food}}
@@ -70,7 +70,7 @@ script(type='text/ng-template', id='partials/options.inventory.inventory.html')
menu.inventory-list(type='list') menu.inventory-list(type='list')
li.customize-menu li.customize-menu
menu.pets-menu(label='Eggs') 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}}') 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 p
| {{egg.value}} | {{egg.value}}
@@ -78,7 +78,7 @@ script(type='text/ng-template', id='partials/options.inventory.inventory.html')
li.customize-menu li.customize-menu
menu.pets-menu(label='Hatching Potions') 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}}') 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 p
| {{pot.value}} | {{pot.value}}
@@ -86,7 +86,7 @@ script(type='text/ng-template', id='partials/options.inventory.inventory.html')
li.customize-menu li.customize-menu
menu.pets-menu(label='Food') 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}}') 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 p
| {{food.value}} | {{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. Shall I bring you your steed, {{user.profile.name}}? Click a mount to saddle up.
h4 {{mountCount}} / {{totalPets}} Mounts Tamed h4 {{mountCount}} / {{totalPets}} Mounts Tamed
menu.pets(type='list') menu.pets(type='list')
li.customize-menu(ng-repeat='egg in Items.eggs') li.customize-menu(ng-repeat='egg in Content.eggs')
menu 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)') 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}}') //div(class='Mount_Head_{{mount}}')
button(class="pet-button pet-not-owned", ng-hide='user.items.mounts[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 h4 {{petCount}} / {{totalPets}} Pets Found
menu.pets(type='list') menu.pets(type='list')
li.customize-menu(ng-repeat='egg in Items.eggs') li.customize-menu(ng-repeat='egg in Content.eggs')
menu 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)') 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}') .progress(ng-class='{"progress-success": user.items.pets[pet]<50}')
.bar(style="width: {{user.items.pets[pet]/.5}}%;") .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 li.customize-menu
menu.pets-menu(label='Food') menu.pets-menu(label='Food')
div(ng-repeat='(food,points) in ownedItems(user.items.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}} .badge.badge-info.stack-count {{points}}
// Remove this once we have images in // 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 li.customize-menu
menu(label='Head') menu(label='Head')
menu menu
button.broad_armor_base_0.customize-option(type='button', ng-click='set("preferences.size","broad")') 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.slim_armor_base_0.customize-option(type='button', ng-click='set({"preferences.size":"slim"})')
.span4 .span4
h3 Hair h3 Hair
@@ -15,60 +15,60 @@ script(id='partials/options.profile.avatar.html', type='text/ng-template')
// Color // Color
li.customize-menu li.customize-menu
menu(label='Color') 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:#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:#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:#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:#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:#2e2e2e;', ng-click='set({"preferences.hair.color": "black"})')
// Bangs // Bangs
li.customize-menu li.customize-menu
menu(label='Bangs') menu(label='Bangs')
button(class='head_base_0 customize-option', type='button', ng-click='set("preferences.hair.bangs",0)') 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_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_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_3_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set({"preferences.hair.bangs":3})')
// Beard // Beard
li.customize-menu li.customize-menu
menu(label='Beard') menu(label='Beard')
button(class='head_base_0 customize-option', type='button', ng-click='set("preferences.hair.beard",0)') 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_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_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='hair_beard_3_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set({"preferences.hair.beard":3})')
// Mustache // Mustache
li.customize-menu li.customize-menu
menu(label='Mustache') menu(label='Mustache')
button(class='head_base_0 customize-option', type='button', ng-click='set("preferences.hair.mustache",0)') 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_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='hair_mustache_2_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set({"preferences.hair.mustache":2})')
// Base // Base
li.customize-menu li.customize-menu
menu(label='Base') menu(label='Base')
button(class='head_base_0 customize-option', type='button', ng-click='set("preferences.hair.base",0)') 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_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_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_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_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_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_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_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='hair_base_8_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set({"preferences.hair.base":8})')
.span4 .span4
// skin // skin
li.customize-menu li.customize-menu
menu(label='Basic Skins') 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_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_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_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_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_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_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_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_orc', ng-click='set({"preferences.skin":"orc" })')
// Rainbow Skin // Rainbow Skin
h5. 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_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_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.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 // Special Events
// restore to d4df481 to see purchasing + "limited edition" code // 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') .span4.border-right(ng-show='user.stats.lvl >= 5')
h4 h4
| {{user.stats.class}}&nbsp; | {{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}} h6 Points: {{user.stats.points}}
fieldset fieldset
label.checkbox 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 | 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.") 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 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()') select(ng-model='language.code', ng-options='lang.code as lang.name for lang in avalaibleLanguages', ng-change='changeLanguage()')
hr hr
h4 Misc 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-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-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='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='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 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-model='user.party.order',
ng-controller='ChatCtrl', ng-controller='ChatCtrl',
ng-options='k as v for (k , v) in partyOrderChoices', 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') table.table.table-striped(bindonce='group')
tr(ng-repeat='member in group.members') tr(ng-repeat='member in group.members')

View File

@@ -11,15 +11,15 @@
// stat bars // stat bars
.hero-stats .hero-stats
.meter.health(title='Health') .meter.health(title='Health')
.bar(style='width: {{percent(user.stats.hp, 50)}}%;') .bar(style='width: {{Shared.percent(user.stats.hp, 50)}}%;')
span.meter-text span.meter-text
i.icon-heart i.icon-heart
| {{user.stats.hp | number:0}} / 50 | {{user.stats.hp | number:0}} / 50
.meter.experience(title='Experience') .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 span.meter-text
i.icon-star 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 // FIXME doesn't look great here, but the "Experience" CSS title rollover covers it where it was before
span(ng-show='user.history.exp') span(ng-show='user.history.exp')
a(ng-click='toggleChart("exp")', tooltip='Progress') 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> {{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 .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') span(class='weapon_healer_6')
.modal-footer .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 .row-fluid
.span4 .span4
p 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 .span8
p 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! | 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 p
span.item-drop-icon(class='Pet_Egg_Wolf', style='margin-left: 0px') span.item-drop-icon(class='Pet_Egg_Wolf', style='margin-left: 0px')
span. 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 br
p. 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! <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}} | : {{profile.stats.lvl}}
p p
strong Experience strong Experience
| : {{profile.stats.exp | number:0}} / {{tnl(profile.stats.lvl)}} | : {{profile.stats.exp | number:0}} / {{Shared.tnl(profile.stats.lvl)}}
p p
strong Strength strong Strength
| :&nbsp; | :&nbsp;

View File

@@ -18,10 +18,10 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template")
// Gold & Gems // Gold & Gems
span.option-box.pull-right.wallet(bo-if='main && list.type=="reward"') span.option-box.pull-right.wallet(bo-if='main && list.type=="reward"')
.money .money
| {{gold(user.stats.gp)}} | {{Shared.gold(user.stats.gp)}}
span.shop_gold(tooltip='Gold') span.shop_gold(tooltip='Gold')
.money .money
| {{silver(user.stats.gp)}} | {{Shared.silver(user.stats.gp)}}
span.shop_silver(tooltip='Silver') span.shop_silver(tooltip='Silver')
// Header // Header
@@ -62,7 +62,7 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template")
// Spells // Spells
ul.items(ng-if='main && list.type=="reward" && user.stats.class') 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 .task-meta-controls
span.task-notes(popover-trigger='mouseenter', popover-placement='left', popover='{{spell.notes}}', popover-title='{{spell.text}}') span.task-notes(popover-trigger='mouseenter', popover-placement='left', popover='{{spell.notes}}', popover-title='{{spell.text}}')
i.icon-comment 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 // right-hand side control buttons
.task-meta-controls .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 legend.option-title Repeat
.task-controls.tile-group.repeat-days(bindonce) .task-controls.tile-group.repeat-days(bindonce)
// note, does not use data-toggle="buttons-checkbox" - it would interfere with our own click binding // 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.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.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.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.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.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.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.s}', type='button', ng-click='task.challenge.id || (task.repeat.s = !task.repeat.s)', bo-text='moment.weekdaysMin(6)')
// if Reward, pricing // if Reward, pricing
fieldset.option-group.option-short(ng-if='task.type=="reward" && !task.challenge.id') fieldset.option-group.option-short(ng-if='task.type=="reward" && !task.challenge.id')