diff --git a/src/sections/resources/2023/AugustScepter.tsx b/src/sections/resources/2023/AugustScepter.tsx index 95b18439..e87ba303 100644 --- a/src/sections/resources/2023/AugustScepter.tsx +++ b/src/sections/resources/2023/AugustScepter.tsx @@ -24,11 +24,44 @@ import MainLink from "../../../components/MainLink"; import Tile from "../../../components/Tile"; import { haveUnrestricted } from "../../../util/available"; import { skillLink } from "../../../util/links"; -import { plural } from "../../../util/text"; +import { ordinal, plural } from "../../../util/text"; -const augustScepter = $item`august scepter`; +const ALL_SKILLS = new Map([ + [1, ["Mountain Climbing Day!", "a +adv buff"]], + [2, ["Find an Eleven-Leaf Clover Day", "lucky!"]], + [3, ["Watermelon Day!", "a watermelon"]], + [4, ["Water Balloon Day!", "three water balloons"]], + [5, ["Oyster Day!", "some oyster eggs"]], + [6, ["Fresh Breath Day!", "a +com buff"]], + [7, ["Lighthouse Day!", "an item/meat buff"]], + [8, ["Cat Day!", "a catfight, meow"]], + [9, ["Hand Holding Day!", "a foe's hand held"]], + [10, ["World Lion Day!", "roars like a lion"]], + [11, ["Presidential Joke Day!", "myst stats"]], + [12, ["Elephant Day!", "mus stats"]], + [13, ["Left/Off Hander's Day!", "double offhands"]], + [14, ["Financial Awareness Day!", "bad meatgain"]], + [15, ["Relaxation Day!", "a full heal"]], + [16, ["Roller Coaster Day!", "-full & +food%"]], + [17, ["Thriftshop Day!", "a 1000 meat coupon"]], + [18, ["Serendipity Day!", "a bunch of items"]], + [19, ["Honey Bee Awareness Day!", "stalked by bees"]], + [20, ["Mosquito Day!", "HP regen"]], + [21, ["Spumoni Day!", "stats of all kinds"]], + [22, ["Tooth Fairy Day!", "a free tooth monster"]], + [23, ["Ride the Wind Day!", "mox stats"]], + [24, ["Waffle Day!", "three waffles"]], + [25, ["Banana Split Day!", "a banana split"]], + [26, ["Toilet Paper Day!", "some toilet paper"]], + [27, ["Just Because Day!", "three random effects"]], + [28, ["Race Your Mouse Day!", "a melting fam equip"]], + [29, ["More Herbs, Less Salt Day!", "a food stat enhancer"]], + [30, ["Beach Day!", "a +7 adv accessory"]], + [31, ["Cabernet Sauvignon Day!", "two bottles of +booze% wine"]], +]); const AugustScepter: FC = () => { + const augustScepter = $item`august scepter`; const skillsAvailable = 5 - get("_augSkillsCast"); if (!have(augustScepter) || skillsAvailable < 1) return null; @@ -39,262 +72,149 @@ const AugustScepter: FC = () => { ); - const augSkillsToValue: Record = { - "Aug. 1st: Mountain Climbing Day!": "a +adv buff", - "Aug. 2nd: Find an Eleven-Leaf Clover Day": "lucky!", - "Aug. 3rd: Watermelon Day!": "a watermelon", - "Aug. 4th: Water Balloon Day!": "three water balloons", - "Aug. 5th: Oyster Day!": "some oyster eggs", - "Aug. 6th: Fresh Breath Day!": "a +com buff", - "Aug. 7th: Lighthouse Day!": "an item/meat buff", - "Aug. 8th: Cat Day!": "a catfight, meow", - "Aug. 9th: Hand Holding Day!": "a foe's hand held", - "Aug. 10th: World Lion Day!": "roars like a lion", - "Aug. 11th: Presidential Joke Day!": "myst stats", - "Aug. 12th: Elephant Day!": "mus stats", - "Aug. 13th: Left/Off Hander's Day!": "double offhands", - "Aug. 14th: Financial Awareness Day!": "bad meatgain", - "Aug. 15th: Relaxation Day!": "a full heal", - "Aug. 16th: Roller Coaster Day!": "-full & +food%", - "Aug. 17th: Thriftshop Day!": "a 1000 meat coupon", - "Aug. 18th: Serendipity Day!": "a bunch of items", - "Aug. 19th: Honey Bee Awareness Day!": "stalked by bees", - "Aug. 20th: Mosquito Day!": "HP regen", - "Aug. 21st: Spumoni Day!": "stats of all kinds", - "Aug. 22nd: Tooth Fairy Day!": "a free tooth monster", - "Aug. 23rd: Ride the Wind Day!": "mox stats", - "Aug. 24th: Waffle Day!": "three waffles", - "Aug. 25th: Banana Split Day!": "a banana split", - "Aug. 26th: Toilet Paper Day!": "some toilet paper", - "Aug. 27th: Just Because Day!": "three random effects", - "Aug. 28th: Race Your Mouse Day!": "a melting fam equip", - "Aug. 29th: More Herbs, Less Salt Day!": "a food stat enhancer", - "Aug. 30th: Beach Day!": "a +7 adv accessory", - "Aug. 31st: Cabernet Sauvignon Day!": "two bottles of +booze% wine", - }; - const mainstatAugustSkill = byStat({ Muscle: 12, Mysticality: 11, Moxie: 23, }); - const grabNumber = (s: string) => { - const match = s.match(/\d+/); - return match ? parseInt(match[0]) : 0; - }; - - const usefulAugustSkills: Record = {}; + const usefulAugustSkills: [number, ReactNode][] = []; - Object.entries(augSkillsToValue).forEach(([augSkillName]) => { - const augSkillNumber = grabNumber(augSkillName); - const augSkillPref = `_aug${augSkillNumber}Cast`; + if (get("questL13Final") !== "finished") { + const statsGained = Math.floor( + 50 * + myLevel() * + (1.0 + numericModifier(`${myPrimestat()} Experience Percent`) / 100.0), + ); + usefulAugustSkills.push([mainstatAugustSkill, `+${statsGained} mainstat`]); + } + if (myPath() !== $path`Slow and Steady`) { if ( - [3, 4, 5, 8, 14, 15, 19, 20, 21, 25, 26, 27, 29].includes(augSkillNumber) + availableAmount($item`goat cheese`) <= 2 && + !haveUnrestricted($item`Mayam Calendar`) && + questStep("questL08Trapper") < 2 ) { - return; - } - if (get(augSkillPref)) return; - - if (get("questL13Final") !== "finished") { - if (augSkillNumber === mainstatAugustSkill) { - const statsGained = Math.floor( - 50 * - myLevel() * - (1.0 + - numericModifier(`${myPrimestat()} Experience Percent`) / 100.0), - ); - usefulAugustSkills[augSkillNumber] = `+${statsGained} mainstat`; - } - } - - if (myPath() !== $path`Slow and Steady`) { - if ( - availableAmount($item`goat cheese`) <= 2 && - !haveUnrestricted($item`Mayam Calendar`) && - questStep("questL08Trapper") < 2 - ) { - if (augSkillNumber === 1) { - usefulAugustSkills[1] = ( - <> - +2-5 turns{" "} - - (spend turns @ the Goatlet) - - - ); - } - } - - if (augSkillNumber === 30) { - usefulAugustSkills[30] = ( - <> - +7 advs rollover accessory{" "} - - (melting) - - - ); - } + usefulAugustSkills.push([ + 1, + <> + +2-5 turns{" "} + + (spend turns @ the Goatlet) + + , + ]); } - const manorCheck = - questStep("questL11Manor") < 3 && get("manorDrawerCount") >= 21; - const blastingAddendum = - manorCheck && !have($item`blasting soda`) ? ( + usefulAugustSkills.push([ + 30, + <> + +7 advs rollover accessory{" "} - {" "} - (blasting soda!) + (melting) - ) : null; - - if (augSkillNumber === 16) { - usefulAugustSkills[16] = ( - <>-1 fullness, +100% food drop{blastingAddendum} - ); - } + , + ]); + } - if (manorCheck && !have($item`bottle of Chateau de Vinegar`)) { - if (augSkillNumber === 31) { - usefulAugustSkills[31] = ( - <> - +100% booze drop wine{" "} - - (chateau de vinegar!) - - - ); - } - } + const manorCheck = + questStep("questL11Manor") < 3 && get("manorDrawerCount") >= 21; + const blastingAddendum = + manorCheck && !have($item`blasting soda`) ? ( + + {" "} + (blasting soda!) + + ) : null; - if (augSkillNumber === 7) { - usefulAugustSkills[7] = <>+50% item, +100% meat{buffString}; - } - if (augSkillNumber === 2) { - usefulAugustSkills[2] = ( - <> - get{" "} - - Lucky! - - - ); - } - if (augSkillNumber === 24) { - usefulAugustSkills[24] = "3 waffles, for monster replacement"; - } - if (augSkillNumber === 22) { - usefulAugustSkills[22] = "free fight for teeeeeeeeeeeth"; - } - if (questStep("questL08Trapper") < 2) { - if (augSkillNumber === 6) { - usefulAugustSkills[6] = <>+10% combat{buffString}; - } - } - if (augSkillNumber === 9) { - usefulAugustSkills[9] = "hold hands for a minor sniff"; - } - if (augSkillNumber === 10) { - usefulAugustSkills[10] = <>non-free reusable banishes{buffString}; - } + usefulAugustSkills.push([ + 16, + <>-1 fullness, +100% food drop{blastingAddendum}, + ]); - const usefulOffhands = have($item`deck of lewd playing cards`); - const protestorsRemaining = Math.max( - 0, - Math.min(80, 80 - get("zeppelinProtestors")), - ); - - if (usefulOffhands && protestorsRemaining > 10) { - if (augSkillNumber === 13) { - usefulAugustSkills[13] = ( - <> - double offhand enchantments{" "} - - (sleaze for protestors) - - - ); - } - } + if (manorCheck && !have($item`bottle of Chateau de Vinegar`)) { + usefulAugustSkills.push([ + 31, + <> + +100% booze drop wine{" "} + + (chateau de vinegar!) + + , + ]); + } - if ( - have($skill`Transcendent Olfaction`) && - (have($familiar`Pair of Stomping Boots`) || - (have($skill`The Ode to Booze`) && - have($familiar`Frumious Bandersnatch`))) - ) { - if (!have($item`astral pet sweater`)) { - if (augSkillNumber === 28) { - usefulAugustSkills[28] = ( - <> - +10 weight familiar equipment{" "} - - (melting) - - - ); - } - } - } - }); + usefulAugustSkills.push([7, <>+50% item, +100% meat{buffString}]); + usefulAugustSkills.push([ + 2, + <> + get{" "} + + Lucky! + + , + ]); + usefulAugustSkills.push([24, "3 waffles, for monster replacement"]); + usefulAugustSkills.push([22, "free fight for teeeeeeeeeeeth"]); - const table = Object.entries(usefulAugustSkills).map(([day, reason]) => { - const skillName = Object.keys(augSkillsToValue).find((name) => - name.startsWith(`Aug. ${day}`), - ); - return ( - - - {day} - - - {skillName ? ( - {reason} - ) : ( - reason - )} - - - ); - }); + if (questStep("questL08Trapper") < 2) { + usefulAugustSkills.push([6, <>+10% combat{buffString}]); + } + usefulAugustSkills.push([9, "hold hands for a minor sniff"]); + usefulAugustSkills.push([10, <>non-free reusable banishes{buffString}]); - const summarizeAugust = - "Celebrate August tidings; cast skills corresponding to the given day to get valuable benefits."; + const usefulOffhands = have($item`deck of lewd playing cards`); + const protestorsRemaining = Math.max( + 0, + Math.min(80, 80 - get("zeppelinProtestors")), + ); - const description = - table.length > 0 ? ( + if (usefulOffhands && protestorsRemaining > 10) { + usefulAugustSkills.push([ + 13, <> - {summarizeAugust} - - {table} -
- - ) : ( - {summarizeAugust} - ); + double offhand enchantments{" "} + + (sleaze for protestors) + + , + ]); + } - const title = `Cast ${plural(skillsAvailable, "August Scepter skill")}`; - const subtitle = "All buffs are 30 turns."; + if ( + have($skill`Transcendent Olfaction`) && + (have($familiar`Pair of Stomping Boots`) || + (have($skill`The Ode to Booze`) && + have($familiar`Frumious Bandersnatch`))) + ) { + if (!have($item`astral pet sweater`)) { + usefulAugustSkills.push([ + 28, + <> + +10 weight familiar equipment{" "} + + (melting) + + , + ]); + } + } - const allSkills = Object.entries(augSkillsToValue) - .sort((a, b) => grabNumber(a[0]) - grabNumber(b[0])) - .map(([augSkill, augSkillValue]) => { - const augSkillNumber = grabNumber(augSkill); - const lineColor = get(`_aug${augSkillNumber}Cast`) ? "gray.500" : "black"; + const table = usefulAugustSkills + .sort(([a], [b]) => a - b) + .map(([day, reason]) => { + const skillName = `Aug. ${ordinal(day)}: ${ALL_SKILLS.get(day)?.[0]}`; return ( - - - {augSkillNumber} + + + {day} - - {augSkillValue} + + {skillName ? ( + {reason} + ) : ( + reason + )} ); @@ -306,19 +226,50 @@ const AugustScepter: FC = () => { Well, you asked for it! - {allSkills} + + {[...ALL_SKILLS.entries()] + .sort(([a], [b]) => a - b) + .map(([skillNumber, [, skillDesc]]) => { + const lineColor = get(`_aug${skillNumber}Cast`) + ? "gray.500" + : "black"; + return ( + + + + + ); + })} +
+ {skillNumber} + + {skillDesc} +
); return ( - {subtitle} - {description} + + Celebrate August tidings; cast skills corresponding to the given day to + get valuable benefits. + + {table.length > 0 && ( + + {table} +
+ )} { - const cursedMonkeysPaw = $item`cursed monkey's paw`; - - const monkeyWishesLeft = CursedMonkeyPaw.wishes(); - - if (!haveUnrestricted(cursedMonkeysPaw) || monkeyWishesLeft === 0) { - return null; - } - - const showWish = ({ - target, - currentlyAccessible, - additionalDescription, - }: MonkeyWish) => { - const color = currentlyAccessible ? "black" : "gray.500"; - - return ( - - {target.name} - {additionalDescription && <>: {additionalDescription}} - - ); - }; +interface MonkeySkill { + fingerCount: number; + theSkill: Skill; + description: string; +} - const inRunWishes: MonkeyWish[] = [ +function inRunWishes() { + return [ { target: $item`sonar-in-a-biscuit`, shouldDisplay: @@ -282,36 +265,20 @@ const CursedMonkeysPaw = () => { currentlyAccessible: canAdventure($location`The Valley of Rof L'm Fao`), }, ]; +} - const aftercoreWishes: MonkeyWish[] = [ +function aftercoreWishes() { + return [ { target: $item`bag of foreign bribes`, shouldDisplay: canAdventure($location`The Ice Hotel`), currentlyAccessible: true, }, ]; +} - const showWishes = (wishes: MonkeyWish[]) => { - const currentWishes = wishes - .filter((wish) => wish.shouldDisplay && wish.currentlyAccessible) - .map(showWish); - const futureWishes = wishes - .filter((wish) => wish.shouldDisplay && !wish.currentlyAccessible) - .map(showWish); - return [...currentWishes, ...futureWishes]; - }; - - const options = inRun() - ? showWishes(inRunWishes) - : showWishes(aftercoreWishes); - - interface MonkeySkill { - fingerCount: number; - theSkill: Skill; - description: string; - } - - const monkeySkills: MonkeySkill[] = [ +function monkeySkills(): MonkeySkill[] { + return [ { fingerCount: 5, theSkill: $skill`Monkey Slap`, @@ -334,6 +301,45 @@ const CursedMonkeysPaw = () => { description: "Olfaction-lite", }, ]; +} + +function showWish({ + target, + currentlyAccessible, + additionalDescription, +}: MonkeyWish) { + const color = currentlyAccessible ? "black" : "gray.500"; + + return ( + + {target.name} + {additionalDescription && <>: {additionalDescription}} + + ); +} + +function showWishes(wishes: MonkeyWish[]) { + const currentWishes = wishes + .filter((wish) => wish.shouldDisplay && wish.currentlyAccessible) + .map(showWish); + const futureWishes = wishes + .filter((wish) => wish.shouldDisplay && !wish.currentlyAccessible) + .map(showWish); + return [...currentWishes, ...futureWishes]; +} + +const CursedMonkeysPaw = () => { + const cursedMonkeysPaw = $item`cursed monkey's paw`; + + const monkeyWishesLeft = CursedMonkeyPaw.wishes(); + + if (!haveUnrestricted(cursedMonkeysPaw) || monkeyWishesLeft === 0) { + return null; + } + + const options = inRun() + ? showWishes(inRunWishes()) + : showWishes(aftercoreWishes()); return ( { )} Monkey skills: - {monkeySkills.map((skill) => ( + {monkeySkills().map((skill) => ( {plural(skill.fingerCount, "finger", "fingers")}: diff --git a/src/sections/resources/2023/PatrioticEagle.tsx b/src/sections/resources/2023/PatrioticEagle.tsx index ee8cc4dd..3bd53e89 100644 --- a/src/sections/resources/2023/PatrioticEagle.tsx +++ b/src/sections/resources/2023/PatrioticEagle.tsx @@ -31,35 +31,76 @@ import { plural } from "../../../util/text"; const PLEDGE_ZONES: readonly { effect: string; - locations: [string, string][]; + locations: Record; }[] = [ { effect: "+30% item", - locations: [ - ["Haunted Library", "The Haunted Library"], - ["Haunted Laundry Room", "The Haunted Laundry Room"], - ["Whitey's Grove", "Whitey's Grove"], - ], + locations: { + "The Haunted Library": "Haunted Library", + "The Haunted Laundry Room": "Haunted Laundry Room", + "Whitey's Grove": "Whitey's Grove", + }, }, { effect: "+50% meat", - locations: [ - ["Ninja Snowmen Lair", "Lair of the Ninja Snowmen"], - ["Hidden Hospital", "The Hidden Hospital"], - ["Haunted Bathroom", "The Haunted Bathroom"], - ["the Oasis", "The Oasis"], - ], + locations: { + "Lair of the Ninja Snowmen": "Ninja Snowmen Lair", + "The Hidden Hospital": "Hidden Hospital", + "The Haunted Bathroom": "Haunted Bathroom", + "The Oasis": "the Oasis", + }, }, { effect: "+100% init", - locations: [ - ["Haunted Kitchen", "The Haunted Kitchen"], - ["Oil Peak", "Oil Peak"], - ["Oliver's Tavern", "An Unusually Quiet Barroom Brawl"], - ], + locations: { + "The Haunted Kitchen": "Haunted Kitchen", + "Oil Peak": "Oil Peak", + "An Unusually Quiet Barroom Brawl": "Oliver's Tavern", + }, }, ]; +const PLEDGE_ZONES_ALL: Record = {}; +for (const { effect, locations } of PLEDGE_ZONES) { + for (const [longName, shortName] of Object.entries(locations)) { + PLEDGE_ZONES_ALL[longName] = [effect, shortName]; + } +} + +const generatePledgeZones = ( + locations: [string, string][], + effect: string, +): ReactNode => { + const available = locations.filter(([loc]) => + canAdventure($location`${loc}`), + ); + return ( + available.length > 0 && ( + + {effect}:{" "} + {available.map(([name]) => name).join(", ")} + + ) + ); +}; + +const generatePhylumOptions = ( + phylum: string, + options: [string, string, boolean][], +): ReactNode => { + const available = options.filter( + ([, loc, useful]) => canAdventure($location`${loc}`) && useful, + ); + return ( + available.length > 0 && ( + + {phylum}:{" "} + {available.map(([name]) => name).join(", ")} + + ) + ); +}; + const PatrioticEagle = () => { const patrioticEagle = $familiar`Patriotic Eagle`; const haveEagle = haveUnrestricted(patrioticEagle); @@ -73,12 +114,8 @@ const PatrioticEagle = () => { const canUseCitizen = !haveCitizen && canEquip(patrioticEagle) && myPath() !== $path`Avant Guard`; const location = myLocation(); - const pledgeZone = PLEDGE_ZONES.map(({ effect, locations }) => ({ - effect, - location: locations.find(([, name]) => name === location.identifierString), - })).find(({ location }) => location !== undefined); - const pledgeZoneEffect = pledgeZone?.effect; - const pledgeZoneName = pledgeZone?.location?.[0]; + const pledgeZone = PLEDGE_ZONES_ALL[location.identifierString] ?? []; + const [pledgeZoneEffect, pledgeZoneName] = pledgeZone; useNag( () => ({ @@ -112,7 +149,7 @@ const PatrioticEagle = () => { ], ); - if (!haveUnrestricted(patrioticEagle)) return null; + if (!haveEagle) return null; const possibleAppearanceLocations = rwbMonster ? getMonsterLocations(rwbMonster).filter((location) => @@ -120,42 +157,10 @@ const PatrioticEagle = () => { ) : []; - const generatePledgeZones = ( - locations: [string, string][], - effect: string, - ): ReactNode => - locations.some(([, loc]) => canAdventure($location`${loc}`)) && ( - - {effect}:{" "} - {locations - .filter(([, loc]) => canAdventure($location`${loc}`)) - .map(([name]) => name) - .join(", ")} - - ); - const pledgeZones = PLEDGE_ZONES.map(({ effect, locations }) => - generatePledgeZones(locations, effect), + generatePledgeZones(Object.entries(locations), effect), ); - const generatePhylumOptions = ( - phylum: string, - options: [string, string, boolean][], - ): ReactNode => - options.some( - ([, loc, useful]) => canAdventure($location`${loc}`) && useful, - ) && ( - - {phylum}:{" "} - {options - .filter( - ([, loc, useful]) => canAdventure($location`${loc}`) && useful, - ) - .map(([name]) => name) - .join(", ")} - - ); - const phylumOptions = [ generatePhylumOptions("Dude", [ [