Profectus-Demo/src/data/common.tsx

258 lines
9.1 KiB
TypeScript
Raw Normal View History

2022-01-14 04:25:47 +00:00
import {
Clickable,
ClickableOptions,
createClickable,
GenericClickable
2022-03-04 03:39:48 +00:00
} from "features/clickables/clickable";
import { GenericConversion } from "features/conversion";
import {
CoercableComponent,
jsx,
JSXFunction,
OptionsFunc,
Replace,
setDefault
} from "features/feature";
2022-03-04 03:39:48 +00:00
import { displayResource } from "features/resources/resource";
2022-01-14 04:25:47 +00:00
import {
createTreeNode,
GenericTree,
GenericTreeNode,
TreeNode,
TreeNodeOptions
2022-03-04 03:39:48 +00:00
} from "features/trees/tree";
import { Modifier } from "game/modifiers";
import { Persistent, persistent } from "game/persistence";
2022-03-04 03:39:48 +00:00
import player from "game/player";
import Decimal, { DecimalSource, format } from "util/bignum";
import { WithRequired } from "util/common";
2022-01-14 04:25:47 +00:00
import {
Computable,
convertComputable,
2022-03-05 22:42:55 +00:00
GetComputableType,
2022-01-14 04:25:47 +00:00
GetComputableTypeWithDefault,
processComputable,
ProcessedComputable
2022-03-04 03:39:48 +00:00
} from "util/computed";
import { renderJSX } from "util/vue";
2022-01-14 04:25:47 +00:00
import { computed, Ref, unref } from "vue";
import "./common.css";
2022-01-14 04:25:47 +00:00
export interface ResetButtonOptions extends ClickableOptions {
conversion: GenericConversion;
tree: GenericTree;
treeNode: GenericTreeNode;
resetDescription?: Computable<string>;
showNextAt?: Computable<boolean>;
display?: Computable<CoercableComponent>;
canClick?: Computable<boolean>;
2022-04-11 02:16:20 +00:00
minimumGain?: Computable<DecimalSource>;
2022-01-14 04:25:47 +00:00
}
2022-03-09 01:40:51 +00:00
export type ResetButton<T extends ResetButtonOptions> = Replace<
2022-01-14 04:25:47 +00:00
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>>;
2022-04-11 02:16:20 +00:00
minimumGain: GetComputableTypeWithDefault<T["minimumGain"], 1>;
2022-01-14 04:25:47 +00:00
onClick: VoidFunction;
}
>;
export type GenericResetButton = Replace<
GenericClickable & ResetButton<ResetButtonOptions>,
{
resetDescription: ProcessedComputable<string>;
showNextAt: ProcessedComputable<boolean>;
display: ProcessedComputable<CoercableComponent>;
canClick: ProcessedComputable<boolean>;
2022-04-11 02:16:20 +00:00
minimumGain: ProcessedComputable<DecimalSource>;
2022-01-14 04:25:47 +00:00
}
>;
export function createResetButton<T extends ClickableOptions & ResetButtonOptions>(
optionsFunc: OptionsFunc<T>
2022-01-14 04:25:47 +00:00
): ResetButton<T> {
return createClickable(() => {
const resetButton = optionsFunc();
processComputable(resetButton as T, "showNextAt");
setDefault(resetButton, "showNextAt", true);
2022-04-11 02:16:20 +00:00
setDefault(resetButton, "minimumGain", 1);
if (resetButton.resetDescription == null) {
resetButton.resetDescription = computed(() =>
Decimal.lt(resetButton.conversion.gainResource.value, 1e3) ? "Reset for " : ""
2022-01-14 04:25:47 +00:00
);
} else {
processComputable(resetButton as T, "resetDescription");
}
if (resetButton.display == null) {
resetButton.display = jsx(() => (
2022-01-14 04:25:47 +00:00
<span>
{unref(resetButton.resetDescription as ProcessedComputable<string>)}
2022-01-14 04:25:47 +00:00
<b>
{displayResource(
resetButton.conversion.gainResource,
2022-04-11 02:16:20 +00:00
Decimal.max(
unref(resetButton.conversion.actualGain),
unref(resetButton.minimumGain as ProcessedComputable<DecimalSource>)
)
2022-01-14 04:25:47 +00:00
)}
</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)
2022-04-11 02:53:11 +00:00
? unref(resetButton.conversion.nextAt)
: unref(resetButton.conversion.currentAt)
)}{" "}
{resetButton.conversion.baseResource.displayName}
</div>
2022-01-14 04:25:47 +00:00
</span>
));
}
if (resetButton.canClick == null) {
resetButton.canClick = computed(() =>
2022-04-11 02:16:20 +00:00
Decimal.gte(
unref(resetButton.conversion.actualGain),
unref(resetButton.minimumGain as ProcessedComputable<DecimalSource>)
)
2022-01-14 04:25:47 +00:00
);
}
const onClick = resetButton.onClick;
resetButton.onClick = function () {
if (!unref(resetButton.canClick)) {
return;
}
resetButton.conversion.convert();
resetButton.tree.reset(resetButton.treeNode);
onClick?.();
};
2022-01-14 04:25:47 +00:00
return resetButton;
}) as unknown as ResetButton<T>;
2022-01-14 04:25:47 +00:00
}
export interface LayerTreeNodeOptions extends TreeNodeOptions {
layerID: string;
2022-03-05 22:42:55 +00:00
color: Computable<string>; // marking as required
display?: Computable<CoercableComponent>;
2022-03-05 22:42:55 +00:00
append?: Computable<boolean>;
2022-01-14 04:25:47 +00:00
}
export type LayerTreeNode<T extends LayerTreeNodeOptions> = Replace<
TreeNode<T>,
{
2022-03-05 22:42:55 +00:00
display: GetComputableTypeWithDefault<T["display"], T["layerID"]>;
append: GetComputableType<T["append"]>;
}
>;
export type GenericLayerTreeNode = Replace<
LayerTreeNode<LayerTreeNodeOptions>,
{
display: ProcessedComputable<CoercableComponent>;
2022-03-05 22:42:55 +00:00
append?: ProcessedComputable<boolean>;
2022-01-14 04:25:47 +00:00
}
>;
export function createLayerTreeNode<T extends LayerTreeNodeOptions>(
optionsFunc: OptionsFunc<T>
): LayerTreeNode<T> {
return createTreeNode(() => {
const options = optionsFunc();
2022-03-05 22:42:55 +00:00
processComputable(options as T, "display");
setDefault(options, "display", options.layerID);
processComputable(options as T, "append");
return {
...options,
display: options.display,
2022-03-05 22:42:55 +00:00
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);
2022-01-14 04:25:47 +00:00
}
2022-03-05 22:42:55 +00:00
}
: function () {
player.tabs.splice(1, 1, options.layerID);
}
};
2022-01-25 04:23:30 +00:00
}) as unknown as LayerTreeNode<T>;
2022-01-14 04:25:47 +00:00
}
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];
}