import moment from 'moment'; import SEASONAL_SETS from './seasonalSets'; import { getRepeatingEvents } from './events'; function isAfterNewSchedule (year, month) { if (year >= 2025) { return true; } if (year === 2024) { return month >= 6; } return false; } function backgroundMatcher (month1, month2, oddYear) { return function call (key, date) { const keyLength = key.length; const month = parseInt(key.substring(keyLength - 6, keyLength - 4), 10); const year = parseInt(key.substring(keyLength - 4, keyLength), 10); if (isAfterNewSchedule(year, month)) { return month === month1 && date.getFullYear() >= year && (date.getMonth() + 1) >= month; } return (month === month1 || month === month2) && year % 2 === (oddYear ? 1 : 0); }; } function timeTravelersMatcher (month1, month2) { return function call (item, date) { const month = parseInt(item.substring(4, 6), 10); const year = parseInt(item.substring(0, 4), 10); if (date.getFullYear() === year && (date.getMonth() + 1) >= month) { return false; } return month === month1 || month === month2; }; } function inListMatcher (list) { return function call (item) { return list.indexOf(item) !== -1; }; } export const ALWAYS_AVAILABLE_CUSTOMIZATIONS = [ 'animalSkins', 'rainbowSkins', 'rainbowHairColors', 'specialShirts', 'facialHair', 'baseHair1', 'baseHair2', 'baseHair3', ]; function customizationMatcher (list) { return function call (item) { if (ALWAYS_AVAILABLE_CUSTOMIZATIONS.indexOf(item) !== -1) { return true; } return list.indexOf(item) !== -1; }; } export const FIRST_RELEASE_DAY = 1; export const SECOND_RELEASE_DAY = 7; export const THIRD_RELEASE_DAY = 14; export const FOURTH_RELEASE_DAY = 21; export const MONTHLY_SCHEDULE = { // January 0: { [FIRST_RELEASE_DAY]: [ { type: 'timeTravelers', matcher: timeTravelersMatcher(1, 7), }, ], [SECOND_RELEASE_DAY]: [ { type: 'backgrounds', matcher: backgroundMatcher(1, 7, false), }, ], [THIRD_RELEASE_DAY]: [ { type: 'petQuests', items: [ 'nudibranch', 'seaserpent', 'gryphon', 'yarn', 'axolotl', ], }, { type: 'hatchingPotionQuests', items: [ 'silver', ], }, { type: 'bundles', items: [ 'winterQuests', ], }, ], [FOURTH_RELEASE_DAY]: [ { type: 'premiumHatchingPotions', items: [ 'Aurora', 'Cupid', 'IcySnow', ], }, ], }, // February 1: { [FIRST_RELEASE_DAY]: [ { type: 'timeTravelers', matcher: timeTravelersMatcher(2, 8), }, ], [SECOND_RELEASE_DAY]: [ { type: 'backgrounds', matcher: backgroundMatcher(2, 8, false), }, ], [THIRD_RELEASE_DAY]: [ { type: 'petQuests', items: [ 'rooster', 'slime', 'peacock', 'bunny', ], }, { type: 'hatchingPotionQuests', items: [ 'pinkMarble', ], }, { type: 'bundles', items: [ 'cuddleBuddies', 'mythicalMarvels', ], }, ], [FOURTH_RELEASE_DAY]: [ { type: 'premiumHatchingPotions', items: [ 'PolkaDot', 'Celestial', ], }, ], }, // March 2: { [FIRST_RELEASE_DAY]: [ { type: 'timeTravelers', matcher: timeTravelersMatcher(3, 9), }, ], [SECOND_RELEASE_DAY]: [ { type: 'backgrounds', matcher: backgroundMatcher(3, 9, false), }, ], [THIRD_RELEASE_DAY]: [ { type: 'petQuests', items: [ 'frog', 'spider', 'cow', 'pterodactyl', ], }, { type: 'hatchingPotionQuests', items: [ ], }, { type: 'bundles', items: [ 'birdBuddies', ], }, ], [FOURTH_RELEASE_DAY]: [ { type: 'premiumHatchingPotions', items: [ 'StainedGlass', 'Porcelain', ], }, ], }, // April 3: { [FIRST_RELEASE_DAY]: [ { type: 'timeTravelers', matcher: timeTravelersMatcher(4, 10), }, ], [SECOND_RELEASE_DAY]: [ { type: 'backgrounds', matcher: backgroundMatcher(4, 10, false), }, ], [THIRD_RELEASE_DAY]: [ { type: 'petQuests', items: [ 'snake', 'monkey', 'falcon', 'alligator', ], }, { type: 'hatchingPotionQuests', items: [ 'stone', ], }, { type: 'bundles', items: [ 'hugabug', ], }, ], [FOURTH_RELEASE_DAY]: [ { type: 'premiumHatchingPotions', items: [ 'Shimmer', 'Glass', ], }, ], }, // May 4: { [FIRST_RELEASE_DAY]: [ { type: 'timeTravelers', matcher: timeTravelersMatcher(5, 11), }, ], [SECOND_RELEASE_DAY]: [ { type: 'backgrounds', matcher: backgroundMatcher(5, 11, false), }, ], [THIRD_RELEASE_DAY]: [ { type: 'petQuests', items: [ 'octopus', 'horse', 'kraken', 'sloth', ], }, { type: 'hatchingPotionQuests', items: [ ], }, { type: 'bundles', items: [ 'splashyPals', ], }, ], [FOURTH_RELEASE_DAY]: [ { type: 'premiumHatchingPotions', items: [ 'Floral', 'Fairy', 'RoseQuartz', ], }, ], }, // June 5: { [FIRST_RELEASE_DAY]: [ { type: 'timeTravelers', matcher: timeTravelersMatcher(6, 12), }, ], [SECOND_RELEASE_DAY]: [ { type: 'backgrounds', matcher: backgroundMatcher(6, 12, false), }, ], [THIRD_RELEASE_DAY]: [ { type: 'petQuests', items: [ 'trex', 'unicorn', 'velociraptor', 'hippo', 'giraffe', ], }, { type: 'hatchingPotionQuests', items: [ 'turquoise', ], }, { type: 'bundles', items: [ 'delightfulDinos', 'sandySidekicks', ], }, ], [FOURTH_RELEASE_DAY]: [ { type: 'premiumHatchingPotions', items: [ 'Rainbow', 'Sunshine', 'Koi', ], }, ], }, // July 6: { [FIRST_RELEASE_DAY]: [ { type: 'timeTravelers', matcher: timeTravelersMatcher(7, 1), }, ], [SECOND_RELEASE_DAY]: [ { type: 'backgrounds', matcher: backgroundMatcher(7, 1, true), }, ], [THIRD_RELEASE_DAY]: [ { type: 'petQuests', items: [ 'whale', 'dilatory_derby', 'armadillo', 'guineapig', ], }, { type: 'hatchingPotionQuests', items: [ 'fluorite', ], }, { type: 'bundles', items: [ 'aquaticAmigos', 'jungleBuddies', ], }, ], [FOURTH_RELEASE_DAY]: [ { type: 'premiumHatchingPotions', items: [ 'Moonglow', 'Watery', ], }, ], }, // August 7: { [FIRST_RELEASE_DAY]: [ { type: 'timeTravelers', matcher: timeTravelersMatcher(8, 2), }, ], [SECOND_RELEASE_DAY]: [ { type: 'backgrounds', matcher: backgroundMatcher(8, 2, true), }, ], [THIRD_RELEASE_DAY]: [ { type: 'petQuests', items: [ 'turtle', 'penguin', 'butterfly', 'cheetah', ], }, { type: 'hatchingPotionQuests', items: [ 'blackPearl', ], }, { type: 'bundles', items: [ 'featheredFriends', ], }, ], [FOURTH_RELEASE_DAY]: [ { type: 'premiumHatchingPotions', items: [ 'Aquatic', 'StarryNight', 'Sunset', ], }, ], }, // September 8: { [FIRST_RELEASE_DAY]: [ { type: 'timeTravelers', matcher: timeTravelersMatcher(9, 3), }, ], [SECOND_RELEASE_DAY]: [ { type: 'backgrounds', matcher: backgroundMatcher(9, 3, true), }, ], [THIRD_RELEASE_DAY]: [ { type: 'petQuests', items: [ 'squirrel', 'triceratops', 'treeling', 'beetle', ], }, { type: 'hatchingPotionQuests', items: [ 'bronze', ], }, { type: 'bundles', items: [ 'farmFriends', ], }, ], [FOURTH_RELEASE_DAY]: [ { type: 'premiumHatchingPotions', items: [ 'Glow', 'AutumnLeaf', 'Shadow', ], }, ], }, // October 9: { [FIRST_RELEASE_DAY]: [ { type: 'timeTravelers', matcher: timeTravelersMatcher(10, 4), }, ], [SECOND_RELEASE_DAY]: [ { type: 'backgrounds', matcher: backgroundMatcher(10, 4, true), }, ], [THIRD_RELEASE_DAY]: [ { type: 'petQuests', items: [ 'snail', 'rock', 'ferret', 'hedgehog', ], }, { type: 'hatchingPotionQuests', items: [ 'onyx', ], }, { type: 'bundles', items: [ 'witchyFamiliars', ], }, ], [FOURTH_RELEASE_DAY]: [ { type: 'premiumHatchingPotions', items: [ 'Vampire', 'Ghost', 'Spooky', ], }, ], }, // November 10: { [FIRST_RELEASE_DAY]: [ { type: 'timeTravelers', matcher: timeTravelersMatcher(11, 5), }, ], [SECOND_RELEASE_DAY]: [ { type: 'backgrounds', matcher: backgroundMatcher(11, 5, true), }, ], [THIRD_RELEASE_DAY]: [ { type: 'petQuests', items: [ 'sheep', 'kangaroo', 'owl', 'rat', 'badger', ], }, { type: 'hatchingPotionQuests', items: [ 'amber', ], }, { type: 'bundles', items: [ 'forestFriends', ], }, ], [FOURTH_RELEASE_DAY]: [ { type: 'premiumHatchingPotions', items: [ 'Ember', 'Frost', 'Thunderstorm', ], }, ], }, // December 11: { [FIRST_RELEASE_DAY]: [ { type: 'timeTravelers', matcher: timeTravelersMatcher(12, 6), }, ], [SECOND_RELEASE_DAY]: [ { type: 'backgrounds', matcher: backgroundMatcher(12, 6, true), }, ], [THIRD_RELEASE_DAY]: [ { type: 'petQuests', items: [ 'ghost_stag', 'trex_undead', 'harpy', 'sabretooth', 'dolphin', ], }, { type: 'hatchingPotionQuests', items: [ 'ruby', ], }, { type: 'bundles', items: [ 'rockingReptiles', ], }, ], [FOURTH_RELEASE_DAY]: [ { type: 'premiumHatchingPotions', items: [ 'Peppermint', 'Holly', ], }, ], }, }; export const GALA_SWITCHOVER_DAY = 21; export const GALA_KEYS = [ 'winter', 'spring', 'summer', 'fall', ]; export const GALA_SCHEDULE = { // Winter 0: { startMonth: 11, endMonth: 2, matchers: [ { type: 'seasonalGear', items: SEASONAL_SETS.winter, }, { type: 'seasonalSpells', items: [ 'snowball', ], }, { type: 'seasonalQuests', items: [ 'evilsanta', 'evilsanta2', ], }, { type: 'customizations', matcher: customizationMatcher([ 'winteryHairColors', 'winterySkins', ]), }, ], }, // Spring 1: { startMonth: 2, endMonth: 5, matchers: [ { type: 'seasonalGear', items: SEASONAL_SETS.spring, }, { type: 'seasonalSpells', items: [ 'shinySeed', ], }, { type: 'seasonalQuests', items: [ 'egg', ], }, { type: 'customizations', matcher: customizationMatcher([ 'shimmerHairColors', 'pastelSkins', ]), }, ], }, // Summer 2: { startMonth: 5, endMonth: 8, matchers: [ { type: 'seasonalGear', items: SEASONAL_SETS.summer, }, { type: 'seasonalSpells', items: [ 'seafoam', ], }, { type: 'seasonalQuests', items: [ ], }, { type: 'customizations', matcher: customizationMatcher([ 'summerHairColors', 'splashySkins', ]), }, ], }, // Fall 3: { startMonth: 8, endMonth: 11, matchers: [ { type: 'seasonalGear', items: SEASONAL_SETS.fall, }, { type: 'seasonalSpells', items: [ 'spookySparkles', ], }, { type: 'seasonalQuests', items: [ ], }, { type: 'customizations', matcher: customizationMatcher([ 'hauntedHairColors', 'supernaturalSkins', ]), }, ], }, }; export const TYPE_SCHEDULE = { timeTravelers: FIRST_RELEASE_DAY, backgrounds: SECOND_RELEASE_DAY, petQuests: THIRD_RELEASE_DAY, hatchingPotionQuests: THIRD_RELEASE_DAY, bundles: THIRD_RELEASE_DAY, premiumHatchingPotions: FOURTH_RELEASE_DAY, seasonalGear: GALA_SWITCHOVER_DAY, seasonalSpells: GALA_SWITCHOVER_DAY, seasonalQuests: GALA_SWITCHOVER_DAY, customizations: GALA_SWITCHOVER_DAY, }; function getDay (date) { if (date === undefined) { return 0; } return date instanceof moment ? date.date() : date.getDate(); } function getMonth (date) { if (date === undefined) { return 0; } return date instanceof moment ? date.month() : date.getMonth(); } function getGalaIndex (date) { const month = getMonth(date); const todayDay = getDay(date); let galaMonth = month; const galaCount = Object.keys(GALA_SCHEDULE).length; if (todayDay >= GALA_SWITCHOVER_DAY) { galaMonth += 1; } if (galaMonth >= 12) { return 0; } return parseInt((galaCount / 12) * galaMonth, 10); } export function assembleScheduledMatchers (date) { const items = []; const month = getMonth(date); const todayDay = getDay(date); const previousMonth = month === 0 ? 11 : month - 1; for (const [day, value] of Object.entries(MONTHLY_SCHEDULE[previousMonth])) { if (day > todayDay) { items.push(...value); } } for (const [day, value] of Object.entries(MONTHLY_SCHEDULE[month])) { if (day <= todayDay) { items.push(...value); } } const gala = GALA_SCHEDULE[getGalaIndex(date)]; const galaMatchers = gala.matchers; galaMatchers.forEach(matcher => { matcher.endMonth = gala.endMonth; }); items.push(...galaMatchers); getRepeatingEvents(date).forEach(event => { if (event.content) { items.push(...event.content); } }); return items; } let cachedScheduleMatchers = null; let cacheDate = null; function makeMatcherClass (date) { return { matchers: [], end: new Date(), items: [], matchingDate: date, match (key) { if (this.matchers.length === 0) { if (this.items.length > 0) { return inListMatcher(this.items)(key, this.matchingDate); } } else { if (this.items.length > 0 && !inListMatcher(this.items)(key, this.matchingDate)) { return false; } return this.matchers.every(m => m(key, this.matchingDate)); } return false; }, }; } function makeEndDate (checkedDate, matcher) { let end = moment.utc(checkedDate); end.date(TYPE_SCHEDULE[matcher.type]); end.hour(0); end.minute(0); end.second(0); if (matcher.endMonth !== undefined) { end.month(matcher.endMonth); } else if (end.date() <= checkedDate.getDate()) { end = moment(end).add(1, 'months'); } return end.toDate(); } export function clearCachedMatchers () { cacheDate = null; cachedScheduleMatchers = null; } export function getAllScheduleMatchingGroups (date) { const checkedDate = date || new Date(); if (cacheDate !== null && (getDay(checkedDate) !== getDay(cacheDate) || getMonth(checkedDate) !== getMonth(cacheDate))) { // Clear cached matchers, since they are old clearCachedMatchers(); } if (!cachedScheduleMatchers) { // No matchers exist, make new ones cacheDate = new Date(); cachedScheduleMatchers = {}; assembleScheduledMatchers(checkedDate).forEach(matcher => { if (!cachedScheduleMatchers[matcher.type]) { cachedScheduleMatchers[matcher.type] = makeMatcherClass(checkedDate); } cachedScheduleMatchers[matcher.type].end = makeEndDate(checkedDate, matcher); if (matcher.matcher instanceof Function) { cachedScheduleMatchers[matcher.type].matchers.push(matcher.matcher); } else if (matcher.items instanceof Array) { cachedScheduleMatchers[matcher.type].items.push(...matcher.items); } }); } return cachedScheduleMatchers; } export function getScheduleMatchingGroup (type, date) { const matchingGroups = getAllScheduleMatchingGroups(date); if (!matchingGroups[type]) { // No matchers exist for this type return { items: [], match () { return true; }, }; } return matchingGroups[type]; } export function getCurrentGalaKey (date) { return GALA_KEYS[getGalaIndex(date || new Date())]; } export function getCurrentGalaEvent (date) { const checkedDate = date || new Date(); const index = getGalaIndex(checkedDate); const key = GALA_KEYS[index]; const gala = GALA_SCHEDULE[index]; const today = new Date(); return { event: key, npcImageSuffix: `_${key}`, season: key, gear: true, start: `${today.getFullYear()}.${gala.startMonth + 1}.${GALA_SWITCHOVER_DAY}`, end: `${today.getFullYear()}.${gala.endMonth + 1}.${GALA_SWITCHOVER_DAY}`, }; }