mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 22:57:21 +01:00
allow eggs to have a release date
This commit is contained in:
@@ -64,6 +64,6 @@ describe('armoire', () => {
|
|||||||
delete require.cache[require.resolve('../../website/common/script/content/gear/sets/armoire')];
|
delete require.cache[require.resolve('../../website/common/script/content/gear/sets/armoire')];
|
||||||
clock = sinon.useFakeTimers(new Date('2024-02-07T09:00:00.000Z'));
|
clock = sinon.useFakeTimers(new Date('2024-02-07T09:00:00.000Z'));
|
||||||
const febuaryItems = makeArmoireIitemList();
|
const febuaryItems = makeArmoireIitemList();
|
||||||
expect(febuaryItems.length).to.equal(381);
|
expect(febuaryItems.length).to.equal(384);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,29 +5,51 @@ import {
|
|||||||
expectValidTranslationString,
|
expectValidTranslationString,
|
||||||
} from '../helpers/content.helper';
|
} from '../helpers/content.helper';
|
||||||
|
|
||||||
import * as eggs from '../../website/common/script/content/eggs';
|
import eggs from '../../website/common/script/content/eggs';
|
||||||
|
|
||||||
describe('eggs', () => {
|
describe('eggs', () => {
|
||||||
describe('all', () => {
|
let clock;
|
||||||
it('is a combination of drop and quest eggs', () => {
|
|
||||||
const dropNumber = Object.keys(eggs.drops).length;
|
|
||||||
const questNumber = Object.keys(eggs.quests).length;
|
|
||||||
const allNumber = Object.keys(eggs.all).length;
|
|
||||||
|
|
||||||
expect(allNumber).to.be.greaterThan(0);
|
afterEach(() => {
|
||||||
expect(allNumber).to.equal(dropNumber + questNumber);
|
if (clock) {
|
||||||
});
|
clock.restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('contains basic information about each egg', () => {
|
const eggTypes = [
|
||||||
each(eggs.all, (egg, key) => {
|
'drops',
|
||||||
expectValidTranslationString(egg.text);
|
'quests',
|
||||||
expectValidTranslationString(egg.adjective);
|
];
|
||||||
expectValidTranslationString(egg.mountText);
|
|
||||||
expectValidTranslationString(egg.notes);
|
eggTypes.forEach(eggType => {
|
||||||
expect(egg.canBuy).to.be.a('function');
|
describe(eggType, () => {
|
||||||
expect(egg.value).to.be.a('number');
|
it('contains basic information about each egg', () => {
|
||||||
expect(egg.key).to.equal(key);
|
each(eggs[eggType], (egg, key) => {
|
||||||
|
expectValidTranslationString(egg.text);
|
||||||
|
expectValidTranslationString(egg.adjective);
|
||||||
|
expectValidTranslationString(egg.mountText);
|
||||||
|
expectValidTranslationString(egg.notes);
|
||||||
|
expect(egg.canBuy).to.be.a('function');
|
||||||
|
expect(egg.value).to.be.a('number');
|
||||||
|
expect(egg.key).to.equal(key);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not contain unreleased eggs', () => {
|
||||||
|
clock = sinon.useFakeTimers(new Date('2024-05-20'));
|
||||||
|
const questEggs = eggs.quests;
|
||||||
|
expect(questEggs.Giraffe).to.not.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Releases eggs when appropriate without needing restarting', () => {
|
||||||
|
clock = sinon.useFakeTimers(new Date('2024-05-20'));
|
||||||
|
const mayEggs = eggs.quests;
|
||||||
|
clock.restore();
|
||||||
|
clock = sinon.useFakeTimers(new Date('2024-06-20'));
|
||||||
|
const juneEggs = eggs.quests;
|
||||||
|
expect(juneEggs.Giraffe).to.exist;
|
||||||
|
expect(Object.keys(mayEggs).length).to.equal(Object.keys(juneEggs).length - 1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,11 +7,20 @@ import {
|
|||||||
import t from '../../website/common/script/content/translation';
|
import t from '../../website/common/script/content/translation';
|
||||||
|
|
||||||
import * as stable from '../../website/common/script/content/stable';
|
import * as stable from '../../website/common/script/content/stable';
|
||||||
import * as eggs from '../../website/common/script/content/eggs';
|
import eggs from '../../website/common/script/content/eggs';
|
||||||
import * as potions from '../../website/common/script/content/hatching-potions';
|
import * as potions from '../../website/common/script/content/hatching-potions';
|
||||||
|
|
||||||
describe('stable', () => {
|
describe('stable', () => {
|
||||||
describe('dropPets', () => {
|
describe('dropPets', () => {
|
||||||
|
let clock;
|
||||||
|
beforeEach(() => {
|
||||||
|
clock = sinon.useFakeTimers(new Date('2020-05-20'));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
clock.restore();
|
||||||
|
});
|
||||||
|
|
||||||
it('contains a pet for each drop potion * each drop egg', () => {
|
it('contains a pet for each drop potion * each drop egg', () => {
|
||||||
const numberOfDropPotions = Object.keys(potions.drops).length;
|
const numberOfDropPotions = Object.keys(potions.drops).length;
|
||||||
const numberOfDropEggs = Object.keys(eggs.drops).length;
|
const numberOfDropEggs = Object.keys(eggs.drops).length;
|
||||||
|
|||||||
15
website/common/script/content/constants/release_dates.js
Normal file
15
website/common/script/content/constants/release_dates.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
export const ARMOIRE_RELEASE_DATES = {
|
||||||
|
somethingSpooky: { year: 2023, month: 10 },
|
||||||
|
cookingImplementsTwo: { year: 2023, month: 11 },
|
||||||
|
greenTrapper: { year: 2023, month: 12 },
|
||||||
|
schoolUniform: { year: 2024, month: 1 },
|
||||||
|
whiteLoungeWear: { year: 2024, month: 2 },
|
||||||
|
hatterSet: { year: 2024, month: 3 },
|
||||||
|
optimistSet: { year: 2024, month: 4 },
|
||||||
|
pottersSet: { year: 2024, month: 5 },
|
||||||
|
beachsideSet: { year: 2024, month: 6 },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EGGS_RELEASE_DATES = {
|
||||||
|
Giraffe: { year: 2024, month: 6, day: 1 },
|
||||||
|
};
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import assign from 'lodash/assign';
|
|
||||||
import defaults from 'lodash/defaults';
|
import defaults from 'lodash/defaults';
|
||||||
import each from 'lodash/each';
|
import each from 'lodash/each';
|
||||||
import t from './translation';
|
import t from './translation';
|
||||||
|
import { filterReleased } from './is_released';
|
||||||
|
import { EGGS_RELEASE_DATES } from './constants/release_dates';
|
||||||
|
import datedMemoize from '../fns/datedMemoize';
|
||||||
|
|
||||||
function applyEggDefaults (set, config) {
|
function applyEggDefaults (set, config) {
|
||||||
each(set, (egg, key) => {
|
each(set, (egg, key) => {
|
||||||
@@ -410,10 +412,17 @@ applyEggDefaults(quests, {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const all = assign({}, drops, quests);
|
function filterEggs (eggs) {
|
||||||
|
return filterReleased(eggs, 'key', EGGS_RELEASE_DATES);
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
const memoizedFilter = datedMemoize(filterEggs);
|
||||||
drops,
|
|
||||||
quests,
|
export default {
|
||||||
all,
|
get drops () {
|
||||||
|
return memoizedFilter({ memoizeConfig: true, identifier: 'drops' }, drops);
|
||||||
|
},
|
||||||
|
get quests () {
|
||||||
|
return memoizedFilter({ memoizeConfig: true, identifier: 'quests' }, quests);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ import defaults from 'lodash/defaults';
|
|||||||
import find from 'lodash/find';
|
import find from 'lodash/find';
|
||||||
import forEach from 'lodash/forEach';
|
import forEach from 'lodash/forEach';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import nconf from 'nconf';
|
|
||||||
import upperFirst from 'lodash/upperFirst';
|
import upperFirst from 'lodash/upperFirst';
|
||||||
import { ownsItem } from '../gear-helper';
|
import { ownsItem } from '../gear-helper';
|
||||||
import { ATTRIBUTES } from '../../../constants';
|
import { ATTRIBUTES } from '../../../constants';
|
||||||
import t from '../../translation';
|
import t from '../../translation';
|
||||||
import memoize from '../../../fns/datedMemoize';
|
import memoize from '../../../fns/datedMemoize';
|
||||||
|
import { ARMOIRE_RELEASE_DATES as releaseDates } from '../../constants/release_dates';
|
||||||
|
import { buildReleaseDate } from '../../is_released';
|
||||||
|
|
||||||
const armor = {
|
const armor = {
|
||||||
lunarArmor: {
|
lunarArmor: {
|
||||||
@@ -1833,19 +1834,7 @@ const weapon = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const SWITCHOVER_TIME = nconf.get('CONTENT_SWITCHOVER_TIME_OFFSET') || 0;
|
|
||||||
const releaseDay = 7;
|
const releaseDay = 7;
|
||||||
const releaseDates = {
|
|
||||||
somethingSpooky: { year: 2023, month: 10 },
|
|
||||||
cookingImplementsTwo: { year: 2023, month: 11 },
|
|
||||||
greenTrapper: { year: 2023, month: 12 },
|
|
||||||
schoolUniform: { year: 2024, month: 1 },
|
|
||||||
whiteLoungeWear: { year: 2024, month: 2 },
|
|
||||||
hatterSet: { year: 2024, month: 3 },
|
|
||||||
optimistSet: { year: 2024, month: 4 },
|
|
||||||
pottersSet: { year: 2024, month: 5 },
|
|
||||||
beachsideSet: { year: 2024, month: 6 },
|
|
||||||
};
|
|
||||||
|
|
||||||
forEach({
|
forEach({
|
||||||
armor,
|
armor,
|
||||||
@@ -1890,12 +1879,12 @@ forEach({
|
|||||||
|
|
||||||
function updateReleased (type) {
|
function updateReleased (type) {
|
||||||
const today = moment();
|
const today = moment();
|
||||||
const releaseDateEndPart = `${String(releaseDay).padStart(2, '0')}T${String(SWITCHOVER_TIME).padStart(2, '0')}:00-0500`;
|
|
||||||
const returnType = {};
|
const returnType = {};
|
||||||
forEach(type, (gearItem, gearKey) => {
|
forEach(type, (gearItem, gearKey) => {
|
||||||
let released;
|
let released;
|
||||||
if (releaseDates[gearItem.set]) {
|
if (releaseDates[gearItem.set]) {
|
||||||
const releaseDateString = `${releaseDates[gearItem.set].year}-${String(releaseDates[gearItem.set].month).padStart(2, '0')}-${releaseDateEndPart}`;
|
const components = releaseDates[gearItem.set];
|
||||||
|
const releaseDateString = buildReleaseDate(components.year, components.month, releaseDay);
|
||||||
released = today.isAfter(releaseDateString);
|
released = today.isAfter(releaseDateString);
|
||||||
} else {
|
} else {
|
||||||
released = true;
|
released = true;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import defaults from 'lodash/defaults';
|
import defaults from 'lodash/defaults';
|
||||||
import each from 'lodash/each';
|
import each from 'lodash/each';
|
||||||
|
import assign from 'lodash/assign';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import t from './translation';
|
import t from './translation';
|
||||||
import { tasksByCategory } from './tasks';
|
import { tasksByCategory } from './tasks';
|
||||||
@@ -18,7 +19,7 @@ import {
|
|||||||
|
|
||||||
import achievements from './achievements';
|
import achievements from './achievements';
|
||||||
|
|
||||||
import * as eggs from './eggs';
|
import eggs from './eggs';
|
||||||
import * as hatchingPotions from './hatching-potions';
|
import * as hatchingPotions from './hatching-potions';
|
||||||
import * as stable from './stable';
|
import * as stable from './stable';
|
||||||
import gear from './gear';
|
import gear from './gear';
|
||||||
@@ -167,7 +168,7 @@ api.special = api.spells.special;
|
|||||||
|
|
||||||
api.dropEggs = eggs.drops;
|
api.dropEggs = eggs.drops;
|
||||||
api.questEggs = eggs.quests;
|
api.questEggs = eggs.quests;
|
||||||
api.eggs = eggs.all;
|
api.eggs = assign({}, eggs.drops, eggs.quests);
|
||||||
|
|
||||||
api.timeTravelStable = {
|
api.timeTravelStable = {
|
||||||
pets: {
|
pets: {
|
||||||
|
|||||||
30
website/common/script/content/is_released.js
Normal file
30
website/common/script/content/is_released.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import moment from 'moment';
|
||||||
|
import filter from 'lodash/filter';
|
||||||
|
import { pickBy } from 'lodash';
|
||||||
|
import nconf from 'nconf';
|
||||||
|
|
||||||
|
const SWITCHOVER_TIME = nconf.get('CONTENT_SWITCHOVER_TIME_OFFSET') || 0;
|
||||||
|
|
||||||
|
const releaseDateEndPart = `T${String(SWITCHOVER_TIME).padStart(2, '0')}:00-0000`;
|
||||||
|
|
||||||
|
export function buildReleaseDate (year, month, day = 1) {
|
||||||
|
return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}${releaseDateEndPart}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isReleased (item, fieldName, releaseDateMap, releaseByDefault) {
|
||||||
|
if (releaseDateMap[item[fieldName]]) {
|
||||||
|
const release = releaseDateMap[item[fieldName]];
|
||||||
|
if (release.day) {
|
||||||
|
return moment().isAfter(moment(buildReleaseDate(release.year, release.month, release.day)));
|
||||||
|
}
|
||||||
|
return moment().isAfter(releaseDateMap[item[fieldName]]);
|
||||||
|
}
|
||||||
|
return releaseByDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function filterReleased (items, fieldName, releaseDateMap, releaseByDefault = true) {
|
||||||
|
if (typeof items === 'object') {
|
||||||
|
return pickBy(items, item => isReleased(item, fieldName, releaseDateMap, releaseByDefault));
|
||||||
|
}
|
||||||
|
return filter(items, item => isReleased(item, fieldName, releaseDateMap, releaseByDefault));
|
||||||
|
}
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
import each from 'lodash/each';
|
import each from 'lodash/each';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { EVENTS } from './constants/events';
|
import { EVENTS } from './constants/events';
|
||||||
import {
|
import allEggs from './eggs';
|
||||||
drops as dropEggs,
|
|
||||||
quests as questEggs,
|
|
||||||
} from './eggs';
|
|
||||||
import {
|
import {
|
||||||
drops as dropPotions,
|
drops as dropPotions,
|
||||||
premium as premiumPotions,
|
premium as premiumPotions,
|
||||||
@@ -12,10 +9,14 @@ import {
|
|||||||
} from './hatching-potions';
|
} from './hatching-potions';
|
||||||
import t from './translation';
|
import t from './translation';
|
||||||
|
|
||||||
|
const STABLE_RELEASE_DATES = {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
const petInfo = {};
|
const petInfo = {};
|
||||||
const mountInfo = {};
|
const mountInfo = {};
|
||||||
|
|
||||||
function constructSet (type, eggs, potions) {
|
function constructSet (type, eggs, potions, hasMounts = true) {
|
||||||
const pets = {};
|
const pets = {};
|
||||||
const mounts = {};
|
const mounts = {};
|
||||||
|
|
||||||
@@ -37,52 +38,24 @@ function constructSet (type, eggs, potions) {
|
|||||||
potion: potion.text,
|
potion: potion.text,
|
||||||
egg: egg.text,
|
egg: egg.text,
|
||||||
}));
|
}));
|
||||||
mountInfo[key] = getAnimalData(t('mountName', {
|
|
||||||
potion: potion.text,
|
|
||||||
mount: egg.mountText,
|
|
||||||
}));
|
|
||||||
|
|
||||||
pets[key] = true;
|
pets[key] = true;
|
||||||
mounts[key] = true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return [pets, mounts];
|
if (hasMounts) {
|
||||||
}
|
mountInfo[key] = getAnimalData(t('mountName', {
|
||||||
|
potion: potion.text,
|
||||||
function constructPetOnlySet (type, eggs, potions) {
|
mount: egg.mountText,
|
||||||
const pets = {};
|
}));
|
||||||
|
mounts[key] = true;
|
||||||
each(eggs, egg => {
|
|
||||||
each(potions, potion => {
|
|
||||||
const key = `${egg.key}-${potion.key}`;
|
|
||||||
|
|
||||||
function getAnimalData (text) {
|
|
||||||
return {
|
|
||||||
key,
|
|
||||||
type,
|
|
||||||
potion: potion.key,
|
|
||||||
egg: egg.key,
|
|
||||||
text,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
petInfo[key] = getAnimalData(t('petName', {
|
|
||||||
potion: potion.text,
|
|
||||||
egg: egg.text,
|
|
||||||
}));
|
|
||||||
pets[key] = true;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (hasMounts) {
|
||||||
|
return [pets, mounts];
|
||||||
|
}
|
||||||
return pets;
|
return pets;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [dropPets, dropMounts] = constructSet('drop', dropEggs, dropPotions);
|
|
||||||
const [premiumPets, premiumMounts] = constructSet('premium', dropEggs, premiumPotions);
|
|
||||||
const [questPets, questMounts] = constructSet('quest', questEggs, dropPotions);
|
|
||||||
const wackyPets = constructPetOnlySet('wacky', dropEggs, wackyPotions);
|
|
||||||
|
|
||||||
const canFindSpecial = {
|
const canFindSpecial = {
|
||||||
pets: {
|
pets: {
|
||||||
// Veteran Pet Ladder - awarded on major updates
|
// Veteran Pet Ladder - awarded on major updates
|
||||||
@@ -158,6 +131,11 @@ const canFindSpecial = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [dropPets, dropMounts] = constructSet('drop', allEggs.drops, dropPotions);
|
||||||
|
const [premiumPets, premiumMounts] = constructSet('premium', allEggs.drops, premiumPotions);
|
||||||
|
const [questPets, questMounts] = constructSet('quest', allEggs.quests, dropPotions);
|
||||||
|
const wackyPets = constructSet('wacky', allEggs.drops, wackyPotions, false);
|
||||||
|
|
||||||
const specialPets = {
|
const specialPets = {
|
||||||
'Wolf-Veteran': 'veteranWolf',
|
'Wolf-Veteran': 'veteranWolf',
|
||||||
'Wolf-Cerberus': 'cerberusPup',
|
'Wolf-Cerberus': 'cerberusPup',
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ const memoize = fn => {
|
|||||||
identifier = config.identifier;
|
identifier = config.identifier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (identifier.length === 0) {
|
||||||
|
identifier = args.filter(arg => typeof arg === 'string').join('-');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!checkedDate) {
|
if (!checkedDate) {
|
||||||
checkedDate = new Date();
|
checkedDate = new Date();
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { drops as eggs } from '../content/eggs';
|
import allEggs from '../content/eggs';
|
||||||
import { drops as hatchingPotions } from '../content/hatching-potions';
|
import { drops as hatchingPotions } from '../content/hatching-potions';
|
||||||
import randomVal from '../libs/randomVal';
|
import randomVal from '../libs/randomVal';
|
||||||
|
|
||||||
export default function firstDrops (user) {
|
export default function firstDrops (user) {
|
||||||
const eggDrop = randomVal(eggs);
|
const eggDrop = randomVal(allEggs.drops);
|
||||||
const potionDrop = randomVal(hatchingPotions);
|
const potionDrop = randomVal(hatchingPotions);
|
||||||
|
|
||||||
user.items.eggs = {
|
user.items.eggs = {
|
||||||
|
|||||||
Reference in New Issue
Block a user