1
0
Fork 0
mirror of https://github.com/thepaperpilot/Advent-Incremental.git synced 2025-03-28 03:38:46 +00:00

Get dye elf functional and into management

This commit is contained in:
Seth Posner 2022-12-18 17:07:09 -08:00
parent 507cf0f081
commit bacac4e72c
6 changed files with 153 additions and 107 deletions

View file

@ -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( modifiers.push(
createMultiplicativeModifier(() => ({ createMultiplicativeModifier(() => ({
multiplier: boosts.orange1, 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( modifiers.push(
createMultiplicativeModifier(() => ({ createMultiplicativeModifier(() => ({
multiplier: boosts.green1, 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( modifiers.push(
createMultiplicativeModifier(() => ({ createMultiplicativeModifier(() => ({
multiplier: boosts.purple1, 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( modifiers.push(
createMultiplicativeModifier(() => ({ createMultiplicativeModifier(() => ({
multiplier: 2, multiplier: 2,
@ -156,21 +156,17 @@ const layer = createLayer(id, function (this: BaseLayer) {
modifiers.push( modifiers.push(
createMultiplicativeModifier(() => ({ createMultiplicativeModifier(() => ({
multiplier: 2, multiplier: 2,
description: "Wrapping Paper Milestone 1", description: "Carol Level 1",
enabled: wrappingPaper.milestones.primaryBoost.earned enabled: management.elfTraining.dyeElfTraining.milestones[0].earned
})) }))
); );
} }
if ( if (["orange", "green", "purple"].includes(options.color)) {
options.color == "orange" ||
options.color == "green" ||
options.color == "purple"
) {
modifiers.push( modifiers.push(
createMultiplicativeModifier(() => ({ createMultiplicativeModifier(() => ({
multiplier: 2, multiplier: 2,
description: "Wrapping Paper Milestone 2", description: "Carol Level 2",
enabled: wrappingPaper.milestones.secondaryBoost.earned enabled: management.elfTraining.dyeElfTraining.milestones[1].earned
})) }))
); );
} }
@ -194,6 +190,24 @@ const layer = createLayer(id, function (this: BaseLayer) {
}) as WithRequired<Modifier, "description" | "revert">; }) as WithRequired<Modifier, "description" | "revert">;
const computedToGenerate = computed(() => toGenerate.apply(0)); 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 buyable: ElfBuyable = createBuyable(() => {
const costs = convertComputable(options.costs); const costs = convertComputable(options.costs);
return { return {
@ -250,12 +264,12 @@ const layer = createLayer(id, function (this: BaseLayer) {
let v = buyable.amount.value; 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, 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 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); return Decimal.div(v, 10).plus(1);
}, },
inverseCostPre(x: DecimalSource) { inverseCostPre(x: DecimalSource) {
let v = Decimal.sub(x, 1).mul(10); 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, 10)) v = Decimal.mul(v, 5).root(2);
if (Decimal.gte(v, 25)) v = Decimal.mul(v, 20).root(2); if (Decimal.gte(v, 25)) v = Decimal.mul(v, 20).root(2);
return Decimal.isNaN(v) ? Decimal.dZero : v.floor().max(0); return Decimal.isNaN(v) ? Decimal.dZero : v.floor().max(0);
@ -283,12 +297,33 @@ const layer = createLayer(id, function (this: BaseLayer) {
); );
}), }),
onPurchase(cost?: DecimalSource) { 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); if (buyMax) {
buyable.amount.value = Decimal.add(buyable.amount.value, 1); 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 => { unref(costs).forEach(c => {
c.res.value = Decimal.sub( c.res.value = Decimal.sub(
c.res.value, c.res.value,

View file

@ -36,7 +36,7 @@ import plastic from "./plastic";
import trees from "./trees"; import trees from "./trees";
import workshop from "./workshop"; import workshop from "./workshop";
import wrappingPaper from "./wrapping-paper"; import wrappingPaper from "./wrapping-paper";
import dyes from "./dyes"; import dyes, { enumColor } from "./dyes";
export interface ElfBuyable extends GenericBuyable { export interface ElfBuyable extends GenericBuyable {
/** The inverse function of the cost formula, used to calculate the maximum amount that can be bought by elves. */ /** 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) showIf(management.elfTraining.fertilizerElfTraining.milestones[4].earned.value)
}); });
const managementElves2 = [metalElf]; 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({ const dyeElf = createElf({
name: "Carol", name: "Carol",
description: description:
"Carol will automatically purchase all dyes you can afford, without actually spending any resources.", "Carol will automatically purchase all dyes you can afford, without actually spending any resources.",
buyable: Object.values(dyes.dyes).map(dye => dye.buyable), buyable: Object.values(dyes.dyes).map(dye => dye.buyable),
cooldownModifier: dyeCooldown, // Note: Buy max will be unlocked at this point cooldownModifier: dyeCooldown, // Note: Buy max will be unlocked at this point
visibility: () => showIf(wrappingPaper.milestones.unlockDyeElf.earned.value), visibility: () => showIf(wrappingPaper.unlockDyeElfMilestone.earned.value),
buyMax: true 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 wrappingPaperElves = [dyeElf];
const elves = { const elves = {
@ -1076,11 +1087,6 @@ const layer = createLayer(id, function (this: BaseLayer) {
Decimal.gte(coal.coal.value, coalGoal) Decimal.gte(coal.coal.value, coalGoal)
) { ) {
main.completeDay(); main.completeDay();
} else if (
main.currentlyMastering.value?.name === name &&
Decimal.gte(coal.coal.value, options.masteryGoal ?? options.goal)
) {
main.completeMastery();
} }
}); });

View file

@ -1056,6 +1056,47 @@ const layer = createLayer(id, () => {
} }
})) }))
] as Array<GenericMilestone>; ] 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 // ------------------------------------------------------------------------------- Milestone display
const currentShown = persistent<string>("Holly"); const currentShown = persistent<string>("Holly");
@ -1132,10 +1173,10 @@ const layer = createLayer(id, () => {
const oilElfTraining = createElfTraining(elves.elves.oilElf, oilElfMilestones); const oilElfTraining = createElfTraining(elves.elves.oilElf, oilElfMilestones);
const heavyDrillElfTraining = createElfTraining( const heavyDrillElfTraining = createElfTraining(
elves.elves.heavyDrillElf, elves.elves.heavyDrillElf,
heavyDrillElfMilestones heavyDrillElfMilestones);
); const dyeElfTraining = createElfTraining(elves.elves.dyeElf, dyeElfMilestones);
const row5Elves = [coalDrillElfTraining, heavyDrillElfTraining, oilElfTraining]; const row5Elves = [coalDrillElfTraining, heavyDrillElfTraining, oilElfTraining];
const row6Elves = [metalElfTraining]; const row6Elves = [metalElfTraining, dyeElfTraining];
const elfTraining = { const elfTraining = {
cutterElfTraining, cutterElfTraining,
planterElfTraining, planterElfTraining,
@ -1152,7 +1193,8 @@ const layer = createLayer(id, () => {
coalDrillElfTraining, coalDrillElfTraining,
metalElfTraining, metalElfTraining,
oilElfTraining, oilElfTraining,
heavyDrillElfTraining heavyDrillElfTraining,
dyeElfTraining
}; };
const day12Elves = [ const day12Elves = [
cutterElfTraining, cutterElfTraining,
@ -1637,6 +1679,7 @@ const layer = createLayer(id, () => {
main.completeDay(); main.completeDay();
} else if ( } else if (
main.day.value === advancedDay && main.day.value === advancedDay &&
day12Elves.every(elf => elf.level.value >= 5) &&
day13Elves.every(elf => elf.level.value >= 5) day13Elves.every(elf => elf.level.value >= 5)
) { ) {
main.completeDay(); main.completeDay();
@ -1804,6 +1847,16 @@ const layer = createLayer(id, () => {
{ earned: persistent<boolean>(false) }, { earned: persistent<boolean>(false) },
{ 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) }, teaching: { bought: persistent<boolean>(false) },
@ -1860,8 +1913,9 @@ const layer = createLayer(id, () => {
{main.day.value === day {main.day.value === day
? `Get all elves to level 3.` ? `Get all elves to level 3.`
: main.day.value === advancedDay && main.days[advancedDay - 1].opened.value : main.day.value === advancedDay && main.days[advancedDay - 1].opened.value
? `Get all elves to level 5.` ? `Get all elves to level 5.`
: `${name} Complete!`}{" "} : `${name} Complete!`
}{" "}
- -
<button <button
class="button" class="button"
@ -1878,13 +1932,13 @@ const layer = createLayer(id, () => {
<Spacer /> <Spacer />
{Decimal.gt(schools.amount.value, 0) ? ( {Decimal.gt(schools.amount.value, 0) ? (
<> <>
<br /> <Spacer />
Click on an elf to see their milestones. Click on an elf to see their milestones.
<br /> <Spacer />
<br /> <Spacer />
{render(focusButton)} {render(focusButton)}
{renderGrid(upgrades, upgrades2)} {renderGrid(upgrades, upgrades2)}
<br /> <Spacer />
{renderGrid( {renderGrid(
[focusMeter], [focusMeter],
treeElfTraining, treeElfTraining,

View file

@ -283,12 +283,18 @@ const layer = createLayer(id, function (this: BaseLayer) {
buyableName: "Metal Machines", buyableName: "Metal Machines",
visibility: () => showIf(elves.elves.metalElf.bought.value) visibility: () => showIf(elves.elves.metalElf.bought.value)
}); });
const dyeBook = createBook({ const primaryDyeBook = createBook({
name: "Arts and Crafts", name: "Arts and Crafts",
elfName: "Carol", elfName: "Carol",
buyableName: "Dyes", buyableName: "Primary Dyes",
visibility: () => showIf(elves.elves.dyeElf.bought.value) 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 = { const books = {
cuttersBook, cuttersBook,
plantersBook, plantersBook,
@ -306,7 +312,8 @@ const layer = createLayer(id, function (this: BaseLayer) {
heavyDrillBook, heavyDrillBook,
oilBook, oilBook,
metalBook, metalBook,
dyeBook primaryDyeBook,
secondaryDyeBook
}; };
const sumBooks = computed(() => const sumBooks = computed(() =>
Object.values(books).reduce((acc, curr) => acc.add(curr.amount.value), new Decimal(0)) 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) }, heavyDrillBook: { amount: persistent<DecimalSource>(0) },
oilBook: { amount: persistent<DecimalSource>(0) }, oilBook: { amount: persistent<DecimalSource>(0) },
metalBook: { 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: { upgrades: {
clothUpgrade: { bought: persistent<boolean>(false) }, clothUpgrade: { bought: persistent<boolean>(false) },

View file

@ -1,9 +1,8 @@
import Spacer from "components/layout/Spacer.vue"; import Spacer from "components/layout/Spacer.vue";
import { createCollapsibleMilestones } from "data/common";
import { createBar, GenericBar } from "features/bars/bar"; import { createBar, GenericBar } from "features/bars/bar";
import { BuyableOptions, createBuyable, GenericBuyable } from "features/buyable"; import { BuyableOptions, createBuyable, GenericBuyable } from "features/buyable";
import { createClickable } from "features/clickables/clickable"; 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 { createMilestone } from "features/milestones/milestone";
import MainDisplay from "features/resources/MainDisplay.vue"; import MainDisplay from "features/resources/MainDisplay.vue";
import { createResource, Resource } from "features/resources/resource"; 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 { computed, Ref, unref, watchEffect } from "vue";
import { main } from "../projEntry"; import { main } from "../projEntry";
import { default as dyes, type enumColor } from "./dyes"; import { default as dyes, type enumColor } from "./dyes";
import metal from "./metal";
const id = "wrappingPaper"; const id = "wrappingPaper";
const day = 15; const day = 15;
@ -270,70 +268,16 @@ const layer = createLayer(id, () => {
"Total Wrapping Paper" "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(() => ({ const unlockDyeElfMilestone = createMilestone(() => ({
display: { display: {
requirement: milestoneCosts[5] + " Total Wrapping Paper", requirement: "10 Total Wrapping Paper",
effectDisplay: "Unlock a new elf to help with dyes" effectDisplay: "Unlock a new elf to help with dyes"
}, },
shouldEarn: () => Decimal.gte(wrappingPaperSum.value, milestoneCosts[5]), shouldEarn: () => Decimal.gte(wrappingPaperSum.value, 10)
visibility: () => showIf(buyMaxSecondaryMilestone.earned.value)
})); }));
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 masteryReq = computed(() => Decimal.pow(2, masteredDays.value).times(30));
const enterMasteryButton = createClickable(() => ({ const enterMasteryButton = createClickable(() => ({
display: () => ({ display: () => ({
title: main.isMastery.value ? "Stop Decorating" : "Begin Decoration", title: main.isMastery.value ? "Stop Decorating" : "Begin Decoration",
@ -444,14 +388,13 @@ const layer = createLayer(id, () => {
<Spacer /> <Spacer />
{render(enterMasteryButton)} {render(enterMasteryButton)}
<Spacer /> <Spacer />
{milestonesDisplay()} {render(unlockDyeElfMilestone)}
</div> </div>
); );
}), }),
wrappingPaper, wrappingPaper,
boosts, boosts,
milestones, unlockDyeElfMilestone,
collapseMilestones,
minWidth: 700 minWidth: 700
}; };
}); });

View file

@ -1,5 +1,5 @@
import Select from "components/fields/Select.vue"; 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 { Component, GatherProps, getUniqueID, jsx, setDefault, Visibility } from "features/feature";
import MilestoneComponent from "features/milestones/Milestone.vue"; import MilestoneComponent from "features/milestones/Milestone.vue";
import { globalBus } from "game/events"; import { globalBus } from "game/events";
@ -55,7 +55,7 @@ export interface BaseMilestone {
earned: Persistent<boolean>; earned: Persistent<boolean>;
complete: VoidFunction; complete: VoidFunction;
type: typeof MilestoneType; type: typeof MilestoneType;
[Component]: typeof MilestoneComponent; [Component]: GenericComponent;
[GatherProps]: () => Record<string, unknown>; [GatherProps]: () => Record<string, unknown>;
} }
@ -85,7 +85,7 @@ export function createMilestone<T extends MilestoneOptions>(
const milestone = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>); const milestone = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>);
milestone.id = getUniqueID("milestone-"); milestone.id = getUniqueID("milestone-");
milestone.type = MilestoneType; milestone.type = MilestoneType;
milestone[Component] = MilestoneComponent; milestone[Component] = MilestoneComponent as GenericComponent;
milestone.earned = earned; milestone.earned = earned;
milestone.complete = function () { milestone.complete = function () {