From 98cb997056f4de177004092038bd3b519a39b2cf Mon Sep 17 00:00:00 2001 From: thepaperpilot Date: Sun, 2 Apr 2023 21:47:31 -0500 Subject: [PATCH 1/9] Make conversions pass the variable to the formula --- src/features/conversion.ts | 41 ++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/src/features/conversion.ts b/src/features/conversion.ts index f5146ca..9eeb792 100644 --- a/src/features/conversion.ts +++ b/src/features/conversion.ts @@ -1,7 +1,12 @@ import type { OptionsFunc, Replace } from "features/feature"; import { setDefault } from "features/feature"; import type { Resource } from "features/resources/resource"; -import { InvertibleFormula } from "game/formulas/types"; +import Formula from "game/formulas/formulas"; +import { + IntegrableFormula, + InvertibleFormula, + InvertibleIntegralFormula +} from "game/formulas/types"; import type { BaseLayer } from "game/layers"; import type { DecimalSource } from "util/bignum"; import Decimal from "util/bignum"; @@ -15,9 +20,11 @@ import { computed, unref } from "vue"; export interface ConversionOptions { /** * The formula used to determine how much {@link gainResource} should be earned by this converting. - * When evaluating, the variable will always be overidden to the amount of {@link baseResource}. + * The passed value will be a Formula representing the {@link baseResource} variable. */ - formula: InvertibleFormula; + formula: ( + variable: InvertibleFormula & IntegrableFormula & InvertibleIntegralFormula + ) => InvertibleFormula; /** * How much of the output resource the conversion can currently convert for. * Typically this will be set for you in a conversion constructor. @@ -98,6 +105,7 @@ export type Conversion = Replace< export type GenericConversion = Replace< Conversion, { + formula: InvertibleFormula; currentGain: ProcessedComputable; actualGain: ProcessedComputable; currentAt: ProcessedComputable; @@ -120,9 +128,14 @@ export function createConversion( return createLazyProxy(() => { const conversion = optionsFunc(); + (conversion as GenericConversion).formula = conversion.formula( + Formula.variable(conversion.baseResource) + ); if (conversion.currentGain == null) { conversion.currentGain = computed(() => { - let gain = conversion.formula.evaluate(conversion.baseResource.value); + let gain = (conversion as GenericConversion).formula.evaluate( + conversion.baseResource.value + ); gain = Decimal.floor(gain).max(0); if (unref(conversion.buyMax) === false) { @@ -136,14 +149,14 @@ export function createConversion( } if (conversion.currentAt == null) { conversion.currentAt = computed(() => { - return conversion.formula.invert( + return (conversion as GenericConversion).formula.invert( Decimal.floor(unref((conversion as GenericConversion).currentGain)) ); }); } if (conversion.nextAt == null) { conversion.nextAt = computed(() => { - return conversion.formula.invert( + return (conversion as GenericConversion).formula.invert( Decimal.floor(unref((conversion as GenericConversion).currentGain)).add(1) ); }); @@ -205,7 +218,9 @@ export function createIndependentConversion( if (conversion.currentGain == null) { conversion.currentGain = computed(() => { - let gain = conversion.formula.evaluate(conversion.baseResource.value); + let gain = (conversion as unknown as GenericConversion).formula.evaluate( + conversion.baseResource.value + ); gain = Decimal.floor(gain).max(conversion.gainResource.value); if (unref(conversion.buyMax) === false) { gain = gain.min(Decimal.add(conversion.gainResource.value, 1)); @@ -216,7 +231,9 @@ export function createIndependentConversion( if (conversion.actualGain == null) { conversion.actualGain = computed(() => { let gain = Decimal.sub( - conversion.formula.evaluate(conversion.baseResource.value), + (conversion as unknown as GenericConversion).formula.evaluate( + conversion.baseResource.value + ), conversion.gainResource.value ).max(0); @@ -227,9 +244,11 @@ export function createIndependentConversion( }); } setDefault(conversion, "convert", function () { - const amountGained = unref((conversion as GenericConversion).actualGain); - conversion.gainResource.value = unref((conversion as GenericConversion).currentGain); - (conversion as GenericConversion).spend(amountGained); + const amountGained = unref((conversion as unknown as GenericConversion).actualGain); + conversion.gainResource.value = unref( + (conversion as unknown as GenericConversion).currentGain + ); + (conversion as unknown as GenericConversion).spend(amountGained); conversion.onConvert?.(amountGained); }); From 804d48ae80efd8c4ec7b85bda37b2dbca263cbfe Mon Sep 17 00:00:00 2001 From: thepaperpilot Date: Sun, 2 Apr 2023 21:56:43 -0500 Subject: [PATCH 2/9] Fix cost requirement requiring spendResources Also fixed it defaulting to false instead of true --- src/game/requirements.tsx | 48 +++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/src/game/requirements.tsx b/src/game/requirements.tsx index 9e775de..acb7f7b 100644 --- a/src/game/requirements.tsx +++ b/src/game/requirements.tsx @@ -1,5 +1,12 @@ import { isArray } from "@vue/shared"; -import { CoercableComponent, isVisible, jsx, setDefault, Visibility } from "features/feature"; +import { + CoercableComponent, + isVisible, + jsx, + Replace, + setDefault, + Visibility +} from "features/feature"; import { displayResource, Resource } from "features/resources/resource"; import Decimal, { DecimalSource } from "lib/break_eternity"; import { @@ -78,7 +85,7 @@ export interface CostRequirementOptions { * When calculating multiple levels to be handled at once, whether it should consider resources used for each level as spent. Setting this to false causes calculations to be faster with larger numbers and supports more math functions. * @see {Formula} */ - spendResources: Computable; + spendResources?: Computable; /** * Pass-through to {@link Requirement.pay}. May be required for maximizing support. * @see {@link cost} for restrictions on maximizing support. @@ -86,7 +93,15 @@ export interface CostRequirementOptions { pay?: (amount?: DecimalSource) => void; } -export type CostRequirement = Requirement & CostRequirementOptions; +export type CostRequirement = Replace< + Requirement & CostRequirementOptions, + { + cost: ProcessedComputable | GenericFormula; + visibility: ProcessedComputable; + requiresPay: ProcessedComputable; + spendResources: ProcessedComputable; + } +>; /** * Lazily creates a requirement with the given options, that is based on meeting an amount of a resource. @@ -109,13 +124,7 @@ export function createCostRequirement( {displayResource( req.resource, req.cost instanceof Formula - ? calculateCost( - req.cost, - amount ?? 1, - unref( - req.spendResources as ProcessedComputable | undefined - ) ?? true - ) + ? calculateCost(req.cost, amount ?? 1, unref(req.spendResources) as boolean) : unref(req.cost as ProcessedComputable) )}{" "} {req.resource.displayName} @@ -127,13 +136,7 @@ export function createCostRequirement( {displayResource( req.resource, req.cost instanceof Formula - ? calculateCost( - req.cost, - amount ?? 1, - unref( - req.spendResources as ProcessedComputable | undefined - ) ?? true - ) + ? calculateCost(req.cost, amount ?? 1, unref(req.spendResources) as boolean) : unref(req.cost as ProcessedComputable) )}{" "} {req.resource.displayName} @@ -146,16 +149,11 @@ export function createCostRequirement( processComputable(req as T, "requiresPay"); setDefault(req, "requiresPay", true); processComputable(req as T, "spendResources"); - setDefault(req, "spendResources", false); + setDefault(req, "spendResources", true); setDefault(req, "pay", function (amount?: DecimalSource) { const cost = req.cost instanceof Formula - ? calculateCost( - req.cost, - amount ?? 1, - unref(req.spendResources as ProcessedComputable | undefined) ?? - true - ) + ? calculateCost(req.cost, amount ?? 1, unref(req.spendResources) as boolean) : unref(req.cost as ProcessedComputable); req.resource.value = Decimal.sub(req.resource.value, cost).max(0); }); @@ -166,7 +164,7 @@ export function createCostRequirement( req.requirementMet = calculateMaxAffordable( req.cost as InvertibleFormula, req.resource, - unref(req.spendResources as ProcessedComputable | undefined) ?? true + unref(req.spendResources) as boolean ); } else { req.requirementMet = computed(() => { From f717fe8db286c0048ea4dd6b9424da70c58c25f0 Mon Sep 17 00:00:00 2001 From: thepaperpilot Date: Sun, 2 Apr 2023 21:42:17 -0500 Subject: [PATCH 3/9] Make every component use GenericComponent some projects seem to require this and others not? --- src/features/achievements/achievement.tsx | 5 +++-- src/features/bars/bar.ts | 4 ++-- src/features/boards/board.ts | 6 +++--- src/features/challenges/challenge.tsx | 12 +++++++++--- src/features/clickables/clickable.ts | 4 ++-- src/features/grids/grid.ts | 12 +++++++++--- src/features/infoboxes/infobox.ts | 12 +++++++++--- src/features/links/links.ts | 6 +++--- src/features/milestones/milestone.tsx | 4 ++-- src/features/repeatable.tsx | 12 +++++++++--- src/features/tabs/tab.ts | 12 +++++++++--- src/features/tabs/tabFamily.ts | 16 +++++++++++----- src/features/trees/tree.ts | 16 +++++++++++----- src/features/upgrades/upgrade.ts | 4 ++-- 14 files changed, 84 insertions(+), 41 deletions(-) diff --git a/src/features/achievements/achievement.tsx b/src/features/achievements/achievement.tsx index 7daaf7e..4776fd1 100644 --- a/src/features/achievements/achievement.tsx +++ b/src/features/achievements/achievement.tsx @@ -3,6 +3,7 @@ import { CoercableComponent, Component, GatherProps, + GenericComponent, getUniqueID, isVisible, OptionsFunc, @@ -48,7 +49,7 @@ export interface BaseAchievement { earned: Persistent; complete: VoidFunction; type: typeof AchievementType; - [Component]: typeof AchievementComponent; + [Component]: GenericComponent; [GatherProps]: () => Record; } @@ -79,7 +80,7 @@ export function createAchievement( const achievement = optionsFunc?.() ?? ({} as ReturnType>); achievement.id = getUniqueID("achievement-"); achievement.type = AchievementType; - achievement[Component] = AchievementComponent; + achievement[Component] = AchievementComponent as GenericComponent; achievement.earned = earned; achievement.complete = function () { diff --git a/src/features/bars/bar.ts b/src/features/bars/bar.ts index f0436f4..bab5887 100644 --- a/src/features/bars/bar.ts +++ b/src/features/bars/bar.ts @@ -40,7 +40,7 @@ export interface BarOptions { export interface BaseBar { id: string; type: typeof BarType; - [Component]: typeof BarComponent; + [Component]: GenericComponent; [GatherProps]: () => Record; } @@ -77,7 +77,7 @@ export function createBar( const bar = optionsFunc(); bar.id = getUniqueID("bar-"); bar.type = BarType; - bar[Component] = BarComponent; + bar[Component] = BarComponent as GenericComponent; processComputable(bar as T, "visibility"); setDefault(bar, "visibility", Visibility.Visible); diff --git a/src/features/boards/board.ts b/src/features/boards/board.ts index cc17d2e..528bc26 100644 --- a/src/features/boards/board.ts +++ b/src/features/boards/board.ts @@ -1,5 +1,5 @@ import BoardComponent from "features/boards/Board.vue"; -import type { OptionsFunc, Replace, StyleValue } from "features/feature"; +import type { GenericComponent, OptionsFunc, Replace, StyleValue } from "features/feature"; import { Component, findFeatures, @@ -178,7 +178,7 @@ export interface BaseBoard { selectedAction: Ref; mousePosition: Ref<{ x: number; y: number } | null>; type: typeof BoardType; - [Component]: typeof BoardComponent; + [Component]: GenericComponent; [GatherProps]: () => Record; } @@ -221,7 +221,7 @@ export function createBoard( const board = optionsFunc(); board.id = getUniqueID("board-"); board.type = BoardType; - board[Component] = BoardComponent; + board[Component] = BoardComponent as GenericComponent; if (board.state) { deletePersistent(state); diff --git a/src/features/challenges/challenge.tsx b/src/features/challenges/challenge.tsx index 34f850c..2925595 100644 --- a/src/features/challenges/challenge.tsx +++ b/src/features/challenges/challenge.tsx @@ -1,7 +1,13 @@ import { isArray } from "@vue/shared"; import Toggle from "components/fields/Toggle.vue"; import ChallengeComponent from "features/challenges/Challenge.vue"; -import type { CoercableComponent, OptionsFunc, Replace, StyleValue } from "features/feature"; +import type { + CoercableComponent, + GenericComponent, + OptionsFunc, + Replace, + StyleValue +} from "features/feature"; import { Component, GatherProps, @@ -67,7 +73,7 @@ export interface BaseChallenge { toggle: VoidFunction; complete: (remainInChallenge?: boolean) => void; type: typeof ChallengeType; - [Component]: typeof ChallengeComponent; + [Component]: GenericComponent; [GatherProps]: () => Record; } @@ -106,7 +112,7 @@ export function createChallenge( challenge.id = getUniqueID("challenge-"); challenge.type = ChallengeType; - challenge[Component] = ChallengeComponent; + challenge[Component] = ChallengeComponent as GenericComponent; challenge.completions = completions; challenge.active = active; diff --git a/src/features/clickables/clickable.ts b/src/features/clickables/clickable.ts index c7c83e3..b44854b 100644 --- a/src/features/clickables/clickable.ts +++ b/src/features/clickables/clickable.ts @@ -42,7 +42,7 @@ export interface ClickableOptions { export interface BaseClickable { id: string; type: typeof ClickableType; - [Component]: typeof ClickableComponent; + [Component]: GenericComponent; [GatherProps]: () => Record; } @@ -73,7 +73,7 @@ export function createClickable( const clickable = optionsFunc?.() ?? ({} as ReturnType>); clickable.id = getUniqueID("clickable-"); clickable.type = ClickableType; - clickable[Component] = ClickableComponent; + clickable[Component] = ClickableComponent as GenericComponent; processComputable(clickable as T, "visibility"); setDefault(clickable, "visibility", Visibility.Visible); diff --git a/src/features/grids/grid.ts b/src/features/grids/grid.ts index 53fd7fb..b7899ae 100644 --- a/src/features/grids/grid.ts +++ b/src/features/grids/grid.ts @@ -1,4 +1,10 @@ -import type { CoercableComponent, OptionsFunc, Replace, StyleValue } from "features/feature"; +import type { + CoercableComponent, + GenericComponent, + OptionsFunc, + Replace, + StyleValue +} from "features/feature"; import { Component, GatherProps, getUniqueID, setDefault, Visibility } from "features/feature"; import GridComponent from "features/grids/Grid.vue"; import type { Persistent, State } from "game/persistence"; @@ -206,7 +212,7 @@ export interface BaseGrid { cells: Record; cellState: Persistent>; type: typeof GridType; - [Component]: typeof GridComponent; + [Component]: GenericComponent; [GatherProps]: () => Record; } @@ -242,7 +248,7 @@ export function createGrid( return createLazyProxy(() => { const grid = optionsFunc(); grid.id = getUniqueID("grid-"); - grid[Component] = GridComponent; + grid[Component] = GridComponent as GenericComponent; grid.cellState = cellState; diff --git a/src/features/infoboxes/infobox.ts b/src/features/infoboxes/infobox.ts index 81d7bf6..ccd2bd3 100644 --- a/src/features/infoboxes/infobox.ts +++ b/src/features/infoboxes/infobox.ts @@ -1,4 +1,10 @@ -import type { CoercableComponent, OptionsFunc, Replace, StyleValue } from "features/feature"; +import type { + CoercableComponent, + GenericComponent, + OptionsFunc, + Replace, + StyleValue +} from "features/feature"; import { Component, GatherProps, getUniqueID, setDefault, Visibility } from "features/feature"; import InfoboxComponent from "features/infoboxes/Infobox.vue"; import type { Persistent } from "game/persistence"; @@ -30,7 +36,7 @@ export interface BaseInfobox { id: string; collapsed: Persistent; type: typeof InfoboxType; - [Component]: typeof InfoboxComponent; + [Component]: GenericComponent; [GatherProps]: () => Record; } @@ -63,7 +69,7 @@ export function createInfobox( const infobox = optionsFunc(); infobox.id = getUniqueID("infobox-"); infobox.type = InfoboxType; - infobox[Component] = InfoboxComponent; + infobox[Component] = InfoboxComponent as GenericComponent; infobox.collapsed = collapsed; diff --git a/src/features/links/links.ts b/src/features/links/links.ts index 5ab8779..77ea0ba 100644 --- a/src/features/links/links.ts +++ b/src/features/links/links.ts @@ -1,4 +1,4 @@ -import type { OptionsFunc, Replace } from "features/feature"; +import type { GenericComponent, OptionsFunc, Replace } from "features/feature"; import { GatherProps, Component } from "features/feature"; import type { Position } from "game/layers"; import type { Computable, GetComputableType, ProcessedComputable } from "util/computed"; @@ -22,7 +22,7 @@ export interface LinksOptions { export interface BaseLinks { type: typeof LinksType; - [Component]: typeof LinksComponent; + [Component]: GenericComponent; [GatherProps]: () => Record; } @@ -46,7 +46,7 @@ export function createLinks( return createLazyProxy(() => { const links = optionsFunc(); links.type = LinksType; - links[Component] = LinksComponent; + links[Component] = LinksComponent as GenericComponent; processComputable(links as T, "links"); diff --git a/src/features/milestones/milestone.tsx b/src/features/milestones/milestone.tsx index 30cd243..0a973db 100644 --- a/src/features/milestones/milestone.tsx +++ b/src/features/milestones/milestone.tsx @@ -69,7 +69,7 @@ export interface BaseMilestone { earned: Persistent; complete: VoidFunction; type: typeof MilestoneType; - [Component]: typeof MilestoneComponent; + [Component]: GenericComponent; [GatherProps]: () => Record; } @@ -99,7 +99,7 @@ export function createMilestone( const milestone = optionsFunc?.() ?? ({} as ReturnType>); milestone.id = getUniqueID("milestone-"); milestone.type = MilestoneType; - milestone[Component] = MilestoneComponent; + milestone[Component] = MilestoneComponent as GenericComponent; milestone.earned = earned; milestone.complete = function () { diff --git a/src/features/repeatable.tsx b/src/features/repeatable.tsx index b442068..ec338eb 100644 --- a/src/features/repeatable.tsx +++ b/src/features/repeatable.tsx @@ -1,6 +1,12 @@ import { isArray } from "@vue/shared"; import ClickableComponent from "features/clickables/Clickable.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 { DefaultValue, Persistent, persistent } from "game/persistence"; import { @@ -88,7 +94,7 @@ export interface BaseRepeatable { /** A symbol that helps identify features of the same type. */ type: typeof RepeatableType; /** The Vue component used to render this feature. */ - [Component]: typeof ClickableComponent; + [Component]: GenericComponent; /** A function to gather the props the vue component requires for this feature. */ [GatherProps]: () => Record; } @@ -131,7 +137,7 @@ export function createRepeatable( repeatable.id = getUniqueID("repeatable-"); repeatable.type = RepeatableType; - repeatable[Component] = ClickableComponent; + repeatable[Component] = ClickableComponent as GenericComponent; repeatable.amount = amount; repeatable.amount[DefaultValue] = repeatable.initialAmount ?? 0; diff --git a/src/features/tabs/tab.ts b/src/features/tabs/tab.ts index 3f205fc..6917009 100644 --- a/src/features/tabs/tab.ts +++ b/src/features/tabs/tab.ts @@ -1,4 +1,10 @@ -import type { CoercableComponent, OptionsFunc, Replace, StyleValue } from "features/feature"; +import type { + CoercableComponent, + GenericComponent, + OptionsFunc, + Replace, + StyleValue +} from "features/feature"; import { Component, GatherProps, getUniqueID } from "features/feature"; import TabComponent from "features/tabs/Tab.vue"; import type { Computable, GetComputableType } from "util/computed"; @@ -15,7 +21,7 @@ export interface TabOptions { export interface BaseTab { id: string; type: typeof TabType; - [Component]: typeof TabComponent; + [Component]: GenericComponent; [GatherProps]: () => Record; } @@ -37,7 +43,7 @@ export function createTab( const tab = optionsFunc(); tab.id = getUniqueID("tab-"); tab.type = TabType; - tab[Component] = TabComponent; + tab[Component] = TabComponent as GenericComponent; tab[GatherProps] = function (this: GenericTab) { const { display } = this; diff --git a/src/features/tabs/tabFamily.ts b/src/features/tabs/tabFamily.ts index 9652f3d..32df1e7 100644 --- a/src/features/tabs/tabFamily.ts +++ b/src/features/tabs/tabFamily.ts @@ -1,4 +1,10 @@ -import type { CoercableComponent, OptionsFunc, Replace, StyleValue } from "features/feature"; +import type { + CoercableComponent, + GenericComponent, + OptionsFunc, + Replace, + StyleValue +} from "features/feature"; import { Component, GatherProps, @@ -37,7 +43,7 @@ export interface TabButtonOptions { export interface BaseTabButton { type: typeof TabButtonType; - [Component]: typeof TabButtonComponent; + [Component]: GenericComponent; } export type TabButton = Replace< @@ -73,7 +79,7 @@ export interface BaseTabFamily { activeTab: Ref; selected: Persistent; type: typeof TabFamilyType; - [Component]: typeof TabFamilyComponent; + [Component]: GenericComponent; [GatherProps]: () => Record; } @@ -107,13 +113,13 @@ export function createTabFamily( tabFamily.id = getUniqueID("tabFamily-"); tabFamily.type = TabFamilyType; - tabFamily[Component] = TabFamilyComponent; + tabFamily[Component] = TabFamilyComponent as GenericComponent; tabFamily.tabs = Object.keys(tabs).reduce>( (parsedTabs, tab) => { const tabButton: TabButtonOptions & Partial = tabs[tab](); tabButton.type = TabButtonType; - tabButton[Component] = TabButtonComponent; + tabButton[Component] = TabButtonComponent as GenericComponent; processComputable(tabButton as TabButtonOptions, "visibility"); setDefault(tabButton, "visibility", Visibility.Visible); diff --git a/src/features/trees/tree.ts b/src/features/trees/tree.ts index bcbf333..a3efba7 100644 --- a/src/features/trees/tree.ts +++ b/src/features/trees/tree.ts @@ -1,4 +1,10 @@ -import type { CoercableComponent, OptionsFunc, Replace, StyleValue } from "features/feature"; +import type { + CoercableComponent, + GenericComponent, + OptionsFunc, + Replace, + StyleValue +} from "features/feature"; import { Component, GatherProps, getUniqueID, setDefault, Visibility } from "features/feature"; import type { Link } from "features/links/links"; import type { GenericReset } from "features/reset"; @@ -39,7 +45,7 @@ export interface TreeNodeOptions { export interface BaseTreeNode { id: string; type: typeof TreeNodeType; - [Component]: typeof TreeNodeComponent; + [Component]: GenericComponent; [GatherProps]: () => Record; } @@ -72,7 +78,7 @@ export function createTreeNode( const treeNode = optionsFunc?.() ?? ({} as ReturnType>); treeNode.id = getUniqueID("treeNode-"); treeNode.type = TreeNodeType; - treeNode[Component] = TreeNodeComponent; + treeNode[Component] = TreeNodeComponent as GenericComponent; processComputable(treeNode as T, "visibility"); setDefault(treeNode, "visibility", Visibility.Visible); @@ -157,7 +163,7 @@ export interface BaseTree { isResetting: Ref; resettingNode: Ref; type: typeof TreeType; - [Component]: typeof TreeComponent; + [Component]: GenericComponent; [GatherProps]: () => Record; } @@ -186,7 +192,7 @@ export function createTree( const tree = optionsFunc(); tree.id = getUniqueID("tree-"); tree.type = TreeType; - tree[Component] = TreeComponent; + tree[Component] = TreeComponent as GenericComponent; tree.isResetting = ref(false); tree.resettingNode = shallowRef(null); diff --git a/src/features/upgrades/upgrade.ts b/src/features/upgrades/upgrade.ts index 924e28f..5bcf5df 100644 --- a/src/features/upgrades/upgrade.ts +++ b/src/features/upgrades/upgrade.ts @@ -61,7 +61,7 @@ export interface BaseUpgrade { canPurchase: Ref; purchase: VoidFunction; type: typeof UpgradeType; - [Component]: typeof UpgradeComponent; + [Component]: GenericComponent; [GatherProps]: () => Record; } @@ -92,7 +92,7 @@ export function createUpgrade( const upgrade = optionsFunc(); upgrade.id = getUniqueID("upgrade-"); upgrade.type = UpgradeType; - upgrade[Component] = UpgradeComponent; + upgrade[Component] = UpgradeComponent as GenericComponent; upgrade.bought = bought; upgrade.canPurchase = computed(() => requirementsMet(upgrade.requirements)); From 572566c4c15197c291b6d3d3f2ae10deb9a41bbf Mon Sep 17 00:00:00 2001 From: thepaperpilot Date: Sun, 2 Apr 2023 22:03:58 -0500 Subject: [PATCH 4/9] Updated prestige layer to new conversion --- src/data/layers/prestige.tsx | 7 +++---- src/features/conversion.ts | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/data/layers/prestige.tsx b/src/data/layers/prestige.tsx index 4490ded..30fe99f 100644 --- a/src/data/layers/prestige.tsx +++ b/src/data/layers/prestige.tsx @@ -3,7 +3,7 @@ * @hidden */ import { main } from "data/projEntry"; -import { createCumulativeConversion, createPolynomialScaling } from "features/conversion"; +import { createCumulativeConversion } from "features/conversion"; import { jsx } from "features/feature"; import { createHotkey } from "features/hotkey"; import { createReset } from "features/reset"; @@ -23,10 +23,9 @@ const layer = createLayer(id, function (this: BaseLayer) { const points = createResource(0, "prestige points"); const conversion = createCumulativeConversion(() => ({ - scaling: createPolynomialScaling(10, 0.5), + formula: x => x.div(10).sqrt(), baseResource: main.points, - gainResource: points, - roundUpCost: true + gainResource: points })); const reset = createReset(() => ({ diff --git a/src/features/conversion.ts b/src/features/conversion.ts index 9eeb792..1ccf26b 100644 --- a/src/features/conversion.ts +++ b/src/features/conversion.ts @@ -92,6 +92,7 @@ export interface BaseConversion { export type Conversion = Replace< T & BaseConversion, { + formula: InvertibleFormula; currentGain: GetComputableTypeWithDefault>; actualGain: GetComputableTypeWithDefault>; currentAt: GetComputableTypeWithDefault>; @@ -105,7 +106,6 @@ export type Conversion = Replace< export type GenericConversion = Replace< Conversion, { - formula: InvertibleFormula; currentGain: ProcessedComputable; actualGain: ProcessedComputable; currentAt: ProcessedComputable; From 528afc6b590edf235e6c6f26433594bd32a6e8b4 Mon Sep 17 00:00:00 2001 From: thepaperpilot Date: Sun, 2 Apr 2023 22:09:13 -0500 Subject: [PATCH 5/9] Fix tooltips being pinnable causing SO --- src/features/tooltips/tooltip.ts | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/features/tooltips/tooltip.ts b/src/features/tooltips/tooltip.ts index 13cb931..2cb977d 100644 --- a/src/features/tooltips/tooltip.ts +++ b/src/features/tooltips/tooltip.ts @@ -1,6 +1,6 @@ import type { CoercableComponent, Replace, StyleValue } from "features/feature"; import { Component, GatherProps, setDefault } from "features/feature"; -import { persistent } from "game/persistence"; +import { deletePersistent, Persistent, persistent } from "game/persistence"; import { Direction } from "util/common"; import type { Computable, @@ -71,18 +71,22 @@ export function addTooltip( processComputable(options as T, "yoffset"); if (options.pinnable) { - if ("pinned" in element) { - console.error( - "Cannot add pinnable tooltip to element that already has a property called 'pinned'" - ); - options.pinnable = false; - } else { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (element as any).pinned = options.pinned = persistent(false, false); - } + options.pinned = persistent(false, false); } nextTick(() => { + if (options.pinnable) { + if ("pinned" in element) { + console.error( + "Cannot add pinnable tooltip to element that already has a property called 'pinned'" + ); + options.pinnable = false; + deletePersistent(options.pinned as Persistent); + } else { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (element as any).pinned = options.pinned; + } + } const elementComponent = element[Component]; element[Component] = TooltipComponent; const elementGatherProps = element[GatherProps].bind(element); From 165eba688e2fc67c2073f8378d340655d7acbd2f Mon Sep 17 00:00:00 2001 From: thepaperpilot Date: Sun, 2 Apr 2023 22:25:18 -0500 Subject: [PATCH 6/9] Slightly improve resource imports --- src/features/resources/resource.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/features/resources/resource.ts b/src/features/resources/resource.ts index be42e7e..8e203d3 100644 --- a/src/features/resources/resource.ts +++ b/src/features/resources/resource.ts @@ -1,8 +1,6 @@ import { globalBus } from "game/events"; -import { NonPersistent, Persistent, State } from "game/persistence"; -import { persistent } from "game/persistence"; -import player from "game/player"; -import settings from "game/settings"; +import type { Persistent, State } from "game/persistence"; +import { NonPersistent, persistent } from "game/persistence"; import type { DecimalSource } from "util/bignum"; import Decimal, { format, formatWhole } from "util/bignum"; import type { ProcessedComputable } from "util/computed"; From e6c7ad62a79d991d96f1282d87be23b09692d490 Mon Sep 17 00:00:00 2001 From: thepaperpilot Date: Sun, 2 Apr 2023 23:33:49 -0500 Subject: [PATCH 7/9] Document challenge --- src/features/challenges/challenge.tsx | 69 +++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/src/features/challenges/challenge.tsx b/src/features/challenges/challenge.tsx index 2925595..3a1ba48 100644 --- a/src/features/challenges/challenge.tsx +++ b/src/features/challenges/challenge.tsx @@ -36,47 +36,87 @@ import { createLazyProxy } from "util/proxies"; import type { Ref, WatchStopHandle } from "vue"; import { computed, unref, watch } from "vue"; -export const ChallengeType = Symbol("ChallengeType"); +/** A symbol used to identify {@link Challenge} features. */ +export const ChallengeType = Symbol("Challenge"); +/** + * An object that configures a {@link Challenge}. + */ export interface ChallengeOptions { + /** Whether this challenge should be visible. */ visibility?: Computable; + /** Whether this challenge can be started. */ canStart?: Computable; + /** The reset function for this challenge. */ reset?: GenericReset; + /** The requirement(s) to complete this challenge. */ requirements: Requirements; + /** Whether or not completing this challenge should grant multiple completions if requirements met. Requires {@link requirements} to be a requirement or array of requirements with {@link Requirement.canMaximize} true. */ maximize?: Computable; + /** The maximum number of times the challenge can be completed. */ completionLimit?: Computable; + /** Shows a marker on the corner of the feature. */ mark?: Computable; + /** Dictionary of CSS classes to apply to this feature. */ classes?: Computable>; + /** CSS to apply to this feature. */ style?: Computable; + /** The display to use for this challenge. */ display?: Computable< | CoercableComponent | { + /** A header to appear at the top of the display. */ title?: CoercableComponent; + /** The main text that appears in the display. */ description: CoercableComponent; + /** A description of the current goal for this challenge. */ goal?: CoercableComponent; + /** A description of what will change upon completing this challenge. */ reward?: CoercableComponent; + /** A description of the current effect of this challenge. */ effectDisplay?: CoercableComponent; } >; + /** A function that is called when the challenge is completed. */ onComplete?: VoidFunction; + /** A function that is called when the challenge is exited. */ onExit?: VoidFunction; + /** A function that is called when the challenge is entered. */ onEnter?: VoidFunction; } +/** + * The properties that are added onto a processed {@link ChallengeOptions} to create a {@link Challenge}. + */ export interface BaseChallenge { + /** An auto-generated ID for identifying challenges that appear in the DOM. Will not persist between refreshes or updates. */ id: string; + /** The current amount of times this challenge can be completed. */ canComplete: Ref; + /** The current number of times this challenge has been completed. */ completions: Persistent; + /** Whether or not this challenge has been completed. */ completed: Ref; + /** Whether or not this challenge's completion count is at its limit. */ maxed: Ref; + /** Whether or not this challenge is currently active. */ active: Persistent; + /** A function to enter or leave the challenge. */ toggle: VoidFunction; + /** + * A function to complete this challenge. + * @param remainInChallenge - Optional parameter to specify if the challenge should remain active after completion. + */ complete: (remainInChallenge?: boolean) => void; + /** A symbol that helps identify features of the same type. */ type: typeof ChallengeType; + /** The Vue component used to render this feature. */ [Component]: GenericComponent; + /** A function to gather the props the vue component requires for this feature. */ [GatherProps]: () => Record; } +/** An object that represents a feature that can be entered and exited, and have one or more completions with scaling requirements. */ export type Challenge = Replace< T & BaseChallenge, { @@ -92,6 +132,7 @@ export type Challenge = Replace< } >; +/** A type that matches any valid {@link Challenge} object. */ export type GenericChallenge = Replace< Challenge, { @@ -102,6 +143,10 @@ export type GenericChallenge = Replace< } >; +/** + * Lazily creates a challenge with the given options. + * @param optionsFunc Challenge options. + */ export function createChallenge( optionsFunc: OptionsFunc ): Challenge { @@ -248,6 +293,12 @@ export function createChallenge( }); } +/** + * This will automatically complete a challenge when it's requirements are met. + * @param challenge The challenge to auto-complete + * @param autoActive Whether or not auto-completing should currently occur + * @param exitOnComplete Whether or not to exit the challenge after auto-completion + */ export function setupAutoComplete( challenge: GenericChallenge, autoActive: Computable = true, @@ -264,19 +315,27 @@ export function setupAutoComplete( ); } +/** + * Utility for taking an array of challenges where only one may be active at a time, and giving a ref to the one currently active (or null if none are active) + * @param challenges The list of challenges that are mutually exclusive + */ export function createActiveChallenge( challenges: GenericChallenge[] -): Ref { - return computed(() => challenges.find(challenge => challenge.active.value)); +): Ref { + return computed(() => challenges.find(challenge => challenge.active.value) ?? null); } +/** + * Utility for reporting if any challenge in a list is currently active. Intended for preventing entering a challenge if another is already active. + * @param challenges List of challenges that are mutually exclusive + */ export function isAnyChallengeActive( - challenges: GenericChallenge[] | Ref + challenges: GenericChallenge[] | Ref ): Ref { if (isArray(challenges)) { challenges = createActiveChallenge(challenges); } - return computed(() => (challenges as Ref).value != null); + return computed(() => (challenges as Ref).value != null); } declare module "game/settings" { From 742d2293d043d8c3e38c58ff3d844734c78e9757 Mon Sep 17 00:00:00 2001 From: thepaperpilot Date: Sun, 2 Apr 2023 23:49:51 -0500 Subject: [PATCH 8/9] Made achievements use requirements system, and document them --- src/features/achievements/achievement.tsx | 83 ++++++++++++++++------- 1 file changed, 60 insertions(+), 23 deletions(-) diff --git a/src/features/achievements/achievement.tsx b/src/features/achievements/achievement.tsx index 4776fd1..baa86ca 100644 --- a/src/features/achievements/achievement.tsx +++ b/src/features/achievements/achievement.tsx @@ -1,3 +1,4 @@ +import { isArray } from "@vue/shared"; import AchievementComponent from "features/achievements/Achievement.vue"; import { CoercableComponent, @@ -5,7 +6,6 @@ import { GatherProps, GenericComponent, getUniqueID, - isVisible, OptionsFunc, Replace, setDefault, @@ -16,6 +16,12 @@ import "game/notifications"; import type { Persistent } from "game/persistence"; import { persistent } from "game/persistence"; import player from "game/player"; +import { + createBooleanRequirement, + createVisibilityRequirement, + Requirements, + requirementsMet +} from "game/requirements"; import settings from "game/settings"; import type { Computable, @@ -31,28 +37,50 @@ import { useToast } from "vue-toastification"; const toast = useToast(); +/** A symbol used to identify {@link Achievement} features. */ export const AchievementType = Symbol("Achievement"); +/** + * An object that configures an {@link Achievement}. + */ export interface AchievementOptions { + /** Whether this achievement should be visible. */ visibility?: Computable; - shouldEarn?: () => boolean; + /** The requirement(s) to earn this achievement. Can be left null if using {@link BaseAchievement.complete}. */ + requirements?: Requirements; + /** The display to use for this achievement. */ display?: Computable; + /** Shows a marker on the corner of the feature. */ mark?: Computable; + /** An image to display as the background for this achievement. */ image?: Computable; + /** CSS to apply to this feature. */ style?: Computable; + /** Dictionary of CSS classes to apply to this feature. */ classes?: Computable>; + /** A function that is called when the achievement is completed. */ onComplete?: VoidFunction; } +/** + * The properties that are added onto a processed {@link AchievementOptions} to create an {@link Achievement}. + */ export interface BaseAchievement { + /** An auto-generated ID for identifying achievements that appear in the DOM. Will not persist between refreshes or updates. */ id: string; + /** Whether or not this achievement has been earned. */ earned: Persistent; + /** A function to complete this achievement. */ complete: VoidFunction; + /** A symbol that helps identify features of the same type. */ type: typeof AchievementType; + /** The Vue component used to render this feature. */ [Component]: GenericComponent; + /** A function to gather the props the vue component requires for this feature. */ [GatherProps]: () => Record; } +/** An object that represents a feature with that is passively earned upon meeting certain requirements. */ export type Achievement = Replace< T & BaseAchievement, { @@ -65,6 +93,7 @@ export type Achievement = Replace< } >; +/** A type that matches any valid {@link Achievement} object. */ export type GenericAchievement = Replace< Achievement, { @@ -72,6 +101,10 @@ export type GenericAchievement = Replace< } >; +/** + * Lazily creates a achievement with the given options. + * @param optionsFunc Achievement options. + */ export function createAchievement( optionsFunc?: OptionsFunc ): Achievement { @@ -85,6 +118,21 @@ export function createAchievement( achievement.earned = earned; achievement.complete = function () { earned.value = true; + const genericAchievement = achievement as GenericAchievement; + genericAchievement.onComplete?.(); + if (genericAchievement.display != null) { + const Display = coerceComponent(unref(genericAchievement.display)); + toast.info( +
+

Achievement earned!

+
+ {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */} + {/* @ts-ignore */} + +
+
+ ); + } }; processComputable(achievement as T, "visibility"); @@ -100,30 +148,19 @@ export function createAchievement( return { visibility, display, earned, image, style: unref(style), classes, mark, id }; }; - if (achievement.shouldEarn) { + if (achievement.requirements) { const genericAchievement = achievement as GenericAchievement; + const requirements = [ + createVisibilityRequirement(genericAchievement), + createBooleanRequirement(() => !genericAchievement.earned.value), + ...(isArray(achievement.requirements) + ? achievement.requirements + : [achievement.requirements]) + ]; watchEffect(() => { if (settings.active !== player.id) return; - if ( - !genericAchievement.earned.value && - isVisible(genericAchievement.visibility) && - genericAchievement.shouldEarn?.() - ) { - genericAchievement.earned.value = true; - genericAchievement.onComplete?.(); - if (genericAchievement.display != null) { - const Display = coerceComponent(unref(genericAchievement.display)); - toast.info( -
-

Achievement earned!

-
- {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */} - {/* @ts-ignore */} - -
-
- ); - } + if (requirementsMet(requirements)) { + genericAchievement.complete(); } }); } From 7c7fb38dd802a9d63227b3118fd624688f314f9f Mon Sep 17 00:00:00 2001 From: thepaperpilot Date: Mon, 3 Apr 2023 00:34:45 -0500 Subject: [PATCH 9/9] Merge milestones and achievements Yay for removing a whole redundant feature! --- src/data/common.tsx | 36 ++-- src/features/achievements/Achievement.vue | 82 +++++++- src/features/achievements/achievement.tsx | 141 ++++++++++++- src/features/milestones/Milestone.vue | 128 ------------ src/features/milestones/milestone.tsx | 235 ---------------------- 5 files changed, 226 insertions(+), 396 deletions(-) delete mode 100644 src/features/milestones/Milestone.vue delete mode 100644 src/features/milestones/milestone.tsx diff --git a/src/data/common.tsx b/src/data/common.tsx index befb408..d163f72 100644 --- a/src/data/common.tsx +++ b/src/data/common.tsx @@ -1,10 +1,10 @@ import Collapsible from "components/layout/Collapsible.vue"; +import { GenericAchievement } from "features/achievements/achievement"; import type { Clickable, ClickableOptions, GenericClickable } from "features/clickables/clickable"; import { createClickable } from "features/clickables/clickable"; import type { GenericConversion } from "features/conversion"; import type { CoercableComponent, JSXFunction, OptionsFunc, Replace } from "features/feature"; import { jsx, setDefault } from "features/feature"; -import { GenericMilestone } from "features/milestones/milestone"; import { displayResource, Resource } from "features/resources/resource"; import type { GenericTree, GenericTreeNode, TreeNode, TreeNodeOptions } from "features/trees/tree"; import { createTreeNode } from "features/trees/tree"; @@ -384,35 +384,35 @@ export function colorText(textToColor: string, color = "var(--accent2)"): JSX.El } /** - * Creates a collapsible display of a list of milestones - * @param milestones A dictionary of the milestones to display, inserted in the order from easiest to hardest + * Creates a collapsible display of a list of achievements + * @param achievements A dictionary of the achievements to display, inserted in the order from easiest to hardest */ -export function createCollapsibleMilestones(milestones: Record) { - // Milestones are typically defined from easiest to hardest, and we want to show hardest first - const orderedMilestones = Object.values(milestones).reverse(); - const collapseMilestones = persistent(true, false); - const lockedMilestones = computed(() => - orderedMilestones.filter(m => m.earned.value === false) +export function createCollapsibleAchievements(achievements: Record) { + // Achievements are typically defined from easiest to hardest, and we want to show hardest first + const orderedAchievements = Object.values(achievements).reverse(); + const collapseAchievements = persistent(true, false); + const lockedAchievements = computed(() => + orderedAchievements.filter(m => m.earned.value === false) ); const { firstFeature, collapsedContent, hasCollapsedContent } = getFirstFeature( - orderedMilestones, + orderedAchievements, m => m.earned.value ); const display = jsx(() => { - const milestonesToDisplay = [...lockedMilestones.value]; + const achievementsToDisplay = [...lockedAchievements.value]; if (firstFeature.value) { - milestonesToDisplay.push(firstFeature.value); + achievementsToDisplay.push(firstFeature.value); } return renderColJSX( - ...milestonesToDisplay, + ...achievementsToDisplay, jsx(() => ( @@ -420,7 +420,7 @@ export function createCollapsibleMilestones(milestones: Record - + - - - diff --git a/src/features/milestones/milestone.tsx b/src/features/milestones/milestone.tsx deleted file mode 100644 index 0a973db..0000000 --- a/src/features/milestones/milestone.tsx +++ /dev/null @@ -1,235 +0,0 @@ -import Select from "components/fields/Select.vue"; -import type { - CoercableComponent, - GenericComponent, - OptionsFunc, - Replace, - StyleValue -} from "features/feature"; -import { - Component, - GatherProps, - getUniqueID, - isVisible, - jsx, - setDefault, - Visibility -} from "features/feature"; -import MilestoneComponent from "features/milestones/Milestone.vue"; -import { globalBus } from "game/events"; -import "game/notifications"; -import type { Persistent } from "game/persistence"; -import { persistent } from "game/persistence"; -import player from "game/player"; -import settings, { registerSettingField } from "game/settings"; -import { camelToTitle } from "util/common"; -import type { - Computable, - GetComputableType, - GetComputableTypeWithDefault, - ProcessedComputable -} from "util/computed"; -import { processComputable } from "util/computed"; -import { createLazyProxy } from "util/proxies"; -import { coerceComponent, isCoercableComponent } from "util/vue"; -import { computed, unref, watchEffect } from "vue"; -import { useToast } from "vue-toastification"; - -const toast = useToast(); - -export const MilestoneType = Symbol("Milestone"); - -export enum MilestoneDisplay { - All = "all", - //Last = "last", - Configurable = "configurable", - Incomplete = "incomplete", - None = "none" -} - -export interface MilestoneOptions { - visibility?: Computable; - shouldEarn?: () => boolean; - style?: Computable; - classes?: Computable>; - display?: Computable< - | CoercableComponent - | { - requirement: CoercableComponent; - effectDisplay?: CoercableComponent; - optionsDisplay?: CoercableComponent; - } - >; - showPopups?: Computable; - onComplete?: VoidFunction; -} - -export interface BaseMilestone { - id: string; - earned: Persistent; - complete: VoidFunction; - type: typeof MilestoneType; - [Component]: GenericComponent; - [GatherProps]: () => Record; -} - -export type Milestone = Replace< - T & BaseMilestone, - { - visibility: GetComputableTypeWithDefault; - style: GetComputableType; - classes: GetComputableType; - display: GetComputableType; - showPopups: GetComputableType; - } ->; - -export type GenericMilestone = Replace< - Milestone, - { - visibility: ProcessedComputable; - } ->; - -export function createMilestone( - optionsFunc?: OptionsFunc -): Milestone { - const earned = persistent(false, false); - return createLazyProxy(() => { - const milestone = optionsFunc?.() ?? ({} as ReturnType>); - milestone.id = getUniqueID("milestone-"); - milestone.type = MilestoneType; - milestone[Component] = MilestoneComponent as GenericComponent; - - milestone.earned = earned; - milestone.complete = function () { - const genericMilestone = milestone as GenericMilestone; - earned.value = true; - genericMilestone.onComplete?.(); - if (genericMilestone.display != null && unref(genericMilestone.showPopups) === true) { - const display = unref(genericMilestone.display); - const Display = coerceComponent( - isCoercableComponent(display) ? display : display.requirement - ); - toast( - <> -

Milestone earned!

-
- {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */} - {/* @ts-ignore */} - -
- - ); - } - }; - - processComputable(milestone as T, "visibility"); - setDefault(milestone, "visibility", Visibility.Visible); - const visibility = milestone.visibility as ProcessedComputable; - milestone.visibility = computed(() => { - const display = unref((milestone as GenericMilestone).display); - switch (settings.msDisplay) { - default: - case MilestoneDisplay.All: - return unref(visibility); - case MilestoneDisplay.Configurable: - if ( - unref(milestone.earned) && - !( - display != null && - typeof display == "object" && - "optionsDisplay" in (display as Record) - ) - ) { - return Visibility.None; - } - return unref(visibility); - case MilestoneDisplay.Incomplete: - if (unref(milestone.earned)) { - return Visibility.None; - } - return unref(visibility); - case MilestoneDisplay.None: - return Visibility.None; - } - }); - - processComputable(milestone as T, "style"); - processComputable(milestone as T, "classes"); - processComputable(milestone as T, "display"); - processComputable(milestone as T, "showPopups"); - - milestone[GatherProps] = function (this: GenericMilestone) { - const { visibility, display, style, classes, earned, id } = this; - return { visibility, display, style: unref(style), classes, earned, id }; - }; - - if (milestone.shouldEarn) { - const genericMilestone = milestone as GenericMilestone; - watchEffect(() => { - if (settings.active !== player.id) return; - if ( - !genericMilestone.earned.value && - isVisible(genericMilestone.visibility) && - genericMilestone.shouldEarn?.() - ) { - genericMilestone.earned.value = true; - genericMilestone.onComplete?.(); - if ( - genericMilestone.display != null && - unref(genericMilestone.showPopups) === true - ) { - const display = unref(genericMilestone.display); - const Display = coerceComponent( - isCoercableComponent(display) ? display : display.requirement - ); - toast( - <> -

Milestone earned!

-
- {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */} - {/* @ts-ignore */} - -
- - ); - } - } - }); - } - - return milestone as unknown as Milestone; - }); -} - -declare module "game/settings" { - interface Settings { - msDisplay: MilestoneDisplay; - } -} - -globalBus.on("loadSettings", settings => { - setDefault(settings, "msDisplay", MilestoneDisplay.All); -}); - -const msDisplayOptions = Object.values(MilestoneDisplay).map(option => ({ - label: camelToTitle(option), - value: option -})); - -registerSettingField( - jsx(() => ( -