mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-15 05:37:22 +01:00
Squashed commit of the following:
commit28193f86fbAuthor: Phillip Thelen <phillip@habitica.com> Date: Fri Jun 21 11:12:18 2024 +0200 Fix serving memoized content commit877fe48225Author: Phillip Thelen <phillip@habitica.com> Date: Thu Jun 20 12:23:24 2024 +0200 correctly memoize conent api commite0f6f79c5bAuthor: Phillip Thelen <phillip@habitica.com> Date: Thu Jun 20 10:11:27 2024 +0200 don’t build multiple times on heroku commitf62254d68eAuthor: Phillip Thelen <phillip@habitica.com> Date: Wed Jun 19 19:40:20 2024 +0200 fix client command commitd054e6fc16Author: Phillip Thelen <phillip@habitica.com> Date: Wed Jun 19 19:36:57 2024 +0200 correct build call commit7231f699c1Author: Phillip Thelen <phillip@habitica.com> Date: Wed Jun 19 19:32:32 2024 +0200 try setting up with heroku buildpack commit1dae0793fdAuthor: Phillip Thelen <phillip@habitica.com> Date: Wed Jun 19 18:50:32 2024 +0200 call gulp build:prod commitf18fbe86b6Author: Phillip Thelen <phillip@habitica.com> Date: Wed Jun 19 18:40:53 2024 +0200 build client commit61a61724caAuthor: Phillip Thelen <phillip@habitica.com> Date: Wed Jun 19 18:33:18 2024 +0200 testing commit93cf30eb18Author: Phillip Thelen <phillip@habitica.com> Date: Wed Jun 19 18:20:25 2024 +0200 integration fix commitcff08adcd0Author: Phillip Thelen <phillip@habitica.com> Date: Wed Jun 19 18:13:20 2024 +0200 specify dev docker file commit4da2ed4a1fAuthor: Phillip Thelen <phillip@habitica.com> Date: Wed Jun 19 18:10:07 2024 +0200 initialize stub commit11c5b26c59Author: Phillip Thelen <phillip@habitica.com> Date: Wed Jun 19 18:08:45 2024 +0200 test heroku file commitac85bb2e2dAuthor: Phillip Thelen <phillip@habitica.com> Date: Wed Jun 19 18:03:15 2024 +0200 fix stub reference commit74dfb2710fAuthor: Phillip Thelen <phillip@habitica.com> Date: Wed Jun 19 18:01:27 2024 +0200 test fixes commit8dbd3c3db1Author: Phillip Thelen <phillip@habitica.com> Date: Wed Jun 19 17:37:04 2024 +0200 fix canOwn test commit74b3b348ffAuthor: Phillip Thelen <phillip@habitica.com> Date: Wed Jun 19 17:32:31 2024 +0200 fix buy test commit3386d61fdeAuthor: Phillip Thelen <phillip@habitica.com> Date: Wed Jun 19 17:30:37 2024 +0200 fix debug tests commit19da14531cAuthor: Phillip Thelen <phillip@habitica.com> Date: Wed Jun 19 17:05:25 2024 +0200 add chameleon to featured quests commit254dd80f24Author: Phillip Thelen <phillip@habitica.com> Date: Wed Jun 19 17:05:14 2024 +0200 fix import commit0bc3f16b4bAuthor: Phillip Thelen <phillip@habitica.com> Date: Wed Jun 19 16:33:22 2024 +0200 add new content to new release file commit5184973bd5Author: Phillip Thelen <phillip@habitica.com> Date: Wed Jun 19 16:33:11 2024 +0200 fix release date tests commitb6accca5caAuthor: Phillip Thelen <phillip@habitica.com> Date: Wed Jun 19 16:33:06 2024 +0200 fix armoire tests commitfec68e6211Author: Phillip Thelen <phillip@habitica.com> Date: Wed Jun 19 16:02:03 2024 +0200 fix tests commitfc63c906ddAuthor: Phillip Thelen <phillip@habitica.com> Date: Mon Jun 10 14:44:21 2024 +0200 Improve test coverage commit3333f8f0f5Author: Phillip Thelen <phillip@habitica.com> Date: Mon Jun 10 14:24:59 2024 +0200 allow hatching potions to have a release date commit89a3ac3ddeAuthor: Phillip Thelen <phillip@habitica.com> Date: Mon Jun 10 14:11:38 2024 +0200 allow eggs to have a release date # Conflicts: # test/content/armoire.test.js commit16551ec83fMerge:f5f4974a732645bf6023Author: CuriousMagpie <eilatan@gmail.com> Date: Tue Jun 18 15:03:12 2024 -0400 Merge branch '2024-07-content-prebuild' into subs-private commit2645bf6023Author: CuriousMagpie <eilatan@gmail.com> Date: Tue Jun 18 15:02:47 2024 -0400 update habitica images commitf5f4974a73Author: CuriousMagpie <eilatan@gmail.com> Date: Tue Jun 18 14:58:13 2024 -0400 update habitica-images commit162e337d14Merge:f2506c323121a7d36b7bAuthor: CuriousMagpie <eilatan@gmail.com> Date: Tue Jun 18 13:46:03 2024 -0400 Merge branch '2024-07-content-prebuild' into subs-private commit21a7d36b7bAuthor: CuriousMagpie <eilatan@gmail.com> Date: Tue Jun 18 13:45:09 2024 -0400 update sprites commitf2506c3231Author: CuriousMagpie <eilatan@gmail.com> Date: Tue Jun 18 13:24:21 2024 -0400 updated sprites css commitd47641e25aAuthor: CuriousMagpie <eilatan@gmail.com> Date: Tue Jun 18 12:46:59 2024 -0400 typo fix commitfb8479ad1eAuthor: CuriousMagpie <eilatan@gmail.com> Date: Mon Jun 17 13:44:36 2024 -0400 finish July prebuild commit3810cf3ef3Author: CuriousMagpie <eilatan@gmail.com> Date: Fri Jun 14 10:42:47 2024 -0400 add chameleon quest commitd05da3722cAuthor: CuriousMagpie <eilatan@gmail.com> Date: Thu Jun 13 17:12:43 2024 -0400 add June background notes commitb8a3440ef2Author: CuriousMagpie <eilatan@gmail.com> Date: Thu Jun 13 16:40:04 2024 -0400 fix mystery item and background description commit44d63032d8Author: CuriousMagpie <eilatan@gmail.com> Date: Thu Jun 13 15:38:23 2024 -0400 add subscriber gear, enchanted armoire, and background commit9d7da91ec6Author: CuriousMagpie <eilatan@gmail.com> Date: Thu Jun 13 14:44:59 2024 -0400 add sprites
This commit is contained in:
@@ -3,10 +3,13 @@ FROM node:20
|
||||
# Install global packages
|
||||
RUN npm install -g gulp-cli mocha
|
||||
|
||||
# Copy package.json and package-lock.json into image, then install
|
||||
# dependencies.
|
||||
# Copy package.json and package-lock.json into image
|
||||
WORKDIR /usr/src/habitica
|
||||
COPY ["package.json", "package-lock.json", "./"]
|
||||
# Copy the remaining source files in.
|
||||
COPY . /usr/src/habitica
|
||||
# Install dependencies
|
||||
RUN npm install
|
||||
RUN npm run postinstall
|
||||
RUN npm run client:build
|
||||
RUN gulp build:prod
|
||||
|
||||
@@ -107,7 +107,9 @@
|
||||
"debug": "gulp nodemon --inspect",
|
||||
"mongo:dev": "run-rs -v 5.0.23 -l ubuntu1804 --keep --dbpath mongodb-data --number 1 --quiet",
|
||||
"postinstall": "git config --global url.\"https://\".insteadOf git:// && gulp build && cd website/client && npm install",
|
||||
"apidoc": "gulp apidoc"
|
||||
"apidoc": "gulp apidoc",
|
||||
|
||||
"heroku-postbuild": "npm run client:build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"axios": "^1.4.0",
|
||||
|
||||
@@ -55,7 +55,7 @@ describe('contentLib', () => {
|
||||
beforeEach(() => {
|
||||
resSpy = generateRes();
|
||||
if (fs.existsSync(contentLib.CONTENT_CACHE_PATH)) {
|
||||
fs.rmdirSync(contentLib.CONTENT_CACHE_PATH, { recursive: true });
|
||||
fs.rmSync(contentLib.CONTENT_CACHE_PATH, { recursive: true });
|
||||
}
|
||||
fs.mkdirSync(contentLib.CONTENT_CACHE_PATH);
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import armoireSet from '../../../website/common/script/content/gear/sets/armoire
|
||||
describe('armoireSet items', () => {
|
||||
it('checks if canOwn has the same id', () => {
|
||||
Object.keys(armoireSet).forEach(type => {
|
||||
if (type === 'all') return;
|
||||
Object.keys(armoireSet[type]).forEach(itemKey => {
|
||||
const ownedKey = `${type}_armoire_${itemKey}`;
|
||||
expect(armoireSet[type][itemKey].canOwn({
|
||||
|
||||
@@ -3,38 +3,26 @@ import forEach from 'lodash/forEach';
|
||||
import {
|
||||
expectValidTranslationString,
|
||||
} from '../helpers/content.helper';
|
||||
|
||||
function makeArmoireIitemList () {
|
||||
const armoire = require('../../website/common/script/content/gear/sets/armoire').default;
|
||||
const items = [];
|
||||
items.push(...Object.values(armoire.armor));
|
||||
items.push(...Object.values(armoire.body));
|
||||
items.push(...Object.values(armoire.eyewear));
|
||||
items.push(...Object.values(armoire.head));
|
||||
items.push(...Object.values(armoire.headAccessory));
|
||||
items.push(...Object.values(armoire.shield));
|
||||
items.push(...Object.values(armoire.weapon));
|
||||
return items;
|
||||
}
|
||||
import armoire from '../../website/common/script/content/gear/sets/armoire';
|
||||
|
||||
describe('armoire', () => {
|
||||
let clock;
|
||||
beforeEach(() => {
|
||||
delete require.cache[require.resolve('../../website/common/script/content/gear/sets/armoire')];
|
||||
});
|
||||
afterEach(() => {
|
||||
clock.restore();
|
||||
if (clock) {
|
||||
clock.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it('does not return unreleased gear', async () => {
|
||||
clock = sinon.useFakeTimers(new Date('2024-01-02'));
|
||||
const items = makeArmoireIitemList();
|
||||
const items = armoire.all;
|
||||
expect(items.length).to.equal(377);
|
||||
expect(items.filter(item => item.set === 'pottersSet' || item.set === 'optimistSet' || item.set === 'schoolUniform')).to.be.an('array').that.is.empty;
|
||||
});
|
||||
|
||||
it('released gear has all required properties', async () => {
|
||||
clock = sinon.useFakeTimers(new Date('2024-05-08'));
|
||||
const items = makeArmoireIitemList();
|
||||
const items = armoire.all;
|
||||
expect(items.length).to.equal(396);
|
||||
forEach(items, item => {
|
||||
if (item.set !== undefined) {
|
||||
@@ -48,29 +36,30 @@ describe('armoire', () => {
|
||||
|
||||
it('releases gear when appropriate', async () => {
|
||||
clock = sinon.useFakeTimers(new Date('2024-01-01T00:00:00.000Z'));
|
||||
const items = makeArmoireIitemList();
|
||||
const items = armoire.all;
|
||||
expect(items.length).to.equal(377);
|
||||
clock.restore();
|
||||
delete require.cache[require.resolve('../../website/common/script/content/gear/sets/armoire')];
|
||||
clock = sinon.useFakeTimers(new Date('2024-01-08'));
|
||||
const januaryItems = makeArmoireIitemList();
|
||||
const januaryItems = armoire.all;
|
||||
expect(januaryItems.length).to.equal(381);
|
||||
clock.restore();
|
||||
delete require.cache[require.resolve('../../website/common/script/content/gear/sets/armoire')];
|
||||
clock = sinon.useFakeTimers(new Date('2024-02-07'));
|
||||
const januaryItems2 = makeArmoireIitemList();
|
||||
const januaryItems2 = armoire.all;
|
||||
expect(januaryItems2.length).to.equal(381);
|
||||
clock.restore();
|
||||
delete require.cache[require.resolve('../../website/common/script/content/gear/sets/armoire')];
|
||||
clock = sinon.useFakeTimers(new Date('2024-02-07T16:00:00.000Z'));
|
||||
const febuaryItems = makeArmoireIitemList();
|
||||
clock = sinon.useFakeTimers(new Date('2024-02-07T09:00:00.000Z'));
|
||||
const febuaryItems = armoire.all;
|
||||
expect(febuaryItems.length).to.equal(384);
|
||||
});
|
||||
|
||||
it('sets have at least 2 items', () => {
|
||||
const armoire = makeArmoireIitemList();
|
||||
const setMap = {};
|
||||
forEach(armoire, item => {
|
||||
forEach(armoire.all, item => {
|
||||
// Gotta have one outlier
|
||||
if (!item.set || item.set.startsWith('armoire-')) return;
|
||||
if (setMap[item.set] === undefined) {
|
||||
setMap[item.set] = 0;
|
||||
}
|
||||
|
||||
@@ -5,29 +5,51 @@ import {
|
||||
expectValidTranslationString,
|
||||
} from '../helpers/content.helper';
|
||||
|
||||
import * as eggs from '../../website/common/script/content/eggs';
|
||||
import eggs from '../../website/common/script/content/eggs';
|
||||
|
||||
describe('eggs', () => {
|
||||
describe('all', () => {
|
||||
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;
|
||||
let clock;
|
||||
|
||||
expect(allNumber).to.be.greaterThan(0);
|
||||
expect(allNumber).to.equal(dropNumber + questNumber);
|
||||
});
|
||||
afterEach(() => {
|
||||
if (clock) {
|
||||
clock.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it('contains basic information about each egg', () => {
|
||||
each(eggs.all, (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);
|
||||
const eggTypes = [
|
||||
'drops',
|
||||
'quests',
|
||||
];
|
||||
|
||||
eggTypes.forEach(eggType => {
|
||||
describe(eggType, () => {
|
||||
it('contains basic information about each egg', () => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
154
test/content/index.test.js
Normal file
154
test/content/index.test.js
Normal file
@@ -0,0 +1,154 @@
|
||||
import content from '../../website/common/script/content';
|
||||
|
||||
describe('content index', () => {
|
||||
let clock;
|
||||
|
||||
afterEach(() => {
|
||||
if (clock) {
|
||||
clock.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it('Releases eggs when appropriate without needing restarting', () => {
|
||||
clock = sinon.useFakeTimers(new Date('2024-06-20'));
|
||||
const mayEggs = content.eggs;
|
||||
expect(mayEggs.Chameleon).to.not.exist;
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2024-07-20'));
|
||||
const juneEggs = content.eggs;
|
||||
expect(juneEggs.Chameleon).to.exist;
|
||||
expect(Object.keys(mayEggs).length, '').to.equal(Object.keys(juneEggs).length - 1);
|
||||
});
|
||||
|
||||
it('Releases hatching potions when appropriate without needing restarting', () => {
|
||||
clock = sinon.useFakeTimers(new Date('2024-05-20'));
|
||||
const mayHatchingPotions = content.hatchingPotions;
|
||||
expect(mayHatchingPotions.Koi).to.not.exist;
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2024-06-20'));
|
||||
const juneHatchingPotions = content.hatchingPotions;
|
||||
expect(juneHatchingPotions.Koi).to.exist;
|
||||
expect(Object.keys(mayHatchingPotions).length, '').to.equal(Object.keys(juneHatchingPotions).length - 1);
|
||||
});
|
||||
|
||||
it('Releases armoire gear when appropriate without needing restarting', () => {
|
||||
clock = sinon.useFakeTimers(new Date('2024-06-20'));
|
||||
const juneGear = content.gear.flat;
|
||||
expect(juneGear.armor_armoire_corsairsCoatAndCape).to.not.exist;
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2024-07-10'));
|
||||
const julyGear = content.gear.flat;
|
||||
expect(julyGear.armor_armoire_corsairsCoatAndCape).to.exist;
|
||||
expect(Object.keys(juneGear).length, '').to.equal(Object.keys(julyGear).length - 3);
|
||||
});
|
||||
|
||||
it('Releases pets gear when appropriate without needing restarting', () => {
|
||||
clock = sinon.useFakeTimers(new Date('2024-06-20'));
|
||||
const junePets = content.petInfo;
|
||||
expect(junePets['Chameleon-Base']).to.not.exist;
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2024-07-10'));
|
||||
const julyPets = content.petInfo;
|
||||
expect(julyPets['Chameleon-Base']).to.exist;
|
||||
expect(Object.keys(junePets).length, '').to.equal(Object.keys(julyPets).length - 10);
|
||||
});
|
||||
|
||||
it('Releases mounts gear when appropriate without needing restarting', () => {
|
||||
clock = sinon.useFakeTimers(new Date('2024-06-20'));
|
||||
const juneMounts = content.mountInfo;
|
||||
expect(juneMounts['Chameleon-Base']).to.not.exist;
|
||||
clock.restore();
|
||||
clock = sinon.useFakeTimers(new Date('2024-07-10'));
|
||||
const julyMounts = content.mountInfo;
|
||||
expect(julyMounts['Chameleon-Base']).to.exist;
|
||||
expect(Object.keys(juneMounts).length, '').to.equal(Object.keys(julyMounts).length - 10);
|
||||
});
|
||||
|
||||
it('marks regular food as buyable and droppable without any events', () => {
|
||||
clock = sinon.useFakeTimers(new Date('2024-06-20'));
|
||||
const { food } = content;
|
||||
Object.keys(food).forEach(key => {
|
||||
if (key === 'Saddle') {
|
||||
expect(food[key].canBuy(), `${key} canBuy`).to.be.true;
|
||||
expect(food[key].canDrop, `${key} canDrop`).to.be.false;
|
||||
return;
|
||||
}
|
||||
let expected = true;
|
||||
if (key.startsWith('Cake_')) {
|
||||
expected = false;
|
||||
} else if (key.startsWith('Candy_')) {
|
||||
expected = false;
|
||||
} else if (key.startsWith('Pie_')) {
|
||||
expected = false;
|
||||
}
|
||||
expect(food[key].canBuy(), `${key} canBuy`).to.equal(expected);
|
||||
expect(food[key].canDrop, `${key} canDrop`).to.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('marks candy as buyable and droppable during habitoween', () => {
|
||||
clock = sinon.useFakeTimers(new Date('2024-10-31'));
|
||||
const { food } = content;
|
||||
Object.keys(food).forEach(key => {
|
||||
if (key === 'Saddle') {
|
||||
expect(food[key].canBuy(), `${key} canBuy`).to.be.true;
|
||||
expect(food[key].canDrop, `${key} canDrop`).to.be.false;
|
||||
return;
|
||||
}
|
||||
let expected = false;
|
||||
if (key.startsWith('Cake_')) {
|
||||
expected = false;
|
||||
} else if (key.startsWith('Candy_')) {
|
||||
expected = true;
|
||||
} else if (key.startsWith('Pie_')) {
|
||||
expected = false;
|
||||
}
|
||||
expect(food[key].canBuy(), `${key} canBuy`).to.equal(expected);
|
||||
expect(food[key].canDrop, `${key} canDrop`).to.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('marks cake as buyable and droppable during birthday', () => {
|
||||
clock = sinon.useFakeTimers(new Date('2024-01-31'));
|
||||
const { food } = content;
|
||||
Object.keys(food).forEach(key => {
|
||||
if (key === 'Saddle') {
|
||||
expect(food[key].canBuy(), `${key} canBuy`).to.be.true;
|
||||
expect(food[key].canDrop, `${key} canDrop`).to.be.false;
|
||||
return;
|
||||
}
|
||||
let expected = false;
|
||||
if (key.startsWith('Cake_')) {
|
||||
expected = true;
|
||||
} else if (key.startsWith('Candy_')) {
|
||||
expected = false;
|
||||
} else if (key.startsWith('Pie_')) {
|
||||
expected = false;
|
||||
}
|
||||
expect(food[key].canBuy(), `${key} canBuy`).to.equal(expected);
|
||||
expect(food[key].canDrop, `${key} canDrop`).to.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('marks pie as buyable and droppable during pi day', () => {
|
||||
clock = sinon.useFakeTimers(new Date('2024-03-14'));
|
||||
const { food } = content;
|
||||
Object.keys(food).forEach(key => {
|
||||
if (key === 'Saddle') {
|
||||
expect(food[key].canBuy(), `${key} canBuy`).to.be.true;
|
||||
expect(food[key].canDrop, `${key} canDrop`).to.be.false;
|
||||
return;
|
||||
}
|
||||
let expected = false;
|
||||
if (key.startsWith('Cake_')) {
|
||||
expected = false;
|
||||
} else if (key.startsWith('Candy_')) {
|
||||
expected = false;
|
||||
} else if (key.startsWith('Pie_')) {
|
||||
expected = true;
|
||||
}
|
||||
expect(food[key].canBuy(), `${key} canBuy`).to.equal(expected);
|
||||
expect(food[key].canDrop, `${key} canDrop`).to.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
82
test/content/releaseDates.test.js
Normal file
82
test/content/releaseDates.test.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import find from 'lodash/find';
|
||||
import maxBy from 'lodash/maxBy';
|
||||
import {
|
||||
ARMOIRE_RELEASE_DATES,
|
||||
EGGS_RELEASE_DATES,
|
||||
HATCHING_POTIONS_RELEASE_DATES,
|
||||
} from '../../website/common/script/content/constants/releaseDates';
|
||||
import armoire from '../../website/common/script/content/gear/sets/armoire';
|
||||
import eggs from '../../website/common/script/content/eggs';
|
||||
import hatchingPotions from '../../website/common/script/content/hatching-potions';
|
||||
|
||||
describe('releaseDates', () => {
|
||||
let clock;
|
||||
|
||||
afterEach(() => {
|
||||
if (clock) {
|
||||
clock.restore();
|
||||
}
|
||||
});
|
||||
describe('armoire', () => {
|
||||
it('should only contain valid armoire names', () => {
|
||||
const lastReleaseDate = maxBy(Object.values(ARMOIRE_RELEASE_DATES), value => new Date(`${value.year}-${value.month + 1}-20`));
|
||||
clock = sinon.useFakeTimers(new Date(`${lastReleaseDate.year}-${lastReleaseDate.month + 1}-20`));
|
||||
Object.keys(ARMOIRE_RELEASE_DATES).forEach(key => {
|
||||
expect(find(armoire.all, { set: key }), `${key} is not a valid armoire set`).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
it('should contain a valid year and month', () => {
|
||||
Object.keys(ARMOIRE_RELEASE_DATES).forEach(key => {
|
||||
const date = ARMOIRE_RELEASE_DATES[key];
|
||||
expect(date.year, `${key} year is not a valid year`).to.be.a('number');
|
||||
expect(date.year).to.be.at.least(2023);
|
||||
expect(date.month, `${key} month is not a valid month`).to.be.a('number');
|
||||
expect(date.month).to.be.within(1, 12);
|
||||
expect(date.day).to.not.exist;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('eggs', () => {
|
||||
it('should only contain valid egg names', () => {
|
||||
const lastReleaseDate = maxBy(Object.values(EGGS_RELEASE_DATES), value => new Date(`${value.year}-${value.month + 1}-${value.day}`));
|
||||
clock = sinon.useFakeTimers(new Date(`${lastReleaseDate.year}-${lastReleaseDate.month + 1}-${lastReleaseDate.day}`));
|
||||
Object.keys(EGGS_RELEASE_DATES).forEach(key => {
|
||||
expect(eggs.all[key], `${key} is not a valid egg name`).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
it('should contain a valid year, month and date', () => {
|
||||
Object.keys(EGGS_RELEASE_DATES).forEach(key => {
|
||||
const date = EGGS_RELEASE_DATES[key];
|
||||
expect(date.year, `${key} year is not a valid year`).to.be.a('number');
|
||||
expect(date.year).to.be.at.least(2024);
|
||||
expect(date.month, `${key} month is not a valid month`).to.be.a('number');
|
||||
expect(date.month).to.be.within(1, 12);
|
||||
expect(date.day, `${key} day is not a valid day`).to.be.a('number');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('hatchingPotions', () => {
|
||||
it('should only contain valid potion names', () => {
|
||||
const lastReleaseDate = maxBy(Object.values(HATCHING_POTIONS_RELEASE_DATES), value => new Date(`${value.year}-${value.month + 1}-${value.day}`));
|
||||
clock = sinon.useFakeTimers(new Date(`${lastReleaseDate.year}-${lastReleaseDate.month + 1}-${lastReleaseDate.day}`));
|
||||
Object.keys(HATCHING_POTIONS_RELEASE_DATES).forEach(key => {
|
||||
expect(hatchingPotions.all[key], `${key} is not a valid potion name`).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
it('should contain a valid year, month and date', () => {
|
||||
Object.keys(HATCHING_POTIONS_RELEASE_DATES).forEach(key => {
|
||||
const date = HATCHING_POTIONS_RELEASE_DATES[key];
|
||||
expect(date.year, `${key} year is not a valid year`).to.be.a('number');
|
||||
expect(date.year).to.be.at.least(2024);
|
||||
expect(date.month, `${key} month is not a valid month`).to.be.a('number');
|
||||
expect(date.month).to.be.within(1, 12);
|
||||
expect(date.day, `${key} day is not a valid day`).to.be.a('number');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -6,12 +6,21 @@ import {
|
||||
} from '../helpers/content.helper';
|
||||
import t from '../../website/common/script/content/translation';
|
||||
|
||||
import * as stable from '../../website/common/script/content/stable';
|
||||
import * as eggs from '../../website/common/script/content/eggs';
|
||||
import * as potions from '../../website/common/script/content/hatching-potions';
|
||||
import stable from '../../website/common/script/content/stable';
|
||||
import eggs from '../../website/common/script/content/eggs';
|
||||
import potions from '../../website/common/script/content/hatching-potions';
|
||||
|
||||
describe('stable', () => {
|
||||
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', () => {
|
||||
const numberOfDropPotions = Object.keys(potions.drops).length;
|
||||
const numberOfDropEggs = Object.keys(eggs.drops).length;
|
||||
|
||||
@@ -320,7 +320,7 @@
|
||||
<script>
|
||||
import each from 'lodash/each';
|
||||
import * as quests from '@/../../common/script/content/quests';
|
||||
import { mountInfo, petInfo } from '@/../../common/script/content/stable';
|
||||
import stable from '@/../../common/script/content/stable';
|
||||
import content from '@/../../common/script/content';
|
||||
import gear from '@/../../common/script/content/gear';
|
||||
import styleHelper from '@/mixins/styleHelper';
|
||||
@@ -330,6 +330,8 @@ import userLink from '../userLink';
|
||||
import PurchaseHistoryTable from '../ui/purchaseHistoryTable.vue';
|
||||
import { userStateMixin } from '../../mixins/userState';
|
||||
|
||||
const { mountInfo, petInfo } = stable;
|
||||
|
||||
export default {
|
||||
components: {
|
||||
userLink,
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mountInfo } from '@/../../common/script/content/stable';
|
||||
import stable from '@/../../common/script/content/stable';
|
||||
import markdownDirective from '@/directives/markdown';
|
||||
|
||||
export default {
|
||||
@@ -105,7 +105,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
openDialog (mountKey) {
|
||||
this.mount = mountInfo[mountKey];
|
||||
this.mount = stable.mountInfo[mountKey];
|
||||
this.$root.$emit('bv::show::modal', 'mount-raised-modal');
|
||||
},
|
||||
close () {
|
||||
|
||||
@@ -586,8 +586,8 @@ import reduce from 'lodash/reduce';
|
||||
import moment from 'moment';
|
||||
|
||||
import planGemLimits from '@/../../common/script/libs/planGemLimits';
|
||||
import { drops as dropEggs } from '@/../../common/script/content/eggs';
|
||||
import { drops as dropPotions } from '@/../../common/script/content/hatching-potions';
|
||||
import eggs from '@/../../common/script/content/eggs';
|
||||
import hatchingPotions from '@/../../common/script/content/hatching-potions';
|
||||
import { avatarEditorUtilities } from '@/mixins/avatarEditUtilities';
|
||||
import numberInvalid from '@/mixins/numberInvalid';
|
||||
import spellsMixin from '@/mixins/spells';
|
||||
@@ -617,6 +617,9 @@ import EquipmentAttributesGrid from '../inventory/equipment/attributesGrid.vue';
|
||||
import Item from '@/components/inventory/item';
|
||||
import Avatar from '@/components/avatar';
|
||||
|
||||
const dropEggs = eggs.drops;
|
||||
const dropPotions = hatchingPotions.drops;
|
||||
|
||||
const dropEggKeys = keys(dropEggs);
|
||||
|
||||
const amountOfDropEggs = size(dropEggs);
|
||||
|
||||
@@ -983,6 +983,10 @@
|
||||
"backgroundShellGateText": "Shell Gate",
|
||||
"backgroundShellGateNotes": "March through the decorated coral of a Shell Gate.",
|
||||
|
||||
"backgrounds072024": "SET 122: Released July 2024",
|
||||
"backgroundRiverBottomText": "River Bottom",
|
||||
"backgroundRiverBottomNotes": "Explore a River Bottom.",
|
||||
|
||||
"timeTravelBackgrounds": "Steampunk Backgrounds",
|
||||
"backgroundAirshipText": "Airship",
|
||||
"backgroundAirshipNotes": "Become a sky sailor on board your very own Airship.",
|
||||
|
||||
@@ -255,6 +255,10 @@
|
||||
"questEggGiraffeMountText": "Giraffe",
|
||||
"questEggGiraffeAdjective": "a towering",
|
||||
|
||||
"questEggChameleonText": "Chameleon",
|
||||
"questEggChameleonMountText": "Chameleon",
|
||||
"questEggChameleonAdjective": "a chaotic",
|
||||
|
||||
"eggNotes": "Find a hatching potion to pour on this egg, and it will hatch into <%= eggAdjective(locale) %> <%= eggText(locale) %>.",
|
||||
|
||||
"hatchingPotionBase": "Base",
|
||||
|
||||
@@ -770,6 +770,8 @@
|
||||
"weaponArmoirePottersWheelNotes": "Throw some clay on this wheel and make a bowl or a mug or a vase or a slightly different bowl. If you're lucky, a ghost might visit while you create! Increases Perception by <%= per %>. Enchanted Armoire: Potter Set (Item 4 of 4).",
|
||||
"weaponArmoireShadyBeachUmbrellaText": "Beach Umbrella",
|
||||
"weaponArmoireShadyBeachUmbrellaNotes": "The shade of this rainbow-colored umbrella conceals you briefly from the day star and any unwanted bothers. Increases Perception by <%= per %>. Enchanted Armoire: Beachside Set (Item 3 of 4).",
|
||||
"weaponArmoireCorsairsBladeText": "Corsair’s Blade",
|
||||
"weaponArmoireCorsairsBladeNotes": "Whether you wield it to plunder or to protect, you can be glad you brought this fierce blade to sea with you. Just be sure to stow it safely when not in use. Increases Strength by <%= str %>. Enchanted Armoire: Corsair Set (Item 3 of 3)",
|
||||
|
||||
"armor": "armor",
|
||||
"armorCapitalized": "Armor",
|
||||
@@ -1400,6 +1402,8 @@
|
||||
"armorMystery202401Notes": "These robes appear as delicate as crystal snowflakes, but will keep you plenty warm as you work your wintry magic. Confers no benefit. January 2024 Subscriber Item.",
|
||||
"armorMystery202406Text": "Phantom Buccaneer’s Attire",
|
||||
"armorMystery202406Notes": "Haunt your enemies with style and flair! Confers no benefit. June 2024 Subscriber Item.",
|
||||
"armorMystery202407Text": "Amiable Axolotl Suit",
|
||||
"armorMystery202407Notes": "Glide through lakes and canals with your sweeping pink tail!",
|
||||
|
||||
"armorMystery301404Text": "Steampunk Suit",
|
||||
"armorMystery301404Notes": "Dapper and dashing, wot! Confers no benefit. February 3015 Subscriber Item.",
|
||||
@@ -1616,6 +1620,8 @@
|
||||
"armorArmoireYellowStripedSwimsuitNotes": "What could be more delightful than battling sea monsters on the beach? Increases Constitution by <%= con %>. Enchanted Armoire: Beachside Set (Item 1 of 4).",
|
||||
"armorArmoireBlueStripedSwimsuitText": "Blue Striped Swimsuit",
|
||||
"armorArmoireBlueStripedSwimsuitNotes": "What could be more exciting than battling sea monsters on the beach? Increases Constitution by <%= con %>. Enchanted Armoire: Beachside Set (Item 2 of 4).",
|
||||
"armorArmoireCorsairsCoatAndCapeText": "Corsair’s Coat and Cape",
|
||||
"armorArmoireCorsairsCoatAndCapeNotes": "Whether you’re biding your time on the docks or watching for danger on the open seas, these will surely keep you feeling dry and looking dramatic. Just keep your balance on deck. Increases Constitution by <%= con %>. Enchanted Armoire: Corsair Set (Item 1 of 3)",
|
||||
|
||||
"headgear": "helm",
|
||||
"headgearCapitalized": "Headgear",
|
||||
@@ -2279,6 +2285,8 @@
|
||||
"headMystery202404Notes": "This hat will connect you with the earth and allow you to hear secret wishes from many creatures. Confers no benefit. April 2024 Subscriber Item.",
|
||||
"headMystery202406Text": "Phantom Buccaneer’s Hat",
|
||||
"headMystery202406Notes": "The ghostly feathers that adorn this hat glow faintly, like the waves of a spectral sea. Confers no benefit. June 2024 Subscriber Item.",
|
||||
"headMystery202407Text": "Amiable Axolotl Hood",
|
||||
"headMystery202407Notes": "These magical gills will let you breathe underwater!",
|
||||
|
||||
"headMystery301404Text": "Fancy Top Hat",
|
||||
"headMystery301404Notes": "A fancy top hat for the finest of gentlefolk! January 3015 Subscriber Item. Confers no benefit.",
|
||||
@@ -2475,6 +2483,8 @@
|
||||
"headArmoireHattersTopHatNotes": "Our hats are off to you, and yours is on! What’s hidden in your hat is anybody’s guess (but we’re hoping it’s a bunny). Increases Perception by <%= per %>. Enchanted Armoire: Hatter Set (Item 1 of 4).",
|
||||
"headArmoirePottersBandanaText": "Bandana",
|
||||
"headArmoirePottersBandanaNotes": "Look the part and keep your hair out of your face while you work. It’s a win-win! Increases Intelligence by <%= int %>. Enchanted Armoire: Potter Set (Item 2 of 4).",
|
||||
"headArmoireCorsairsBandanaText": "Corsair’s Bandana",
|
||||
"headArmoireCorsairsBandanaNotes": "Whether you’re keeping your head covered in case a seagull flies overhead or making sure your foes never see you sweat, this bandana is essential. Just add a decorative bead for every adventure you complete. Increases Intelligence by <%= int %>. Enchanted Armoire: Corsair Set (Item 2 of 3)",
|
||||
|
||||
"offhand": "off-hand item",
|
||||
"offHandCapitalized": "Off-Hand Item",
|
||||
|
||||
@@ -895,7 +895,13 @@
|
||||
"questGiraffeBoss": "Gear-affe",
|
||||
"questGiraffeDropGiraffeEgg": "Giraffe (Egg)",
|
||||
"QuestGiraffeUnlockText": "Unlocks Giraffe Eggs for purchase in the Market.",
|
||||
"questPinkMarbleUnlockText": "Unlocks Pink Marble Hatching Potions for purchase in the Market.",
|
||||
|
||||
"questChameleonText": "The Chaotic Chameleon",
|
||||
"questChameleonNotes": "It’s a beautiful day in a warm, rainy corner of the Taskwoods. You’re on the hunt for new additions to your leaf collection when a branch in front of you changes color without warning! Then it moves!<br><br>Stumbling backwards, you realize this is not a branch at all, but a huge chameleon! Each part of his body keeps changing colors as his eyes dart in different directions.<br><br>“Are you all right?” you ask the chameleon.<br><br>“Ahhh, well,” he says, looking a little flustered. “I’ve been trying to blend in… but it’s so overwhelming… the colors keep coming and going! It’s hard to focus on just one....”<br><br>“Aha,” you say, “I think I can help. We’ll sharpen your focus with a little challenge! Get your colors ready!”<br><br>“You’re on!” replied the chameleon.",
|
||||
"questChameleonCompletion": "After a few lively turns the Chameleon went through every color of the rainbow, perfectly matching each color you requested.<br><br>“Wow,” he says, “working together and making it into a game really helped me concentrate! Please take these as a reward, you’re earned them! Teach these little guys how to change all the colors of the rainbow when they hatch.”",
|
||||
"questChameleonBoss": "Chaotic Chameleon",
|
||||
"questChameleonDropChameleonEgg": "Chameleon (Egg)",
|
||||
"QuestChameleonUnlockText": "Unlocks Chameleon Eggs for purchase in the Market",
|
||||
|
||||
"questFungiText": "The Moody Mushroom",
|
||||
"questFungiNotes": "It’s been a rainy spring in Habitica and the ground around the stables is spongy and damp. You notice quite a few mushrooms have appeared along the wooden stable walls and fences. There’s a fog hanging about, not quite letting the sun peek through, and it’s a bit dispiriting.<br><br>Out of the mist you see the outline of the April Fool, not at all his usual bouncy self.<br><br>”I’d hoped to bring you all some delightful Fungi Magic Hatching Potions so that you can keep your mushroom friends from my special day forever,” he says, his expression alarmingly unsmiling. “But this cold fog is really getting to me, it’s making me feel too tired and dismal to work my usual magic.”<br><br>“Oh no, sorry to hear that,” you say, noticing your own increasingly somber mood. “This fog is really making the day gloomy. I wonder where it came from…”<br><br>A low rumble sounds across the fields, and you see an outline emerging from the mist. You’re alarmed to see a gigantic and unhappy looking mushroom creature, and the mist appears to be emanating from it.<br><br>“Aha,” says the Fool, “I think this fungal fellow may be the source of our blues. Let’s see if we can summon a little cheer for our friend here and ourselves.”",
|
||||
|
||||
@@ -163,6 +163,7 @@
|
||||
"mysterySet202404": "Mycelial Magus Set",
|
||||
"mysterySet202405": "Gilded Dragon Set",
|
||||
"mysterySet202406": "Phantom Buccaneer Set",
|
||||
"mysterySet202407": "Amiable Axolotl Set",
|
||||
"mysterySet301404": "Steampunk Standard Set",
|
||||
"mysterySet301405": "Steampunk Accessories Set",
|
||||
"mysterySet301703": "Peacock Steampunk Set",
|
||||
|
||||
@@ -623,6 +623,9 @@ const backgrounds = {
|
||||
backgrounds062024: {
|
||||
shell_gate: { },
|
||||
},
|
||||
backgrounds072024: {
|
||||
river_bottom: { },
|
||||
},
|
||||
eventBackgrounds: {
|
||||
birthday_bash: {
|
||||
price: 0,
|
||||
|
||||
21
website/common/script/content/constants/releaseDates.js
Normal file
21
website/common/script/content/constants/releaseDates.js
Normal file
@@ -0,0 +1,21 @@
|
||||
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 },
|
||||
corsairSet: { year: 2024, month: 7 },
|
||||
};
|
||||
|
||||
export const EGGS_RELEASE_DATES = {
|
||||
Giraffe: { year: 2024, month: 6, day: 1 },
|
||||
Chameleon: { year: 2024, month: 7, day: 1 },
|
||||
};
|
||||
|
||||
export const HATCHING_POTIONS_RELEASE_DATES = {
|
||||
Koi: { year: 2024, month: 6, day: 1 },
|
||||
};
|
||||
@@ -377,6 +377,7 @@ export const MONTHLY_SCHEDULE = {
|
||||
'dilatory_derby',
|
||||
'armadillo',
|
||||
'guineapig',
|
||||
'chameleon',
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import assign from 'lodash/assign';
|
||||
import defaults from 'lodash/defaults';
|
||||
import each from 'lodash/each';
|
||||
import assign from 'lodash/assign';
|
||||
import t from './translation';
|
||||
import { filterReleased } from './is_released';
|
||||
import { EGGS_RELEASE_DATES } from './constants/releaseDates';
|
||||
import datedMemoize from '../fns/datedMemoize';
|
||||
|
||||
function applyEggDefaults (set, config) {
|
||||
each(set, (egg, key) => {
|
||||
@@ -396,6 +399,12 @@ const quests = {
|
||||
adjective: t('questEggGiraffeAdjective'),
|
||||
canBuy: hasQuestAchievementFunction('giraffe'),
|
||||
},
|
||||
Chameleon: {
|
||||
text: t('questEggChameleonText'),
|
||||
mountText: t('questEggChameleonMountText'),
|
||||
adjective: t('questEggChameleonAdjective'),
|
||||
canBuy: hasQuestAchievementFunction('chameleon'),
|
||||
},
|
||||
};
|
||||
|
||||
applyEggDefaults(drops, {
|
||||
@@ -410,10 +419,20 @@ applyEggDefaults(quests, {
|
||||
},
|
||||
});
|
||||
|
||||
const all = assign({}, drops, quests);
|
||||
function filterEggs (eggs) {
|
||||
return filterReleased(eggs, 'key', EGGS_RELEASE_DATES);
|
||||
}
|
||||
|
||||
export {
|
||||
drops,
|
||||
quests,
|
||||
all,
|
||||
const memoizedFilter = datedMemoize(filterEggs);
|
||||
|
||||
export default {
|
||||
get drops () {
|
||||
return memoizedFilter({ memoizeConfig: true, identifier: 'drops' }, drops);
|
||||
},
|
||||
get quests () {
|
||||
return memoizedFilter({ memoizeConfig: true, identifier: 'quests' }, quests);
|
||||
},
|
||||
get all () {
|
||||
return assign({}, this.drops, this.quests);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -2,12 +2,13 @@ import defaults from 'lodash/defaults';
|
||||
import find from 'lodash/find';
|
||||
import forEach from 'lodash/forEach';
|
||||
import moment from 'moment';
|
||||
import nconf from 'nconf';
|
||||
import upperFirst from 'lodash/upperFirst';
|
||||
import { ownsItem } from '../gear-helper';
|
||||
import { ATTRIBUTES } from '../../../constants';
|
||||
import t from '../../translation';
|
||||
import memoize from '../../../fns/datedMemoize';
|
||||
import { ARMOIRE_RELEASE_DATES as releaseDates } from '../../constants/releaseDates';
|
||||
import { buildReleaseDate } from '../../is_released';
|
||||
|
||||
const armor = {
|
||||
lunarArmor: {
|
||||
@@ -485,6 +486,10 @@ const armor = {
|
||||
con: 13,
|
||||
set: 'beachsideSet',
|
||||
},
|
||||
corsairsCoatAndCape: {
|
||||
con: 14,
|
||||
set: 'corsairSet',
|
||||
},
|
||||
};
|
||||
|
||||
const body = {
|
||||
@@ -994,6 +999,10 @@ const head = {
|
||||
int: 8,
|
||||
set: 'pottersSet',
|
||||
},
|
||||
corsairsBandana: {
|
||||
int: 7,
|
||||
set: 'corsairSet',
|
||||
},
|
||||
};
|
||||
|
||||
const shield = {
|
||||
@@ -1831,21 +1840,13 @@ const weapon = {
|
||||
per: 12,
|
||||
set: 'beachsideSet',
|
||||
},
|
||||
corsairsBlade: {
|
||||
str: 7,
|
||||
set: 'corsairSet',
|
||||
},
|
||||
};
|
||||
|
||||
const SWITCHOVER_TIME = nconf.get('CONTENT_SWITCHOVER_TIME_OFFSET') || 0;
|
||||
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({
|
||||
armor,
|
||||
@@ -1890,12 +1891,12 @@ forEach({
|
||||
|
||||
function updateReleased (type) {
|
||||
const today = moment();
|
||||
const releaseDateEndPart = `${String(releaseDay).padStart(2, '0')}T${String(SWITCHOVER_TIME).padStart(2, '0')}:00-0500`;
|
||||
const returnType = {};
|
||||
forEach(type, (gearItem, gearKey) => {
|
||||
let released;
|
||||
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);
|
||||
} else {
|
||||
released = true;
|
||||
@@ -1931,4 +1932,16 @@ export default {
|
||||
get weapon () {
|
||||
return memoizedUpdatReleased({ identifier: 'weapon', memoizeConfig: true }, weapon);
|
||||
},
|
||||
// convenience method for tests mostly. Not used in the app
|
||||
get all () {
|
||||
const items = [];
|
||||
items.push(...Object.values(this.armor));
|
||||
items.push(...Object.values(this.body));
|
||||
items.push(...Object.values(this.eyewear));
|
||||
items.push(...Object.values(this.head));
|
||||
items.push(...Object.values(this.headAccessory));
|
||||
items.push(...Object.values(this.shield));
|
||||
items.push(...Object.values(this.weapon));
|
||||
return items;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -66,6 +66,7 @@ const armor = {
|
||||
202310: { },
|
||||
202401: { },
|
||||
202406: { },
|
||||
202407: { },
|
||||
301404: { },
|
||||
301703: { },
|
||||
301704: { },
|
||||
@@ -226,6 +227,7 @@ const head = {
|
||||
202403: { },
|
||||
202404: { },
|
||||
202406: { },
|
||||
202407: { },
|
||||
301404: { },
|
||||
301405: { },
|
||||
301703: { },
|
||||
|
||||
@@ -2,6 +2,9 @@ import defaults from 'lodash/defaults';
|
||||
import each from 'lodash/each';
|
||||
import { assign } from 'lodash';
|
||||
import t from './translation';
|
||||
import datedMemoize from '../fns/datedMemoize';
|
||||
import { filterReleased } from './is_released';
|
||||
import { HATCHING_POTIONS_RELEASE_DATES } from './constants/releaseDates';
|
||||
|
||||
function hasQuestAchievementFunction (key) {
|
||||
return user => user.achievements.quests && user.achievements.quests[key] > 0;
|
||||
@@ -194,8 +197,23 @@ each(wacky, (pot, key) => {
|
||||
});
|
||||
});
|
||||
|
||||
const all = assign({}, drops, premium, wacky);
|
||||
function filterEggs (eggs) {
|
||||
return filterReleased(eggs, 'key', HATCHING_POTIONS_RELEASE_DATES);
|
||||
}
|
||||
|
||||
export {
|
||||
drops, premium, wacky, all,
|
||||
const memoizedFilter = datedMemoize(filterEggs);
|
||||
|
||||
export default {
|
||||
get drops () {
|
||||
return memoizedFilter({ memoizeConfig: true, identifier: 'drops' }, drops);
|
||||
},
|
||||
get premium () {
|
||||
return memoizedFilter({ memoizeConfig: true, identifier: 'premium' }, premium);
|
||||
},
|
||||
get wacky () {
|
||||
return memoizedFilter({ memoizeConfig: true, identifier: 'wacky' }, wacky);
|
||||
},
|
||||
get all () {
|
||||
return assign({}, this.drops, this.premium, this.wacky);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -18,9 +18,9 @@ import {
|
||||
|
||||
import achievements from './achievements';
|
||||
|
||||
import * as eggs from './eggs';
|
||||
import * as hatchingPotions from './hatching-potions';
|
||||
import * as stable from './stable';
|
||||
import eggs from './eggs';
|
||||
import hatchingPotions from './hatching-potions';
|
||||
import stable from './stable';
|
||||
import gear from './gear';
|
||||
import { quests, questsByLevel, userCanOwnQuestCategories } from './quests';
|
||||
|
||||
@@ -38,6 +38,7 @@ import { REPEATING_EVENTS, getRepeatingEvents } from './constants/events';
|
||||
import loginIncentives from './loginIncentives';
|
||||
|
||||
import officialPinnedItems from './officialPinnedItems';
|
||||
import memoize from '../fns/datedMemoize';
|
||||
|
||||
const api = {};
|
||||
|
||||
@@ -165,9 +166,18 @@ api.cardTypes = {
|
||||
|
||||
api.special = api.spells.special;
|
||||
|
||||
api.dropEggs = eggs.drops;
|
||||
api.questEggs = eggs.quests;
|
||||
api.eggs = eggs.all;
|
||||
Object.defineProperty(api, 'dropEggs', {
|
||||
get () { return eggs.drops; },
|
||||
enumerable: true,
|
||||
});
|
||||
Object.defineProperty(api, 'questEggs', {
|
||||
get () { return eggs.quests; },
|
||||
enumerable: true,
|
||||
});
|
||||
Object.defineProperty(api, 'eggs', {
|
||||
get () { return eggs.all; },
|
||||
enumerable: true,
|
||||
});
|
||||
|
||||
api.timeTravelStable = {
|
||||
pets: {
|
||||
@@ -186,298 +196,354 @@ api.timeTravelStable = {
|
||||
},
|
||||
};
|
||||
|
||||
api.dropHatchingPotions = hatchingPotions.drops;
|
||||
api.premiumHatchingPotions = hatchingPotions.premium;
|
||||
api.wackyHatchingPotions = hatchingPotions.wacky;
|
||||
api.hatchingPotions = hatchingPotions.all;
|
||||
|
||||
api.pets = stable.dropPets;
|
||||
api.premiumPets = stable.premiumPets;
|
||||
api.questPets = stable.questPets;
|
||||
api.specialPets = stable.specialPets;
|
||||
api.wackyPets = stable.wackyPets;
|
||||
api.petInfo = stable.petInfo;
|
||||
|
||||
api.mounts = stable.dropMounts;
|
||||
api.questMounts = stable.questMounts;
|
||||
api.premiumMounts = stable.premiumMounts;
|
||||
api.specialMounts = stable.specialMounts;
|
||||
api.mountInfo = stable.mountInfo;
|
||||
|
||||
api.food = {
|
||||
Meat: {
|
||||
text: t('foodMeat'),
|
||||
textA: t('foodMeatA'),
|
||||
textThe: t('foodMeatThe'),
|
||||
target: 'Base',
|
||||
},
|
||||
Milk: {
|
||||
text: t('foodMilk'),
|
||||
textA: t('foodMilkA'),
|
||||
textThe: t('foodMilkThe'),
|
||||
target: 'White',
|
||||
},
|
||||
Potatoe: {
|
||||
text: t('foodPotatoe'),
|
||||
textA: t('foodPotatoeA'),
|
||||
textThe: t('foodPotatoeThe'),
|
||||
target: 'Desert',
|
||||
},
|
||||
Strawberry: {
|
||||
text: t('foodStrawberry'),
|
||||
textA: t('foodStrawberryA'),
|
||||
textThe: t('foodStrawberryThe'),
|
||||
target: 'Red',
|
||||
},
|
||||
Chocolate: {
|
||||
text: t('foodChocolate'),
|
||||
textA: t('foodChocolateA'),
|
||||
textThe: t('foodChocolateThe'),
|
||||
target: 'Shade',
|
||||
},
|
||||
Fish: {
|
||||
text: t('foodFish'),
|
||||
textA: t('foodFishA'),
|
||||
textThe: t('foodFishThe'),
|
||||
target: 'Skeleton',
|
||||
},
|
||||
RottenMeat: {
|
||||
text: t('foodRottenMeat'),
|
||||
textA: t('foodRottenMeatA'),
|
||||
textThe: t('foodRottenMeatThe'),
|
||||
target: 'Zombie',
|
||||
},
|
||||
CottonCandyPink: {
|
||||
text: t('foodCottonCandyPink'),
|
||||
textA: t('foodCottonCandyPinkA'),
|
||||
textThe: t('foodCottonCandyPinkThe'),
|
||||
target: 'CottonCandyPink',
|
||||
},
|
||||
CottonCandyBlue: {
|
||||
text: t('foodCottonCandyBlue'),
|
||||
textA: t('foodCottonCandyBlueA'),
|
||||
textThe: t('foodCottonCandyBlueThe'),
|
||||
target: 'CottonCandyBlue',
|
||||
},
|
||||
Honey: {
|
||||
text: t('foodHoney'),
|
||||
textA: t('foodHoneyA'),
|
||||
textThe: t('foodHoneyThe'),
|
||||
target: 'Golden',
|
||||
},
|
||||
Saddle: {
|
||||
sellWarningNote: t('foodSaddleSellWarningNote'),
|
||||
text: t('foodSaddleText'),
|
||||
value: 5,
|
||||
notes: t('foodSaddleNotes'),
|
||||
canDrop: false,
|
||||
},
|
||||
/* eslint-disable camelcase */
|
||||
Cake_Skeleton: {
|
||||
text: t('foodCakeSkeleton'),
|
||||
textA: t('foodCakeSkeletonA'),
|
||||
textThe: t('foodCakeSkeletonThe'),
|
||||
target: 'Skeleton',
|
||||
},
|
||||
Cake_Base: {
|
||||
text: t('foodCakeBase'),
|
||||
textA: t('foodCakeBaseA'),
|
||||
textThe: t('foodCakeBaseThe'),
|
||||
target: 'Base',
|
||||
},
|
||||
Cake_CottonCandyBlue: {
|
||||
text: t('foodCakeCottonCandyBlue'),
|
||||
textA: t('foodCakeCottonCandyBlueA'),
|
||||
textThe: t('foodCakeCottonCandyBlueThe'),
|
||||
target: 'CottonCandyBlue',
|
||||
},
|
||||
Cake_CottonCandyPink: {
|
||||
text: t('foodCakeCottonCandyPink'),
|
||||
textA: t('foodCakeCottonCandyPinkA'),
|
||||
textThe: t('foodCakeCottonCandyPinkThe'),
|
||||
target: 'CottonCandyPink',
|
||||
},
|
||||
Cake_Shade: {
|
||||
text: t('foodCakeShade'),
|
||||
textA: t('foodCakeShadeA'),
|
||||
textThe: t('foodCakeShadeThe'),
|
||||
target: 'Shade',
|
||||
},
|
||||
Cake_White: {
|
||||
text: t('foodCakeWhite'),
|
||||
textA: t('foodCakeWhiteA'),
|
||||
textThe: t('foodCakeWhiteThe'),
|
||||
target: 'White',
|
||||
},
|
||||
Cake_Golden: {
|
||||
text: t('foodCakeGolden'),
|
||||
textA: t('foodCakeGoldenA'),
|
||||
textThe: t('foodCakeGoldenThe'),
|
||||
target: 'Golden',
|
||||
},
|
||||
Cake_Zombie: {
|
||||
text: t('foodCakeZombie'),
|
||||
textA: t('foodCakeZombieA'),
|
||||
textThe: t('foodCakeZombieThe'),
|
||||
target: 'Zombie',
|
||||
},
|
||||
Cake_Desert: {
|
||||
text: t('foodCakeDesert'),
|
||||
textA: t('foodCakeDesertA'),
|
||||
textThe: t('foodCakeDesertThe'),
|
||||
target: 'Desert',
|
||||
},
|
||||
Cake_Red: {
|
||||
text: t('foodCakeRed'),
|
||||
textA: t('foodCakeRedA'),
|
||||
textThe: t('foodCakeRedThe'),
|
||||
target: 'Red',
|
||||
},
|
||||
Candy_Skeleton: {
|
||||
text: t('foodCandySkeleton'),
|
||||
textA: t('foodCandySkeletonA'),
|
||||
textThe: t('foodCandySkeletonThe'),
|
||||
target: 'Skeleton',
|
||||
},
|
||||
Candy_Base: {
|
||||
text: t('foodCandyBase'),
|
||||
textA: t('foodCandyBaseA'),
|
||||
textThe: t('foodCandyBaseThe'),
|
||||
target: 'Base',
|
||||
},
|
||||
Candy_CottonCandyBlue: {
|
||||
text: t('foodCandyCottonCandyBlue'),
|
||||
textA: t('foodCandyCottonCandyBlueA'),
|
||||
textThe: t('foodCandyCottonCandyBlueThe'),
|
||||
target: 'CottonCandyBlue',
|
||||
},
|
||||
Candy_CottonCandyPink: {
|
||||
text: t('foodCandyCottonCandyPink'),
|
||||
textA: t('foodCandyCottonCandyPinkA'),
|
||||
textThe: t('foodCandyCottonCandyPinkThe'),
|
||||
target: 'CottonCandyPink',
|
||||
},
|
||||
Candy_Shade: {
|
||||
text: t('foodCandyShade'),
|
||||
textA: t('foodCandyShadeA'),
|
||||
textThe: t('foodCandyShadeThe'),
|
||||
target: 'Shade',
|
||||
},
|
||||
Candy_White: {
|
||||
text: t('foodCandyWhite'),
|
||||
textA: t('foodCandyWhiteA'),
|
||||
textThe: t('foodCandyWhiteThe'),
|
||||
target: 'White',
|
||||
},
|
||||
Candy_Golden: {
|
||||
text: t('foodCandyGolden'),
|
||||
textA: t('foodCandyGoldenA'),
|
||||
textThe: t('foodCandyGoldenThe'),
|
||||
target: 'Golden',
|
||||
},
|
||||
Candy_Zombie: {
|
||||
text: t('foodCandyZombie'),
|
||||
textA: t('foodCandyZombieA'),
|
||||
textThe: t('foodCandyZombieThe'),
|
||||
target: 'Zombie',
|
||||
},
|
||||
Candy_Desert: {
|
||||
text: t('foodCandyDesert'),
|
||||
textA: t('foodCandyDesertA'),
|
||||
textThe: t('foodCandyDesertThe'),
|
||||
target: 'Desert',
|
||||
},
|
||||
Candy_Red: {
|
||||
text: t('foodCandyRed'),
|
||||
textA: t('foodCandyRedA'),
|
||||
textThe: t('foodCandyRedThe'),
|
||||
target: 'Red',
|
||||
},
|
||||
Pie_Skeleton: {
|
||||
text: t('foodPieSkeleton'),
|
||||
textA: t('foodPieSkeletonA'),
|
||||
textThe: t('foodPieSkeletonThe'),
|
||||
target: 'Skeleton',
|
||||
},
|
||||
Pie_Base: {
|
||||
text: t('foodPieBase'),
|
||||
textA: t('foodPieBaseA'),
|
||||
textThe: t('foodPieBaseThe'),
|
||||
target: 'Base',
|
||||
},
|
||||
Pie_CottonCandyBlue: {
|
||||
text: t('foodPieCottonCandyBlue'),
|
||||
textA: t('foodPieCottonCandyBlueA'),
|
||||
textThe: t('foodPieCottonCandyBlueThe'),
|
||||
target: 'CottonCandyBlue',
|
||||
},
|
||||
Pie_CottonCandyPink: {
|
||||
text: t('foodPieCottonCandyPink'),
|
||||
textA: t('foodPieCottonCandyPinkA'),
|
||||
textThe: t('foodPieCottonCandyPinkThe'),
|
||||
target: 'CottonCandyPink',
|
||||
},
|
||||
Pie_Shade: {
|
||||
text: t('foodPieShade'),
|
||||
textA: t('foodPieShadeA'),
|
||||
textThe: t('foodPieShadeThe'),
|
||||
target: 'Shade',
|
||||
},
|
||||
Pie_White: {
|
||||
text: t('foodPieWhite'),
|
||||
textA: t('foodPieWhiteA'),
|
||||
textThe: t('foodPieWhiteThe'),
|
||||
target: 'White',
|
||||
},
|
||||
Pie_Golden: {
|
||||
text: t('foodPieGolden'),
|
||||
textA: t('foodPieGoldenA'),
|
||||
textThe: t('foodPieGoldenThe'),
|
||||
target: 'Golden',
|
||||
},
|
||||
Pie_Zombie: {
|
||||
text: t('foodPieZombie'),
|
||||
textA: t('foodPieZombieA'),
|
||||
textThe: t('foodPieZombieThe'),
|
||||
target: 'Zombie',
|
||||
},
|
||||
Pie_Desert: {
|
||||
text: t('foodPieDesert'),
|
||||
textA: t('foodPieDesertA'),
|
||||
textThe: t('foodPieDesertThe'),
|
||||
target: 'Desert',
|
||||
},
|
||||
Pie_Red: {
|
||||
text: t('foodPieRed'),
|
||||
textA: t('foodPieRedA'),
|
||||
textThe: t('foodPieRedThe'),
|
||||
target: 'Red',
|
||||
},
|
||||
/* eslint-enable camelcase */
|
||||
};
|
||||
|
||||
let FOOD_SEASON = 'Normal';
|
||||
getRepeatingEvents(moment()).forEach(event => {
|
||||
if (event.foodSeason) {
|
||||
FOOD_SEASON = event.foodSeason;
|
||||
}
|
||||
Object.defineProperty(api, 'dropHatchingPotions', {
|
||||
get () { return hatchingPotions.drops; },
|
||||
enumerable: true,
|
||||
});
|
||||
each(api.food, (food, key) => {
|
||||
let foodType = 'Normal';
|
||||
if (key.startsWith('Cake_')) {
|
||||
foodType = 'Cake';
|
||||
} else if (key.startsWith('Candy_')) {
|
||||
foodType = 'Candy';
|
||||
} else if (key.startsWith('Pie_')) {
|
||||
foodType = 'Pie';
|
||||
}
|
||||
defaults(food, {
|
||||
value: 1,
|
||||
key,
|
||||
notes: t('foodNotes'),
|
||||
canBuy: () => FOOD_SEASON === foodType,
|
||||
canDrop: FOOD_SEASON === foodType,
|
||||
Object.defineProperty(api, 'premiumHatchingPotions', {
|
||||
get () { return hatchingPotions.premium; },
|
||||
enumerable: true,
|
||||
});
|
||||
Object.defineProperty(api, 'wackyHatchingPotions', {
|
||||
get () { return hatchingPotions.wacky; },
|
||||
enumerable: true,
|
||||
});
|
||||
Object.defineProperty(api, 'hatchingPotions', {
|
||||
get () { return hatchingPotions.all; },
|
||||
enumerable: true,
|
||||
});
|
||||
|
||||
Object.defineProperty(api, 'dropPets', {
|
||||
get () { return stable.dropPets; },
|
||||
enumerable: true,
|
||||
});
|
||||
Object.defineProperty(api, 'premiumPets', {
|
||||
get () { return stable.premiumPets; },
|
||||
enumerable: true,
|
||||
});
|
||||
Object.defineProperty(api, 'questPets', {
|
||||
get () { return stable.questPets; },
|
||||
enumerable: true,
|
||||
});
|
||||
Object.defineProperty(api, 'specialPets', {
|
||||
get () { return stable.specialPets; },
|
||||
enumerable: true,
|
||||
});
|
||||
Object.defineProperty(api, 'wackyPets', {
|
||||
get () { return stable.wackyPets; },
|
||||
enumerable: true,
|
||||
});
|
||||
Object.defineProperty(api, 'petInfo', {
|
||||
get () { return stable.petInfo; },
|
||||
enumerable: true,
|
||||
});
|
||||
|
||||
Object.defineProperty(api, 'dropMounts', {
|
||||
get () { return stable.dropMounts; },
|
||||
enumerable: true,
|
||||
});
|
||||
Object.defineProperty(api, 'premiumMounts', {
|
||||
get () { return stable.premiumMounts; },
|
||||
enumerable: true,
|
||||
});
|
||||
Object.defineProperty(api, 'questMounts', {
|
||||
get () { return stable.questMounts; },
|
||||
enumerable: true,
|
||||
});
|
||||
Object.defineProperty(api, 'specialMounts', {
|
||||
get () { return stable.specialMounts; },
|
||||
enumerable: true,
|
||||
});
|
||||
Object.defineProperty(api, 'mountInfo', {
|
||||
get () { return stable.mountInfo; },
|
||||
enumerable: true,
|
||||
});
|
||||
|
||||
function buildFood () {
|
||||
const food = {
|
||||
Meat: {
|
||||
text: t('foodMeat'),
|
||||
textA: t('foodMeatA'),
|
||||
textThe: t('foodMeatThe'),
|
||||
target: 'Base',
|
||||
},
|
||||
Milk: {
|
||||
text: t('foodMilk'),
|
||||
textA: t('foodMilkA'),
|
||||
textThe: t('foodMilkThe'),
|
||||
target: 'White',
|
||||
},
|
||||
Potatoe: {
|
||||
text: t('foodPotatoe'),
|
||||
textA: t('foodPotatoeA'),
|
||||
textThe: t('foodPotatoeThe'),
|
||||
target: 'Desert',
|
||||
},
|
||||
Strawberry: {
|
||||
text: t('foodStrawberry'),
|
||||
textA: t('foodStrawberryA'),
|
||||
textThe: t('foodStrawberryThe'),
|
||||
target: 'Red',
|
||||
},
|
||||
Chocolate: {
|
||||
text: t('foodChocolate'),
|
||||
textA: t('foodChocolateA'),
|
||||
textThe: t('foodChocolateThe'),
|
||||
target: 'Shade',
|
||||
},
|
||||
Fish: {
|
||||
text: t('foodFish'),
|
||||
textA: t('foodFishA'),
|
||||
textThe: t('foodFishThe'),
|
||||
target: 'Skeleton',
|
||||
},
|
||||
RottenMeat: {
|
||||
text: t('foodRottenMeat'),
|
||||
textA: t('foodRottenMeatA'),
|
||||
textThe: t('foodRottenMeatThe'),
|
||||
target: 'Zombie',
|
||||
},
|
||||
CottonCandyPink: {
|
||||
text: t('foodCottonCandyPink'),
|
||||
textA: t('foodCottonCandyPinkA'),
|
||||
textThe: t('foodCottonCandyPinkThe'),
|
||||
target: 'CottonCandyPink',
|
||||
},
|
||||
CottonCandyBlue: {
|
||||
text: t('foodCottonCandyBlue'),
|
||||
textA: t('foodCottonCandyBlueA'),
|
||||
textThe: t('foodCottonCandyBlueThe'),
|
||||
target: 'CottonCandyBlue',
|
||||
},
|
||||
Honey: {
|
||||
text: t('foodHoney'),
|
||||
textA: t('foodHoneyA'),
|
||||
textThe: t('foodHoneyThe'),
|
||||
target: 'Golden',
|
||||
},
|
||||
Saddle: {
|
||||
sellWarningNote: t('foodSaddleSellWarningNote'),
|
||||
text: t('foodSaddleText'),
|
||||
value: 5,
|
||||
notes: t('foodSaddleNotes'),
|
||||
canBuy: () => true,
|
||||
canDrop: false,
|
||||
},
|
||||
/* eslint-disable camelcase */
|
||||
Cake_Skeleton: {
|
||||
text: t('foodCakeSkeleton'),
|
||||
textA: t('foodCakeSkeletonA'),
|
||||
textThe: t('foodCakeSkeletonThe'),
|
||||
target: 'Skeleton',
|
||||
},
|
||||
Cake_Base: {
|
||||
text: t('foodCakeBase'),
|
||||
textA: t('foodCakeBaseA'),
|
||||
textThe: t('foodCakeBaseThe'),
|
||||
target: 'Base',
|
||||
},
|
||||
Cake_CottonCandyBlue: {
|
||||
text: t('foodCakeCottonCandyBlue'),
|
||||
textA: t('foodCakeCottonCandyBlueA'),
|
||||
textThe: t('foodCakeCottonCandyBlueThe'),
|
||||
target: 'CottonCandyBlue',
|
||||
},
|
||||
Cake_CottonCandyPink: {
|
||||
text: t('foodCakeCottonCandyPink'),
|
||||
textA: t('foodCakeCottonCandyPinkA'),
|
||||
textThe: t('foodCakeCottonCandyPinkThe'),
|
||||
target: 'CottonCandyPink',
|
||||
},
|
||||
Cake_Shade: {
|
||||
text: t('foodCakeShade'),
|
||||
textA: t('foodCakeShadeA'),
|
||||
textThe: t('foodCakeShadeThe'),
|
||||
target: 'Shade',
|
||||
},
|
||||
Cake_White: {
|
||||
text: t('foodCakeWhite'),
|
||||
textA: t('foodCakeWhiteA'),
|
||||
textThe: t('foodCakeWhiteThe'),
|
||||
target: 'White',
|
||||
},
|
||||
Cake_Golden: {
|
||||
text: t('foodCakeGolden'),
|
||||
textA: t('foodCakeGoldenA'),
|
||||
textThe: t('foodCakeGoldenThe'),
|
||||
target: 'Golden',
|
||||
},
|
||||
Cake_Zombie: {
|
||||
text: t('foodCakeZombie'),
|
||||
textA: t('foodCakeZombieA'),
|
||||
textThe: t('foodCakeZombieThe'),
|
||||
target: 'Zombie',
|
||||
},
|
||||
Cake_Desert: {
|
||||
text: t('foodCakeDesert'),
|
||||
textA: t('foodCakeDesertA'),
|
||||
textThe: t('foodCakeDesertThe'),
|
||||
target: 'Desert',
|
||||
},
|
||||
Cake_Red: {
|
||||
text: t('foodCakeRed'),
|
||||
textA: t('foodCakeRedA'),
|
||||
textThe: t('foodCakeRedThe'),
|
||||
target: 'Red',
|
||||
},
|
||||
Candy_Skeleton: {
|
||||
text: t('foodCandySkeleton'),
|
||||
textA: t('foodCandySkeletonA'),
|
||||
textThe: t('foodCandySkeletonThe'),
|
||||
target: 'Skeleton',
|
||||
},
|
||||
Candy_Base: {
|
||||
text: t('foodCandyBase'),
|
||||
textA: t('foodCandyBaseA'),
|
||||
textThe: t('foodCandyBaseThe'),
|
||||
target: 'Base',
|
||||
},
|
||||
Candy_CottonCandyBlue: {
|
||||
text: t('foodCandyCottonCandyBlue'),
|
||||
textA: t('foodCandyCottonCandyBlueA'),
|
||||
textThe: t('foodCandyCottonCandyBlueThe'),
|
||||
target: 'CottonCandyBlue',
|
||||
},
|
||||
Candy_CottonCandyPink: {
|
||||
text: t('foodCandyCottonCandyPink'),
|
||||
textA: t('foodCandyCottonCandyPinkA'),
|
||||
textThe: t('foodCandyCottonCandyPinkThe'),
|
||||
target: 'CottonCandyPink',
|
||||
},
|
||||
Candy_Shade: {
|
||||
text: t('foodCandyShade'),
|
||||
textA: t('foodCandyShadeA'),
|
||||
textThe: t('foodCandyShadeThe'),
|
||||
target: 'Shade',
|
||||
},
|
||||
Candy_White: {
|
||||
text: t('foodCandyWhite'),
|
||||
textA: t('foodCandyWhiteA'),
|
||||
textThe: t('foodCandyWhiteThe'),
|
||||
target: 'White',
|
||||
},
|
||||
Candy_Golden: {
|
||||
text: t('foodCandyGolden'),
|
||||
textA: t('foodCandyGoldenA'),
|
||||
textThe: t('foodCandyGoldenThe'),
|
||||
target: 'Golden',
|
||||
},
|
||||
Candy_Zombie: {
|
||||
text: t('foodCandyZombie'),
|
||||
textA: t('foodCandyZombieA'),
|
||||
textThe: t('foodCandyZombieThe'),
|
||||
target: 'Zombie',
|
||||
},
|
||||
Candy_Desert: {
|
||||
text: t('foodCandyDesert'),
|
||||
textA: t('foodCandyDesertA'),
|
||||
textThe: t('foodCandyDesertThe'),
|
||||
target: 'Desert',
|
||||
},
|
||||
Candy_Red: {
|
||||
text: t('foodCandyRed'),
|
||||
textA: t('foodCandyRedA'),
|
||||
textThe: t('foodCandyRedThe'),
|
||||
target: 'Red',
|
||||
},
|
||||
Pie_Skeleton: {
|
||||
text: t('foodPieSkeleton'),
|
||||
textA: t('foodPieSkeletonA'),
|
||||
textThe: t('foodPieSkeletonThe'),
|
||||
target: 'Skeleton',
|
||||
},
|
||||
Pie_Base: {
|
||||
text: t('foodPieBase'),
|
||||
textA: t('foodPieBaseA'),
|
||||
textThe: t('foodPieBaseThe'),
|
||||
target: 'Base',
|
||||
},
|
||||
Pie_CottonCandyBlue: {
|
||||
text: t('foodPieCottonCandyBlue'),
|
||||
textA: t('foodPieCottonCandyBlueA'),
|
||||
textThe: t('foodPieCottonCandyBlueThe'),
|
||||
target: 'CottonCandyBlue',
|
||||
},
|
||||
Pie_CottonCandyPink: {
|
||||
text: t('foodPieCottonCandyPink'),
|
||||
textA: t('foodPieCottonCandyPinkA'),
|
||||
textThe: t('foodPieCottonCandyPinkThe'),
|
||||
target: 'CottonCandyPink',
|
||||
},
|
||||
Pie_Shade: {
|
||||
text: t('foodPieShade'),
|
||||
textA: t('foodPieShadeA'),
|
||||
textThe: t('foodPieShadeThe'),
|
||||
target: 'Shade',
|
||||
},
|
||||
Pie_White: {
|
||||
text: t('foodPieWhite'),
|
||||
textA: t('foodPieWhiteA'),
|
||||
textThe: t('foodPieWhiteThe'),
|
||||
target: 'White',
|
||||
},
|
||||
Pie_Golden: {
|
||||
text: t('foodPieGolden'),
|
||||
textA: t('foodPieGoldenA'),
|
||||
textThe: t('foodPieGoldenThe'),
|
||||
target: 'Golden',
|
||||
},
|
||||
Pie_Zombie: {
|
||||
text: t('foodPieZombie'),
|
||||
textA: t('foodPieZombieA'),
|
||||
textThe: t('foodPieZombieThe'),
|
||||
target: 'Zombie',
|
||||
},
|
||||
Pie_Desert: {
|
||||
text: t('foodPieDesert'),
|
||||
textA: t('foodPieDesertA'),
|
||||
textThe: t('foodPieDesertThe'),
|
||||
target: 'Desert',
|
||||
},
|
||||
Pie_Red: {
|
||||
text: t('foodPieRed'),
|
||||
textA: t('foodPieRedA'),
|
||||
textThe: t('foodPieRedThe'),
|
||||
target: 'Red',
|
||||
},
|
||||
/* eslint-enable camelcase */
|
||||
};
|
||||
|
||||
let FOOD_SEASON = 'Normal';
|
||||
getRepeatingEvents(moment()).forEach(event => {
|
||||
if (event.foodSeason) {
|
||||
FOOD_SEASON = event.foodSeason;
|
||||
}
|
||||
});
|
||||
each(food, (foodItem, key) => {
|
||||
let foodType = 'Normal';
|
||||
if (key.startsWith('Cake_')) {
|
||||
foodType = 'Cake';
|
||||
} else if (key.startsWith('Candy_')) {
|
||||
foodType = 'Candy';
|
||||
} else if (key.startsWith('Pie_')) {
|
||||
foodType = 'Pie';
|
||||
}
|
||||
defaults(foodItem, {
|
||||
value: 1,
|
||||
key,
|
||||
notes: t('foodNotes'),
|
||||
canBuy: () => FOOD_SEASON === foodType,
|
||||
canDrop: FOOD_SEASON === foodType,
|
||||
});
|
||||
});
|
||||
|
||||
return food;
|
||||
}
|
||||
|
||||
const memoizedBuildFood = memoize(buildFood);
|
||||
|
||||
Object.defineProperty(api, 'food', {
|
||||
get () { return memoizedBuildFood(); },
|
||||
});
|
||||
|
||||
api.appearances = appearances;
|
||||
|
||||
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));
|
||||
}
|
||||
@@ -232,6 +232,38 @@ const QUEST_PETS = {
|
||||
unlock: t('questButterflyUnlockText'),
|
||||
},
|
||||
},
|
||||
chameleon: {
|
||||
text: t('questChameleonText'),
|
||||
notes: t('questChameleonNotes'),
|
||||
completion: t('questChameleonCompletion'),
|
||||
value: 4,
|
||||
category: 'pet',
|
||||
boss: {
|
||||
name: t('questChameleonBoss'),
|
||||
hp: 400,
|
||||
str: 1.5,
|
||||
},
|
||||
drop: {
|
||||
items: [
|
||||
{
|
||||
type: 'eggs',
|
||||
key: 'Chameleon',
|
||||
text: t('questChameleonDropChameleonEgg'),
|
||||
}, {
|
||||
type: 'eggs',
|
||||
key: 'Chameleon',
|
||||
text: t('questChameleonDropChameleonEgg'),
|
||||
}, {
|
||||
type: 'eggs',
|
||||
key: 'Chameleon',
|
||||
text: t('questChameleonDropChameleonEgg'),
|
||||
},
|
||||
],
|
||||
gp: 35,
|
||||
exp: 250,
|
||||
unlock: t('questChameleonUnlockText'),
|
||||
},
|
||||
},
|
||||
cheetah: {
|
||||
text: t('questCheetahText'),
|
||||
notes: t('questCheetahNotes'),
|
||||
|
||||
@@ -20,6 +20,7 @@ const potentialFeaturedPetQuests = [
|
||||
'giraffe',
|
||||
|
||||
'guineapig',
|
||||
'chameleon',
|
||||
|
||||
'cheetah',
|
||||
|
||||
@@ -34,7 +35,6 @@ const potentialFeaturedPetQuests = [
|
||||
'sabretooth',
|
||||
];
|
||||
|
||||
// hatching potions and food names should be capitalized lest you break the market
|
||||
const featuredItems = {
|
||||
market () {
|
||||
const featured = [{
|
||||
|
||||
@@ -1,21 +1,12 @@
|
||||
import each from 'lodash/each';
|
||||
import moment from 'moment';
|
||||
import { EVENTS } from './constants/events';
|
||||
import {
|
||||
drops as dropEggs,
|
||||
quests as questEggs,
|
||||
} from './eggs';
|
||||
import {
|
||||
drops as dropPotions,
|
||||
premium as premiumPotions,
|
||||
wacky as wackyPotions,
|
||||
} from './hatching-potions';
|
||||
import allEggs from './eggs';
|
||||
import allPotions from './hatching-potions';
|
||||
import t from './translation';
|
||||
import memoize from '../fns/datedMemoize';
|
||||
|
||||
const petInfo = {};
|
||||
const mountInfo = {};
|
||||
|
||||
function constructSet (type, eggs, potions) {
|
||||
function constructSet (type, eggs, potions, petInfo, mountInfo, hasMounts = true) {
|
||||
const pets = {};
|
||||
const mounts = {};
|
||||
|
||||
@@ -37,52 +28,24 @@ function constructSet (type, eggs, potions) {
|
||||
potion: potion.text,
|
||||
egg: egg.text,
|
||||
}));
|
||||
mountInfo[key] = getAnimalData(t('mountName', {
|
||||
potion: potion.text,
|
||||
mount: egg.mountText,
|
||||
}));
|
||||
|
||||
pets[key] = true;
|
||||
mounts[key] = true;
|
||||
});
|
||||
});
|
||||
|
||||
return [pets, mounts];
|
||||
}
|
||||
|
||||
function constructPetOnlySet (type, eggs, potions) {
|
||||
const pets = {};
|
||||
|
||||
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,
|
||||
};
|
||||
if (hasMounts) {
|
||||
mountInfo[key] = getAnimalData(t('mountName', {
|
||||
potion: potion.text,
|
||||
mount: egg.mountText,
|
||||
}));
|
||||
mounts[key] = true;
|
||||
}
|
||||
|
||||
petInfo[key] = getAnimalData(t('petName', {
|
||||
potion: potion.text,
|
||||
egg: egg.text,
|
||||
}));
|
||||
pets[key] = true;
|
||||
});
|
||||
});
|
||||
|
||||
if (hasMounts) {
|
||||
return [pets, mounts];
|
||||
}
|
||||
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 = {
|
||||
pets: {
|
||||
// Veteran Pet Ladder - awarded on major updates
|
||||
@@ -208,44 +171,88 @@ const specialMounts = {
|
||||
'JackOLantern-RoyalPurple': 'royalPurpleJackolantern',
|
||||
};
|
||||
|
||||
each(specialPets, (translationString, key) => {
|
||||
petInfo[key] = {
|
||||
key,
|
||||
type: 'special',
|
||||
text: t(translationString),
|
||||
canFind: canFindSpecial.pets[key],
|
||||
};
|
||||
});
|
||||
function buildInfo () {
|
||||
const petInfo = {};
|
||||
const mountInfo = {};
|
||||
|
||||
Object.assign(petInfo['Gryphatrice-Jubilant'], {
|
||||
canBuy () {
|
||||
return moment().isBetween(EVENTS.birthday10.start, EVENTS.birthday10.end);
|
||||
const [dropPets, dropMounts] = constructSet('drop', allEggs.drops, allPotions.drops, petInfo, mountInfo);
|
||||
const [premiumPets, premiumMounts] = constructSet('premium', allEggs.drops, allPotions.premium, petInfo, mountInfo);
|
||||
const [questPets, questMounts] = constructSet('quest', allEggs.quests, allPotions.drops, petInfo, mountInfo);
|
||||
const wackyPets = constructSet('wacky', allEggs.drops, allPotions.wacky, petInfo, mountInfo, false);
|
||||
|
||||
each(specialPets, (translationString, key) => {
|
||||
petInfo[key] = {
|
||||
key,
|
||||
type: 'special',
|
||||
text: t(translationString),
|
||||
canFind: canFindSpecial.pets[key],
|
||||
};
|
||||
});
|
||||
|
||||
Object.assign(petInfo['Gryphatrice-Jubilant'], {
|
||||
canBuy () {
|
||||
return moment().isBetween(EVENTS.birthday10.start, EVENTS.birthday10.end);
|
||||
},
|
||||
currency: 'gems',
|
||||
event: 'birthday10',
|
||||
value: 60,
|
||||
purchaseType: 'pets',
|
||||
});
|
||||
|
||||
each(specialMounts, (translationString, key) => {
|
||||
mountInfo[key] = {
|
||||
key,
|
||||
type: 'special',
|
||||
text: t(translationString),
|
||||
canFind: canFindSpecial.mounts[key],
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
dropPets,
|
||||
premiumPets,
|
||||
questPets,
|
||||
wackyPets,
|
||||
dropMounts,
|
||||
questMounts,
|
||||
premiumMounts,
|
||||
specialPets,
|
||||
specialMounts,
|
||||
petInfo,
|
||||
mountInfo,
|
||||
};
|
||||
}
|
||||
|
||||
const memoizedBuildInfo = memoize(buildInfo);
|
||||
|
||||
export default {
|
||||
get dropPets () {
|
||||
return memoizedBuildInfo().dropPets;
|
||||
},
|
||||
get premiumPets () {
|
||||
return memoizedBuildInfo().premiumPets;
|
||||
},
|
||||
get questPets () {
|
||||
return memoizedBuildInfo().questPets;
|
||||
},
|
||||
get wackyPets () {
|
||||
return memoizedBuildInfo().wackyPets;
|
||||
},
|
||||
get dropMounts () {
|
||||
return memoizedBuildInfo().dropMounts;
|
||||
},
|
||||
get questMounts () {
|
||||
return memoizedBuildInfo().questMounts;
|
||||
},
|
||||
get premiumMounts () {
|
||||
return memoizedBuildInfo().premiumMounts;
|
||||
},
|
||||
get petInfo () {
|
||||
return memoizedBuildInfo().petInfo;
|
||||
},
|
||||
get mountInfo () {
|
||||
return memoizedBuildInfo().mountInfo;
|
||||
},
|
||||
currency: 'gems',
|
||||
event: 'birthday10',
|
||||
value: 60,
|
||||
purchaseType: 'pets',
|
||||
});
|
||||
|
||||
each(specialMounts, (translationString, key) => {
|
||||
mountInfo[key] = {
|
||||
key,
|
||||
type: 'special',
|
||||
text: t(translationString),
|
||||
canFind: canFindSpecial.mounts[key],
|
||||
};
|
||||
});
|
||||
|
||||
export {
|
||||
dropPets,
|
||||
premiumPets,
|
||||
questPets,
|
||||
wackyPets,
|
||||
dropMounts,
|
||||
questMounts,
|
||||
premiumMounts,
|
||||
specialPets,
|
||||
specialMounts,
|
||||
petInfo,
|
||||
mountInfo,
|
||||
};
|
||||
|
||||
@@ -33,6 +33,10 @@ const memoize = fn => {
|
||||
identifier = config.identifier;
|
||||
}
|
||||
}
|
||||
|
||||
if (identifier.length === 0) {
|
||||
identifier = args.filter(arg => typeof arg === 'string').join('-');
|
||||
}
|
||||
}
|
||||
if (!checkedDate) {
|
||||
checkedDate = new Date();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { drops as eggs } from '../content/eggs';
|
||||
import { drops as hatchingPotions } from '../content/hatching-potions';
|
||||
import allEggs from '../content/eggs';
|
||||
import allPotions from '../content/hatching-potions';
|
||||
import randomVal from '../libs/randomVal';
|
||||
|
||||
export default function firstDrops (user) {
|
||||
const eggDrop = randomVal(eggs);
|
||||
const potionDrop = randomVal(hatchingPotions);
|
||||
const eggDrop = randomVal(allEggs.drops);
|
||||
const potionDrop = randomVal(allPotions.drops);
|
||||
|
||||
user.items.eggs = {
|
||||
...user.items.eggs,
|
||||
|
||||
Reference in New Issue
Block a user