Squashed commit of the following:

commit 28193f86fb
Author: Phillip Thelen <phillip@habitica.com>
Date:   Fri Jun 21 11:12:18 2024 +0200

    Fix serving memoized content

commit 877fe48225
Author: Phillip Thelen <phillip@habitica.com>
Date:   Thu Jun 20 12:23:24 2024 +0200

    correctly memoize conent api

commit e0f6f79c5b
Author: Phillip Thelen <phillip@habitica.com>
Date:   Thu Jun 20 10:11:27 2024 +0200

    don’t build multiple times on heroku

commit f62254d68e
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 19:40:20 2024 +0200

    fix client command

commit d054e6fc16
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 19:36:57 2024 +0200

    correct build call

commit 7231f699c1
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 19:32:32 2024 +0200

    try setting up with heroku buildpack

commit 1dae0793fd
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 18:50:32 2024 +0200

    call gulp build:prod

commit f18fbe86b6
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 18:40:53 2024 +0200

    build client

commit 61a61724ca
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 18:33:18 2024 +0200

    testing

commit 93cf30eb18
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 18:20:25 2024 +0200

    integration fix

commit cff08adcd0
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 18:13:20 2024 +0200

    specify dev docker file

commit 4da2ed4a1f
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 18:10:07 2024 +0200

    initialize stub

commit 11c5b26c59
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 18:08:45 2024 +0200

    test heroku file

commit ac85bb2e2d
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 18:03:15 2024 +0200

    fix stub reference

commit 74dfb2710f
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 18:01:27 2024 +0200

    test fixes

commit 8dbd3c3db1
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 17:37:04 2024 +0200

    fix canOwn test

commit 74b3b348ff
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 17:32:31 2024 +0200

    fix buy test

commit 3386d61fde
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 17:30:37 2024 +0200

    fix debug tests

commit 19da14531c
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 17:05:25 2024 +0200

    add chameleon to featured quests

commit 254dd80f24
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 17:05:14 2024 +0200

    fix import

commit 0bc3f16b4b
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 16:33:22 2024 +0200

    add new content to new release file

commit 5184973bd5
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 16:33:11 2024 +0200

    fix release date tests

commit b6accca5ca
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 16:33:06 2024 +0200

    fix armoire tests

commit fec68e6211
Author: Phillip Thelen <phillip@habitica.com>
Date:   Wed Jun 19 16:02:03 2024 +0200

    fix tests

commit fc63c906dd
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Jun 10 14:44:21 2024 +0200

    Improve test coverage

commit 3333f8f0f5
Author: Phillip Thelen <phillip@habitica.com>
Date:   Mon Jun 10 14:24:59 2024 +0200

    allow hatching potions to have a release date

commit 89a3ac3dde
Author: 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

commit 16551ec83f
Merge: f5f4974a73 2645bf6023
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Jun 18 15:03:12 2024 -0400

    Merge branch '2024-07-content-prebuild' into subs-private

commit 2645bf6023
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Jun 18 15:02:47 2024 -0400

    update habitica images

commit f5f4974a73
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Jun 18 14:58:13 2024 -0400

    update habitica-images

commit 162e337d14
Merge: f2506c3231 21a7d36b7b
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Jun 18 13:46:03 2024 -0400

    Merge branch '2024-07-content-prebuild' into subs-private

commit 21a7d36b7b
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Jun 18 13:45:09 2024 -0400

    update sprites

commit f2506c3231
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Jun 18 13:24:21 2024 -0400

    updated sprites css

commit d47641e25a
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Jun 18 12:46:59 2024 -0400

    typo fix

commit fb8479ad1e
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Mon Jun 17 13:44:36 2024 -0400

    finish July prebuild

commit 3810cf3ef3
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Fri Jun 14 10:42:47 2024 -0400

    add chameleon quest

commit d05da3722c
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Jun 13 17:12:43 2024 -0400

    add June background notes

commit b8a3440ef2
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Jun 13 16:40:04 2024 -0400

    fix mystery item and background description

commit 44d63032d8
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Jun 13 15:38:23 2024 -0400

    add subscriber gear, enchanted armoire, and background

commit 9d7da91ec6
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Jun 13 14:44:59 2024 -0400

    add sprites
This commit is contained in:
Sabe Jones
2024-06-28 09:49:08 -05:00
parent b46e2da61b
commit 287014518d
31 changed files with 976 additions and 468 deletions

View File

@@ -623,6 +623,9 @@ const backgrounds = {
backgrounds062024: {
shell_gate: { },
},
backgrounds072024: {
river_bottom: { },
},
eventBackgrounds: {
birthday_bash: {
price: 0,

View 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 },
};

View File

@@ -377,6 +377,7 @@ export const MONTHLY_SCHEDULE = {
'dilatory_derby',
'armadillo',
'guineapig',
'chameleon',
],
},
{

View File

@@ -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);
},
};

View File

@@ -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;
},
};

View File

@@ -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: { },

View File

@@ -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);
},
};

View File

@@ -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;

View 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));
}

View File

@@ -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'),

View File

@@ -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 = [{

View File

@@ -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,
};