From bacac4e72c57ed207bc4b82920344e5607995e48 Mon Sep 17 00:00:00 2001
From: Seth Posner <smartseth@hotmail.com>
Date: Sun, 18 Dec 2022 17:07:09 -0800
Subject: [PATCH] Get dye elf functional and into management

---
 src/data/layers/dyes.tsx              | 73 +++++++++++++++++++-------
 src/data/layers/elves.tsx             | 22 +++++---
 src/data/layers/management.tsx        | 74 +++++++++++++++++++++++----
 src/data/layers/paper.tsx             | 16 ++++--
 src/data/layers/wrapping-paper.tsx    | 69 +++----------------------
 src/features/milestones/milestone.tsx |  6 +--
 6 files changed, 153 insertions(+), 107 deletions(-)

diff --git a/src/data/layers/dyes.tsx b/src/data/layers/dyes.tsx
index 7eeb7c1..1bfd305 100644
--- a/src/data/layers/dyes.tsx
+++ b/src/data/layers/dyes.tsx
@@ -112,7 +112,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
                     }))
                 );
             }
-            if (options.color === "red" || options.color === "yellow") {
+            if (["red", "yellow"].includes(options.color)) {
                 modifiers.push(
                     createMultiplicativeModifier(() => ({
                         multiplier: boosts.orange1,
@@ -120,7 +120,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
                     }))
                 );
             }
-            if (options.color == "yellow" || options.color == "blue") {
+            if (["yellow", "blue"].includes(options.color)) {
                 modifiers.push(
                     createMultiplicativeModifier(() => ({
                         multiplier: boosts.green1,
@@ -128,7 +128,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
                     }))
                 );
             }
-            if (options.color == "red" || options.color == "blue") {
+            if (["red", "blue"].includes(options.color)) {
                 modifiers.push(
                     createMultiplicativeModifier(() => ({
                         multiplier: boosts.purple1,
@@ -136,7 +136,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
                     }))
                 );
             }
-            if (options.color == "red" || options.color == "yellow" || options.color == "blue") {
+            if (["red", "yellow", "blue"].includes(options.color)) {
                 modifiers.push(
                     createMultiplicativeModifier(() => ({
                         multiplier: 2,
@@ -156,21 +156,17 @@ const layer = createLayer(id, function (this: BaseLayer) {
                 modifiers.push(
                     createMultiplicativeModifier(() => ({
                         multiplier: 2,
-                        description: "Wrapping Paper Milestone 1",
-                        enabled: wrappingPaper.milestones.primaryBoost.earned
+                        description: "Carol Level 1",
+                        enabled: management.elfTraining.dyeElfTraining.milestones[0].earned
                     }))
                 );
             }
-            if (
-                options.color == "orange" ||
-                options.color == "green" ||
-                options.color == "purple"
-            ) {
+            if (["orange", "green", "purple"].includes(options.color)) {
                 modifiers.push(
                     createMultiplicativeModifier(() => ({
                         multiplier: 2,
-                        description: "Wrapping Paper Milestone 2",
-                        enabled: wrappingPaper.milestones.secondaryBoost.earned
+                        description: "Carol Level 2",
+                        enabled: management.elfTraining.dyeElfTraining.milestones[1].earned
                     }))
                 );
             }
@@ -194,6 +190,24 @@ const layer = createLayer(id, function (this: BaseLayer) {
         }) as WithRequired<Modifier, "description" | "revert">;
         const computedToGenerate = computed(() => toGenerate.apply(0));
 
+        let dyeBook: ElfBuyable & {
+            resource: Resource;
+            freeLevels: ComputedRef<DecimalSource>;
+            totalAmount: ComputedRef<DecimalSource>;
+        };
+        switch (options.color) {
+            case 'red':
+            case 'yellow':
+            case 'blue':
+                dyeBook = paper.books.primaryDyeBook;
+                break;
+            case 'orange':
+            case 'green':
+            case 'purple':
+                dyeBook = paper.books.secondaryDyeBook;
+                break;
+        }
+
         const buyable: ElfBuyable = createBuyable(() => {
             const costs = convertComputable(options.costs);
             return {
@@ -250,12 +264,12 @@ const layer = createLayer(id, function (this: BaseLayer) {
                     let v = buyable.amount.value;
                     if (Decimal.gte(v, 25)) v = Decimal.pow(v, 2).div(20); // intentional price jump #2
                     if (Decimal.gte(v, 10)) v = Decimal.pow(v, 2).div(5); // intentional price jump
-                    v = Decimal.mul(v, Decimal.pow(0.95, paper.books.dyeBook.totalAmount.value));
+                    v = Decimal.mul(v, Decimal.pow(0.95, dyeBook.totalAmount.value));
                     return Decimal.div(v, 10).plus(1);
                 },
                 inverseCostPre(x: DecimalSource) {
                     let v = Decimal.sub(x, 1).mul(10);
-                    v = v.div(Decimal.pow(0.95, paper.books.dyeBook.totalAmount.value));
+                    v = v.div(Decimal.pow(0.95, dyeBook.totalAmount.value));
                     if (Decimal.gte(v, 10)) v = Decimal.mul(v, 5).root(2);
                     if (Decimal.gte(v, 25)) v = Decimal.mul(v, 20).root(2);
                     return Decimal.isNaN(v) ? Decimal.dZero : v.floor().max(0);
@@ -283,12 +297,33 @@ const layer = createLayer(id, function (this: BaseLayer) {
                     );
                 }),
                 onPurchase(cost?: DecimalSource) {
-                    const trueCost = cost ?? unref(buyable.cost) ?? Decimal.dInf;
+                    let buyMax = false;
+                    switch (options.color) {
+                        case 'red':
+                        case 'yellow':
+                        case 'blue':
+                            buyMax = management.elfTraining.dyeElfTraining.milestones[2].earned.value;
+                            break;
+                        case 'orange':
+                        case 'green':
+                        case 'purple':
+                            buyMax = management.elfTraining.dyeElfTraining.milestones[4].earned.value;
+                            break;
+                    }
 
-                    amount.value = Decimal.add(amount.value, computedToGenerate.value);
-                    buyable.amount.value = Decimal.add(buyable.amount.value, 1);
+                    if (buyMax) {
+                        const buyAmount = this.inverseCost().sub(this.amount.value).plus(1);
+                        if (buyAmount.lte(0)) return;
 
-                    if (!wrappingPaper.milestones.secondaryNoReset.earned.value) {
+                        amount.value = Decimal.times(computedToGenerate.value, buyAmount).add(amount.value);
+                        buyable.amount.value = Decimal.add(buyable.amount.value, buyAmount);
+                    }
+                    else {
+                        amount.value = Decimal.add(amount.value, computedToGenerate.value);
+                        buyable.amount.value = Decimal.add(buyable.amount.value, 1);
+                    }
+                    if (!management.elfTraining.dyeElfTraining.milestones[3].earned.value) {
+                        const trueCost = cost ?? unref(buyable.cost) ?? Decimal.dInf;
                         unref(costs).forEach(c => {
                             c.res.value = Decimal.sub(
                                 c.res.value,
diff --git a/src/data/layers/elves.tsx b/src/data/layers/elves.tsx
index 4474603..f511ff9 100644
--- a/src/data/layers/elves.tsx
+++ b/src/data/layers/elves.tsx
@@ -36,7 +36,7 @@ import plastic from "./plastic";
 import trees from "./trees";
 import workshop from "./workshop";
 import wrappingPaper from "./wrapping-paper";
-import dyes from "./dyes";
+import dyes, { enumColor } from "./dyes";
 
 export interface ElfBuyable extends GenericBuyable {
     /** The inverse function of the cost formula, used to calculate the maximum amount that can be bought by elves. */
@@ -902,14 +902,25 @@ const layer = createLayer(id, function (this: BaseLayer) {
             showIf(management.elfTraining.fertilizerElfTraining.milestones[4].earned.value)
     });
     const managementElves2 = [metalElf];
+
+    const dyeColors = Object.fromEntries((["red", "yellow", "blue", "orange", "green", "purple"] as enumColor[])
+        .map(color => [dyes.dyes[color].buyable.id, color])) as Record<string, enumColor>;
     const dyeElf = createElf({
         name: "Carol",
         description:
             "Carol will automatically purchase all dyes you can afford, without actually spending any resources.",
         buyable: Object.values(dyes.dyes).map(dye => dye.buyable),
         cooldownModifier: dyeCooldown, // Note: Buy max will be unlocked at this point
-        visibility: () => showIf(wrappingPaper.milestones.unlockDyeElf.earned.value),
-        buyMax: true
+        visibility: () => showIf(wrappingPaper.unlockDyeElfMilestone.earned.value),
+        buyMax: management.elfTraining.dyeElfTraining.milestones[2].earned,
+        onAutoPurchase(buyable, amount) {
+            if (["orange", "green", "purple"].includes(dyeColors[buyable.id])) {
+                if (false) { // does not have ribbon milestone 1
+                    buyable.amount.value = Decimal.sub(buyable.amount.value, amount)
+                    return;
+                }
+            }
+        }
     });
     const wrappingPaperElves = [dyeElf];
     const elves = {
@@ -1076,11 +1087,6 @@ const layer = createLayer(id, function (this: BaseLayer) {
             Decimal.gte(coal.coal.value, coalGoal)
         ) {
             main.completeDay();
-        } else if (
-            main.currentlyMastering.value?.name === name &&
-            Decimal.gte(coal.coal.value, options.masteryGoal ?? options.goal)
-        ) {
-            main.completeMastery();
         }
     });
 
diff --git a/src/data/layers/management.tsx b/src/data/layers/management.tsx
index 342f427..7eb06f3 100644
--- a/src/data/layers/management.tsx
+++ b/src/data/layers/management.tsx
@@ -1056,6 +1056,47 @@ const layer = createLayer(id, () => {
             }
         }))
     ] as Array<GenericMilestone>;
+    const dyeElfMilestones = [
+        createMilestone(() => ({
+            display: {
+                requirement: "Carol Level 1",
+                effectDisplay: "Double primary dye gain"
+            },
+            shouldEarn: () => dyeElfTraining.level.value >= 1
+        })),
+        createMilestone(() => ({
+            display: {
+                requirement: "Carol Level 2",
+                effectDisplay: "Double secondary dye gain"
+            },
+            shouldEarn: () => dyeElfTraining.level.value >= 2,
+            visibility: () => showIf(dyeElfMilestones[0].earned.value)
+        })),
+        createMilestone(() => ({
+            display: {
+                requirement: "Carol Level 3",
+                effectDisplay: "Buy maximum primary dyes"
+            },
+            shouldEarn: () => dyeElfTraining.level.value >= 3,
+            visibility: () => showIf(dyeElfMilestones[1].earned.value)
+        })),
+        createMilestone(() => ({
+            display: {
+                requirement: "Carol Level 4",
+                effectDisplay: "Secondary dyes don't spend primary dyes"
+            },
+            shouldEarn: () => dyeElfTraining.level.value >= 4,
+            visibility: () => showIf(dyeElfMilestones[2].earned.value && main.day.value >= 16)
+        })),
+        createMilestone(() => ({
+            display: {
+                requirement: "Carol Level 5",
+                effectDisplay: "Buy maximum secondary dyes"
+            },
+            shouldEarn: () => dyeElfTraining.level.value >= 5,
+            visibility: () => showIf(dyeElfMilestones[3].earned.value && main.day.value >= 16)
+        }))
+    ] as Array<GenericMilestone>;
     // ------------------------------------------------------------------------------- Milestone display
 
     const currentShown = persistent<string>("Holly");
@@ -1132,10 +1173,10 @@ const layer = createLayer(id, () => {
     const oilElfTraining = createElfTraining(elves.elves.oilElf, oilElfMilestones);
     const heavyDrillElfTraining = createElfTraining(
         elves.elves.heavyDrillElf,
-        heavyDrillElfMilestones
-    );
+        heavyDrillElfMilestones);
+    const dyeElfTraining = createElfTraining(elves.elves.dyeElf, dyeElfMilestones);
     const row5Elves = [coalDrillElfTraining, heavyDrillElfTraining, oilElfTraining];
-    const row6Elves = [metalElfTraining];
+    const row6Elves = [metalElfTraining, dyeElfTraining];
     const elfTraining = {
         cutterElfTraining,
         planterElfTraining,
@@ -1152,7 +1193,8 @@ const layer = createLayer(id, () => {
         coalDrillElfTraining,
         metalElfTraining,
         oilElfTraining,
-        heavyDrillElfTraining
+        heavyDrillElfTraining,
+        dyeElfTraining
     };
     const day12Elves = [
         cutterElfTraining,
@@ -1637,6 +1679,7 @@ const layer = createLayer(id, () => {
             main.completeDay();
         } else if (
             main.day.value === advancedDay &&
+            day12Elves.every(elf => elf.level.value >= 5) &&
             day13Elves.every(elf => elf.level.value >= 5)
         ) {
             main.completeDay();
@@ -1804,6 +1847,16 @@ const layer = createLayer(id, () => {
                     { earned: persistent<boolean>(false) },
                     { earned: persistent<boolean>(false) }
                 ]
+            },
+            dyeElfTraining: {
+                exp: persistent<DecimalSource>(0),
+                milestones: [
+                    { earned: persistent<boolean>(false) },
+                    { earned: persistent<boolean>(false) },
+                    { earned: persistent<boolean>(false) },
+                    { earned: persistent<boolean>(false) },
+                    { earned: persistent<boolean>(false) }
+                ]
             }
         },
         teaching: { bought: persistent<boolean>(false) },
@@ -1860,8 +1913,9 @@ const layer = createLayer(id, () => {
                 {main.day.value === day
                     ? `Get all elves to level 3.`
                     : main.day.value === advancedDay && main.days[advancedDay - 1].opened.value
-                    ? `Get all elves to level 5.`
-                    : `${name} Complete!`}{" "}
+                        ? `Get all elves to level 5.`
+                        : `${name} Complete!`
+                }{" "}
                 -
                 <button
                     class="button"
@@ -1878,13 +1932,13 @@ const layer = createLayer(id, () => {
                 <Spacer />
                 {Decimal.gt(schools.amount.value, 0) ? (
                     <>
-                        <br />
+                        <Spacer />
                         Click on an elf to see their milestones.
-                        <br />
-                        <br />
+                        <Spacer />
+                        <Spacer />
                         {render(focusButton)}
                         {renderGrid(upgrades, upgrades2)}
-                        <br />
+                        <Spacer />
                         {renderGrid(
                             [focusMeter],
                             treeElfTraining,
diff --git a/src/data/layers/paper.tsx b/src/data/layers/paper.tsx
index e8ed248..4a0db7b 100644
--- a/src/data/layers/paper.tsx
+++ b/src/data/layers/paper.tsx
@@ -283,12 +283,18 @@ const layer = createLayer(id, function (this: BaseLayer) {
         buyableName: "Metal Machines",
         visibility: () => showIf(elves.elves.metalElf.bought.value)
     });
-    const dyeBook = createBook({
+    const primaryDyeBook = createBook({
         name: "Arts and Crafts",
         elfName: "Carol",
-        buyableName: "Dyes",
+        buyableName: "Primary Dyes",
         visibility: () => showIf(elves.elves.dyeElf.bought.value)
     });
+    const secondaryDyeBook = createBook({
+        name: "",
+        elfName: "Carol",
+        buyableName: "Secondary Dyes",
+        visibility: () => showIf(elves.elves.dyeElf.bought.value && true) // ribbons secondary dye book milestone
+    });
     const books = {
         cuttersBook,
         plantersBook,
@@ -306,7 +312,8 @@ const layer = createLayer(id, function (this: BaseLayer) {
         heavyDrillBook,
         oilBook,
         metalBook,
-        dyeBook
+        primaryDyeBook,
+        secondaryDyeBook
     };
     const sumBooks = computed(() =>
         Object.values(books).reduce((acc, curr) => acc.add(curr.amount.value), new Decimal(0))
@@ -488,7 +495,8 @@ const layer = createLayer(id, function (this: BaseLayer) {
             heavyDrillBook: { amount: persistent<DecimalSource>(0) },
             oilBook: { amount: persistent<DecimalSource>(0) },
             metalBook: { amount: persistent<DecimalSource>(0) },
-            dyeBook: { amount: persistent<DecimalSource>(0) }
+            primaryDyeBook: { amount: persistent<DecimalSource>(0) },
+            secondaryDyeBook: { amount: persistent<DecimalSource>(0) }
         },
         upgrades: {
             clothUpgrade: { bought: persistent<boolean>(false) },
diff --git a/src/data/layers/wrapping-paper.tsx b/src/data/layers/wrapping-paper.tsx
index 698c9da..b5a9f6f 100644
--- a/src/data/layers/wrapping-paper.tsx
+++ b/src/data/layers/wrapping-paper.tsx
@@ -1,9 +1,8 @@
 import Spacer from "components/layout/Spacer.vue";
-import { createCollapsibleMilestones } from "data/common";
 import { createBar, GenericBar } from "features/bars/bar";
 import { BuyableOptions, createBuyable, GenericBuyable } from "features/buyable";
 import { createClickable } from "features/clickables/clickable";
-import { jsx, JSXFunction, showIf } from "features/feature";
+import { jsx, JSXFunction } from "features/feature";
 import { createMilestone } from "features/milestones/milestone";
 import MainDisplay from "features/resources/MainDisplay.vue";
 import { createResource, Resource } from "features/resources/resource";
@@ -16,7 +15,6 @@ import { render, renderRow } from "util/vue";
 import { computed, Ref, unref, watchEffect } from "vue";
 import { main } from "../projEntry";
 import { default as dyes, type enumColor } from "./dyes";
-import metal from "./metal";
 
 const id = "wrappingPaper";
 const day = 15;
@@ -270,70 +268,16 @@ const layer = createLayer(id, () => {
         "Total Wrapping Paper"
     );
 
-    const milestoneCosts = [10, 30, 90, 270, 810, 2430];
-
-    const primaryBoostMilestone = createMilestone(() => ({
-        display: {
-            requirement: milestoneCosts[0] + " Total Wrapping Paper",
-            effectDisplay: "Double primary colour dye gain"
-        },
-        shouldEarn: () => Decimal.gte(wrappingPaperSum.value, milestoneCosts[0]),
-        visibility: () => showIf(true)
-    }));
-    const secondaryBoostMilestone = createMilestone(() => ({
-        display: {
-            requirement: milestoneCosts[1] + " Total Wrapping Paper",
-            effectDisplay: "Double secondary colour dye gain"
-        },
-        shouldEarn: () => Decimal.gte(wrappingPaperSum.value, milestoneCosts[1]),
-        visibility: () => showIf(primaryBoostMilestone.earned.value)
-    }));
-    const buyMaxPrimaryMilestone = createMilestone(() => ({
-        display: {
-            requirement: milestoneCosts[2] + " Total Wrapping Paper",
-            effectDisplay: "Buy maximum primary colour dyes"
-        },
-        shouldEarn: () => Decimal.gte(wrappingPaperSum.value, milestoneCosts[2]),
-        visibility: () => showIf(secondaryBoostMilestone.earned.value)
-    }));
-    const secondaryNoResetMilestone = createMilestone(() => ({
-        display: {
-            requirement: milestoneCosts[3] + " Total Wrapping Paper",
-            effectDisplay: "Secondary colour dyes don't spend primary colour dyes"
-        },
-        shouldEarn: () => Decimal.gte(wrappingPaperSum.value, milestoneCosts[3]),
-        visibility: () => showIf(buyMaxPrimaryMilestone.earned.value)
-    }));
-    const buyMaxSecondaryMilestone = createMilestone(() => ({
-        display: {
-            requirement: milestoneCosts[4] + " Total Wrapping Paper",
-            effectDisplay: "Buy maximum secondary colour dyes"
-        },
-        shouldEarn: () => Decimal.gte(wrappingPaperSum.value, milestoneCosts[4]),
-        visibility: () => showIf(secondaryNoResetMilestone.earned.value)
-    }));
     const unlockDyeElfMilestone = createMilestone(() => ({
         display: {
-            requirement: milestoneCosts[5] + " Total Wrapping Paper",
+            requirement: "10 Total Wrapping Paper",
             effectDisplay: "Unlock a new elf to help with dyes"
         },
-        shouldEarn: () => Decimal.gte(wrappingPaperSum.value, milestoneCosts[5]),
-        visibility: () => showIf(buyMaxSecondaryMilestone.earned.value)
+        shouldEarn: () => Decimal.gte(wrappingPaperSum.value, 10)
     }));
 
-    const milestones = {
-        primaryBoost: primaryBoostMilestone,
-        secondaryBoost: secondaryBoostMilestone,
-        buyMaxPrimary: buyMaxPrimaryMilestone,
-        secondaryNoReset: secondaryNoResetMilestone,
-        buyMaxSecondary: buyMaxSecondaryMilestone,
-        unlockDyeElf: unlockDyeElfMilestone
-    };
-
-    const { collapseMilestones, display: milestonesDisplay } =
-        createCollapsibleMilestones(milestones);
-
     const masteryReq = computed(() => Decimal.pow(2, masteredDays.value).times(30));
+
     const enterMasteryButton = createClickable(() => ({
         display: () => ({
             title: main.isMastery.value ? "Stop Decorating" : "Begin Decoration",
@@ -444,14 +388,13 @@ const layer = createLayer(id, () => {
                     <Spacer />
                     {render(enterMasteryButton)}
                     <Spacer />
-                    {milestonesDisplay()}
+                    {render(unlockDyeElfMilestone)}
                 </div>
             );
         }),
         wrappingPaper,
         boosts,
-        milestones,
-        collapseMilestones,
+        unlockDyeElfMilestone,
         minWidth: 700
     };
 });
diff --git a/src/features/milestones/milestone.tsx b/src/features/milestones/milestone.tsx
index d70d6ec..e016e64 100644
--- a/src/features/milestones/milestone.tsx
+++ b/src/features/milestones/milestone.tsx
@@ -1,5 +1,5 @@
 import Select from "components/fields/Select.vue";
-import type { CoercableComponent, OptionsFunc, Replace, StyleValue } from "features/feature";
+import type { CoercableComponent, GenericComponent, OptionsFunc, Replace, StyleValue } from "features/feature";
 import { Component, GatherProps, getUniqueID, jsx, setDefault, Visibility } from "features/feature";
 import MilestoneComponent from "features/milestones/Milestone.vue";
 import { globalBus } from "game/events";
@@ -55,7 +55,7 @@ export interface BaseMilestone {
     earned: Persistent<boolean>;
     complete: VoidFunction;
     type: typeof MilestoneType;
-    [Component]: typeof MilestoneComponent;
+    [Component]: GenericComponent;
     [GatherProps]: () => Record<string, unknown>;
 }
 
@@ -85,7 +85,7 @@ export function createMilestone<T extends MilestoneOptions>(
         const milestone = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>);
         milestone.id = getUniqueID("milestone-");
         milestone.type = MilestoneType;
-        milestone[Component] = MilestoneComponent;
+        milestone[Component] = MilestoneComponent as GenericComponent;
 
         milestone.earned = earned;
         milestone.complete = function () {