mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 22:57:21 +01:00
Login Incentives (#8230)
* feat(incentives): login bennies WIP * feat(content): incentive prize content WIP * fix(content): placeholders pass tests * WIP(content): Bard instrument placeholder * feat(content): Incentives build * chore(sprites): compile and fix some strings * WIP(incentives): quests and backgrounds * fix(quests): correct buy/launch handling * [WIP] Incentives rewarding (#8226) * Added login incentive rewards * Updated incentive rewards * Added incentive modal and updated notification structure * Added analytics to sleeping * Added login incentives to user analytics * Fixed unit tests and ensured that prizes are incremented and not replaced * Updated style of daily login incentive modal * Added rewards modal * Added translations * Added loigin incentive ui elements to profile * Updated login incentives structure and abstracted to common.content * Added dynamic display for login incentives on profile * Added purple potion image * Updated daily login modal * Fixed progress calculation * Added bard gear * Updated login incentive rewards * Fixed styles and text * Added multiple read for notifications * Fixed lint issues * Fixed styles and added 50 limit * Updated quest keys * Added login incentives reward page * Fixed tests * Fixed linting and tests * Read named notifications route. Add image for backgrounds * Fixed style issues and added tranlsations to login incentive notification * Hided abiltiy to purchase incentive backgrounds and added message to detail how to unlock * Updated awarded message * Fixed text and updated progress counter to display better * Fixed purple potion reward text * Fixed check in backgrouns reward text * fix(quest): pass tests * Added display of multiple rewards * Updated modal styles * Fixed neagtive 50 issue * Remvoed total count from daily login incentives modal * Fixed magic paw display * fix(awards): give bunnies again * WIP(incentives): more progress on BG shop * fix(incentives): actually award backgrounds * fix(incentives): more BG fixy * fix(backgrounds): don't gem-buy checkin bgs * Added dust bunny notification * fix(incentives): don't redisplay bunny award * chore(news): Bailey and different promo sprite
This commit is contained in:
@@ -35,7 +35,7 @@ angular.module('habitrpg')
|
||||
if (isUserLoaded && notification.type === 'CRON') {
|
||||
// If the user is already loaded, do not show the notification, syncing will show it
|
||||
// (the user will be synced automatically)
|
||||
$rootScope.User.readNotification(notification.id);
|
||||
// $rootScope.User.readNotifications([notification.id]);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -360,6 +360,9 @@ habitrpg.controller("InventoryCtrl",
|
||||
if (!premiumPotion) {
|
||||
return false;
|
||||
}
|
||||
if (premiumPotion.key === 'RoyalPurple') {
|
||||
return true;
|
||||
}
|
||||
if (user.items.hatchingPotions[premiumPotion.key] > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
habitrpg.controller('NotificationCtrl',
|
||||
['$scope', '$rootScope', 'Shared', 'Content', 'User', 'Guide', 'Notification', 'Analytics', 'Achievement',
|
||||
function ($scope, $rootScope, Shared, Content, User, Guide, Notification, Analytics, Achievement) {
|
||||
['$scope', '$rootScope', 'Shared', 'Content', 'User', 'Guide', 'Notification', 'Analytics', 'Achievement', 'Social',
|
||||
function ($scope, $rootScope, Shared, Content, User, Guide, Notification, Analytics, Achievement, Social) {
|
||||
|
||||
$rootScope.$watch('user.stats.hp', function (after, before) {
|
||||
if (after <= 0){
|
||||
@@ -86,6 +86,7 @@ habitrpg.controller('NotificationCtrl',
|
||||
function handleUserNotifications (after) {
|
||||
if (!after || after.length === 0) return;
|
||||
|
||||
var notificationsToRead = [];
|
||||
after.forEach(function (notification) {
|
||||
if (lastShownNotifications.indexOf(notification.id) !== -1) {
|
||||
return;
|
||||
@@ -140,14 +141,18 @@ habitrpg.controller('NotificationCtrl',
|
||||
trasnferGroupNotification(notification);
|
||||
markAsRead = false;
|
||||
break;
|
||||
case 'LOGIN_INCENTIVE':
|
||||
Notification.showLoginIncentive(User.user, notification.data, Social.loadWidgets);
|
||||
break;
|
||||
default:
|
||||
markAsRead = false; // If the notification is not implemented, skip it
|
||||
break;
|
||||
}
|
||||
|
||||
if (markAsRead) User.readNotification(notification.id);
|
||||
if (markAsRead) notificationsToRead.push(notification.id);
|
||||
});
|
||||
|
||||
User.readNotifications(notificationsToRead);
|
||||
User.user.notifications = []; // reset the notifications
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ habitrpg.controller("UserCtrl", ['$rootScope', '$scope', '$location', 'User', '$
|
||||
$scope.$watch('_editing.profile', function(value){
|
||||
if(value === true) $scope.editingProfile = angular.copy(User.user.profile);
|
||||
});
|
||||
|
||||
|
||||
$scope.costume = Costume;
|
||||
|
||||
$scope.allocate = function(stat){
|
||||
@@ -51,6 +51,8 @@ habitrpg.controller("UserCtrl", ['$rootScope', '$scope', '$location', 'User', '$
|
||||
User.set({'flags.warnedLowHealth':true});
|
||||
}
|
||||
|
||||
$scope.backgroundShopSets = Shared.shops.getBackgroundShopSets();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@@ -63,27 +65,43 @@ habitrpg.controller("UserCtrl", ['$rootScope', '$scope', '$location', 'User', '$
|
||||
(fullSet ? 3.75 : 1.75) : // (Backgrounds) 15G per set, 7G per individual
|
||||
(fullSet ? 1.25 : 0.5); // (Hair, skin, etc) 5G per set, 2G per individual
|
||||
|
||||
|
||||
if (fullSet) {
|
||||
if (confirm(window.env.t('purchaseFor',{cost:cost*4})) !== true) return;
|
||||
if (User.user.balance < cost) return $rootScope.openModal('buyGems');
|
||||
} else if (!_.get(User.user, 'purchased.' + path)) {
|
||||
if (confirm(window.env.t('purchaseFor',{cost:cost*4})) !== true) return;
|
||||
if (User.user.balance < cost) return $rootScope.openModal('buyGems');
|
||||
if (path.indexOf('background.blue') === -1 && path.indexOf('background.green') === -1 && path.indexOf('background.red') === -1 && path.indexOf('background.purple') === -1 && path.indexOf('background.yellow') === -1) {
|
||||
if (fullSet) {
|
||||
if (confirm(window.env.t('purchaseFor',{cost:cost*4})) !== true) return;
|
||||
if (User.user.balance < cost) return $rootScope.openModal('buyGems');
|
||||
} else if (!_.get(User.user, 'purchased.' + path)) {
|
||||
if (confirm(window.env.t('purchaseFor',{cost:cost*4})) !== true) return;
|
||||
if (User.user.balance < cost) return $rootScope.openModal('buyGems');
|
||||
}
|
||||
}
|
||||
User.unlock({query:{path:path}})
|
||||
}
|
||||
|
||||
$scope.ownsSet = function(type,_set) {
|
||||
return !_.find(_set,function(v,k){
|
||||
$scope.ownsSet = function(type, _set) {
|
||||
return !_.find(_set,function(v,k) {
|
||||
if (type === 'background') k = v.key;
|
||||
return !User.user.purchased[type][k];
|
||||
});
|
||||
}
|
||||
$scope.setKeys = function(type,_set){
|
||||
return _.map(_set, function(v,k){
|
||||
};
|
||||
|
||||
$scope.setKeys = function(type, _set) {
|
||||
return _.map(_set, function(v,k) {
|
||||
if (type === 'background') k = v.key;
|
||||
return type+'.'+k;
|
||||
}).join(',');
|
||||
}
|
||||
};
|
||||
|
||||
$scope.getProgressDisplay = function () {
|
||||
var currentLoginDay = Content.loginIncentives[$scope.profile.loginIncentives];
|
||||
var nextRewardAt = currentLoginDay.nextRewardAt;
|
||||
return ' ' + ($scope.profile.loginIncentives - currentLoginDay.prevRewardKey) + '/' + (nextRewardAt - currentLoginDay.prevRewardKey);
|
||||
};
|
||||
|
||||
$scope.incentivesProgress = function () {
|
||||
var currentLoginDay = Content.loginIncentives[$scope.profile.loginIncentives];
|
||||
var previousRewardDay = currentLoginDay.prevRewardKey;
|
||||
var nextRewardAt = currentLoginDay.nextRewardAt;
|
||||
return ($scope.profile.loginIncentives - previousRewardDay)/(nextRewardAt - previousRewardDay) * 100;
|
||||
};
|
||||
}
|
||||
]);
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
*/
|
||||
|
||||
angular.module('habitrpg').factory('Guide',
|
||||
['$rootScope', 'User', '$timeout', '$state', 'Analytics',
|
||||
function($rootScope, User, $timeout, $state, Analytics) {
|
||||
['$rootScope', 'User', '$timeout', '$state', 'Analytics', 'Notification', 'Shared', 'Social',
|
||||
function($rootScope, User, $timeout, $state, Analytics, Notification, Shared, Social) {
|
||||
|
||||
var chapters = {
|
||||
intro: [
|
||||
@@ -202,12 +202,26 @@ function($rootScope, User, $timeout, $state, Analytics) {
|
||||
if (lastKnownStep === -2) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (i > lastKnownStep) {
|
||||
if (step.gold) ups['stats.gp'] = User.user.stats.gp + step.gold;
|
||||
if (step.experience) ups['stats.exp'] = User.user.stats.exp + step.experience;
|
||||
ups['flags.tour.'+k] = i;
|
||||
}
|
||||
|
||||
if (step.final) { // -2 indicates complete
|
||||
if (k === 'intro') {
|
||||
// Manually show bunny scroll reward
|
||||
var rewardData = {
|
||||
reward: [Shared.content.quests.dustbunnies],
|
||||
rewardKey: ['inventory_quest_scroll_dustbunnies'],
|
||||
rewardText: Shared.content.quests.dustbunnies.text(),
|
||||
message: window.env.t('checkinEarned'),
|
||||
nextRewardAt: 1,
|
||||
};
|
||||
Notification.showLoginIncentive(User.user, rewardData, Social.loadWidgets);
|
||||
}
|
||||
//Mark tour complete
|
||||
ups['flags.tour.'+k] = -2;
|
||||
Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'tutorial','eventLabel':k+'-web','eventValue':i+1,'complete':true})
|
||||
}
|
||||
@@ -259,9 +273,11 @@ function($rootScope, User, $timeout, $state, Analytics) {
|
||||
_.times(page, function(p){
|
||||
opts.steps = opts.steps.concat(chapters[chapter][p]);
|
||||
})
|
||||
|
||||
var end = opts.steps.length;
|
||||
opts.steps = opts.steps.concat(chapters[chapter][page]);
|
||||
chap._removeState('end');
|
||||
|
||||
if (chap._inited) {
|
||||
chap.goTo(end);
|
||||
} else {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Set up "+1 Exp", "Level Up", etc notifications
|
||||
*/
|
||||
angular.module("habitrpg").factory("Notification",
|
||||
['$filter', function($filter) {
|
||||
['$filter', 'Shared', '$rootScope', function($filter, Shared, $rootScope) {
|
||||
|
||||
/**
|
||||
Show "+ 5 {gold_coin} 3 {silver_coin}"
|
||||
@@ -139,6 +139,28 @@ angular.module("habitrpg").factory("Notification",
|
||||
});
|
||||
}
|
||||
|
||||
// Login incentive
|
||||
// @TODO: Document reward data param
|
||||
// @TODO: loadWidgets is a circular dependency but we should not inject it this way
|
||||
function showLoginIncentive (user, rewardData, loadWidgets) {
|
||||
var modalScope = $rootScope.$new();
|
||||
modalScope.data = rewardData;
|
||||
var nextRewardKey = Shared.content.loginIncentives[user.loginIncentives].nextRewardAt;
|
||||
modalScope.nextReward = Shared.content.loginIncentives[nextRewardKey];
|
||||
modalScope.user = user;
|
||||
// modalScope.loadWidgets = Social.loadWidgets;
|
||||
modalScope.loadWidgets = loadWidgets;
|
||||
|
||||
var modalKey = 'login-incentives';
|
||||
if (rewardData.rewardKey) {
|
||||
modalKey = 'login-incentives-reward-unlocked';
|
||||
}
|
||||
|
||||
$rootScope.openModal(modalKey, {
|
||||
scope: modalScope
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
coins: coins,
|
||||
crit: crit,
|
||||
@@ -152,6 +174,7 @@ angular.module("habitrpg").factory("Notification",
|
||||
mp: mp,
|
||||
streak: streak,
|
||||
text: text,
|
||||
quest: quest
|
||||
quest: quest,
|
||||
showLoginIncentive: showLoginIncentive,
|
||||
};
|
||||
}]);
|
||||
|
||||
@@ -17,7 +17,8 @@ angular.module('habitrpg')
|
||||
if (quest.lvl && user.stats.lvl < quest.lvl) return true;
|
||||
}
|
||||
if (user.achievements.quests) return (quest.previous && !user.achievements.quests[quest.previous]);
|
||||
return (quest.previous);
|
||||
|
||||
return quest.locked;
|
||||
}
|
||||
|
||||
function _preventQuestModal(quest) {
|
||||
@@ -34,6 +35,10 @@ angular.module('habitrpg')
|
||||
alert(window.env.t('mustLvlQuest', {level: quest.lvl}))
|
||||
return 'mustLvlQuest';
|
||||
}
|
||||
|
||||
if (quest.unlockCondition && quest.unlockCondition.condition === 'create account') {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function buyQuest(quest) {
|
||||
@@ -51,6 +56,20 @@ angular.module('habitrpg')
|
||||
return reject('Invite or start party');
|
||||
}
|
||||
|
||||
if (item.unlockCondition && item.unlockCondition.condition === 'create account') {
|
||||
alert(window.env.t('createAccountQuest'));
|
||||
return reject('Awarded to new accounts');
|
||||
}
|
||||
|
||||
if (item.unlockCondition && item.unlockCondition.condition === 'login incentive') {
|
||||
if (user.loginIncentives > item.unlockCondition.incentiveThreshold) {
|
||||
alert(window.env.t('loginIncentiveQuestObtained', {count: item.unlockCondition.incentiveThreshold}));
|
||||
} else {
|
||||
alert(window.env.t('loginIncentiveQuest', {count: item.unlockCondition.incentiveThreshold}));
|
||||
}
|
||||
return reject('Login incentive item');
|
||||
}
|
||||
|
||||
resolve(item);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,7 +16,18 @@ angular.module('habitrpg')
|
||||
});
|
||||
};
|
||||
|
||||
function readNotifications (notificationIds) {
|
||||
if (!notificationIds || notificationIds.length === 0) return;
|
||||
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: 'api/v3/notifications/read',
|
||||
data: {notificationIds: notificationIds},
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
readNotification: readNotification,
|
||||
readNotifications: readNotifications,
|
||||
};
|
||||
}]);
|
||||
|
||||
@@ -328,6 +328,10 @@ angular.module('habitrpg')
|
||||
UserNotifications.readNotification(notificationId);
|
||||
},
|
||||
|
||||
readNotifications: function (notificationIds) {
|
||||
UserNotifications.readNotifications(notificationIds);
|
||||
},
|
||||
|
||||
addTag: function(data) {
|
||||
user.ops.addTag(data);
|
||||
Tags.createTag(data.body);
|
||||
|
||||
Reference in New Issue
Block a user