import { Clickable, ClickableOptions, createClickable, GenericClickable } from "features/clickables/clickable"; import { GenericConversion } from "features/conversion"; import { CoercableComponent, jsx, JSXFunction, OptionsFunc, Replace, setDefault } from "features/feature"; import { displayResource } from "features/resources/resource"; import { createTreeNode, GenericTree, GenericTreeNode, TreeNode, TreeNodeOptions } from "features/trees/tree"; import { Modifier } from "game/modifiers"; import { DefaultValue, Persistent, persistent } from "game/persistence"; import player from "game/player"; import Decimal, { DecimalSource, format } from "util/bignum"; import { WithRequired } from "util/common"; import { Computable, convertComputable, GetComputableType, GetComputableTypeWithDefault, processComputable, ProcessedComputable } from "util/computed"; import { renderJSX } from "util/vue"; import { computed, Ref, unref } from "vue"; import "./common.css"; export interface ResetButtonOptions extends ClickableOptions { conversion: GenericConversion; tree: GenericTree; treeNode: GenericTreeNode; resetDescription?: Computable<string>; showNextAt?: Computable<boolean>; display?: Computable<CoercableComponent>; canClick?: Computable<boolean>; minimumGain?: Computable<DecimalSource>; resetTime?: Persistent<DecimalSource>; } export type ResetButton<T extends ResetButtonOptions> = Replace< Clickable<T>, { resetDescription: GetComputableTypeWithDefault<T["resetDescription"], Ref<string>>; showNextAt: GetComputableTypeWithDefault<T["showNextAt"], true>; display: GetComputableTypeWithDefault<T["display"], Ref<JSX.Element>>; canClick: GetComputableTypeWithDefault<T["canClick"], Ref<boolean>>; minimumGain: GetComputableTypeWithDefault<T["minimumGain"], 1>; onClick: VoidFunction; } >; export type GenericResetButton = Replace< GenericClickable & ResetButton<ResetButtonOptions>, { resetDescription: ProcessedComputable<string>; showNextAt: ProcessedComputable<boolean>; display: ProcessedComputable<CoercableComponent>; canClick: ProcessedComputable<boolean>; minimumGain: ProcessedComputable<DecimalSource>; } >; export function createResetButton<T extends ClickableOptions & ResetButtonOptions>( optionsFunc: OptionsFunc<T> ): ResetButton<T> { return createClickable(() => { const resetButton = optionsFunc(); processComputable(resetButton as T, "showNextAt"); setDefault(resetButton, "showNextAt", true); setDefault(resetButton, "minimumGain", 1); if (resetButton.resetDescription == null) { resetButton.resetDescription = computed(() => Decimal.lt(resetButton.conversion.gainResource.value, 1e3) ? "Reset for " : "" ); } else { processComputable(resetButton as T, "resetDescription"); } if (resetButton.display == null) { resetButton.display = jsx(() => ( <span> {unref(resetButton.resetDescription as ProcessedComputable<string>)} <b> {displayResource( resetButton.conversion.gainResource, Decimal.max( unref(resetButton.conversion.actualGain), unref(resetButton.minimumGain as ProcessedComputable<DecimalSource>) ) )} </b>{" "} {resetButton.conversion.gainResource.displayName} <div v-show={unref(resetButton.showNextAt)}> <br /> {unref(resetButton.conversion.buyMax) ? "Next:" : "Req:"}{" "} {displayResource( resetButton.conversion.baseResource, unref(resetButton.conversion.buyMax) || Decimal.floor(unref(resetButton.conversion.actualGain)).neq(1) ? unref(resetButton.conversion.nextAt) : unref(resetButton.conversion.currentAt) )}{" "} {resetButton.conversion.baseResource.displayName} </div> </span> )); } if (resetButton.canClick == null) { resetButton.canClick = computed(() => Decimal.gte( unref(resetButton.conversion.actualGain), unref(resetButton.minimumGain as ProcessedComputable<DecimalSource>) ) ); } const onClick = resetButton.onClick; resetButton.onClick = function () { if (!unref(resetButton.canClick)) { return; } resetButton.conversion.convert(); resetButton.tree.reset(resetButton.treeNode); if (resetButton.resetTime) { resetButton.resetTime.value = resetButton.resetTime[DefaultValue]; } onClick?.(); }; return resetButton; }) as unknown as ResetButton<T>; } export interface LayerTreeNodeOptions extends TreeNodeOptions { layerID: string; color: Computable<string>; // marking as required display?: Computable<CoercableComponent>; append?: Computable<boolean>; } export type LayerTreeNode<T extends LayerTreeNodeOptions> = Replace< TreeNode<T>, { display: GetComputableTypeWithDefault<T["display"], T["layerID"]>; append: GetComputableType<T["append"]>; } >; export type GenericLayerTreeNode = Replace< LayerTreeNode<LayerTreeNodeOptions>, { display: ProcessedComputable<CoercableComponent>; append?: ProcessedComputable<boolean>; } >; export function createLayerTreeNode<T extends LayerTreeNodeOptions>( optionsFunc: OptionsFunc<T> ): LayerTreeNode<T> { return createTreeNode(() => { const options = optionsFunc(); processComputable(options as T, "display"); setDefault(options, "display", options.layerID); processComputable(options as T, "append"); return { ...options, display: options.display, onClick: unref((options as unknown as GenericLayerTreeNode).append) ? function () { if (player.tabs.includes(options.layerID)) { const index = player.tabs.lastIndexOf(options.layerID); player.tabs.splice(index, 1); } else { player.tabs.push(options.layerID); } } : function () { player.tabs.splice(1, 1, options.layerID); } }; }) as unknown as LayerTreeNode<T>; } export function createCollapsibleModifierSections( sections: { title: string; subtitle?: string; modifier: WithRequired<Modifier, "description">; base?: Computable<DecimalSource>; unit?: string; baseText?: Computable<CoercableComponent>; visible?: Computable<boolean>; }[] ): [JSXFunction, Persistent<boolean>[]] { const processedBase = sections.map(s => convertComputable(s.base)); const processedBaseText = sections.map(s => convertComputable(s.baseText)); const processedVisible = sections.map(s => convertComputable(s.visible)); const collapsed = sections.map(() => persistent<boolean>(false)); const jsxFunc = jsx(() => { const sectionJSX = sections.map((s, i) => { if (unref(processedVisible[i]) === false) return null; const header = ( <h3 onClick={() => (collapsed[i].value = !collapsed[i].value)} style="cursor: pointer" > <span class={"modifier-toggle" + (unref(collapsed[i]) ? " collapsed" : "")}> ▼ </span> {s.title} {s.subtitle ? <span class="subtitle"> ({s.subtitle})</span> : null} </h3> ); const modifiers = unref(collapsed[i]) ? null : ( <> <div class="modifier-container"> <span class="modifier-amount"> {format(unref(processedBase[i]) ?? 1)} {s.unit} </span> <span class="modifier-description"> {renderJSX(unref(processedBaseText[i]) ?? "Base")} </span> </div> {renderJSX(unref(s.modifier.description))} </> ); return ( <> {i === 0 ? null : <br />} <div> {header} <br /> {modifiers} <hr /> Total: {format(s.modifier.apply(unref(processedBase[i]) ?? 1))} {s.unit} </div> </> ); }); return <>{sectionJSX}</>; }); return [jsxFunc, collapsed]; }