2023 November Content Prebuild (#14954)

* feat(content): October sub items

* feat(content): October bgs and armoire

* feat(content): add Dune Buddy achievement

* feat(releases): timed Backgrounds and Armoire

* feat(content): timed release achievement

* feat(content): November subscriber items

* feat(content): November pet quest bundle

* feat(content): November magic hatching potions

* feat(content): November backgrounds and armoire

* feat(content): new achievement code -- needs work

* update package.lock on local/origin repos

* fix(content): added October headgear info and November set name

* fix(typo): spelling is important

* feat(content): added logic to allow for pets & mounts to be in one achievement and fixed issues with image

* fix(armoire): correct month

* fix(armoire): deprecate armoireEmpty flag

---------

Co-authored-by: SabreCat <sabe@habitica.com>
Co-authored-by: Sabe Jones <sabrecat@gmail.com>
This commit is contained in:
Natalie
2023-11-07 09:55:50 -05:00
committed by GitHub
parent 1892d6288a
commit a80ef78be0
24 changed files with 354 additions and 143 deletions
@@ -197,6 +197,7 @@ const animalSetAchievs = {
icon: 'achievement-duneBuddy',
titleKey: 'achievementDuneBuddy',
textKey: 'achievementDuneBuddyText',
release: '2023-10-17T08:00-04:00',
},
plantParent: {
icon: 'achievement-plantParent',
@@ -213,6 +214,12 @@ const animalSetAchievs = {
titleKey: 'achievementReptacularRumble',
textKey: 'achievementReptacularRumbleText',
},
roughRider: {
icon: 'achievement-roughRider',
titleKey: 'achievementRoughRider',
textKey: 'achievementRoughRiderText',
release: '2023-11-14T08:00-05:00',
},
woodlandWizard: {
icon: 'achievement-woodlandWizard',
titleKey: 'achievementWoodlandWizard',
@@ -1,10 +1,12 @@
import forOwn from 'lodash/forOwn';
import camelCase from 'lodash/camelCase';
import forOwn from 'lodash/forOwn';
import moment from 'moment';
import omitBy from 'lodash/omitBy';
import upperFirst from 'lodash/upperFirst';
import t from '../translation';
/* eslint-disable camelcase */
const backgrounds = {
const plannedBackgrounds = {
backgrounds062014: {
beach: { },
fairy_ring: { },
@@ -585,6 +587,11 @@ const backgrounds = {
monstrous_cave: { },
jack_o_lantern_stacks: { },
},
backgrounds112023: {
giant_cat: { },
barrel_cellar: { },
autumn_tree_tunnel: { },
},
eventBackgrounds: {
birthday_bash: {
price: 0,
@@ -627,8 +634,16 @@ const backgrounds = {
};
/* eslint-enable quote-props */
const releaseDates = {
backgrounds102023: '2023-10-10T08:00-04:00',
backgrounds112023: '2023-11-07T08:00-05:00',
};
const flat = {};
const backgrounds = omitBy(plannedBackgrounds, (bgSet, key) => releaseDates[key]
&& moment().isBefore(releaseDates[key]));
forOwn(backgrounds, (backgroundsInSet, set) => {
forOwn(backgroundsInSet, (background, bgKey) => {
background.key = bgKey;
+2 -1
View File
@@ -163,8 +163,9 @@ const bundles = {
'rock',
'yarn',
],
event: EVENTS.bundle202311,
canBuy () {
return moment().isBetween('2021-03-16T08:00-05:00', '2021-03-31T20:00-05:00');
return moment().isBetween(EVENTS.bundle202311.start, EVENTS.bundle202311.end);
},
type: 'quests',
value: 7,
@@ -120,6 +120,16 @@ const ANIMAL_SET_ACHIEVEMENTS = {
achievementKey: 'reptacularRumble',
notificationType: 'ACHIEVEMENT_ANIMAL_SET',
},
roughRider: {
type: 'petMount',
species: [
'Cactus',
'Hedgehog',
'Rock',
],
achievementKey: 'roughRider',
notificationType: 'ACHIEVEMENT_ANIMAL_SET',
},
woodlandWizard: {
type: 'pet',
species: [
@@ -10,11 +10,19 @@ const gemsPromo = {
export const EVENTS = {
noEvent: {
start: '2023-11-01T00:00-05:00',
start: '2023-12-01T00:00-05:00',
end: '2023-12-21T08:00-05:00',
season: 'normal',
npcImageSuffix: '',
},
potions202311: {
start: '2023-11-14T08:00-04:00',
end: '2023-11-30T23:59-04:00',
},
bundle202311: {
start: '2023-11-09T08:00-04:00',
end: '2023-11-30T23:59-04:00',
},
spooky_extra_gems: {
start: '2023-10-24T08:00-04:00',
end: '2023-10-31T23:59-04:00',
@@ -1,6 +1,7 @@
import defaults from 'lodash/defaults';
import find from 'lodash/find';
import forEach from 'lodash/forEach';
import moment from 'moment';
import upperFirst from 'lodash/upperFirst';
import { ownsItem } from '../gear-helper';
import { ATTRIBUTES } from '../../../constants';
@@ -1248,6 +1249,10 @@ const shield = {
int: 4,
set: 'cleaningSuppliesTwo',
},
saucepan: {
per: 10,
set: 'cookingImplementsTwo',
},
};
const headAccessory = {
@@ -1709,6 +1714,15 @@ const weapon = {
int: 3,
set: 'somethingSpooky',
},
rollingPin: {
str: 10,
set: 'cookingImplementsTwo',
},
};
const releaseDates = {
somethingSpooky: '2023-10-10T08:00-04:00',
cookingImplementsTwo: '2023-11-07T08:00-05:00',
};
forEach({
@@ -1744,6 +1758,7 @@ forEach({
notes = t(`${setKey}Armoire${upperFirst(gearKey)}Notes`);
}
defaults(gearItem, {
released: releaseDates[gearItem.set] ? moment().isAfter(releaseDates[gearItem.set]) : true,
canOwn: ownsItem(`${setKey}_armoire_${gearKey}`),
notes,
text: t(`${setKey}Armoire${upperFirst(gearKey)}Text`),
@@ -127,13 +127,13 @@ const premium = {
value: 2,
text: t('hatchingPotionEmber'),
limited: true,
event: EVENTS.bundle202211,
event: EVENTS.potions202311,
_addlNotes: t('eventAvailabilityReturning', {
availableDate: t('dateEndNovember'),
previousDate: t('novemberYYYY', { year: 2021 }),
previousDate: t('januaryYYYY', { year: 2023 }),
}),
canBuy () {
return moment().isBetween(EVENTS.bundle202211.start, EVENTS.bundle202211.end);
return moment().isBetween(EVENTS.potions202311.start, EVENTS.potions202311.end);
},
},
Thunderstorm: {
@@ -257,13 +257,13 @@ const premium = {
value: 2,
text: t('hatchingPotionFrost'),
limited: true,
event: EVENTS.bundle202211,
event: EVENTS.potions202311,
_addlNotes: t('eventAvailabilityReturning', {
availableDate: t('dateEndNovember'),
previousDate: t('novemberYYYY', { year: 2020 }),
previousDate: t('novemberYYYY', { year: 2022 }),
}),
canBuy () {
return moment().isBetween(EVENTS.bundle202211.start, EVENTS.bundle202211.end);
return moment().isBetween(EVENTS.potions202311.start, EVENTS.potions202311.end);
},
},
IcySnow: {
@@ -449,13 +449,13 @@ const premium = {
value: 2,
text: t('hatchingPotionAutumnLeaf'),
limited: true,
event: EVENTS.birthday10,
event: EVENTS.potions202311,
_addlNotes: t('eventAvailabilityReturning', {
availableDate: t('dateStartFebruary'),
previousDate: t('novemberYYYY', { year: 2021 }),
availableDate: t('dateEndNovember'),
previousDate: t('januaryYYYY', { year: 2023 }),
}),
canBuy () {
return moment().isBetween(EVENTS.birthday10.start, EVENTS.birthday10.end);
return moment().isBetween(EVENTS.potions202311.start, EVENTS.potions202311.end);
},
},
BlackPearl: {
+2 -2
View File
@@ -99,8 +99,8 @@ api.potion = {
api.armoire = {
type: 'armoire',
text: t('armoireText'),
notes (user, count) {
if (user.flags.armoireEmpty) {
notes (count) {
if (count === 0) {
return t('armoireNotesEmpty')();
}
return `${t('armoireNotesFull')()} ${count}`;
@@ -45,19 +45,19 @@ const featuredItems = {
];
},
quests () {
if (moment().isBetween(EVENTS.bundle202310.start, EVENTS.bundle202310.end)) {
if (moment().isBetween(EVENTS.bundle202311.start, EVENTS.bundle202311.end)) {
return [
{
type: 'bundles',
path: 'bundles.sandySidekicks',
path: 'bundles.oddballs',
},
{
type: 'quests',
path: 'quests.beetle',
path: 'quests.gryphon',
},
{
type: 'quests',
path: 'quests.frog',
path: 'quests.armadillo',
},
];
}
+4 -1
View File
@@ -1,5 +1,6 @@
import each from 'lodash/each';
import filter from 'lodash/filter';
import has from 'lodash/has';
import keys from 'lodash/keys';
import size from 'lodash/size';
import content from './content/index';
@@ -50,7 +51,9 @@ export function remainingGearInSet (userGear = {}, set) {
const gear = filter(content.gear.flat, item => {
const setMatches = item.klass === set;
const hasItem = userGear[item.key];
if (has(item, 'released')) {
return item.released && setMatches && !hasItem;
}
return setMatches && !hasItem;
});
+14 -9
View File
@@ -1,4 +1,5 @@
import get from 'lodash/get';
import moment from 'moment';
import content from '../content/index';
import i18n from '../i18n';
@@ -35,15 +36,18 @@ function contribText (contrib, backer, language) {
}
function _add (result, data) {
result[data.key] = {
title: data.title,
text: data.text,
icon: data.icon,
earned: data.earned,
value: data.value,
index: index += 1,
optionalCount: data.optionalCount,
};
const thisContent = achievsContent[data.key];
if (!thisContent || !thisContent.release || moment().isAfter(thisContent.release)) {
result[data.key] = {
title: data.title,
text: data.text,
icon: data.icon,
earned: data.earned,
value: data.value,
index: index += 1,
optionalCount: data.optionalCount,
};
}
}
function _addSimpleWithCustomPath (result, user, data) {
@@ -224,6 +228,7 @@ function _getBasicAchievements (user, language) {
_addSimple(result, user, { path: 'dinosaurDynasty', language });
_addSimple(result, user, { path: 'bonelessBoss', language });
_addSimple(result, user, { path: 'duneBuddy', language });
_addSimple(result, user, { path: 'roughRider', language });
_addSimpleWithMasterCount(result, user, { path: 'beastMaster', language });
_addSimpleWithMasterCount(result, user, { path: 'mountMaster', language });
+1 -1
View File
@@ -292,7 +292,7 @@ export default function getItemInfo (user, type, item, officialPinnedItems, lang
itemInfo = {
key: item.key,
text: item.text(language),
notes: item.notes(user, count.remainingGearInSet(user.items.gear.owned, 'armoire')), // TODO count
notes: item.notes(count.remainingGearInSet(user.items.gear.owned, 'armoire')),
value: item.value,
currency: 'gold',
purchaseType: 'armoire',
+3 -6
View File
@@ -2,7 +2,6 @@ import filter from 'lodash/filter';
import isEmpty from 'lodash/isEmpty';
import pick from 'lodash/pick';
import content from '../../content/index';
import * as count from '../../count';
import splitWhitespace from '../../libs/splitWhitespace';
import {
NotAuthorized,
@@ -33,7 +32,7 @@ export class BuyArmoireOperation extends AbstractGoldItemOperation { // eslint-d
let result = {};
const armoireResult = randomValFns.trueRandom();
const eligibleEquipment = filter(content.gear.flat, eligible => eligible.klass === 'armoire' && !user.items.gear.owned[eligible.key]);
const eligibleEquipment = filter(content.gear.flat, eligible => eligible.klass === 'armoire' && eligible.released && !user.items.gear.owned[eligible.key]);
const armoireHasEquipment = !isEmpty(eligibleEquipment);
if (
@@ -83,6 +82,7 @@ export class BuyArmoireOperation extends AbstractGoldItemOperation { // eslint-d
}
_gearResult (user, eligibleEquipment) {
const emptied = eligibleEquipment.length === 1;
eligibleEquipment.sort();
const drop = randomVal(eligibleEquipment);
@@ -102,10 +102,6 @@ export class BuyArmoireOperation extends AbstractGoldItemOperation { // eslint-d
dropText: drop.text(this.req.language),
});
if (count.remainingGearInSet(user.items.gear.owned, 'armoire') === 0) {
user.flags.armoireEmpty = true;
}
removeItemByPath(user, `gear.flat.${drop.key}`);
if (this.analytics) {
@@ -116,6 +112,7 @@ export class BuyArmoireOperation extends AbstractGoldItemOperation { // eslint-d
type: 'gear',
dropKey: drop.key,
dropText: drop.text(this.req.language),
emptied,
};
return {
+12 -2
View File
@@ -86,16 +86,26 @@ export default function hatch (user, req = {}, analytics) {
if (content.dropHatchingPotions[hatchingPotion]) {
forEach(content.animalSetAchievements, achievement => {
if (!user.achievements[achievement.achievementKey]) {
if (achievement.type === 'pet') {
if (achievement.type === 'pet' || achievement.type === 'petMount') {
let achieved = true;
forEach(achievement.species, species => {
if (!achieved) return;
const petIndex = findIndex(
keys(content.dropHatchingPotions),
color => !user.items.pets[`${species}-${color}`],
color => !user.items.pets[`${species}-${color}`] || user.items.pets[`${species}-${color}`] === -1,
);
if (petIndex !== -1) achieved = false;
});
if (achievement.type === 'petMount') {
forEach(achievement.species, species => {
if (!achieved) return;
const mountIndex = findIndex(
keys(content.dropHatchingPotions),
color => !user.items.mounts[`${species}-${color}`],
);
if (mountIndex !== -1) achieved = false;
});
}
if (achieved) {
user.achievements[achievement.achievementKey] = true;
if (user.addNotification) {