mirror of
https://github.com/sudoxnym/habitica-self-host.git
synced 2026-07-14 02:02:19 +00:00
Merge branch 'release' into develop
This commit is contained in:
commit
57fe1d6b22
20 changed files with 213 additions and 19 deletions
118
migrations/archive/2023/20231017_pet_group_achievements.js
Normal file
118
migrations/archive/2023/20231017_pet_group_achievements.js
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = '20231017_pet_group_achievements';
|
||||
import { model as User } from '../../../website/server/models/user';
|
||||
|
||||
const progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
async function updateUser (user) {
|
||||
count++;
|
||||
|
||||
const set = {
|
||||
migration: MIGRATION_NAME,
|
||||
};
|
||||
|
||||
if (user && user.items && user.items.pets) {
|
||||
const pets = user.items.pets;
|
||||
if (pets['Armadillo-Base']
|
||||
&& pets['Armadillo-CottonCandyBlue']
|
||||
&& pets['Armadillo-CottonCandyPink']
|
||||
&& pets['Armadillo-Desert']
|
||||
&& pets['Armadillo-Golden']
|
||||
&& pets['Armadillo-Red']
|
||||
&& pets['Armadillo-Shade']
|
||||
&& pets['Armadillo-Skeleton']
|
||||
&& pets['Armadillo-White']
|
||||
&& pets['Armadillo-Zombie']
|
||||
&& pets['Cactus-Base']
|
||||
&& pets['Cactus-CottonCandyBlue']
|
||||
&& pets['Cactus-CottonCandyPink']
|
||||
&& pets['Cactus-Desert']
|
||||
&& pets['Cactus-Golden']
|
||||
&& pets['Cactus-Red']
|
||||
&& pets['Cactus-Shade']
|
||||
&& pets['Cactus-Skeleton']
|
||||
&& pets['Cactus-White']
|
||||
&& pets['Cactus-Zombie']
|
||||
&& pets['Fox-Base']
|
||||
&& pets['Fox-CottonCandyBlue']
|
||||
&& pets['Fox-CottonCandyPink']
|
||||
&& pets['Fox-Desert']
|
||||
&& pets['Fox-Golden']
|
||||
&& pets['Fox-Red']
|
||||
&& pets['Fox-Shade']
|
||||
&& pets['Fox-Skeleton']
|
||||
&& pets['Fox-White']
|
||||
&& pets['Fox-Zombie']
|
||||
&& pets['Frog-Base']
|
||||
&& pets['Frog-CottonCandyBlue']
|
||||
&& pets['Frog-CottonCandyPink']
|
||||
&& pets['Frog-Desert']
|
||||
&& pets['Frog-Golden']
|
||||
&& pets['Frog-Red']
|
||||
&& pets['Frog-Shade']
|
||||
&& pets['Frog-Skeleton']
|
||||
&& pets['Frog-White']
|
||||
&& pets['Frog-Zombie']
|
||||
&& pets['Snake-Base']
|
||||
&& pets['Snake-CottonCandyBlue']
|
||||
&& pets['Snake-CottonCandyPink']
|
||||
&& pets['Snake-Desert']
|
||||
&& pets['Snake-Golden']
|
||||
&& pets['Snake-Red']
|
||||
&& pets['Snake-Shade']
|
||||
&& pets['Snake-Skeleton']
|
||||
&& pets['Snake-White']
|
||||
&& pets['Snake-Zombie']
|
||||
&& pets['Spider-Base']
|
||||
&& pets['Spider-CottonCandyBlue']
|
||||
&& pets['Spider-CottonCandyPink']
|
||||
&& pets['Spider-Desert']
|
||||
&& pets['Spider-Golden']
|
||||
&& pets['Spider-Red']
|
||||
&& pets['Spider-Shade']
|
||||
&& pets['Spider-Skeleton']
|
||||
&& pets['Spider-White']
|
||||
&& pets['Spider-Zombie']) {
|
||||
set['achievements.duneBuddy'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||
|
||||
return await User.updateOne({ _id: user._id }, { $set: set }).exec();
|
||||
}
|
||||
|
||||
export default async function processUsers () {
|
||||
let query = {
|
||||
migration: { $ne: MIGRATION_NAME },
|
||||
'auth.timestamps.loggedin': { $gt: new Date('2023-09-16') },
|
||||
};
|
||||
|
||||
const fields = {
|
||||
_id: 1,
|
||||
items: 1,
|
||||
};
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const users = await User // eslint-disable-line no-await-in-loop
|
||||
.find(query)
|
||||
.limit(250)
|
||||
.sort({_id: 1})
|
||||
.select(fields)
|
||||
.lean()
|
||||
.exec();
|
||||
|
||||
if (users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
console.warn(`\n${count} users processed\n`);
|
||||
break;
|
||||
} else {
|
||||
query._id = {
|
||||
$gt: users[users.length - 1]._id,
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
};
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "habitica",
|
||||
"version": "5.7.0",
|
||||
"version": "5.9.1",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "5.7.0",
|
||||
"version": "5.9.1",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.22.10",
|
||||
|
|
|
|||
|
|
@ -153,6 +153,11 @@
|
|||
width: 60px;
|
||||
height: 64px;
|
||||
}
|
||||
.achievement-duneBuddy2x {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/achievement-duneBuddy2x.png');
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
.achievement-dustDevil2x {
|
||||
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/achievement-dustDevil2x.png');
|
||||
width: 48px;
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ export default {
|
|||
this.close();
|
||||
},
|
||||
hide () {
|
||||
this.$store.dispatch('user:statSync');
|
||||
this.$store.dispatch('user:set', { 'party.quest.completed': '' });
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -454,7 +454,7 @@
|
|||
>
|
||||
<div
|
||||
:id="achievKey + '-achievement'"
|
||||
class="box achievement-container"
|
||||
class="box achievement-container d-flex justify-content-center align-items-middle"
|
||||
:class="{'achievement-unearned': !achievement.earned}"
|
||||
>
|
||||
<b-popover
|
||||
|
|
@ -472,7 +472,7 @@
|
|||
</b-popover>
|
||||
<div
|
||||
v-if="achievement.earned"
|
||||
class="achievement"
|
||||
class="achievement m-auto"
|
||||
:class="achievement.icon + '2x'"
|
||||
>
|
||||
<div
|
||||
|
|
@ -484,7 +484,7 @@
|
|||
</div>
|
||||
<div
|
||||
v-if="!achievement.earned"
|
||||
class="achievement achievement-unearned achievement-unearned2x"
|
||||
class="achievement achievement-unearned achievement-unearned2x m-auto"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -922,7 +922,6 @@
|
|||
background: $white;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 16px;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
hr {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
>
|
||||
<div
|
||||
:id="achievKey + '-achievement'"
|
||||
class="box achievement-container"
|
||||
class="box achievement-container d-flex justify-content-center align-items-middle"
|
||||
:class="{'achievement-unearned': !achievement.earned}"
|
||||
>
|
||||
<b-popover
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
</b-popover>
|
||||
<div
|
||||
v-if="achievement.earned"
|
||||
class="achievement"
|
||||
class="achievement m-auto"
|
||||
:class="achievement.icon + '2x'"
|
||||
>
|
||||
<div
|
||||
|
|
@ -142,7 +142,6 @@
|
|||
background: $white;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 16px;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
hr {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ export const NotificationMixins = {
|
|||
this.notify(`${this.sign(val)}${val}`, 'damage');
|
||||
},
|
||||
exp (val) {
|
||||
if (val === 0) return;
|
||||
const message = getXPMessage(val);
|
||||
this.notify(message, 'xp', 'glyphicon glyphicon-star', this.sign(val));
|
||||
},
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ import svgGold from '@/assets/svg/gold.svg';
|
|||
import level from '@/assets/svg/level.svg';
|
||||
import streakIcon from '@/assets/svg/streak.svg';
|
||||
import { mapState } from '@/libs/store';
|
||||
import { MAX_LEVEL_HARD_CAP } from '../../../../../common/script/constants';
|
||||
import { MAX_LEVEL_HARD_CAP, MAX_FIELD_HARD_CAP } from '../../../../../common/script/constants';
|
||||
|
||||
export default {
|
||||
components: { SaveCancelButtons },
|
||||
|
|
@ -231,10 +231,6 @@ export default {
|
|||
return;
|
||||
}
|
||||
|
||||
if (this.restoreValues.lvl > MAX_LEVEL_HARD_CAP) {
|
||||
this.restoreValues.lvl = MAX_LEVEL_HARD_CAP;
|
||||
}
|
||||
|
||||
const userChangedLevel = this.restoreValues.lvl !== this.user.stats.lvl;
|
||||
const userDidNotChangeExp = this.restoreValues.exp === this.user.stats.exp;
|
||||
if (userChangedLevel && userDidNotChangeExp) {
|
||||
|
|
@ -265,6 +261,9 @@ export default {
|
|||
) {
|
||||
this.restoreValues[stat] = this.user.stats[stat];
|
||||
valid = false;
|
||||
} else if (this.restoreValues[stat] > MAX_FIELD_HARD_CAP) {
|
||||
this.restoreValues[stat] = MAX_FIELD_HARD_CAP;
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -274,6 +273,9 @@ export default {
|
|||
|| inputLevel < 1) {
|
||||
this.restoreValues.lvl = this.user.stats.lvl;
|
||||
valid = false;
|
||||
} else if (inputLevel > MAX_LEVEL_HARD_CAP) {
|
||||
this.restoreValues.lvl = MAX_LEVEL_HARD_CAP;
|
||||
valid = false;
|
||||
}
|
||||
|
||||
const inputStreak = Number(this.restoreValues.streak);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import disableClassesOp from '@/../../common/script/ops/disableClasses';
|
|||
import openMysteryItemOp from '@/../../common/script/ops/openMysteryItem';
|
||||
import { unEquipByType } from '../../../../common/script/ops/unequip';
|
||||
import markPMSRead from '../../../../common/script/ops/markPMSRead';
|
||||
import updateStats from '../../../../common/script/fns/updateStats';
|
||||
|
||||
export function fetch (store, options = {}) { // eslint-disable-line no-shadow
|
||||
return loadAsyncResource({
|
||||
|
|
@ -179,6 +180,10 @@ export async function getPurchaseHistory () {
|
|||
return response.data.data;
|
||||
}
|
||||
|
||||
export function statSync (store) {
|
||||
updateStats(store.state.user.data, store.state.user.data.stats);
|
||||
return axios.post('/api/v4/user/stat-sync');
|
||||
}
|
||||
|
||||
export function newPrivateMessageTo (store, params) {
|
||||
const { member } = params;
|
||||
|
|
|
|||
|
|
@ -153,5 +153,8 @@
|
|||
"achievementDinosaurDynastyModalText": "You collected all the bird and dinosaur pets!",
|
||||
"achievementBonelessBoss": "Boneless Boss",
|
||||
"achievementBonelessBossText": "Has hatched all standard colors of invertebrate pets: Beetle, Butterfly, Cuttlefish, Nudibranch, Octopus, Snail, and Spider!",
|
||||
"achievementBonelessBossModalText": "You collected all the invertebrate pets!"
|
||||
"achievementBonelessBossModalText": "You collected all the invertebrate pets!",
|
||||
"achievementDuneBuddy": "Dune Buddy",
|
||||
"achievementDuneBuddyText": "Has hatched all standard colors of desert dwelling pets: Armadillo, Cactus, Fox, Frog, Snake, and Spider!",
|
||||
"achievementDuneBuddyNotes": "You collected all the desert dwelling pets!"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ export const MAX_HEALTH = 50;
|
|||
export const MAX_LEVEL = 100;
|
||||
export const MAX_STAT_POINTS = MAX_LEVEL;
|
||||
export const MAX_LEVEL_HARD_CAP = 9999;
|
||||
export const MAX_FIELD_HARD_CAP = 99999999;
|
||||
export const ATTRIBUTES = ['str', 'int', 'con', 'per'];
|
||||
export const MAX_INCENTIVES = 500;
|
||||
|
||||
|
|
|
|||
|
|
@ -193,6 +193,11 @@ const animalSetAchievs = {
|
|||
titleKey: 'achievementDomesticated',
|
||||
textKey: 'achievementDomesticatedText',
|
||||
},
|
||||
duneBuddy: {
|
||||
icon: 'achievement-duneBuddy',
|
||||
titleKey: 'achievementDuneBuddy',
|
||||
textKey: 'achievementDuneBuddyText',
|
||||
},
|
||||
plantParent: {
|
||||
icon: 'achievement-plantParent',
|
||||
titleKey: 'achievementPlantParent',
|
||||
|
|
|
|||
|
|
@ -72,6 +72,19 @@ const ANIMAL_SET_ACHIEVEMENTS = {
|
|||
achievementKey: 'domesticated',
|
||||
notificationType: 'ACHIEVEMENT_ANIMAL_SET',
|
||||
},
|
||||
duneBuddy: {
|
||||
type: 'pet',
|
||||
species: [
|
||||
'Armadillo',
|
||||
'Cactus',
|
||||
'Fox',
|
||||
'Frog',
|
||||
'Snake',
|
||||
'Spider',
|
||||
],
|
||||
achievementKey: 'duneBuddy',
|
||||
notificationType: 'ACHIEVEMENT_ANIMAL_SET',
|
||||
},
|
||||
plantParent: {
|
||||
type: 'pet',
|
||||
species: [
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@ const gemsPromo = {
|
|||
|
||||
export const EVENTS = {
|
||||
noEvent: {
|
||||
start: '2023-11-01T00:00-04:00',
|
||||
end: '2023-12-21T08:00-04:00',
|
||||
start: '2023-11-01T00:00-05:00',
|
||||
end: '2023-12-21T08:00-05:00',
|
||||
season: 'normal',
|
||||
npcImageSuffix: '',
|
||||
},
|
||||
spooky_extra_gems: {
|
||||
start: '2022-10-24T08:00-04:00',
|
||||
end: '2022-10-31T23:59-04:00',
|
||||
start: '2023-10-24T08:00-04:00',
|
||||
end: '2023-10-31T23:59-04:00',
|
||||
gemsPromo,
|
||||
},
|
||||
bundle202310: {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
MAX_INCENTIVES,
|
||||
MAX_LEVEL,
|
||||
MAX_LEVEL_HARD_CAP,
|
||||
MAX_FIELD_HARD_CAP,
|
||||
MAX_STAT_POINTS,
|
||||
MAX_SUMMARY_SIZE_FOR_CHALLENGES,
|
||||
MAX_SUMMARY_SIZE_FOR_GUILDS,
|
||||
|
|
@ -124,6 +125,7 @@ api.constants = {
|
|||
MAX_MESSAGE_LENGTH,
|
||||
MAX_GIFT_MESSAGE_LENGTH,
|
||||
MAX_LEVEL_HARD_CAP,
|
||||
MAX_FIELD_HARD_CAP,
|
||||
};
|
||||
// TODO Move these under api.constants
|
||||
api.maxLevel = MAX_LEVEL;
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@ function _getBasicAchievements (user, language) {
|
|||
_addSimple(result, user, { path: 'plantParent', language });
|
||||
_addSimple(result, user, { path: 'dinosaurDynasty', language });
|
||||
_addSimple(result, user, { path: 'bonelessBoss', language });
|
||||
_addSimple(result, user, { path: 'duneBuddy', language });
|
||||
|
||||
_addSimpleWithMasterCount(result, user, { path: 'beastMaster', language });
|
||||
_addSimpleWithMasterCount(result, user, { path: 'mountMaster', language });
|
||||
|
|
|
|||
|
|
@ -1775,4 +1775,26 @@ api.movePinnedItem = {
|
|||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @api {post} /api/v3/user/stat-sync
|
||||
* Request a refresh of user stats, including processing of pending level-ups
|
||||
* @apiName StatSync
|
||||
* @apiGroup User
|
||||
*
|
||||
* @apiSuccess {Object} data The user object
|
||||
*/
|
||||
|
||||
api.statSync = {
|
||||
method: 'POST',
|
||||
middlewares: [authWithHeaders()],
|
||||
url: '/user/stat-sync',
|
||||
async handler (req, res) {
|
||||
const { user } = res.locals;
|
||||
common.fns.updateStats(user, user.stats);
|
||||
await user.save();
|
||||
|
||||
res.respond(200, user);
|
||||
},
|
||||
};
|
||||
|
||||
export default api;
|
||||
|
|
|
|||
|
|
@ -362,6 +362,8 @@ schema.pre('save', true, function preSaveUser (next, done) {
|
|||
}
|
||||
}
|
||||
|
||||
// Enforce min/max values without displaying schema errors to end user
|
||||
|
||||
if (this.isDirectSelected('preferences')) {
|
||||
if (
|
||||
_.isNaN(this.preferences.dayStart)
|
||||
|
|
@ -372,6 +374,20 @@ schema.pre('save', true, function preSaveUser (next, done) {
|
|||
}
|
||||
}
|
||||
|
||||
if (this.isSelected('stats')) {
|
||||
const statMaximum = common.constants.MAX_FIELD_HARD_CAP;
|
||||
const levelMaximum = common.constants.MAX_LEVEL_HARD_CAP;
|
||||
|
||||
_.each(['hp', 'mp', 'exp', 'gp'], stat => {
|
||||
if (this.stats[stat] > statMaximum) {
|
||||
this.stats[stat] = statMaximum;
|
||||
}
|
||||
});
|
||||
if (this.stats.lvl > levelMaximum) {
|
||||
this.stats.lvl = levelMaximum;
|
||||
}
|
||||
}
|
||||
|
||||
// our own version incrementer
|
||||
if (this.isDirectSelected('_v')) {
|
||||
if (_.isNaN(this._v) || !_.isNumber(this._v)) this._v = 0;
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@ export const UserSchema = new Schema({
|
|||
plantParent: Boolean,
|
||||
dinosaurDynasty: Boolean,
|
||||
bonelessBoss: Boolean,
|
||||
duneBuddy: Boolean,
|
||||
// Onboarding Guide
|
||||
createdTask: Boolean,
|
||||
completedTask: Boolean,
|
||||
|
|
|
|||
Loading…
Reference in a new issue