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:
Sabe Jones
2016-11-23 19:34:09 -06:00
committed by GitHub
parent dcc06931cc
commit 25b0ff38c4
180 changed files with 34773 additions and 32923 deletions

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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
}

View File

@@ -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;
};
}
]);

View File

@@ -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 {

View File

@@ -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,
};
}]);

View File

@@ -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);
});
}

View File

@@ -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,
};
}]);

View File

@@ -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);