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:
@@ -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;
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user