diff --git a/test/common/ops/armoireCanOwn.js b/test/common/ops/armoireCanOwn.js index a0dd802aa0..117e9e45ee 100644 --- a/test/common/ops/armoireCanOwn.js +++ b/test/common/ops/armoireCanOwn.js @@ -1,11 +1,11 @@ -import * as armoireSet from '../../../website/common/script/content/gear/sets/armoire'; +import armoireSet from '../../../website/common/script/content/gear/sets/armoire'; describe('armoireSet items', () => { it('checks if canOwn has the same id', () => { + console.log(armoireSet); Object.keys(armoireSet).forEach(type => { Object.keys(armoireSet[type]).forEach(itemKey => { const ownedKey = `${type}_armoire_${itemKey}`; - expect(armoireSet[type][itemKey].canOwn({ items: { gear: { diff --git a/test/common/ops/buy/buyArmoire.js b/test/common/ops/buy/buyArmoire.js index b1e1545f91..172625a9f6 100644 --- a/test/common/ops/buy/buyArmoire.js +++ b/test/common/ops/buy/buyArmoire.js @@ -17,9 +17,7 @@ function getFullArmoire () { _.each(content.gearTypes, type => { _.each(content.gear.tree[type].armoire, gearObject => { - if (gearObject.released) { - fullArmoire[gearObject.key] = true; - } + fullArmoire[gearObject.key] = true; }); }); diff --git a/test/content/armoire.test.js b/test/content/armoire.test.js index b647941f3e..64cc99a3f2 100644 --- a/test/content/armoire.test.js +++ b/test/content/armoire.test.js @@ -5,23 +5,15 @@ import { } from '../helpers/content.helper'; function makeArmoireIitemList () { - const { - armor, - body, - eyewear, - head, - headAccessory, - shield, - weapon, - } = require('../../website/common/script/content/gear/sets/armoire'); + const armoire = require('../../website/common/script/content/gear/sets/armoire').default; const items = []; - items.push(...Object.values(armor)); - items.push(...Object.values(body)); - items.push(...Object.values(eyewear)); - items.push(...Object.values(head)); - items.push(...Object.values(headAccessory)); - items.push(...Object.values(shield)); - items.push(...Object.values(weapon)); + 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; } @@ -37,9 +29,7 @@ describe('armoire', () => { clock = sinon.useFakeTimers(new Date('2024-01-01')); const items = makeArmoireIitemList(); expect(items.length).to.equal(377); - forEach(items, item => { - expect(item.released, item.key).to.be.true; - }); + 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 () => { @@ -52,7 +42,6 @@ describe('armoire', () => { expect(item.set, item.key).to.not.be.empty; } expectValidTranslationString(item.text); - expect(item.released, item.key).to.be.a('boolean'); expect(item.value, item.key).to.be.a('number'); }); }); diff --git a/website/common/locales/en/subscriber.json b/website/common/locales/en/subscriber.json index 43a9d6e935..94352557ee 100644 --- a/website/common/locales/en/subscriber.json +++ b/website/common/locales/en/subscriber.json @@ -162,6 +162,7 @@ "mysterySet202403": "Lucky Legend Set", "mysterySet202404": "Mycelial Magus Set", "mysterySet202405": "Gilded Dragon Set", + "mysterySet202406": "Gilded Dragon Set", "mysterySet301404": "Steampunk Standard Set", "mysterySet301405": "Steampunk Accessories Set", "mysterySet301703": "Peacock Steampunk Set", diff --git a/website/common/script/content/constants/schedule.js b/website/common/script/content/constants/schedule.js index 3d20de6da5..8864c962b0 100644 --- a/website/common/script/content/constants/schedule.js +++ b/website/common/script/content/constants/schedule.js @@ -801,8 +801,8 @@ function getMonth (date) { } function getGalaIndex (date) { - const month = date instanceof moment ? date.month() : date.getMonth(); - const todayDay = date instanceof moment ? date.date() : date.getDate(); + const month = getMonth(date); + const todayDay = getDay(date); let galaMonth = month; const galaCount = Object.keys(GALA_SCHEDULE).length; if (todayDay >= GALA_SWITCHOVER_DAY) { @@ -816,8 +816,8 @@ function getGalaIndex (date) { export function assembleScheduledMatchers (date) { const items = []; - const month = date instanceof moment ? date.month() : date.getMonth(); - const todayDay = date instanceof moment ? date.date() : date.getDate(); + 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) { diff --git a/website/common/script/content/gear/armor.js b/website/common/script/content/gear/armor.js index a090c56364..0cb9c3460c 100644 --- a/website/common/script/content/gear/armor.js +++ b/website/common/script/content/gear/armor.js @@ -7,7 +7,7 @@ import { armor as wizardArmor } from './sets/wizard'; import { armor as specialArmor } from './sets/special'; import { armor as mysteryArmor } from './sets/mystery'; -import { armor as armoireArmor } from './sets/armoire'; +import armoire from './sets/armoire'; const armor = { base: baseArmor, @@ -19,7 +19,9 @@ const armor = { special: specialArmor, mystery: mysteryArmor, - armoire: armoireArmor, + get armoire () { + return armoire.armor; + }, }; export default armor; diff --git a/website/common/script/content/gear/body.js b/website/common/script/content/gear/body.js index 19349eb2a2..5de8ea482f 100644 --- a/website/common/script/content/gear/body.js +++ b/website/common/script/content/gear/body.js @@ -2,13 +2,15 @@ import { body as baseBody } from './sets/base'; import { body as mysteryBody } from './sets/mystery'; import { body as specialBody } from './sets/special'; -import { body as armoireBody } from './sets/armoire'; +import armoire from './sets/armoire'; const body = { base: baseBody, mystery: mysteryBody, special: specialBody, - armoire: armoireBody, + get armoire () { + return armoire.body; + }, }; export default body; diff --git a/website/common/script/content/gear/eyewear.js b/website/common/script/content/gear/eyewear.js index 2fdf54e079..038723c7cb 100644 --- a/website/common/script/content/gear/eyewear.js +++ b/website/common/script/content/gear/eyewear.js @@ -1,6 +1,6 @@ import { eyewear as baseEyewear } from './sets/base'; -import { eyewear as armoireEyewear } from './sets/armoire'; +import armoire from './sets/armoire'; import { eyewear as mysteryEyewear } from './sets/mystery'; import { eyewear as specialEyewear } from './sets/special'; @@ -8,7 +8,9 @@ const eyewear = { base: baseEyewear, special: specialEyewear, mystery: mysteryEyewear, - armoire: armoireEyewear, + get armoire () { + return armoire.eyewear; + }, }; export default eyewear; diff --git a/website/common/script/content/gear/head-accessory.js b/website/common/script/content/gear/head-accessory.js index 146c0e77b8..60903ba16b 100644 --- a/website/common/script/content/gear/head-accessory.js +++ b/website/common/script/content/gear/head-accessory.js @@ -2,13 +2,15 @@ import { headAccessory as baseHeadAccessory } from './sets/base'; import { headAccessory as specialHeadAccessory } from './sets/special'; import { headAccessory as mysteryHeadAccessory } from './sets/mystery'; -import { headAccessory as armoireHeadAccessory } from './sets/armoire'; +import armoire from './sets/armoire'; const headAccessory = { base: baseHeadAccessory, special: specialHeadAccessory, mystery: mysteryHeadAccessory, - armoire: armoireHeadAccessory, + get armoire () { + return armoire.headAccessory; + }, }; export default headAccessory; diff --git a/website/common/script/content/gear/head.js b/website/common/script/content/gear/head.js index 6eee326efb..928f5223b7 100644 --- a/website/common/script/content/gear/head.js +++ b/website/common/script/content/gear/head.js @@ -5,7 +5,7 @@ import { head as rogueHead } from './sets/rogue'; import { head as warriorHead } from './sets/warrior'; import { head as wizardHead } from './sets/wizard'; -import { head as armoireHead } from './sets/armoire'; +import armoire from './sets/armoire'; import { head as mysteryHead } from './sets/mystery'; import { head as specialHead } from './sets/special'; @@ -19,7 +19,9 @@ const head = { special: specialHead, mystery: mysteryHead, - armoire: armoireHead, + get armoire () { + return armoire.head; + }, }; export default head; diff --git a/website/common/script/content/gear/index.js b/website/common/script/content/gear/index.js index 07c3d942ee..b4a152839c 100644 --- a/website/common/script/content/gear/index.js +++ b/website/common/script/content/gear/index.js @@ -15,6 +15,7 @@ import back from './back'; import body from './body'; import headAccessory from './head-accessory'; import eyewear from './eyewear'; +import memoize from '../../fns/datedMemoize'; const gear = { weapon, @@ -27,44 +28,65 @@ const gear = { eyewear, }; -/* - The gear is exported as a tree (defined above), and a flat list - (eg, {weapon_healer_1: .., shield_special_0: ...}) since - they are needed in different forms at different points in the app - */ -const flat = {}; +function populateGear (key, klass, type, index, item) { + const set = `${klass}-${index}`; + + defaults(item, { + type, + key, + set, + klass, + index, + str: 0, + int: 0, + per: 0, + con: 0, + canBuy: () => false, + }); + + if (item.canOwn === undefined && (item.mystery || key.indexOf('takeThis') !== -1)) { + item.canOwn = ownsItem(key); + } +} each(GEAR_TYPES, type => { const allGearTypes = CLASSES.concat(['base', 'special', 'mystery', 'armoire']); - each(allGearTypes, klass => { each(gear[type][klass], (item, index) => { const key = `${type}_${klass}_${index}`; - const set = `${klass}-${index}`; - - defaults(item, { - type, - key, - set, - klass, - index, - str: 0, - int: 0, - per: 0, - con: 0, - canBuy: () => false, - }); - - if (item.mystery || key.indexOf('takeThis') !== -1) { - item.canOwn = ownsItem(key); - } - - flat[key] = item; + populateGear(key, klass, type, index, item); }); }); }); +function buildFlatList () { + /* + The gear is exported as a tree (defined above), and a flat list + (eg, {weapon_healer_1: .., shield_special_0: ...}) since + they are needed in different forms at different points in the app + */ + const flat = {}; + + each(GEAR_TYPES, type => { + const allGearTypes = CLASSES.concat(['base', 'special', 'mystery', 'armoire']); + + each(allGearTypes, klass => { + each(gear[type][klass], (item, index) => { + const key = `${type}_${klass}_${index}`; + populateGear(key, klass, type, index, item); + + flat[key] = item; + }); + }); + }); + return flat; +} + +const memoizedFlatList = memoize(buildFlatList); + export default { tree: gear, - flat, + get flat () { + return memoizedFlatList(); + }, }; diff --git a/website/common/script/content/gear/sets/armoire.js b/website/common/script/content/gear/sets/armoire.js index 8074b2f9da..065c5d8e47 100644 --- a/website/common/script/content/gear/sets/armoire.js +++ b/website/common/script/content/gear/sets/armoire.js @@ -6,6 +6,7 @@ import upperFirst from 'lodash/upperFirst'; import { ownsItem } from '../gear-helper'; import { ATTRIBUTES } from '../../../constants'; import t from '../../translation'; +import memoize from '../../../fns/datedMemoize'; const armor = { lunarArmor: { @@ -1339,7 +1340,7 @@ const shield = { con: 8, set: 'pottersSet', }, - buoyantBeachball: { + buoyantBeachBall: { str: 12, set: 'beachsideSet', }, @@ -1853,8 +1854,6 @@ forEach({ shield, weapon, }, (set, setKey) => { - const today = moment(); - const releaseDateEndPart = `${String(releaseDay).padStart(2, '0')}T08:00-0500`; forEach(set, (gearItem, gearKey) => { const gearStats = {}; const gearStatValues = []; @@ -1878,6 +1877,20 @@ forEach({ } else { notes = t(`${setKey}Armoire${upperFirst(gearKey)}Notes`); } + defaults(gearItem, { + canOwn: ownsItem(`${setKey}_armoire_${gearKey}`), + notes, + text: t(`${setKey}Armoire${upperFirst(gearKey)}Text`), + value: 100, + }); + }); +}); + +function updateReleased (type) { + const today = moment(); + const releaseDateEndPart = `${String(releaseDay).padStart(2, '0')}T08: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}`; @@ -1885,25 +1898,35 @@ forEach({ } else { released = true; } - defaults(gearItem, { - released, - canOwn: ownsItem(`${setKey}_armoire_${gearKey}`), - notes, - text: t(`${setKey}Armoire${upperFirst(gearKey)}Text`), - value: 100, - }); - if (gearItem.released === false) { - delete set[gearKey]; + if (released) { + returnType[gearKey] = gearItem; } }); -}); + return returnType; +} -export { - armor, - body, - eyewear, - head, - headAccessory, - shield, - weapon, +const memoizedUpdatReleased = memoize(updateReleased); + +export default { + get armor () { + return memoizedUpdatReleased({ identifier: 'armor', memoizeConfig: true }, armor); + }, + get body () { + return memoizedUpdatReleased({ identifier: 'body', memoizeConfig: true }, body); + }, + get eyewear () { + return memoizedUpdatReleased({ identifier: 'eyewear', memoizeConfig: true }, eyewear); + }, + get head () { + return memoizedUpdatReleased({ identifier: 'head', memoizeConfig: true }, head); + }, + get headAccessory () { + return memoizedUpdatReleased({ identifier: 'headAccessory', memoizeConfig: true }, headAccessory); + }, + get shield () { + return memoizedUpdatReleased({ identifier: 'shield', memoizeConfig: true }, shield); + }, + get weapon () { + return memoizedUpdatReleased({ identifier: 'weapon', memoizeConfig: true }, weapon); + }, }; diff --git a/website/common/script/content/gear/shield.js b/website/common/script/content/gear/shield.js index 46afefd3bb..ad5198e635 100644 --- a/website/common/script/content/gear/shield.js +++ b/website/common/script/content/gear/shield.js @@ -7,7 +7,7 @@ import { weapon as rogueWeapon } from './sets/rogue'; import { shield as warriorShield } from './sets/warrior'; import { shield as wizardShield } from './sets/wizard'; -import { shield as armoireShield } from './sets/armoire'; +import armoire from './sets/armoire'; import { shield as mysteryShield } from './sets/mystery'; import { shield as specialShield } from './sets/special'; @@ -23,7 +23,9 @@ const shield = { special: specialShield, mystery: mysteryShield, - armoire: armoireShield, + get armoire () { + return armoire.shield; + }, }; export default shield; diff --git a/website/common/script/content/gear/weapon.js b/website/common/script/content/gear/weapon.js index 9f3e2f0bdf..e7f8f5ad1f 100644 --- a/website/common/script/content/gear/weapon.js +++ b/website/common/script/content/gear/weapon.js @@ -7,7 +7,7 @@ import { weapon as rogueWeapon } from './sets/rogue'; import { weapon as warriorWeapon } from './sets/warrior'; import { weapon as wizardWeapon } from './sets/wizard'; -import { weapon as armoireWeapon } from './sets/armoire'; +import armoire from './sets/armoire'; import { weapon as mysteryWeapon } from './sets/mystery'; import { weapon as specialWeapon } from './sets/special'; @@ -21,7 +21,9 @@ const weapon = { special: specialWeapon, mystery: mysteryWeapon, - armoire: armoireWeapon, + get armoire () { + return armoire.weapon; + }, }; // Add Two Handed message to all weapons diff --git a/website/common/script/count.js b/website/common/script/count.js index ba26a1d6c8..c5035a1ac4 100644 --- a/website/common/script/count.js +++ b/website/common/script/count.js @@ -49,16 +49,17 @@ export function mountMasterProgress (mounts = {}) { export function remainingGearInSet (userGear = {}, set) { const gear = filter(content.gear.flat, item => { - const setMatches = item.klass === set; + if (item.klass !== set) { + return false; + } const hasItem = userGear[item.key]; if (has(item, 'released')) { - return item.released && setMatches && !hasItem; + return item.released && !hasItem; } - return setMatches && !hasItem; + return !hasItem; }); const count = size(gear); - return count; } diff --git a/website/common/script/fns/datedMemoize.js b/website/common/script/fns/datedMemoize.js new file mode 100644 index 0000000000..028887392a --- /dev/null +++ b/website/common/script/fns/datedMemoize.js @@ -0,0 +1,53 @@ +import moment from 'moment'; + +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(); +} + +const memoize = fn => { + const cache = {}; + const cacheDate = {}; + return (...args) => { + let checkedDate; + let identifier = ''; + if (args.length > 0) { + if (typeof args[0] === 'object' && args[0].memoizeConfig) { + const config = args.shift(); + checkedDate = config.date; + if (config.identifier) { + identifier = config.identifier; + } + } + } + if (!checkedDate) { + checkedDate = new Date(); + } + + if (cacheDate[identifier] && (getDay(checkedDate) !== getDay(cacheDate[identifier]) + || getMonth(checkedDate) !== getMonth(cacheDate[identifier]))) { + // Clear cached results, since they are old + cache[identifier] = undefined; + cacheDate[identifier] = undefined; + } + if (cache[identifier]) { + // result is already cached + return cache[identifier]; + } + const result = fn(...args); + cache[identifier] = result; + cacheDate[identifier] = checkedDate; + return result; + }; +}; + +export default memoize; diff --git a/website/common/script/ops/buy/buyArmoire.js b/website/common/script/ops/buy/buyArmoire.js index 3fe85834fd..5d9bf243ee 100644 --- a/website/common/script/ops/buy/buyArmoire.js +++ b/website/common/script/ops/buy/buyArmoire.js @@ -32,7 +32,7 @@ export class BuyArmoireOperation extends AbstractGoldItemOperation { // eslint-d let result = {}; const armoireResult = randomValFns.trueRandom(); - const eligibleEquipment = filter(content.gear.flat, eligible => eligible.klass === 'armoire' && eligible.released && !user.items.gear.owned[eligible.key]); + const eligibleEquipment = filter(content.gear.flat, eligible => eligible.klass === 'armoire' && !user.items.gear.owned[eligible.key]); const armoireHasEquipment = !isEmpty(eligibleEquipment); if (