mirror of
https://github.com/sudoxnym/habitica.git
synced 2026-04-14 11:46:23 +00:00
commit dd0a410fa6c3741dc0d6793283cf4df3c37790a5
Author: Kalista Payne <sabrecat@gmail.com>
Date: Mon Nov 4 14:24:30 2024 -0600
fix(subs): center next hourglass message
commit 72d92ffd76bb43fee8ba2bbabd211e595afbd664
Author: Kalista Payne <sabrecat@gmail.com>
Date: Fri Nov 1 14:17:59 2024 -0500
fix(subs): don't hide HG preview entirely
commit ea0ecb0c3d519ed3d5c42266367eaaa7283ac5de
Author: Kalista Payne <sabrecat@gmail.com>
Date: Fri Nov 1 13:01:06 2024 -0500
fix(subs): Google wording and HG escape
commit 2bd2c69e18e37c8c8c7106c62f186c372d25c5d2
Author: Kalista Payne <sabrecat@gmail.com>
Date: Fri Nov 1 09:25:30 2024 -0500
fix(layout): tighten cancellation note
commit eb2fc40d241b18d4ffff04c35e744f05e6e9ff52
Author: Kalista Payne <sabrecat@gmail.com>
Date: Thu Oct 24 15:41:43 2024 -0500
fix(g1g1): don't try to find Gems promo during bogo
commit d3eea86bd773c5236e8a0f619639e49db846c2ba
Author: Kalista Payne <sabrecat@gmail.com>
Date: Thu Oct 24 15:00:09 2024 -0500
fix(subs): fix typeError
commit e3ae9a2d6736f238c6aaaec37a5bf38d64afafe8
Author: Kalista Payne <sabrecat@gmail.com>
Date: Thu Oct 24 13:57:27 2024 -0500
fix(subs): also redirect to subs after gift sub
commit 690163a0dec3a45329062905c90454c7cd7c83fd
Author: Phillip Thelen <phillip@habitica.com>
Date: Wed Oct 23 16:42:38 2024 +0200
fix test
commit 2ad7541fc0de429c152e6824f65d2b11b84a9809
Author: Phillip Thelen <phillip@habitica.com>
Date: Wed Oct 23 16:34:52 2024 +0200
fix test
commit 7e337a9e591f2e8b27684567290a70f1b2d58aa0
Author: Phillip Thelen <phillip@habitica.com>
Date: Wed Oct 23 11:54:15 2024 +0200
remove only
commit 7462b8a57f852ecfc52e74fb50d6cff1751bef74
Author: Phillip Thelen <phillip@habitica.com>
Date: Wed Oct 23 11:51:25 2024 +0200
fix bug with incorrectly giving HG bonus
commit acd6183e95a5783dfa29e6c2b142f965c3c67411
Author: Kalista Payne <sabrecat@gmail.com>
Date: Mon Oct 21 17:22:26 2024 -0500
fix(subs): unhovery and un-12-monthy
commit 935e9fd6ec2688ac7339c56ce0ff03bfdae30c77
Author: Kalista Payne <sabrecat@gmail.com>
Date: Fri Oct 18 14:50:17 2024 -0500
fix(subs): try again on gifts
commit 6e1fb7df38d90e5c3ccebee9bb86dbb8f8a4678f
Author: Kalista Payne <sabrecat@gmail.com>
Date: Thu Oct 17 18:19:20 2024 -0500
fix(lint): do negate object ig
commit 71d434b94ea3b1a2c9381fd70f2e637473e00cac
Author: Kalista Payne <sabrecat@gmail.com>
Date: Thu Oct 17 18:15:11 2024 -0500
fix(lint): unnecessary ternary
commit b90b0bb9c39b931714526a9d20910968b055038d
Author: Kalista Payne <sabrecat@gmail.com>
Date: Thu Oct 17 17:34:24 2024 -0500
fix(subs): gifts DON't renew
commit 19469304c5a5881329ea1682e2070f9666d49ee4
Author: Kalista Payne <sabrecat@gmail.com>
Date: Thu Oct 17 17:13:29 2024 -0500
fix(subs): pass autoRenews through Stripe
commit 6819e7b7e518969c58ebab4400f3147f0ddea1b3
Author: Kalista Payne <sabrecat@gmail.com>
Date: Thu Oct 17 16:03:25 2024 -0500
fix(subscriptions): minor visual updates
commit 74633b5e5ea71d66681ad0e84873f3080ab5d361
Author: Kalista Payne <sabrecat@gmail.com>
Date: Wed Oct 16 17:27:09 2024 -0500
fix(subscriptions): more gift layout revisions
commit a90ccb89de36a85acc214bb0b88479e0b78f1660
Author: Kalista Payne <sabrecat@gmail.com>
Date: Wed Oct 16 15:37:50 2024 -0500
fix(subscription): update layout when gifting
commit c24b2db8dc6642669068f0a79d9b0990d43decb9
Author: Phillip Thelen <phillip@habitica.com>
Date: Mon Oct 14 16:11:46 2024 +0200
fix issue with promo hourglasses
commit 7a61c72b47cd3403fe0f3edf91522277738068cc
Author: Phillip Thelen <phillip@habitica.com>
Date: Mon Oct 14 15:59:40 2024 +0200
don’t give additional HG for new sub if they already got one this month
commit f14cb090265ed830eb76c7f452e806257312370e
Author: Phillip Thelen <phillip@habitica.com>
Date: Mon Oct 14 10:38:01 2024 +0200
Admin panel display fixes
commit f4cff698cfb80f9ad2da7ecb626f84277f97eb7c
Author: Kalista Payne <sabrecat@gmail.com>
Date: Thu Oct 3 17:58:59 2024 -0500
fix(stripe): correct redirect after success
commit c468b58f3f783c58e9b48f9698b45473b526d3d4
Author: Kalista Payne <sabrecat@gmail.com>
Date: Thu Oct 3 17:35:37 2024 -0500
fix(subs): correct border-radius and redirect
commit 78fb9e31d64f25aa091e24f95f25dc6dedc844a6
Author: Kalista Payne <sabrecat@gmail.com>
Date: Wed Oct 2 17:41:49 2024 -0500
fix(css): correct and refactor heights and selection states
commit e2babe8053a778b64d51bd3d18866e69fb326a3c
Author: Kalista Payne <sabrecat@gmail.com>
Date: Mon Sep 30 16:45:29 2024 -0500
feat(subscription): max Gems progress readout
commit 61af8302a349f70d60886492b3d4f05dd5463a51
Author: Phillip Thelen <phillip@habitica.com>
Date: Fri Sep 27 15:11:22 2024 +0200
fix test
commit ef8ff0ea9eebcbd682a34fd7f52722b92fdfae16
Author: Phillip Thelen <phillip@habitica.com>
Date: Fri Sep 27 14:14:44 2024 +0200
show date for hourglass bonus if it was received
commit 4bafafdc8d493aad960dcf0d4957d3dad2d5e8da
Author: Phillip Thelen <phillip@habitica.com>
Date: Fri Sep 27 14:12:52 2024 +0200
add new field for cumulative subscription count
commit 30096247b73bdb76aa5b10dd4c964a78d2511e69
Author: Phillip Thelen <phillip@habitica.com>
Date: Fri Sep 27 13:39:49 2024 +0200
fix missing transaction type
commit 70872651b09613a8fe1a19ee2e19dac398b3134d
Author: Phillip Thelen <phillip@habitica.com>
Date: Fri Sep 27 13:31:40 2024 +0200
fix admin panel strings
commit f3398db65f26db558f38ecce8fe4795ff73650cb
Author: Kalista Payne <sabrecat@gmail.com>
Date: Thu Sep 26 23:11:16 2024 -0500
WIP(subs): extant Stripe state
commit c6b2020109b2cdbc7dd8579c884c65f81e757c25
Author: Phillip Thelen <phillip@habitica.com>
Date: Thu Sep 26 11:41:55 2024 +0200
fix admin panel display
commit d9afc96d2db8021db7e6310a009c15004ccc5c38
Author: Phillip Thelen <phillip@habitica.com>
Date: Thu Sep 26 11:40:16 2024 +0200
Fix hourglass logic for upgrades
commit 6e2c8eeb649481afc349e6eb7741bcc82909c3c4
Author: Phillip Thelen <phillip@habitica.com>
Date: Wed Sep 25 17:48:54 2024 +0200
fix hourglass count
commit cd752fbdce79f24bbdbaf6fd9558f207754c5cc3
Author: Kalista Payne <sabrecat@gmail.com>
Date: Fri Sep 20 12:24:21 2024 -0500
WIP(frontend): draft of main subs page view
commit 0102b29d599e47192d7346180ecd549c79177156
Author: Kalista Payne <sabe@habitica.com>
Date: Wed Sep 18 15:29:08 2024 -0500
fix(admin): correct logic and style for shrimple subs
commit 5469a5c5c3fddcf611018c1de077de3499df787a
Author: Kalista Payne <sabe@habitica.com>
Date: Wed Sep 18 15:07:36 2024 -0500
fix(test): short circuit this.
commit 526193ee6c9d07915d0373d07bb8ee0554fe2614
Author: Phillip Thelen <phillip@habitica.com>
Date: Wed Sep 18 14:42:06 2024 +0200
fix gem limit
commit 19cf1636aa1371147ea92478485a653d612d9755
Author: Phillip Thelen <phillip@habitica.com>
Date: Tue Aug 13 17:00:40 2024 +0200
return nextHourglassDate again
commit eea36e3ed54633c345d628d1d3d08e03a3e416a3
Author: Phillip Thelen <phillip@habitica.com>
Date: Tue Aug 13 13:11:22 2024 +0200
subscription test improvements
commit ca78e7433031e79c61aba67235481e0b1c569a55
Author: Phillip Thelen <phillip@habitica.com>
Date: Mon Aug 12 15:46:15 2024 +0200
add more subscription tests
commit f4c4f93a081a89d4c79aec1e87dac97d90c1d587
Author: Phillip Thelen <phillip@habitica.com>
Date: Fri Aug 9 13:35:22 2024 +0200
finish basic implementation of new logic
commit e036742048b92c2e2f29724fb02462f117d91aea
Author: Phillip Thelen <phillip@habitica.com>
Date: Fri Aug 9 11:37:44 2024 +0200
cleanup
commit 643186568866ddea0a234b68d37ad4ab634bd147
Author: Phillip Thelen <phillip@habitica.com>
Date: Wed Aug 7 05:41:18 2024 -0400
update cron tests
commit 930d875ae9d518b0b504ec97638e94c7296ad388
Author: Phillip Thelen <phillip@habitica.com>
Date: Thu Aug 8 10:36:50 2024 +0200
begin refactoring
commit 96623608d064b94cfa40e5da736f13c696995df9
Author: Phillip Thelen <phillip@habitica.com>
Date: Tue Aug 6 16:28:16 2024 +0200
begin removing obsolete tests
475 lines
16 KiB
JavaScript
475 lines
16 KiB
JavaScript
import moment from 'moment';
|
|
import _ from 'lodash';
|
|
import cloneDeep from 'lodash/cloneDeep';
|
|
import nconf from 'nconf';
|
|
import { model as User } from '../models/user';
|
|
import common from '../../common';
|
|
import { preenUserHistory } from './preening';
|
|
import { sleep } from './sleep';
|
|
import { revealMysteryItems } from './payments/subscriptions';
|
|
|
|
const CRON_SAFE_MODE = nconf.get('CRON_SAFE_MODE') === 'true';
|
|
const CRON_SEMI_SAFE_MODE = nconf.get('CRON_SEMI_SAFE_MODE') === 'true';
|
|
const { MAX_INCENTIVES } = common.constants;
|
|
const {
|
|
shouldDo,
|
|
i18n,
|
|
getPlanContext,
|
|
} = common;
|
|
const { scoreTask } = common.ops;
|
|
const { loginIncentives } = common.content;
|
|
// const maxPMs = 200;
|
|
|
|
function setIsDueNextDue (task, user, now) {
|
|
const optionsForShouldDo = cloneDeep(user.preferences.toObject());
|
|
task.isDue = common.shouldDo(now, task, optionsForShouldDo);
|
|
optionsForShouldDo.nextDue = true;
|
|
const nextDue = common.shouldDo(now, task, optionsForShouldDo);
|
|
if (nextDue && nextDue.length > 0) {
|
|
task.nextDue = nextDue.map(dueDate => dueDate.toISOString());
|
|
}
|
|
}
|
|
|
|
export async function recoverCron (status, locals) {
|
|
const { user } = locals;
|
|
|
|
await sleep(0.3);
|
|
|
|
const reloadedUser = await User.findOne({ _id: user._id }).exec();
|
|
|
|
if (!reloadedUser) {
|
|
throw new Error(`User ${user._id} not found while recovering.`);
|
|
} else if (reloadedUser._cronSignature !== 'NOT_RUNNING') {
|
|
status.times += 1;
|
|
|
|
if (status.times < 5) {
|
|
await recoverCron(status, locals);
|
|
} else {
|
|
throw new Error(`Impossible to recover from cron for user ${user._id}.`);
|
|
}
|
|
} else {
|
|
locals.user = reloadedUser;
|
|
}
|
|
}
|
|
|
|
const CLEAR_BUFFS = {
|
|
str: 0,
|
|
int: 0,
|
|
per: 0,
|
|
con: 0,
|
|
stealth: 0,
|
|
streaks: false,
|
|
};
|
|
|
|
async function grantEndOfTheMonthPerks (user, now) {
|
|
const { plan, elapsedMonths } = getPlanContext(user, now);
|
|
|
|
if (elapsedMonths > 0) {
|
|
plan.dateUpdated = now;
|
|
// Award mystery items
|
|
revealMysteryItems(user, elapsedMonths);
|
|
|
|
plan.consecutive.count += elapsedMonths;
|
|
plan.cumulativeCount += elapsedMonths;
|
|
await plan.rewardPerks(user._id, elapsedMonths);
|
|
}
|
|
}
|
|
|
|
function removeTerminatedSubscription (user) {
|
|
const { plan } = user.purchased;
|
|
|
|
_.merge(plan, {
|
|
planId: null,
|
|
customerId: null,
|
|
subscriptionId: null,
|
|
paymentMethod: null,
|
|
});
|
|
|
|
_.merge(plan.consecutive, {
|
|
count: 0,
|
|
});
|
|
|
|
user.markModified('purchased.plan');
|
|
}
|
|
|
|
function resetHabitCounters (user, tasksByType, now, daysMissed) {
|
|
// check if we've passed a day on which we should reset the habit counters, including today
|
|
let resetWeekly = false;
|
|
let resetMonthly = false;
|
|
for (let i = 0; i < daysMissed; i += 1) {
|
|
if (resetWeekly === true && resetMonthly === true) {
|
|
break;
|
|
}
|
|
const thatDay = moment(now)
|
|
.utcOffset(user.getUtcOffset() - user.preferences.dayStart * 60)
|
|
.subtract({ days: i });
|
|
if (thatDay.day() === 1) {
|
|
resetWeekly = true;
|
|
}
|
|
if (thatDay.date() === 1) {
|
|
resetMonthly = true;
|
|
}
|
|
}
|
|
|
|
tasksByType.habits.forEach(task => {
|
|
// reset counters if appropriate
|
|
|
|
let reset = false;
|
|
if (task.frequency === 'daily') {
|
|
reset = true;
|
|
} else if (task.frequency === 'weekly' && resetWeekly === true) {
|
|
reset = true;
|
|
} else if (task.frequency === 'monthly' && resetMonthly === true) {
|
|
reset = true;
|
|
}
|
|
if (reset === true) {
|
|
task.counterUp = 0;
|
|
task.counterDown = 0;
|
|
}
|
|
});
|
|
}
|
|
|
|
function trackCronAnalytics (analytics, user, _progress, options) {
|
|
analytics.track('Cron', {
|
|
category: 'behavior',
|
|
gaLabel: 'Cron Count',
|
|
gaValue: user.flags.cronCount,
|
|
uuid: user._id,
|
|
user,
|
|
resting: user.preferences.sleep,
|
|
cronCount: user.flags.cronCount,
|
|
progressUp: _.min([_progress.up, 900]),
|
|
progressDown: _progress.down,
|
|
headers: options.headers,
|
|
loginIncentives: user.loginIncentives,
|
|
});
|
|
|
|
if (
|
|
user.party && user.party.quest && !user.party.quest.RSVPNeeded
|
|
&& !user.party.quest.completed && user.party.quest.key && !user.preferences.sleep
|
|
) {
|
|
analytics.track('quest participation', {
|
|
category: 'behavior',
|
|
uuid: user._id,
|
|
user,
|
|
questName: user.party.quest.key,
|
|
headers: options.headers,
|
|
}, true);
|
|
}
|
|
}
|
|
|
|
function awardLoginIncentives (user) {
|
|
if (user.loginIncentives > MAX_INCENTIVES) return;
|
|
|
|
// Remove old notifications if they exists
|
|
user.notifications.forEach((notif, index) => {
|
|
if (notif && notif.type === 'LOGIN_INCENTIVE') user.notifications.splice(index, 1);
|
|
});
|
|
|
|
const notificationData = {};
|
|
notificationData.message = i18n.t('checkinEarned', user.preferences.language);
|
|
|
|
const loginIncentive = loginIncentives[user.loginIncentives];
|
|
|
|
if (loginIncentive.rewardKey) {
|
|
loginIncentive.assignReward(user);
|
|
notificationData.reward = loginIncentive.reward;
|
|
notificationData.rewardText = '';
|
|
|
|
// @TODO: Abstract this logic and share it across the server and client
|
|
let count = 0;
|
|
for (const reward of loginIncentive.reward) {
|
|
if (reward.text) {
|
|
notificationData.rewardText += reward.text(user.preferences.language);
|
|
if (reward.key === 'RoyalPurple') {
|
|
notificationData.rewardText = i18n.t('potion', { potionType: notificationData.rewardText }, user.preferences.language);
|
|
}
|
|
} else if (loginIncentive.rewardKey[0] === 'background_blue') {
|
|
notificationData.rewardText = i18n.t('incentiveBackgrounds', user.preferences.language);
|
|
}
|
|
|
|
if (loginIncentive.reward.length > 0 && count < loginIncentive.reward.length - 1) notificationData.rewardText += ', ';
|
|
|
|
count += 1;
|
|
}
|
|
|
|
// Overwrite notificationData.rewardText if rewardName was explicitly declared
|
|
if (loginIncentive.rewardName) {
|
|
notificationData.rewardText = i18n.t(loginIncentive.rewardName, user.preferences.language);
|
|
}
|
|
|
|
notificationData.rewardKey = loginIncentive.rewardKey;
|
|
notificationData.message = i18n.t('unlockedCheckInReward', user.preferences.language);
|
|
}
|
|
|
|
notificationData.nextRewardAt = loginIncentives[user.loginIncentives].nextRewardAt || 0;
|
|
user.addNotification('LOGIN_INCENTIVE', notificationData);
|
|
}
|
|
|
|
// Perform various beginning-of-day reset actions.
|
|
export async function cron (options = {}) {
|
|
const {
|
|
user, tasksByType, analytics, now = new Date(), daysMissed, timezoneUtcOffsetFromUserPrefs,
|
|
} = options;
|
|
let _progress = { down: 0, up: 0, collectedItems: 0 };
|
|
|
|
// Record pre-cron values of HP and MP to show notifications later
|
|
const beforeCronStats = _.pick(user.stats, ['hp', 'mp']);
|
|
|
|
user.preferences.timezoneOffsetAtLastCron = -timezoneUtcOffsetFromUserPrefs;
|
|
// User is only allowed a certain number of drops a day. This resets the count.
|
|
if (user.items.lastDrop.count > 0) user.items.lastDrop.count = 0;
|
|
|
|
// "Perfect Day" achievement for perfect days
|
|
let perfect = true;
|
|
|
|
// Reset Gold-to-Gems cap if it's the start of the month
|
|
const dateUpdatedFalse = !moment(user.purchased.plan.dateUpdated).startOf('month').isSame(moment().startOf('month')) || !user.purchased.plan.dateUpdated;
|
|
|
|
if (user.purchased && user.purchased.plan && dateUpdatedFalse) {
|
|
user.purchased.plan.gemsBought = 0;
|
|
if (!user.purchased.plan.dateUpdated) user.purchased.plan.dateUpdated = moment();
|
|
}
|
|
|
|
if (user.isSubscribed()) {
|
|
await grantEndOfTheMonthPerks(user, now);
|
|
}
|
|
|
|
const { plan } = user.purchased;
|
|
const userHasTerminatedSubscription = plan.dateTerminated
|
|
&& moment(plan.dateTerminated).isBefore(new Date());
|
|
if (!CRON_SAFE_MODE && userHasTerminatedSubscription) removeTerminatedSubscription(user);
|
|
|
|
// Login Incentives
|
|
user.loginIncentives += 1;
|
|
awardLoginIncentives(user);
|
|
|
|
const multiDaysCountAsOneDay = true;
|
|
// If the user does not log in for two or more days,
|
|
// cron (mostly) acts as if it were only one day.
|
|
// When site-wide difficulty settings are introduced, this can be a user preference option.
|
|
|
|
// Tally each task
|
|
let todoTally = 0;
|
|
|
|
// make uncompleted To Do's redder (further incentive to complete them)
|
|
tasksByType.todos.forEach(task => {
|
|
if (
|
|
task.completed
|
|
|| (task.group.assignedDate
|
|
&& moment(task.group.assignedDate).isAfter(user.auth.timestamps.updated))
|
|
) return;
|
|
scoreTask({
|
|
task,
|
|
user,
|
|
direction: 'down',
|
|
cron: true,
|
|
times: multiDaysCountAsOneDay ? 1 : daysMissed,
|
|
});
|
|
|
|
todoTally += task.value;
|
|
});
|
|
|
|
// For incomplete Dailys, add value (further incentive),
|
|
// deduct health, keep records for later decreasing the nightly mana gain.
|
|
// The negative effects are not done when resting in the inn.
|
|
let dailyChecked = 0; // how many dailies were checked?
|
|
let dailyDueUnchecked = 0; // how many dailies were un-checked?
|
|
let atLeastOneDailyDue = false; // were any dailies due?
|
|
if (!user.party.quest.progress.down) user.party.quest.progress.down = 0;
|
|
|
|
tasksByType.dailys.forEach(task => {
|
|
const isTeamBoardTask = task.group.id && !task.userId;
|
|
if (
|
|
!isTeamBoardTask && task.group.assignedDate
|
|
&& moment(task.group.assignedDate).isAfter(user.auth.timestamps.updated)
|
|
) return;
|
|
const { completed } = task;
|
|
// Deduct points for missed Daily tasks
|
|
let evadeTask = 0;
|
|
let scheduleMisses = daysMissed;
|
|
|
|
if (completed) {
|
|
if (!isTeamBoardTask) dailyChecked += 1;
|
|
if (!atLeastOneDailyDue) { // only bother checking until the first thing is found
|
|
const thatDay = moment(now).subtract({ days: daysMissed });
|
|
atLeastOneDailyDue = shouldDo(thatDay.toDate(), task, user.preferences);
|
|
}
|
|
} else {
|
|
// dailys repeat, so need to calculate how many they've missed according to their own schedule
|
|
scheduleMisses = 0;
|
|
|
|
for (let i = 0; i < daysMissed; i += 1) {
|
|
const thatDay = moment(now).subtract({ days: i + 1 });
|
|
|
|
if (shouldDo(thatDay.toDate(), task, user.preferences)) {
|
|
atLeastOneDailyDue = true;
|
|
scheduleMisses += 1;
|
|
if (user.stats.buffs.stealth && !isTeamBoardTask) {
|
|
user.stats.buffs.stealth -= 1;
|
|
evadeTask += 1;
|
|
}
|
|
}
|
|
if (multiDaysCountAsOneDay) break;
|
|
}
|
|
|
|
if (scheduleMisses > evadeTask) {
|
|
// The user did not complete this due Daily
|
|
// (but no penalty if cron is running in safe mode).
|
|
if (CRON_SAFE_MODE) {
|
|
dailyChecked += 1; // allows full allotment of mp to be gained
|
|
} else {
|
|
perfect = false;
|
|
|
|
// Partially completed checklists dock fewer mana points
|
|
if (task.checklist && task.checklist.length > 0) {
|
|
const fractionChecked = _.reduce(
|
|
task.checklist,
|
|
(m, i) => m + (i.completed ? 1 : 0),
|
|
0,
|
|
) / task.checklist.length;
|
|
dailyDueUnchecked += 1 - fractionChecked;
|
|
dailyChecked += fractionChecked;
|
|
} else {
|
|
dailyDueUnchecked += 1;
|
|
}
|
|
|
|
if (!user.preferences.sleep) {
|
|
const delta = scoreTask({
|
|
user,
|
|
task,
|
|
direction: 'down',
|
|
times: multiDaysCountAsOneDay ? 1 : scheduleMisses - evadeTask,
|
|
cron: true,
|
|
});
|
|
|
|
if (!CRON_SEMI_SAFE_MODE) {
|
|
// Apply damage from a boss, less damage for Trivial priority (difficulty)
|
|
user.party.quest.progress.down += delta * (task.priority < 1 ? task.priority : 1);
|
|
// NB: Medium and Hard priorities do not increase damage from boss.
|
|
// This was by accident
|
|
// initially, and when we realised, we could not fix it because users are used to
|
|
// their Medium and Hard Dailies doing an Easy amount of damage from boss.
|
|
// Easy is task.priority = 1. Anything < 1 will be Trivial (0.1) or any future
|
|
// setting between Trivial and Easy.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// add history entry when task was not completed
|
|
task.history.push({
|
|
date: Number(new Date()),
|
|
value: task.value,
|
|
isDue: task.isDue,
|
|
completed: false,
|
|
});
|
|
}
|
|
|
|
task.completed = false;
|
|
setIsDueNextDue(task, user, now);
|
|
|
|
if (completed || scheduleMisses > 0) {
|
|
if (task.checklist) {
|
|
task.checklist.forEach(i => { i.completed = false; });
|
|
}
|
|
}
|
|
});
|
|
|
|
resetHabitCounters(user, tasksByType, now, daysMissed);
|
|
|
|
tasksByType.habits.forEach(task => {
|
|
// slowly reset value to 0 for "onlies" (Habits with + or - but not both)
|
|
// move singleton Habits towards yellow.
|
|
if (task.up === false || task.down === false) {
|
|
task.value = Math.abs(task.value) < 0.1 ? 0 : task.value /= 2;
|
|
}
|
|
});
|
|
|
|
// Finished tallying
|
|
user.history.todos.push({ date: now.toISOString(), value: todoTally });
|
|
|
|
// tally experience
|
|
let expTally = user.stats.exp;
|
|
let lvl = 0; // iterator
|
|
while (lvl < user.stats.lvl - 1) {
|
|
lvl += 1;
|
|
expTally += common.tnl(lvl);
|
|
}
|
|
|
|
user.history.exp.push({ date: now.toISOString(), value: expTally });
|
|
|
|
// Remove any remaining completed todos from the list of active todos
|
|
user.tasksOrder.todos = user.tasksOrder.todos
|
|
.filter(taskOrderId => _.some(
|
|
tasksByType.todos,
|
|
taskType => taskType._id === taskOrderId && taskType.completed === false,
|
|
));
|
|
// TODO also adjust tasksOrder arrays to remove deleted tasks of any kind (including rewards), ensure that all existing tasks are in the arrays, no tasks IDs are duplicated -- https://github.com/HabitRPG/habitica/issues/7645
|
|
|
|
// preen user history so that it doesn't become a performance problem
|
|
// also for subscribed users but differently
|
|
preenUserHistory(user, tasksByType);
|
|
|
|
if (perfect && atLeastOneDailyDue) {
|
|
user.achievements.perfect += 1;
|
|
const lvlDiv2 = Math.ceil(common.capByLevel(user.stats.lvl) / 2);
|
|
user.stats.buffs = {
|
|
str: lvlDiv2,
|
|
int: lvlDiv2,
|
|
per: lvlDiv2,
|
|
con: lvlDiv2,
|
|
stealth: 0,
|
|
streaks: false,
|
|
};
|
|
} else {
|
|
user.stats.buffs = _.cloneDeep(CLEAR_BUFFS);
|
|
}
|
|
|
|
common.setDebuffPotionItems(user);
|
|
|
|
// Add 10 MP, or 10% of max MP if that'd be more.
|
|
// Perform this after Perfect Day for maximum benefit
|
|
// Adjust for fraction of dailies completed
|
|
if (!user.preferences.sleep) {
|
|
if (dailyDueUnchecked === 0 && dailyChecked === 0) dailyChecked = 1;
|
|
user.stats.mp += (_.max([10, 0.1 * common.statsComputed(user).maxMP]) * dailyChecked) / (dailyDueUnchecked + dailyChecked); // eslint-disable-line max-len
|
|
if (user.stats.mp > common.statsComputed(user).maxMP) {
|
|
user.stats.mp = common.statsComputed(user).maxMP;
|
|
}
|
|
}
|
|
|
|
// After all is said and done,
|
|
// progress up user's effect on quest, return those values & reset the user's
|
|
if (!user.preferences.sleep) {
|
|
const { progress } = user.party.quest;
|
|
_progress = progress.toObject(); // clone the old progress object
|
|
_.merge(progress, { down: 0, up: 0, collectedItems: 0 });
|
|
}
|
|
|
|
if (user.pinnedItems && user.pinnedItems.length > 0) {
|
|
user.pinnedItems = common.cleanupPinnedItems(user);
|
|
}
|
|
|
|
// Send notification for changes in HP and MP.
|
|
// First remove a possible previous cron notification because
|
|
// we don't want to flood the users with many cron notifications at once.
|
|
const oldCronNotif = user.notifications.find((notif, index) => {
|
|
if (notif && notif.type === 'CRON') {
|
|
user.notifications.splice(index, 1);
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
|
|
user.addNotification('CRON', {
|
|
hp: user.stats.hp - beforeCronStats.hp - (oldCronNotif ? oldCronNotif.data.hp : 0),
|
|
mp: user.stats.mp - beforeCronStats.mp - (oldCronNotif ? oldCronNotif.data.mp : 0),
|
|
});
|
|
|
|
// Analytics
|
|
user.flags.cronCount += 1;
|
|
trackCronAnalytics(analytics, user, _progress, options);
|
|
|
|
return _progress;
|
|
}
|