forked from profectus/Profectus
Complete the rewrite
Renderables no longer get wrapped in computed refs, because JSX.Elements don't like that (desyncs with the DOM) Relatedly, a lot of display functions got fairly simplified, removing unnecessary local components Added `MaybeGetter` utility type for something that may be a getter function or a static value (but not a ref) Made Achievement.vue use a Renderable for the display. The object of components can still be passed to `createAchievement` Made Challenge.vue use a Renderable for the display. The object of components can still be passed to `createChallenge` Fixed some issues introduced by the rewrite that broke particles systems
This commit is contained in:
parent
4ce1b60a3d
commit
68da6c352e
36 changed files with 478 additions and 893 deletions
src
components
data
features
VueFeature.vue
achievements
bars
challenges
clickables
conversion.tsfeature.tsgrids
infoboxes
particles
resources
tabs
trees
game
util
wrappers
tests/game
|
@ -15,15 +15,16 @@
|
|||
|
||||
<script setup lang="tsx">
|
||||
import "components/common/fields.css";
|
||||
import { MaybeGetter } from "util/computed";
|
||||
import { render, Renderable } from "util/vue";
|
||||
import { MaybeRef, ref, toRef, unref, watch } from "vue";
|
||||
import { ref, toRef, unref, watch } from "vue";
|
||||
import VueNextSelect from "vue-next-select";
|
||||
import "vue-next-select/dist/index.css";
|
||||
|
||||
export type SelectOption = { label: string; value: unknown };
|
||||
|
||||
const props = defineProps<{
|
||||
title?: MaybeRef<Renderable>;
|
||||
title?: MaybeGetter<Renderable>;
|
||||
modelValue?: unknown;
|
||||
options: SelectOption[];
|
||||
placeholder?: string;
|
||||
|
|
|
@ -27,12 +27,13 @@
|
|||
|
||||
<script setup lang="tsx">
|
||||
import "components/common/fields.css";
|
||||
import { MaybeGetter } from "util/computed";
|
||||
import { render, Renderable } from "util/vue";
|
||||
import { computed, MaybeRef, onMounted, shallowRef, unref } from "vue";
|
||||
import { computed, onMounted, shallowRef, unref } from "vue";
|
||||
import VueTextareaAutosize from "vue-textarea-autosize";
|
||||
|
||||
const props = defineProps<{
|
||||
title?: MaybeRef<Renderable>;
|
||||
title?: MaybeGetter<Renderable>;
|
||||
modelValue?: string;
|
||||
textArea?: boolean;
|
||||
placeholder?: string;
|
||||
|
|
|
@ -7,11 +7,12 @@
|
|||
|
||||
<script setup lang="tsx">
|
||||
import "components/common/fields.css";
|
||||
import { MaybeGetter } from "util/computed";
|
||||
import { render, Renderable } from "util/vue";
|
||||
import { computed, MaybeRef } from "vue";
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
title?: MaybeRef<Renderable>;
|
||||
title?: MaybeGetter<Renderable>;
|
||||
modelValue?: boolean;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
|
|
|
@ -8,14 +8,15 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { MaybeGetter } from "util/computed";
|
||||
import { render, Renderable } from "util/vue";
|
||||
import type { MaybeRef, Ref } from "vue";
|
||||
import type { Ref } from "vue";
|
||||
import Col from "./Column.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
collapsed: Ref<boolean>;
|
||||
display: MaybeRef<Renderable>;
|
||||
content: MaybeRef<Renderable>;
|
||||
display: MaybeGetter<Renderable>;
|
||||
content: MaybeGetter<Renderable>;
|
||||
}>();
|
||||
|
||||
const Display = () => render(props.display);
|
||||
|
|
|
@ -17,7 +17,7 @@ import settings from "game/settings";
|
|||
import type { DecimalSource } from "util/bignum";
|
||||
import Decimal, { format, formatSmall, formatTime } from "util/bignum";
|
||||
import { WithRequired } from "util/common";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { render, Renderable, renderCol } from "util/vue";
|
||||
import type { ComputedRef, MaybeRef, MaybeRefOrGetter } from "vue";
|
||||
import { computed, ref, unref } from "vue";
|
||||
|
@ -43,7 +43,7 @@ export interface ResetButtonOptions extends ClickableOptions {
|
|||
* The content to display on the button.
|
||||
* By default, this includes the reset description, and amount of currency to be gained.
|
||||
*/
|
||||
display?: MaybeRefOrGetter<Renderable>;
|
||||
display?: MaybeGetter<Renderable>;
|
||||
/**
|
||||
* Whether or not this button can currently be clicked.
|
||||
* Defaults to checking the current gain amount is greater than {@link minimumGain}
|
||||
|
@ -126,9 +126,8 @@ export function createResetButton<T extends ClickableOptions & ResetButtonOption
|
|||
Decimal.gte(unref(conversion.actualGain), unref(resetButton.minimumGain))
|
||||
),
|
||||
display:
|
||||
processGetter(display) ??
|
||||
computed(
|
||||
(): JSX.Element => (
|
||||
display ??
|
||||
((): JSX.Element => (
|
||||
<span>
|
||||
{unref(resetButton.resetDescription)}
|
||||
<b>
|
||||
|
@ -156,8 +155,7 @@ export function createResetButton<T extends ClickableOptions & ResetButtonOption
|
|||
</div>
|
||||
) : null}
|
||||
</span>
|
||||
)
|
||||
),
|
||||
)),
|
||||
onClick: function (e?: MouseEvent | TouchEvent) {
|
||||
if (unref(resetButton.canClick) === false) {
|
||||
return;
|
||||
|
@ -211,7 +209,7 @@ export function createLayerTreeNode<T extends LayerTreeNodeOptions>(optionsFunc:
|
|||
return {
|
||||
...(props as Omit<typeof props, keyof LayerTreeNodeOptions>),
|
||||
layerID,
|
||||
display: processGetter(display) ?? layerID,
|
||||
display: display ?? layerID,
|
||||
append: processGetter(append) ?? true,
|
||||
onClick() {
|
||||
if (unref<boolean>(layerTreeNode.append)) {
|
||||
|
@ -244,7 +242,7 @@ export interface Section {
|
|||
/** The unit of measurement for the base. **/
|
||||
unit?: string;
|
||||
/** The label to call the base amount. Defaults to "Base". **/
|
||||
baseText?: MaybeRefOrGetter<Renderable>;
|
||||
baseText?: MaybeGetter<Renderable>;
|
||||
/** Whether or not this section should be currently visible to the player. **/
|
||||
visible?: MaybeRefOrGetter<boolean>;
|
||||
/** Determines if numbers larger or smaller than the base should be displayed as red. */
|
||||
|
@ -258,12 +256,12 @@ export interface Section {
|
|||
*/
|
||||
export function createCollapsibleModifierSections(
|
||||
sectionsFunc: () => Section[]
|
||||
): [MaybeRef<Renderable>, Persistent<Record<number, boolean>>] {
|
||||
): [() => Renderable, Persistent<Record<number, boolean>>] {
|
||||
const sections: Section[] = [];
|
||||
const processed:
|
||||
| {
|
||||
base: MaybeRef<DecimalSource | undefined>[];
|
||||
baseText: (MaybeRef<Renderable> | undefined)[];
|
||||
baseText: (MaybeGetter<Renderable> | undefined)[];
|
||||
visible: MaybeRef<boolean | undefined>[];
|
||||
title: MaybeRef<string | undefined>[];
|
||||
subtitle: MaybeRef<string | undefined>[];
|
||||
|
@ -274,7 +272,7 @@ export function createCollapsibleModifierSections(
|
|||
if (!calculated) {
|
||||
sections.push(...sectionsFunc());
|
||||
processed.base = sections.map(s => processGetter(s.base));
|
||||
processed.baseText = sections.map(s => processGetter(s.baseText));
|
||||
processed.baseText = sections.map(s => s.baseText);
|
||||
processed.visible = sections.map(s => processGetter(s.visible));
|
||||
processed.title = sections.map(s => processGetter(s.title));
|
||||
processed.subtitle = sections.map(s => processGetter(s.subtitle));
|
||||
|
@ -284,7 +282,7 @@ export function createCollapsibleModifierSections(
|
|||
}
|
||||
|
||||
const collapsed = persistent<Record<number, boolean>>({}, false);
|
||||
const jsxFunc = computed(() => {
|
||||
const jsxFunc = () => {
|
||||
const sections = calculateSections();
|
||||
|
||||
let firstVisibleSection = true;
|
||||
|
@ -364,7 +362,7 @@ export function createCollapsibleModifierSections(
|
|||
);
|
||||
});
|
||||
return <>{sectionJSX}</>;
|
||||
});
|
||||
};
|
||||
return [jsxFunc, collapsed];
|
||||
}
|
||||
|
||||
|
|
|
@ -1,424 +0,0 @@
|
|||
import { createUpgrade } from "features/clickables/upgrade";
|
||||
import { createResource } from "features/resources/resource";
|
||||
import Board from "game/boards/Board.vue";
|
||||
import CircleProgress from "game/boards/CircleProgress.vue";
|
||||
import SVGNode from "game/boards/SVGNode.vue";
|
||||
import SquareProgress from "game/boards/SquareProgress.vue";
|
||||
import {
|
||||
Draggable,
|
||||
MakeDraggableOptions,
|
||||
NodePosition,
|
||||
makeDraggable,
|
||||
placeInAvailableSpace,
|
||||
setupActions,
|
||||
setupDraggableNode,
|
||||
setupUniqueIds
|
||||
} from "game/boards/board";
|
||||
import type { BaseLayer } from "game/layers";
|
||||
import { createLayer } from "game/layers";
|
||||
import { persistent } from "game/persistence";
|
||||
import { createCostRequirement } from "game/requirements";
|
||||
import { render } from "util/vue";
|
||||
import { ComponentPublicInstance, computed, ref, watch } from "vue";
|
||||
import { setupSelectable } from "../common";
|
||||
|
||||
const board = createLayer("board", function (this: BaseLayer) {
|
||||
type ANode = NodePosition & { id: number; links: number[]; type: "anode"; z: number };
|
||||
type BNode = NodePosition & { id: number; links: number[]; type: "bnode"; z: number };
|
||||
type CNode = typeof cNode & { draggable: Draggable<number | "cNode"> };
|
||||
type NodeTypes = ANode | BNode;
|
||||
|
||||
const board = ref<ComponentPublicInstance<typeof Board>>();
|
||||
|
||||
const { select, deselect, selected } = setupSelectable<number>();
|
||||
const {
|
||||
select: selectAction,
|
||||
deselect: deselectAction,
|
||||
selected: selectedAction
|
||||
} = setupSelectable<number>();
|
||||
|
||||
watch(selected, selected => {
|
||||
if (selected == null) {
|
||||
deselectAction();
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
startDrag,
|
||||
endDrag,
|
||||
drag,
|
||||
nodeBeingDragged,
|
||||
hasDragged,
|
||||
receivingNodes,
|
||||
receivingNode,
|
||||
dragDelta
|
||||
} = setupDraggableNode<number | "cnode">({
|
||||
board,
|
||||
getPosition(id) {
|
||||
return nodesById.value[id] ?? (cNode as CNode).draggable.position.value;
|
||||
},
|
||||
setPosition(id, position) {
|
||||
const node = nodesById.value[id] ?? (cNode as CNode).draggable.position.value;
|
||||
node.x = position.x;
|
||||
node.y = position.y;
|
||||
}
|
||||
});
|
||||
|
||||
// a nodes can be slotted into b nodes to draw a branch between them, with limited connections
|
||||
// a nodes can be selected and have an action to spawn a b node, and vice versa
|
||||
// Newly spawned nodes should find a safe spot to spawn, and display a link to their creator
|
||||
// a nodes use all the stuff circles used to have, and b diamonds
|
||||
// c node also exists but is a single Upgrade element that cannot be selected, but can be dragged
|
||||
// d nodes are a performance test - 1000 simple nodes that have no interactions
|
||||
// Make all nodes animate in (decorator? `fadeIn(feature)?)
|
||||
const nodes = persistent<(ANode | BNode)[]>([
|
||||
{ id: 0, x: 0, y: 0, z: 0, links: [], type: "anode" }
|
||||
]);
|
||||
const nodesById = computed<Record<string, NodeTypes>>(() =>
|
||||
nodes.value.reduce((acc, curr) => ({ ...acc, [curr.id]: curr }), {})
|
||||
);
|
||||
function mouseDownNode(e: MouseEvent | TouchEvent, node: NodeTypes) {
|
||||
const oldZ = node.z;
|
||||
nodes.value.forEach(node => {
|
||||
if (node.z > oldZ) {
|
||||
node.z--;
|
||||
}
|
||||
});
|
||||
node.z = nextId.value;
|
||||
if (nodeBeingDragged.value == null) {
|
||||
startDrag(e, node.id);
|
||||
}
|
||||
deselect();
|
||||
}
|
||||
function mouseUpNode(e: MouseEvent | TouchEvent, node: NodeTypes) {
|
||||
if (!hasDragged.value) {
|
||||
endDrag();
|
||||
if (typeof node.id === "number") {
|
||||
select(node.id);
|
||||
}
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
function translate(node: NodePosition, isDragging: boolean) {
|
||||
let x = node.x;
|
||||
let y = node.y;
|
||||
if (isDragging) {
|
||||
x += dragDelta.value.x;
|
||||
y += dragDelta.value.y;
|
||||
}
|
||||
return ` translate(${x}px,${y}px)`;
|
||||
}
|
||||
function rotate(rotation: number) {
|
||||
return ` rotate(${rotation}deg) `;
|
||||
}
|
||||
function scale(nodeOrBool: NodeTypes | boolean) {
|
||||
const isSelected =
|
||||
typeof nodeOrBool === "boolean" ? nodeOrBool : selected.value === nodeOrBool.id;
|
||||
return isSelected ? " scale(1.2)" : "";
|
||||
}
|
||||
function opacity(node: NodeTypes) {
|
||||
const isDragging = selected.value !== node.id && nodeBeingDragged.value === node.id;
|
||||
if (isDragging) {
|
||||
return "; opacity: 0.5;";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
function zIndex(node: NodeTypes) {
|
||||
if (selected.value === node.id || nodeBeingDragged.value === node.id) {
|
||||
return "; z-index: 100000000";
|
||||
}
|
||||
return "; z-index: " + node.z;
|
||||
}
|
||||
|
||||
const renderANode = function (node: ANode) {
|
||||
return (
|
||||
<SVGNode
|
||||
style={`transform: ${translate(node, nodeBeingDragged.value === node.id)}${opacity(
|
||||
node
|
||||
)}${zIndex(node)}`}
|
||||
onMouseDown={e => mouseDownNode(e, node)}
|
||||
onMouseUp={e => mouseUpNode(e, node)}
|
||||
>
|
||||
<g style={`transform: ${scale(node)}`}>
|
||||
{receivingNodes.value.includes(node.id) && (
|
||||
<circle
|
||||
r="58"
|
||||
fill="var(--background)"
|
||||
stroke={receivingNode.value === node.id ? "#0F0" : "#0F03"}
|
||||
stroke-width="2"
|
||||
/>
|
||||
)}
|
||||
<CircleProgress r={54.5} progress={0.5} stroke="var(--accent2)" />
|
||||
<circle
|
||||
r="50"
|
||||
fill="var(--raised-background)"
|
||||
stroke="var(--outline)"
|
||||
stroke-width="4"
|
||||
/>
|
||||
</g>
|
||||
{selected.value === node.id && selectedAction.value === 0 && (
|
||||
<text y="140" fill="var(--foreground)" class="node-text">
|
||||
Spawn B Node
|
||||
</text>
|
||||
)}
|
||||
<text fill="var(--foreground)" class="node-text">
|
||||
A
|
||||
</text>
|
||||
</SVGNode>
|
||||
);
|
||||
};
|
||||
const aActions = setupActions({
|
||||
node: () => nodesById.value[selected.value ?? ""],
|
||||
shouldShowActions: node => node.type === "anode",
|
||||
actions(node) {
|
||||
return [
|
||||
p => (
|
||||
<g
|
||||
style={`transform: ${translate(p, selectedAction.value === 0)}${scale(
|
||||
selectedAction.value === 0
|
||||
)}`}
|
||||
onClick={() => {
|
||||
if (selectedAction.value === 0) {
|
||||
spawnBNode(node as ANode);
|
||||
} else {
|
||||
selectAction(0);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<circle fill="black" r="20"></circle>
|
||||
<text fill="white" class="material-icons" x="-12" y="12">
|
||||
add
|
||||
</text>
|
||||
</g>
|
||||
)
|
||||
];
|
||||
},
|
||||
distance: 100
|
||||
});
|
||||
const sqrtTwo = Math.sqrt(2);
|
||||
const renderBNode = function (node: BNode) {
|
||||
return (
|
||||
<SVGNode
|
||||
style={`transform: ${translate(node, nodeBeingDragged.value === node.id)}${opacity(
|
||||
node
|
||||
)}${zIndex(node)}`}
|
||||
onMouseDown={e => mouseDownNode(e, node)}
|
||||
onMouseUp={e => mouseUpNode(e, node)}
|
||||
>
|
||||
<g style={`transform: ${scale(node)}${rotate(45)}`}>
|
||||
{receivingNodes.value.includes(node.id) && (
|
||||
<rect
|
||||
width={50 * sqrtTwo + 16}
|
||||
height={50 * sqrtTwo + 16}
|
||||
style={`translate(${(-50 * sqrtTwo + 16) / 2}, ${
|
||||
(-50 * sqrtTwo + 16) / 2
|
||||
})`}
|
||||
fill="var(--background)"
|
||||
stroke={receivingNode.value === node.id ? "#0F0" : "#0F03"}
|
||||
stroke-width="2"
|
||||
/>
|
||||
)}
|
||||
<SquareProgress
|
||||
size={50 * sqrtTwo + 9}
|
||||
progress={0.5}
|
||||
stroke="var(--accent2)"
|
||||
/>
|
||||
<rect
|
||||
width={50 * sqrtTwo}
|
||||
height={50 * sqrtTwo}
|
||||
style={`transform: translate(${(-50 * sqrtTwo) / 2}px, ${
|
||||
(-50 * sqrtTwo) / 2
|
||||
}px)`}
|
||||
fill="var(--raised-background)"
|
||||
stroke="var(--outline)"
|
||||
stroke-width="4"
|
||||
/>
|
||||
</g>
|
||||
{selected.value === node.id && selectedAction.value === 0 && (
|
||||
<text y="140" fill="var(--foreground)" class="node-text">
|
||||
Spawn A Node
|
||||
</text>
|
||||
)}
|
||||
<text fill="var(--foreground)" class="node-text">
|
||||
B
|
||||
</text>
|
||||
</SVGNode>
|
||||
);
|
||||
};
|
||||
const bActions = setupActions({
|
||||
node: () => nodesById.value[selected.value ?? ""],
|
||||
shouldShowActions: node => node.type === "bnode",
|
||||
actions(node) {
|
||||
return [
|
||||
p => (
|
||||
<g
|
||||
style={`transform: ${translate(p, selectedAction.value === 0)}${scale(
|
||||
selectedAction.value === 0
|
||||
)}`}
|
||||
onClick={() => {
|
||||
if (selectedAction.value === 0) {
|
||||
spawnANode(node as BNode);
|
||||
} else {
|
||||
selectAction(0);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<circle fill="white" r="20"></circle>
|
||||
<text fill="black" class="material-icons" x="-12" y="12">
|
||||
add
|
||||
</text>
|
||||
</g>
|
||||
)
|
||||
];
|
||||
},
|
||||
distance: 100
|
||||
});
|
||||
function spawnANode(parent: ANode | BNode) {
|
||||
const node: ANode = {
|
||||
x: parent.x,
|
||||
y: parent.y,
|
||||
z: nextId.value,
|
||||
type: "anode",
|
||||
links: [parent.id],
|
||||
id: nextId.value
|
||||
};
|
||||
placeInAvailableSpace(node, nodes.value);
|
||||
nodes.value.push(node);
|
||||
}
|
||||
function spawnBNode(parent: ANode | BNode) {
|
||||
const node: BNode = {
|
||||
x: parent.x,
|
||||
y: parent.y,
|
||||
z: nextId.value,
|
||||
type: "bnode",
|
||||
links: [parent.id],
|
||||
id: nextId.value
|
||||
};
|
||||
placeInAvailableSpace(node, nodes.value);
|
||||
nodes.value.push(node);
|
||||
}
|
||||
|
||||
const points = createResource(10);
|
||||
const cNode = createUpgrade(() => ({
|
||||
display: <h1>C</h1>,
|
||||
// Purposefully not using noPersist
|
||||
requirements: createCostRequirement(() => ({ cost: 10, resource: points })),
|
||||
style: {
|
||||
x: "100px",
|
||||
y: "100px",
|
||||
"--layer-color": "var(--accent1)"
|
||||
},
|
||||
// no-op to prevent purchasing while dragging
|
||||
onHold: () => {}
|
||||
}));
|
||||
makeDraggable<number | "cnode", MakeDraggableOptions<number | "cnode">>(cNode, () => ({
|
||||
id: "cnode",
|
||||
endDrag,
|
||||
startDrag,
|
||||
hasDragged,
|
||||
nodeBeingDragged,
|
||||
dragDelta,
|
||||
onMouseUp: cNode.purchase
|
||||
}));
|
||||
|
||||
const dNodesPerAxis = 50;
|
||||
const dNodes = (
|
||||
<>
|
||||
{new Array(dNodesPerAxis * dNodesPerAxis).fill(0).map((_, i) => {
|
||||
const x = (Math.floor(i / dNodesPerAxis) - dNodesPerAxis / 2) * 100;
|
||||
const y = ((i % dNodesPerAxis) - dNodesPerAxis / 2) * 100;
|
||||
return (
|
||||
<path
|
||||
fill="var(--bought)"
|
||||
style={`transform: translate(${x}px, ${y}px) scale(0.05)`}
|
||||
d="M62.43,122.88h-1.98c0-16.15-6.04-30.27-18.11-42.34C30.27,68.47,16.16,62.43,0,62.43v-1.98 c16.16,0,30.27-6.04,42.34-18.14C54.41,30.21,60.45,16.1,60.45,0h1.98c0,16.15,6.04,30.27,18.11,42.34 c12.07,12.07,26.18,18.11,42.34,18.11v1.98c-16.15,0-30.27,6.04-42.34,18.11C68.47,92.61,62.43,106.72,62.43,122.88L62.43,122.88z"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
|
||||
const links = computed(() => (
|
||||
<>
|
||||
{nodes.value
|
||||
.reduce(
|
||||
(acc, curr) => [
|
||||
...acc,
|
||||
...curr.links.map(l => ({ from: curr, to: nodesById.value[l] }))
|
||||
],
|
||||
[] as { from: NodeTypes; to: NodeTypes }[]
|
||||
)
|
||||
.map(link => (
|
||||
<line
|
||||
stroke="white"
|
||||
stroke-width={4}
|
||||
x1={
|
||||
nodeBeingDragged.value === link.from.id
|
||||
? dragDelta.value.x + link.from.x
|
||||
: link.from.x
|
||||
}
|
||||
y1={
|
||||
nodeBeingDragged.value === link.from.id
|
||||
? dragDelta.value.y + link.from.y
|
||||
: link.from.y
|
||||
}
|
||||
x2={
|
||||
nodeBeingDragged.value === link.to.id
|
||||
? dragDelta.value.x + link.to.x
|
||||
: link.to.x
|
||||
}
|
||||
y2={
|
||||
nodeBeingDragged.value === link.to.id
|
||||
? dragDelta.value.y + link.to.y
|
||||
: link.to.y
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
));
|
||||
|
||||
const nextId = setupUniqueIds(() => nodes.value);
|
||||
|
||||
function renderNode(node: NodeTypes | typeof cNode) {
|
||||
if (node.type === "anode") {
|
||||
return renderANode(node);
|
||||
} else if (node.type === "bnode") {
|
||||
return renderBNode(node);
|
||||
} else {
|
||||
return render(node);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: "Board",
|
||||
color: "var(--accent1)",
|
||||
display: () => (
|
||||
<>
|
||||
<Board
|
||||
onDrag={drag}
|
||||
onMouseDown={deselect}
|
||||
onMouseUp={endDrag}
|
||||
onMouseLeave={endDrag}
|
||||
ref={board}
|
||||
style={{ height: "600px" }}
|
||||
>
|
||||
<SVGNode>
|
||||
{dNodes}
|
||||
{links.value}
|
||||
</SVGNode>
|
||||
{nodes.value.map(renderNode)}
|
||||
{render(cNode)}
|
||||
<SVGNode>
|
||||
{aActions.value}
|
||||
{bActions.value}
|
||||
</SVGNode>
|
||||
</Board>
|
||||
</>
|
||||
),
|
||||
boardNodes: nodes,
|
||||
cNode,
|
||||
selected: persistent(selected)
|
||||
};
|
||||
});
|
||||
|
||||
export default board;
|
|
@ -18,12 +18,13 @@ import "components/common/features.css";
|
|||
import Node from "components/Node.vue";
|
||||
import type { Visibility } from "features/feature";
|
||||
import { isHidden, isVisible } from "features/feature";
|
||||
import { MaybeGetter } from "util/computed";
|
||||
import { render, Renderable } from "util/vue";
|
||||
import { MaybeRef, unref, type CSSProperties } from "vue";
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
id: string;
|
||||
components: MaybeRef<Renderable>[];
|
||||
components: MaybeGetter<Renderable>[];
|
||||
wrappers: ((el: () => Renderable) => Renderable)[];
|
||||
visibility?: MaybeRef<Visibility | boolean>;
|
||||
style?: MaybeRef<CSSProperties>;
|
||||
|
|
|
@ -29,35 +29,7 @@ const props = defineProps<{
|
|||
small: Achievement["small"];
|
||||
}>();
|
||||
|
||||
const Component = () => {
|
||||
if (props.display == null) {
|
||||
return null;
|
||||
} else if (
|
||||
isRef(props.display) ||
|
||||
typeof props.display === "string" ||
|
||||
isJSXElement(props.display)
|
||||
) {
|
||||
return render(props.display);
|
||||
} else {
|
||||
const { requirement, effectDisplay, optionsDisplay } = props.display;
|
||||
return (
|
||||
<span>
|
||||
{requirement ?
|
||||
render(requirement, el => <h3>{el}</h3>) :
|
||||
displayRequirements(props.requirements ?? [])}
|
||||
{effectDisplay ? (
|
||||
<div>
|
||||
{render(effectDisplay, el => <b>{el}</b>)}
|
||||
</div>
|
||||
) : null}
|
||||
{optionsDisplay != null ? (
|
||||
<div class="equal-spaced">
|
||||
{render(optionsDisplay)}
|
||||
</div>
|
||||
) : null}
|
||||
</span>);
|
||||
}
|
||||
};
|
||||
const Component = () => props.display == null ? <></> : render(props.display);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
} from "game/requirements";
|
||||
import settings, { registerSettingField } from "game/settings";
|
||||
import { camelToTitle } from "util/common";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import {
|
||||
isJSXElement,
|
||||
|
@ -24,7 +24,7 @@ import {
|
|||
vueFeatureMixin,
|
||||
VueFeatureOptions
|
||||
} from "util/vue";
|
||||
import { computed, isRef, MaybeRef, MaybeRefOrGetter, unref, watchEffect } from "vue";
|
||||
import { computed, MaybeRef, MaybeRefOrGetter, unref, watchEffect } from "vue";
|
||||
import { useToast } from "vue-toastification";
|
||||
import Achievement from "./Achievement.vue";
|
||||
|
||||
|
@ -50,14 +50,15 @@ export interface AchievementOptions extends VueFeatureOptions {
|
|||
requirements?: Requirements;
|
||||
/** The display to use for this achievement. */
|
||||
display?:
|
||||
| MaybeRefOrGetter<Renderable>
|
||||
| Renderable
|
||||
| (() => Renderable)
|
||||
| {
|
||||
/** Description of the requirement(s) for this achievement. If unspecified then the requirements will be displayed automatically based on {@link requirements}. */
|
||||
requirement?: MaybeRefOrGetter<Renderable>;
|
||||
requirement?: MaybeGetter<Renderable>;
|
||||
/** Description of what will change (if anything) for achieving this. */
|
||||
effectDisplay?: MaybeRefOrGetter<Renderable>;
|
||||
effectDisplay?: MaybeGetter<Renderable>;
|
||||
/** Any additional things to display on this achievement, such as a toggle for it's effect. */
|
||||
optionsDisplay?: MaybeRefOrGetter<Renderable>;
|
||||
optionsDisplay?: MaybeGetter<Renderable>;
|
||||
};
|
||||
/** Toggles a smaller design for the feature. */
|
||||
small?: MaybeRefOrGetter<boolean>;
|
||||
|
@ -76,13 +77,7 @@ export interface Achievement extends VueFeature {
|
|||
/** A function that is called when the achievement is completed. */
|
||||
onComplete?: VoidFunction;
|
||||
/** The display to use for this achievement. */
|
||||
display?:
|
||||
| MaybeRef<Renderable>
|
||||
| {
|
||||
requirement?: MaybeRef<Renderable>;
|
||||
effectDisplay?: MaybeRef<Renderable>;
|
||||
optionsDisplay?: MaybeRef<Renderable>;
|
||||
};
|
||||
display?: MaybeGetter<Renderable>;
|
||||
/** Toggles a smaller design for the feature. */
|
||||
small?: MaybeRef<boolean>;
|
||||
/** An image to display as the background for this achievement. */
|
||||
|
@ -105,7 +100,15 @@ export function createAchievement<T extends AchievementOptions>(optionsFunc?: ()
|
|||
const earned = persistent<boolean>(false, false);
|
||||
return createLazyProxy(() => {
|
||||
const options = optionsFunc?.() ?? ({} as T);
|
||||
const { requirements, display, small, image, showPopups, onComplete, ...props } = options;
|
||||
const {
|
||||
requirements,
|
||||
display: _display,
|
||||
small,
|
||||
image,
|
||||
showPopups,
|
||||
onComplete,
|
||||
...props
|
||||
} = options;
|
||||
|
||||
const vueFeature = vueFeatureMixin("achievement", options, () => (
|
||||
<Achievement
|
||||
|
@ -117,12 +120,35 @@ export function createAchievement<T extends AchievementOptions>(optionsFunc?: ()
|
|||
/>
|
||||
));
|
||||
|
||||
let display: MaybeGetter<Renderable> | undefined = undefined;
|
||||
if (typeof _display === "object" && !isJSXElement(_display)) {
|
||||
const { requirement, effectDisplay, optionsDisplay } = _display;
|
||||
display = () => (
|
||||
<span>
|
||||
{requirement == null
|
||||
? displayRequirements(requirements ?? [])
|
||||
: render(requirement, el => <h3>{el}</h3>)}
|
||||
{effectDisplay == null ? null : (
|
||||
<div>
|
||||
{render(effectDisplay, el => (
|
||||
<b>{el}</b>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{optionsDisplay != null ? (
|
||||
<div class="equal-spaced">{render(optionsDisplay)}</div>
|
||||
) : null}
|
||||
</span>
|
||||
);
|
||||
} else if (_display != null) {
|
||||
display = _display;
|
||||
}
|
||||
|
||||
const achievement = {
|
||||
type: AchievementType,
|
||||
...(props as Omit<typeof props, keyof VueFeature | keyof AchievementOptions>),
|
||||
...vueFeature,
|
||||
visibility: computed(() => {
|
||||
const display = unref((achievement as Achievement).display);
|
||||
switch (settings.msDisplay) {
|
||||
default:
|
||||
case AchievementDisplay.All:
|
||||
|
@ -131,9 +157,9 @@ export function createAchievement<T extends AchievementOptions>(optionsFunc?: ()
|
|||
if (
|
||||
unref(earned) &&
|
||||
!(
|
||||
display != null &&
|
||||
typeof display === "object" &&
|
||||
"optionsDisplay" in display
|
||||
_display != null &&
|
||||
typeof _display === "object" &&
|
||||
!isJSXElement(_display)
|
||||
)
|
||||
) {
|
||||
return Visibility.None;
|
||||
|
@ -153,19 +179,7 @@ export function createAchievement<T extends AchievementOptions>(optionsFunc?: ()
|
|||
small: processGetter(small),
|
||||
image: processGetter(image),
|
||||
showPopups: processGetter(showPopups) ?? true,
|
||||
display:
|
||||
display == null
|
||||
? undefined
|
||||
: isRef(display) ||
|
||||
typeof display === "string" ||
|
||||
typeof display === "function" ||
|
||||
isJSXElement(display)
|
||||
? processGetter(display)
|
||||
: {
|
||||
requirement: processGetter(display.requirement),
|
||||
effectDisplay: processGetter(display.effectDisplay),
|
||||
optionsDisplay: processGetter(display.optionsDisplay)
|
||||
},
|
||||
display,
|
||||
requirements:
|
||||
requirements == null
|
||||
? undefined
|
||||
|
@ -181,19 +195,18 @@ export function createAchievement<T extends AchievementOptions>(optionsFunc?: ()
|
|||
earned.value = true;
|
||||
achievement.onComplete?.();
|
||||
if (achievement.display != null && unref(achievement.showPopups) === true) {
|
||||
const display = achievement.display;
|
||||
let Display;
|
||||
if (isRef(display) || typeof display === "string" || isJSXElement(display)) {
|
||||
Display = () => render(display);
|
||||
} else if (display.requirement != null) {
|
||||
Display = () => render(display.requirement!);
|
||||
let display = achievement.display;
|
||||
if (typeof _display === "object" && !isJSXElement(_display)) {
|
||||
if (_display.requirement != null) {
|
||||
display = _display.requirement;
|
||||
} else {
|
||||
Display = () => displayRequirements(achievement.requirements ?? []);
|
||||
display = displayRequirements(requirements ?? []);
|
||||
}
|
||||
}
|
||||
toast.info(
|
||||
<div>
|
||||
<h3>Achievement earned!</h3>
|
||||
<div>{Display()}</div>
|
||||
<div>{render(display)}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Bar from "features/bars/Bar.vue";
|
||||
import type { DecimalSource } from "util/bignum";
|
||||
import { Direction } from "util/common";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
||||
import { CSSProperties, MaybeRef, MaybeRefOrGetter } from "vue";
|
||||
|
@ -30,7 +30,7 @@ export interface BarOptions extends VueFeatureOptions {
|
|||
/** The progress value of the bar, from 0 to 1. */
|
||||
progress: MaybeRefOrGetter<DecimalSource>;
|
||||
/** The display to use for this bar. */
|
||||
display?: MaybeRefOrGetter<Renderable>;
|
||||
display?: MaybeGetter<Renderable>;
|
||||
}
|
||||
|
||||
/** An object that represents a feature that displays some sort of progress or completion or resource with a cap. */
|
||||
|
@ -52,7 +52,7 @@ export interface Bar extends VueFeature {
|
|||
/** The progress value of the bar, from 0 to 1. */
|
||||
progress: MaybeRef<DecimalSource>;
|
||||
/** The display to use for this bar. */
|
||||
display?: MaybeRef<Renderable>;
|
||||
display?: MaybeGetter<Renderable>;
|
||||
/** A symbol that helps identify features of the same type. */
|
||||
type: typeof BarType;
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ export function createBar<T extends BarOptions>(optionsFunc: () => T) {
|
|||
textStyle: processGetter(textStyle),
|
||||
fillStyle: processGetter(fillStyle),
|
||||
progress: processGetter(progress),
|
||||
display: processGetter(display)
|
||||
display
|
||||
} satisfies Bar;
|
||||
|
||||
return bar;
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
<script setup lang="tsx">
|
||||
import "components/common/features.css";
|
||||
import { getHighNotifyStyle, getNotifyStyle } from "game/notifications";
|
||||
import { displayRequirements } from "game/requirements";
|
||||
import { render } from "util/vue";
|
||||
import type { Component } from "vue";
|
||||
import { computed, unref } from "vue";
|
||||
|
@ -61,34 +60,7 @@ const notifyStyle = computed(() => {
|
|||
return {};
|
||||
});
|
||||
|
||||
const Component = () => {
|
||||
if (props.display == null) {
|
||||
return null;
|
||||
}
|
||||
if (typeof props.display === "object" && "description" in props.display) {
|
||||
const { title, description, goal, reward, effectDisplay } = props.display;
|
||||
return <span>
|
||||
{title != null ? (<div>{render(title, el => <h3>{el}</h3>)}</div>) : null}
|
||||
{render(description, el => <div>{el}</div>)}
|
||||
<div>
|
||||
<br />
|
||||
Goal: {goal == null ? displayRequirements(props.requirements) : render(goal, el => <h3>{el}</h3>)}
|
||||
</div>
|
||||
{reward != null ? (
|
||||
<div>
|
||||
<br />
|
||||
Reward: {render(reward)}
|
||||
</div>
|
||||
) : null}
|
||||
{effectDisplay != null ? (
|
||||
<div>
|
||||
Currently: {render(effectDisplay)}
|
||||
</div>
|
||||
) : null}
|
||||
</span>;
|
||||
}
|
||||
return render(props.display);
|
||||
}
|
||||
const Component = () => props.display == null ? <></> : render(props.display);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -4,13 +4,20 @@ import type { Reset } from "features/reset";
|
|||
import { globalBus } from "game/events";
|
||||
import type { Persistent } from "game/persistence";
|
||||
import { persistent } from "game/persistence";
|
||||
import { Requirements, maxRequirementsMet } from "game/requirements";
|
||||
import { Requirements, displayRequirements, maxRequirementsMet } from "game/requirements";
|
||||
import settings, { registerSettingField } from "game/settings";
|
||||
import type { DecimalSource } from "util/bignum";
|
||||
import Decimal from "util/bignum";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { Renderable, VueFeature, VueFeatureOptions, vueFeatureMixin } from "util/vue";
|
||||
import {
|
||||
Renderable,
|
||||
VueFeature,
|
||||
VueFeatureOptions,
|
||||
isJSXElement,
|
||||
render,
|
||||
vueFeatureMixin
|
||||
} from "util/vue";
|
||||
import type { MaybeRef, MaybeRefOrGetter, Ref, WatchStopHandle } from "vue";
|
||||
import { computed, unref, watch } from "vue";
|
||||
import Challenge from "./Challenge.vue";
|
||||
|
@ -32,18 +39,19 @@ export interface ChallengeOptions extends VueFeatureOptions {
|
|||
completionLimit?: MaybeRefOrGetter<DecimalSource>;
|
||||
/** The display to use for this challenge. */
|
||||
display?:
|
||||
| MaybeRefOrGetter<Renderable>
|
||||
| Renderable
|
||||
| (() => Renderable)
|
||||
| {
|
||||
/** A header to appear at the top of the display. */
|
||||
title?: MaybeRefOrGetter<Renderable>;
|
||||
title?: MaybeGetter<Renderable>;
|
||||
/** The main text that appears in the display. */
|
||||
description: MaybeRefOrGetter<Renderable>;
|
||||
description: MaybeGetter<Renderable>;
|
||||
/** A description of the current goal for this challenge. If unspecified then the requirements will be displayed automatically based on {@link requirements}. */
|
||||
goal?: MaybeRefOrGetter<Renderable>;
|
||||
goal?: MaybeGetter<Renderable>;
|
||||
/** A description of what will change upon completing this challenge. */
|
||||
reward?: MaybeRefOrGetter<Renderable>;
|
||||
reward?: MaybeGetter<Renderable>;
|
||||
/** A description of the current effect of this challenge. */
|
||||
effectDisplay?: MaybeRefOrGetter<Renderable>;
|
||||
effectDisplay?: MaybeGetter<Renderable>;
|
||||
};
|
||||
/** A function that is called when the challenge is completed. */
|
||||
onComplete?: VoidFunction;
|
||||
|
@ -70,20 +78,7 @@ export interface Challenge extends VueFeature {
|
|||
/** The maximum number of times the challenge can be completed. */
|
||||
completionLimit?: MaybeRef<DecimalSource>;
|
||||
/** The display to use for this challenge. */
|
||||
display?:
|
||||
| MaybeRef<Renderable>
|
||||
| {
|
||||
/** A header to appear at the top of the display. */
|
||||
title?: MaybeRef<Renderable>;
|
||||
/** The main text that appears in the display. */
|
||||
description: MaybeRef<Renderable>;
|
||||
/** A description of the current goal for this challenge. If unspecified then the requirements will be displayed automatically based on {@link requirements}. */
|
||||
goal?: MaybeRef<Renderable>;
|
||||
/** A description of what will change upon completing this challenge. */
|
||||
reward?: MaybeRef<Renderable>;
|
||||
/** A description of the current effect of this challenge. */
|
||||
effectDisplay?: MaybeRef<Renderable>;
|
||||
};
|
||||
display?: MaybeGetter<Renderable>;
|
||||
/** The current amount of times this challenge can be completed. */
|
||||
canComplete: Ref<DecimalSource>;
|
||||
/** The current number of times this challenge has been completed. */
|
||||
|
@ -118,7 +113,7 @@ export function createChallenge<T extends ChallengeOptions>(optionsFunc: () => T
|
|||
requirements,
|
||||
canStart,
|
||||
completionLimit,
|
||||
display,
|
||||
display: _display,
|
||||
reset,
|
||||
onComplete,
|
||||
onEnter,
|
||||
|
@ -139,6 +134,41 @@ export function createChallenge<T extends ChallengeOptions>(optionsFunc: () => T
|
|||
/>
|
||||
));
|
||||
|
||||
let display: MaybeGetter<Renderable> | undefined = undefined;
|
||||
if (typeof _display === "object" && !isJSXElement(_display)) {
|
||||
const { title, description, goal, reward, effectDisplay } = _display;
|
||||
display = () => (
|
||||
<span>
|
||||
{title != null ? (
|
||||
<div>
|
||||
{render(title, el => (
|
||||
<h3>{el}</h3>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
{render(description, el => (
|
||||
<div>{el}</div>
|
||||
))}
|
||||
<div>
|
||||
<br />
|
||||
Goal:{" "}
|
||||
{goal == null
|
||||
? displayRequirements(challenge.requirements)
|
||||
: render(goal, el => <h3>{el}</h3>)}
|
||||
</div>
|
||||
{reward != null ? (
|
||||
<div>
|
||||
<br />
|
||||
Reward: {render(reward)}
|
||||
</div>
|
||||
) : null}
|
||||
{effectDisplay != null ? <div>Currently: {render(effectDisplay)}</div> : null}
|
||||
</span>
|
||||
);
|
||||
} else if (_display != null) {
|
||||
display = _display;
|
||||
}
|
||||
|
||||
const challenge = {
|
||||
type: ChallengeType,
|
||||
...(props as Omit<typeof props, keyof VueFeature | keyof ChallengeOptions>),
|
||||
|
@ -157,18 +187,7 @@ export function createChallenge<T extends ChallengeOptions>(optionsFunc: () => T
|
|||
onComplete,
|
||||
onEnter,
|
||||
onExit,
|
||||
display:
|
||||
display == null
|
||||
? undefined
|
||||
: typeof display === "object" && "description" in display
|
||||
? {
|
||||
title: processGetter(display.title),
|
||||
description: processGetter(display.description),
|
||||
goal: processGetter(display.goal),
|
||||
reward: processGetter(display.reward),
|
||||
effectDisplay: processGetter(display.effectDisplay)
|
||||
}
|
||||
: processGetter(display),
|
||||
display,
|
||||
toggle: function () {
|
||||
if (active.value) {
|
||||
if (
|
||||
|
|
|
@ -5,11 +5,10 @@ import { persistent } from "game/persistence";
|
|||
import Decimal, { DecimalSource } from "lib/break_eternity";
|
||||
import { Unsubscribe } from "nanoevents";
|
||||
import { Direction } from "util/common";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { render, Renderable, VueFeature, vueFeatureMixin } from "util/vue";
|
||||
import { isJSXElement, render, Renderable, VueFeature, vueFeatureMixin } from "util/vue";
|
||||
import { computed, MaybeRef, MaybeRefOrGetter, Ref, ref, unref } from "vue";
|
||||
import { JSX } from "vue/jsx-runtime";
|
||||
import { Bar, BarOptions, createBar } from "../bars/bar";
|
||||
import { type Clickable, ClickableOptions } from "./clickable";
|
||||
|
||||
|
@ -39,7 +38,7 @@ export interface Action extends VueFeature {
|
|||
/** Whether or not the action may be performed. */
|
||||
canClick: MaybeRef<boolean>;
|
||||
/** The display to use for this action. */
|
||||
display?: MaybeRef<Renderable>;
|
||||
display?: MaybeGetter<Renderable>;
|
||||
/** A function that is called when the action is clicked. */
|
||||
onClick: (amount: DecimalSource) => void;
|
||||
/** Whether or not the player is holding down the action. Actions will be considered clicked as soon as the cooldown completes when being held down. */
|
||||
|
@ -62,8 +61,16 @@ export function createAction<T extends ActionOptions>(optionsFunc?: () => T) {
|
|||
const progress = persistent<DecimalSource>(0);
|
||||
return createLazyProxy(() => {
|
||||
const options = optionsFunc?.() ?? ({} as T);
|
||||
const { style, duration, canClick, autoStart, display, barOptions, onClick, ...props } =
|
||||
options;
|
||||
const {
|
||||
style,
|
||||
duration,
|
||||
canClick,
|
||||
autoStart,
|
||||
display: _display,
|
||||
barOptions,
|
||||
onClick,
|
||||
...props
|
||||
} = options;
|
||||
|
||||
const processedCanClick = processGetter(canClick) ?? true;
|
||||
const processedStyle = processGetter(style);
|
||||
|
@ -78,29 +85,24 @@ export function createAction<T extends ActionOptions>(optionsFunc?: () => T) {
|
|||
...(barOptions as Omit<typeof barOptions, keyof VueFeature>)
|
||||
}));
|
||||
|
||||
let Component: () => JSX.Element;
|
||||
if (typeof display === "object" && "description" in display) {
|
||||
const title = processGetter(display.title);
|
||||
const description = processGetter(display.description);
|
||||
|
||||
const Title = () => (title == null ? <></> : render(title, el => <h3>{el}</h3>));
|
||||
const Description = () => render(description, el => <div>{el}</div>);
|
||||
|
||||
Component = () => {
|
||||
return (
|
||||
let display: MaybeGetter<Renderable>;
|
||||
if (typeof _display === "object" && !isJSXElement(_display)) {
|
||||
display = () => (
|
||||
<span>
|
||||
{title != null ? (
|
||||
{_display.title != null ? (
|
||||
<div>
|
||||
<Title />
|
||||
{render(_display.title, el => (
|
||||
<h3>{el}</h3>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
<Description />
|
||||
{render(_display.description, el => (
|
||||
<div>{el}</div>
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
} else if (display != null) {
|
||||
const processedDisplay = processGetter(display);
|
||||
Component = () => render(processedDisplay);
|
||||
} else if (_display != null) {
|
||||
display = _display;
|
||||
}
|
||||
|
||||
const action = {
|
||||
|
@ -135,14 +137,14 @@ export function createAction<T extends ActionOptions>(optionsFunc?: () => T) {
|
|||
unref(processedCanClick) && Decimal.gte(progress.value, unref(action.duration))
|
||||
),
|
||||
autoStart: processGetter(autoStart) ?? false,
|
||||
display: computed(() => (
|
||||
display: () => (
|
||||
<>
|
||||
<div style="flex-grow: 1" />
|
||||
{display == null ? null : <Component />}
|
||||
{display == null ? null : render(display)}
|
||||
<div style="flex-grow: 1" />
|
||||
{render(progressBar)}
|
||||
</>
|
||||
)),
|
||||
),
|
||||
progressBar,
|
||||
onClick: function () {
|
||||
if (unref(action.canClick) === false) {
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
import Clickable from "features/clickables/Clickable.vue";
|
||||
import type { BaseLayer } from "game/layers";
|
||||
import type { Unsubscribe } from "nanoevents";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { render, Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
||||
import {
|
||||
isJSXElement,
|
||||
render,
|
||||
Renderable,
|
||||
VueFeature,
|
||||
vueFeatureMixin,
|
||||
VueFeatureOptions
|
||||
} from "util/vue";
|
||||
import { computed, MaybeRef, MaybeRefOrGetter, unref } from "vue";
|
||||
|
||||
/** A symbol used to identify {@link Clickable} features. */
|
||||
|
@ -17,12 +24,13 @@ export interface ClickableOptions extends VueFeatureOptions {
|
|||
canClick?: MaybeRefOrGetter<boolean>;
|
||||
/** The display to use for this clickable. */
|
||||
display?:
|
||||
| MaybeRefOrGetter<Renderable>
|
||||
| Renderable
|
||||
| (() => Renderable)
|
||||
| {
|
||||
/** A header to appear at the top of the display. */
|
||||
title?: MaybeRefOrGetter<Renderable>;
|
||||
title?: MaybeGetter<Renderable>;
|
||||
/** The main text that appears in the display. */
|
||||
description: MaybeRefOrGetter<Renderable>;
|
||||
description: MaybeGetter<Renderable>;
|
||||
};
|
||||
/** A function that is called when the clickable is clicked. */
|
||||
onClick?: (e?: MouseEvent | TouchEvent) => void;
|
||||
|
@ -39,7 +47,7 @@ export interface Clickable extends VueFeature {
|
|||
/** Whether or not the clickable may be clicked. */
|
||||
canClick: MaybeRef<boolean>;
|
||||
/** The display to use for this clickable. */
|
||||
display?: MaybeRef<Renderable>;
|
||||
display?: MaybeGetter<Renderable>;
|
||||
/** A symbol that helps identify features of the same type. */
|
||||
type: typeof ClickableType;
|
||||
}
|
||||
|
@ -53,26 +61,20 @@ export function createClickable<T extends ClickableOptions>(optionsFunc?: () =>
|
|||
const options = optionsFunc?.() ?? ({} as T);
|
||||
const { canClick, display: _display, onClick: onClick, onHold: onHold, ...props } = options;
|
||||
|
||||
let display: MaybeRef<Renderable> | undefined = undefined;
|
||||
if (typeof _display === "object" && "description" in _display) {
|
||||
const title = processGetter(_display.title);
|
||||
const description = processGetter(_display.description);
|
||||
|
||||
const Title = () => (title == null ? <></> : render(title, el => <h3>{el}</h3>));
|
||||
const Description = () => render(description, el => <div>{el}</div>);
|
||||
|
||||
display = computed(() => (
|
||||
let display: MaybeGetter<Renderable> | undefined = undefined;
|
||||
if (typeof _display === "object" && !isJSXElement(_display)) {
|
||||
display = () => (
|
||||
<span>
|
||||
{title != null ? (
|
||||
{_display.title != null ? (
|
||||
<div>
|
||||
<Title />
|
||||
{render(_display.title, el => <h3>{el}</h3>)}
|
||||
</div>
|
||||
) : null}
|
||||
<Description />
|
||||
{render(_display.description, el => <div>{el}</div>)}
|
||||
</span>
|
||||
));
|
||||
);
|
||||
} else if (_display != null) {
|
||||
display = processGetter(_display);
|
||||
display = _display;
|
||||
}
|
||||
|
||||
const clickable = {
|
||||
|
|
|
@ -11,11 +11,11 @@ import {
|
|||
} from "game/requirements";
|
||||
import type { DecimalSource } from "util/bignum";
|
||||
import Decimal, { formatWhole } from "util/bignum";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { isJSXElement, render, Renderable, VueFeature, vueFeatureMixin } from "util/vue";
|
||||
import type { MaybeRef, MaybeRefOrGetter, Ref } from "vue";
|
||||
import { computed, isRef, unref } from "vue";
|
||||
import { computed, unref } from "vue";
|
||||
import { ClickableOptions } from "./clickable";
|
||||
|
||||
/** A symbol used to identify {@link Repeatable} features. */
|
||||
|
@ -31,14 +31,15 @@ export interface RepeatableOptions extends ClickableOptions {
|
|||
initialAmount?: DecimalSource;
|
||||
/** The display to use for this repeatable. */
|
||||
display?:
|
||||
| MaybeRefOrGetter<Renderable>
|
||||
| Renderable
|
||||
| (() => Renderable)
|
||||
| {
|
||||
/** A header to appear at the top of the display. */
|
||||
title?: MaybeRefOrGetter<Renderable>;
|
||||
title?: MaybeGetter<Renderable>;
|
||||
/** The main text that appears in the display. */
|
||||
description: MaybeRefOrGetter<Renderable>;
|
||||
description: MaybeGetter<Renderable>;
|
||||
/** A description of the current effect of this repeatable, based off its amount. */
|
||||
effectDisplay?: MaybeRefOrGetter<Renderable>;
|
||||
effectDisplay?: MaybeGetter<Renderable>;
|
||||
/** Whether or not to show the current amount of this repeatable at the bottom of the display. */
|
||||
showAmount?: boolean;
|
||||
};
|
||||
|
@ -53,7 +54,7 @@ export interface Repeatable extends VueFeature {
|
|||
/** The initial amount this repeatable has on a new save / after reset. */
|
||||
initialAmount?: DecimalSource;
|
||||
/** The display to use for this repeatable. */
|
||||
display?: MaybeRef<Renderable>;
|
||||
display?: MaybeGetter<Renderable>;
|
||||
/** Whether or not the repeatable may be clicked. */
|
||||
canClick: Ref<boolean>;
|
||||
/** A function that is called when the repeatable is clicked. */
|
||||
|
@ -119,25 +120,19 @@ export function createRepeatable<T extends RepeatableOptions>(optionsFunc: () =>
|
|||
}
|
||||
|
||||
let display;
|
||||
if (typeof _display === "object" && !isRef(_display) && !isJSXElement(_display)) {
|
||||
const title = processGetter(_display.title);
|
||||
const description = processGetter(_display.description);
|
||||
const effectDisplay = processGetter(_display.effectDisplay);
|
||||
const showAmount = processGetter(_display.showAmount);
|
||||
if (typeof _display === "object" && !isJSXElement(_display)) {
|
||||
const { title, description, effectDisplay, showAmount } = _display;
|
||||
|
||||
const Title = title == null ? null : () => render(title, el => <h3>{el}</h3>);
|
||||
const Description = () => render(description, el => <>{el}</>);
|
||||
const EffectDisplay =
|
||||
effectDisplay == null ? null : () => render(effectDisplay, el => <>{el}</>);
|
||||
|
||||
display = computed(() => (
|
||||
display = () => (
|
||||
<span>
|
||||
{Title == null ? null : (
|
||||
{title == null ? null : (
|
||||
<div>
|
||||
<Title />
|
||||
{render(title, el => (
|
||||
<h3>{el}</h3>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<Description />
|
||||
{render(description)}
|
||||
{showAmount === false ? null : (
|
||||
<div>
|
||||
<br />
|
||||
|
@ -147,10 +142,10 @@ export function createRepeatable<T extends RepeatableOptions>(optionsFunc: () =>
|
|||
) : undefined}
|
||||
</div>
|
||||
)}
|
||||
{EffectDisplay == null ? null : (
|
||||
{effectDisplay == null ? null : (
|
||||
<div>
|
||||
<br />
|
||||
Currently: <EffectDisplay />
|
||||
Currently: {render(effectDisplay)}
|
||||
</div>
|
||||
)}
|
||||
{unref(repeatable.maxed) ? null : (
|
||||
|
@ -160,12 +155,9 @@ export function createRepeatable<T extends RepeatableOptions>(optionsFunc: () =>
|
|||
</div>
|
||||
)}
|
||||
</span>
|
||||
));
|
||||
);
|
||||
} else if (_display != null) {
|
||||
const processedDisplay = processGetter(_display);
|
||||
display = computed(() => render(processedDisplay));
|
||||
} else {
|
||||
display = undefined;
|
||||
display = _display;
|
||||
}
|
||||
|
||||
amount[DefaultValue] = initialAmount ?? 0;
|
||||
|
|
|
@ -10,9 +10,16 @@ import {
|
|||
requirementsMet
|
||||
} from "game/requirements";
|
||||
import { isFunction } from "util/common";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { Renderable, VueFeature, VueFeatureOptions, render, vueFeatureMixin } from "util/vue";
|
||||
import {
|
||||
Renderable,
|
||||
VueFeature,
|
||||
VueFeatureOptions,
|
||||
isJSXElement,
|
||||
render,
|
||||
vueFeatureMixin
|
||||
} from "util/vue";
|
||||
import type { MaybeRef, MaybeRefOrGetter, Ref } from "vue";
|
||||
import { computed, unref } from "vue";
|
||||
import Clickable from "./Clickable.vue";
|
||||
|
@ -27,14 +34,15 @@ export const UpgradeType = Symbol("Upgrade");
|
|||
export interface UpgradeOptions extends VueFeatureOptions, ClickableOptions {
|
||||
/** The display to use for this upgrade. */
|
||||
display?:
|
||||
| MaybeRefOrGetter<Renderable>
|
||||
| Renderable
|
||||
| (() => Renderable)
|
||||
| {
|
||||
/** A header to appear at the top of the display. */
|
||||
title?: MaybeRefOrGetter<Renderable>;
|
||||
title?: MaybeGetter<Renderable>;
|
||||
/** The main text that appears in the display. */
|
||||
description: MaybeRefOrGetter<Renderable>;
|
||||
description: MaybeGetter<Renderable>;
|
||||
/** A description of the current effect of the achievement. Useful when the effect changes dynamically. */
|
||||
effectDisplay?: MaybeRefOrGetter<Renderable>;
|
||||
effectDisplay?: MaybeGetter<Renderable>;
|
||||
};
|
||||
/** The requirements to purchase this upgrade. */
|
||||
requirements: Requirements;
|
||||
|
@ -47,7 +55,7 @@ export interface Upgrade extends VueFeature {
|
|||
/** The requirements to purchase this upgrade. */
|
||||
requirements: Requirements;
|
||||
/** The display to use for this upgrade. */
|
||||
display?: MaybeRef<Renderable>;
|
||||
display?: MaybeGetter<Renderable>;
|
||||
/** Whether or not this upgrade has been purchased. */
|
||||
bought: Persistent<boolean>;
|
||||
/** Whether or not the upgrade can currently be purchased. */
|
||||
|
@ -92,30 +100,23 @@ export function createUpgrade<T extends UpgradeOptions>(optionsFunc: () => T) {
|
|||
requirements.push(createVisibilityRequirement(vueFeature.visibility));
|
||||
}
|
||||
|
||||
let display: MaybeRef<Renderable> | undefined = undefined;
|
||||
if (typeof _display === "object" && "description" in _display) {
|
||||
const title = processGetter(_display.title);
|
||||
const description = processGetter(_display.description);
|
||||
const effectDisplay = processGetter(_display.effectDisplay);
|
||||
let display;
|
||||
if (typeof _display === "object" && !isJSXElement(_display)) {
|
||||
const { title, description, effectDisplay } = _display;
|
||||
|
||||
const Title = () => (title == null ? <></> : render(title, el => <h3>{el}</h3>));
|
||||
const Description = () => render(description, el => <div>{el}</div>);
|
||||
const EffectDisplay = () =>
|
||||
effectDisplay == null ? <></> : render(effectDisplay, el => <>{el}</>);
|
||||
|
||||
display = computed(() => (
|
||||
display = () => (
|
||||
<span>
|
||||
{title != null ? (
|
||||
<div>
|
||||
<Title />
|
||||
</div>
|
||||
) : null}
|
||||
<Description />
|
||||
{effectDisplay != null ? (
|
||||
<div>
|
||||
Currently: <EffectDisplay />
|
||||
{render(title, el => (
|
||||
<h3>{el}</h3>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
{render(description, el => (
|
||||
<div>{el}</div>
|
||||
))}
|
||||
{effectDisplay != null ? <div>Currently: {render(effectDisplay)}</div> : null}
|
||||
{bought.value ? null : (
|
||||
<>
|
||||
<br />
|
||||
|
@ -123,9 +124,9 @@ export function createUpgrade<T extends UpgradeOptions>(optionsFunc: () => T) {
|
|||
</>
|
||||
)}
|
||||
</span>
|
||||
));
|
||||
);
|
||||
} else if (_display != null) {
|
||||
display = processGetter(_display);
|
||||
display = _display;
|
||||
}
|
||||
|
||||
const upgrade = {
|
||||
|
|
|
@ -5,7 +5,7 @@ import type { BaseLayer } from "game/layers";
|
|||
import { createBooleanRequirement } from "game/requirements";
|
||||
import type { DecimalSource } from "util/bignum";
|
||||
import Decimal from "util/bignum";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { Renderable } from "util/vue";
|
||||
import { computed, MaybeRef, MaybeRefOrGetter, unref } from "vue";
|
||||
|
@ -310,7 +310,7 @@ export function setupPassiveGeneration(
|
|||
export function createCanConvertRequirement(
|
||||
conversion: Conversion,
|
||||
minGainAmount: MaybeRefOrGetter<DecimalSource> = 1,
|
||||
display?: MaybeRefOrGetter<Renderable>
|
||||
display?: MaybeGetter<Renderable>
|
||||
) {
|
||||
const computedMinGainAmount = processGetter(minGainAmount);
|
||||
return createBooleanRequirement(
|
||||
|
|
|
@ -47,9 +47,12 @@ export function findFeatures(obj: object, ...types: symbol[]): unknown[] {
|
|||
const handleObject = (obj: object) => {
|
||||
Object.keys(obj).forEach(key => {
|
||||
const value: unknown = obj[key as keyof typeof obj];
|
||||
if (value != null && typeof value === "object") {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if (types.includes((value as Record<string, any>).type)) {
|
||||
if (
|
||||
value != null &&
|
||||
typeof value === "object" &&
|
||||
(value as Record<string, unknown>).__v_isVNode !== true
|
||||
) {
|
||||
if (types.includes((value as Record<string, unknown>).type as symbol)) {
|
||||
objects.push(value);
|
||||
} else if (!(value instanceof Decimal) && !isRef(value)) {
|
||||
handleObject(value as Record<string, unknown>);
|
||||
|
@ -66,7 +69,7 @@ export function getFirstFeature<T extends VueFeature>(
|
|||
filter: (feature: T) => boolean
|
||||
): {
|
||||
firstFeature: Ref<T | undefined>;
|
||||
collapsedContent: MaybeRef<Renderable>;
|
||||
collapsedContent: () => Renderable;
|
||||
hasCollapsedContent: Ref<boolean>;
|
||||
} {
|
||||
const filteredFeatures = computed(() =>
|
||||
|
@ -74,7 +77,7 @@ export function getFirstFeature<T extends VueFeature>(
|
|||
);
|
||||
return {
|
||||
firstFeature: computed(() => filteredFeatures.value[0]),
|
||||
collapsedContent: computed(() => renderCol(...filteredFeatures.value.slice(1))),
|
||||
collapsedContent: () => renderCol(...filteredFeatures.value.slice(1)),
|
||||
hasCollapsedContent: computed(() => filteredFeatures.value.length > 1)
|
||||
};
|
||||
}
|
||||
|
@ -90,13 +93,13 @@ export function excludeFeatures(obj: Record<string, unknown>, ...types: symbol[]
|
|||
const handleObject = (obj: Record<string, unknown>) => {
|
||||
Object.keys(obj).forEach(key => {
|
||||
const value = obj[key];
|
||||
if (value != null && typeof value === "object") {
|
||||
if (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
typeof (value as Record<string, any>).type === "symbol" &&
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
!types.includes((value as Record<string, any>).type)
|
||||
value != null &&
|
||||
typeof value === "object" &&
|
||||
(value as Record<string, unknown>).__v_isVNode !== true
|
||||
) {
|
||||
const type = (value as Record<string, unknown>).type;
|
||||
if (typeof type === "symbol" && !types.includes(type)) {
|
||||
objects.push(value);
|
||||
} else if (!(value instanceof Decimal) && !isRef(value)) {
|
||||
handleObject(value as Record<string, unknown>);
|
||||
|
|
|
@ -1,16 +1,23 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import Column from "components/layout/Column.vue";
|
||||
import Row from "components/layout/Row.vue";
|
||||
import Clickable from "features/clickables/Clickable.vue";
|
||||
import { getUniqueID, Visibility } from "features/feature";
|
||||
import type { Persistent, State } from "game/persistence";
|
||||
import { persistent } from "game/persistence";
|
||||
import { isFunction } from "util/common";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { isJSXElement, render, Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
||||
import {
|
||||
isJSXElement,
|
||||
render,
|
||||
Renderable,
|
||||
VueFeature,
|
||||
vueFeatureMixin,
|
||||
VueFeatureOptions
|
||||
} from "util/vue";
|
||||
import type { CSSProperties, MaybeRef, MaybeRefOrGetter, Ref } from "vue";
|
||||
import { computed, isRef, unref } from "vue";
|
||||
import Column from "components/layout/Column.vue";
|
||||
import Row from "components/layout/Row.vue";
|
||||
import Clickable from "features/clickables/Clickable.vue";
|
||||
import { computed, unref } from "vue";
|
||||
|
||||
/** A symbol used to identify {@link Grid} features. */
|
||||
export const GridType = Symbol("Grid");
|
||||
|
@ -39,7 +46,7 @@ export interface GridCell extends VueFeature {
|
|||
/** The persistent state of this cell. */
|
||||
state: State;
|
||||
/** The main text that appears in the display. */
|
||||
display: MaybeRef<Renderable>;
|
||||
display: MaybeGetter<Renderable>;
|
||||
/** A function that is called when the cell is clicked. */
|
||||
onClick?: (e?: MouseEvent | TouchEvent) => void;
|
||||
/** A function that is called when the cell is held down. */
|
||||
|
@ -65,9 +72,12 @@ export interface GridOptions extends VueFeatureOptions {
|
|||
/** A getter for the CSS classes for a cell. */
|
||||
getClasses?: CellMaybeRefOrGetter<Record<string, boolean>>;
|
||||
/** A getter for the display component for a cell. */
|
||||
getDisplay: CellMaybeRefOrGetter<Renderable> | {
|
||||
getTitle?: CellMaybeRefOrGetter<Renderable>;
|
||||
getDescription: CellMaybeRefOrGetter<Renderable>
|
||||
getDisplay:
|
||||
| Renderable
|
||||
| ((row: number, col: number, state: State) => Renderable)
|
||||
| {
|
||||
getTitle?: Renderable | ((row: number, col: number, state: State) => Renderable);
|
||||
getDescription: Renderable | ((row: number, col: number, state: State) => Renderable);
|
||||
};
|
||||
/** A function that is called when a cell is clicked. */
|
||||
onClick?: (row: number, col: number, state: State, e?: MouseEvent | TouchEvent) => void;
|
||||
|
@ -96,7 +106,7 @@ export interface Grid extends VueFeature {
|
|||
/** A getter for the CSS classes for a cell. */
|
||||
getClasses?: ProcessedCellRefOrGetter<Record<string, boolean>>;
|
||||
/** A getter for the display component for a cell. */
|
||||
getDisplay: ProcessedCellRefOrGetter<Renderable>;
|
||||
getDisplay: Renderable | ((row: number, col: number, state: State) => Renderable);
|
||||
/** Get the auto-generated ID for identifying a specific cell of this grid that appears in the DOM. Will not persist between refreshes or updates. */
|
||||
getID: (row: number, col: number, state: State) => string;
|
||||
/** Get the persistent state of the given cell. */
|
||||
|
@ -214,7 +224,7 @@ function getCellHandler(grid: Grid, row: number, col: number): GridCell {
|
|||
return grid.getState(row, col);
|
||||
}
|
||||
case "id":
|
||||
return target.id = target.id ?? getUniqueID("gridcell");
|
||||
return (target.id = target.id ?? getUniqueID("gridcell"));
|
||||
case "components":
|
||||
return [
|
||||
computed(() => (
|
||||
|
@ -264,12 +274,10 @@ function getCellHandler(grid: Grid, row: number, col: number): GridCell {
|
|||
return (grid as any)[key];
|
||||
},
|
||||
set(target, key, value) {
|
||||
console.log("!!?", key, value)
|
||||
if (typeof key !== "string") {
|
||||
return false;
|
||||
}
|
||||
key = `set${key.slice(0, 1).toUpperCase() + key.slice(1)}`;
|
||||
console.log(key, grid[key])
|
||||
if (key in grid && isFunction((grid as any)[key]) && (grid as any)[key].length <= 3) {
|
||||
(grid as any)[key].call(grid, row, col, value);
|
||||
return true;
|
||||
|
@ -334,20 +342,23 @@ export function createGrid<T extends GridOptions>(optionsFunc: () => T) {
|
|||
} = options;
|
||||
|
||||
let getDisplay;
|
||||
if (typeof _getDisplay === "object" && !isRef(_getDisplay) && !isJSXElement(_getDisplay)) {
|
||||
if (typeof _getDisplay === "object" && !isJSXElement(_getDisplay)) {
|
||||
const { getTitle, getDescription } = _getDisplay;
|
||||
const getProcessedTitle = convertCellMaybeRefOrGetter(getTitle);
|
||||
const getProcessedDescription = convertCellMaybeRefOrGetter(getDescription);
|
||||
getDisplay = function (row: number, col: number, state: State) {
|
||||
const title = typeof getProcessedTitle === "function" ? getProcessedTitle(row, col, state) : unref(getProcessedTitle);
|
||||
const description = typeof getProcessedDescription === "function" ? getProcessedDescription(row, col, state) : unref(getProcessedDescription);
|
||||
return <>
|
||||
const title = typeof getTitle === "function" ? getTitle(row, col, state) : getTitle;
|
||||
const description =
|
||||
typeof getDescription === "function"
|
||||
? getDescription(row, col, state)
|
||||
: getDescription;
|
||||
return (
|
||||
<>
|
||||
{title}
|
||||
{description}
|
||||
</>;
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
} else {
|
||||
getDisplay = convertCellMaybeRefOrGetter(_getDisplay);
|
||||
getDisplay = _getDisplay;
|
||||
}
|
||||
|
||||
const grid = {
|
||||
|
@ -357,10 +368,13 @@ export function createGrid<T extends GridOptions>(optionsFunc: () => T) {
|
|||
<Column>
|
||||
{new Array(unref(grid.rows)).fill(0).map((_, row) => (
|
||||
<Row>
|
||||
{new Array(unref(grid.cols)).fill(0).map((_, col) =>
|
||||
render(grid.cells[row][col]))}
|
||||
</Row>))}
|
||||
</Column>)),
|
||||
{new Array(unref(grid.cols))
|
||||
.fill(0)
|
||||
.map((_, col) => render(grid.cells[row][col]))}
|
||||
</Row>
|
||||
))}
|
||||
</Column>
|
||||
)),
|
||||
cellState,
|
||||
cells: new Proxy({} as GridCell[][], {
|
||||
get(target, key: PropertyKey) {
|
||||
|
@ -422,8 +436,10 @@ export function createGrid<T extends GridOptions>(optionsFunc: () => T) {
|
|||
cols: processGetter(cols),
|
||||
getVisibility: convertCellMaybeRefOrGetter(getVisibility ?? true),
|
||||
getCanClick: convertCellMaybeRefOrGetter(getCanClick ?? true),
|
||||
getStartState: typeof getStartState === "function" && getStartState.length > 0 ?
|
||||
getStartState : processGetter(getStartState),
|
||||
getStartState:
|
||||
typeof getStartState === "function" && getStartState.length > 0
|
||||
? getStartState
|
||||
: processGetter(getStartState),
|
||||
getStyle: convertCellMaybeRefOrGetter(getStyle),
|
||||
getClasses: convertCellMaybeRefOrGetter(getClasses),
|
||||
getDisplay,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Infobox from "features/infoboxes/Infobox.vue";
|
||||
import type { Persistent } from "game/persistence";
|
||||
import { persistent } from "game/persistence";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
||||
import { CSSProperties, MaybeRef, MaybeRefOrGetter } from "vue";
|
||||
|
@ -20,9 +20,9 @@ export interface InfoboxOptions extends VueFeatureOptions {
|
|||
/** CSS to apply to the body of the infobox. */
|
||||
bodyStyle?: MaybeRefOrGetter<CSSProperties>;
|
||||
/** A header to appear at the top of the display. */
|
||||
title: MaybeRefOrGetter<Renderable>;
|
||||
title: MaybeGetter<Renderable>;
|
||||
/** The main text that appears in the display. */
|
||||
display: MaybeRefOrGetter<Renderable>;
|
||||
display: MaybeGetter<Renderable>;
|
||||
}
|
||||
|
||||
/** An object that represents a feature that displays information in a collapsible way. */
|
||||
|
@ -34,9 +34,9 @@ export interface Infobox extends VueFeature {
|
|||
/** CSS to apply to the body of the infobox. */
|
||||
bodyStyle?: MaybeRef<CSSProperties>;
|
||||
/** A header to appear at the top of the display. */
|
||||
title: MaybeRef<Renderable>;
|
||||
title: MaybeGetter<Renderable>;
|
||||
/** The main text that appears in the display. */
|
||||
display: MaybeRef<Renderable>;
|
||||
display: MaybeGetter<Renderable>;
|
||||
/** Whether or not this infobox is collapsed. */
|
||||
collapsed: Persistent<boolean>;
|
||||
/** A symbol that helps identify features of the same type. */
|
||||
|
@ -70,8 +70,8 @@ export function createInfobox<T extends InfoboxOptions>(optionsFunc: () => T) {
|
|||
color: processGetter(color) ?? "--layer-color",
|
||||
titleStyle: processGetter(titleStyle),
|
||||
bodyStyle: processGetter(bodyStyle),
|
||||
title: processGetter(title),
|
||||
display: processGetter(display)
|
||||
title,
|
||||
display
|
||||
} satisfies Infobox;
|
||||
|
||||
return infobox;
|
||||
|
|
|
@ -41,6 +41,7 @@ onMounted(() => {
|
|||
});
|
||||
onBeforeUnmount(() => {
|
||||
app.value?.destroy();
|
||||
app.value = null;
|
||||
});
|
||||
|
||||
let isDirty = true;
|
||||
|
|
|
@ -3,8 +3,9 @@ import type { EmitterConfigV3 } from "@pixi/particle-emitter";
|
|||
import { Emitter, upgradeConfig } from "@pixi/particle-emitter";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
||||
import { Ref, shallowRef } from "vue";
|
||||
import { Ref, shallowRef, unref } from "vue";
|
||||
import Particles from "./Particles.vue";
|
||||
import { processGetter } from "util/computed";
|
||||
|
||||
/** A symbol used to identify {@link Particles} features. */
|
||||
export const ParticlesType = Symbol("Particles");
|
||||
|
@ -47,7 +48,10 @@ export interface Particles extends VueFeature {
|
|||
export function createParticles<T extends ParticlesOptions>(optionsFunc?: () => T) {
|
||||
return createLazyProxy(() => {
|
||||
const options = optionsFunc?.() ?? ({} as T);
|
||||
const { onContainerResized, onHotReload, ...props } = options;
|
||||
const { onContainerResized, onHotReload, style: _style, ...props } = options;
|
||||
|
||||
const style = processGetter(_style);
|
||||
options.style = () => ({ position: "static", ...(unref(style) ?? {}) });
|
||||
|
||||
let emittersToAdd: {
|
||||
resolve: (value: Emitter | PromiseLike<Emitter>) => void;
|
||||
|
@ -57,6 +61,7 @@ export function createParticles<T extends ParticlesOptions>(optionsFunc?: () =>
|
|||
function onInit(app: Application) {
|
||||
emittersToAdd.forEach(({ resolve, config }) => resolve(new Emitter(app.stage, config)));
|
||||
emittersToAdd = [];
|
||||
particles.app.value = app;
|
||||
}
|
||||
|
||||
const particles = {
|
||||
|
|
|
@ -19,15 +19,16 @@ import Sticky from "components/layout/Sticky.vue";
|
|||
import type { Resource } from "features/resources/resource";
|
||||
import ResourceVue from "features/resources/Resource.vue";
|
||||
import Decimal from "util/bignum";
|
||||
import { MaybeGetter } from "util/computed";
|
||||
import { Renderable } from "util/vue";
|
||||
import { computed, MaybeRefOrGetter, ref, StyleValue, toValue } from "vue";
|
||||
import { computed, ref, StyleValue, toValue } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
resource: Resource;
|
||||
color?: string;
|
||||
classes?: Record<string, boolean>;
|
||||
style?: StyleValue;
|
||||
effectDisplay?: MaybeRefOrGetter<Renderable>;
|
||||
effectDisplay?: MaybeGetter<Renderable>;
|
||||
}>();
|
||||
|
||||
const displayRef = ref<Element | null>(null);
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { render, Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
||||
import { MaybeRef, MaybeRefOrGetter } from "vue";
|
||||
import { JSX } from "vue/jsx-runtime";
|
||||
import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
||||
|
||||
/** A symbol used to identify {@link Tab} features. */
|
||||
export const TabType = Symbol("Tab");
|
||||
|
@ -12,7 +10,7 @@ export const TabType = Symbol("Tab");
|
|||
*/
|
||||
export interface TabOptions extends VueFeatureOptions {
|
||||
/** The display to use for this tab. */
|
||||
display: MaybeRefOrGetter<Renderable>;
|
||||
display: MaybeGetter<Renderable>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -21,7 +19,7 @@ export interface TabOptions extends VueFeatureOptions {
|
|||
*/
|
||||
export interface Tab extends VueFeature {
|
||||
/** The display to use for this tab. */
|
||||
display: MaybeRef<Renderable>;
|
||||
display: MaybeGetter<Renderable>;
|
||||
/** A symbol that helps identify features of the same type. */
|
||||
type: typeof TabType;
|
||||
}
|
||||
|
@ -38,8 +36,8 @@ export function createTab<T extends TabOptions>(optionsFunc: () => T) {
|
|||
const tab = {
|
||||
type: TabType,
|
||||
...(props as Omit<typeof props, keyof VueFeature | keyof TabOptions>),
|
||||
...vueFeatureMixin("tab", options, (): JSX.Element => render(tab.display)),
|
||||
display: processGetter(display)
|
||||
...vueFeatureMixin("tab", options, display),
|
||||
display
|
||||
} satisfies Tab;
|
||||
|
||||
return tab;
|
||||
|
|
|
@ -4,7 +4,7 @@ import TabButton from "features/tabs/TabButton.vue";
|
|||
import TabFamily from "features/tabs/TabFamily.vue";
|
||||
import type { Persistent } from "game/persistence";
|
||||
import { persistent } from "game/persistence";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
||||
import type { CSSProperties, MaybeRef, MaybeRefOrGetter, Ref } from "vue";
|
||||
|
@ -20,9 +20,9 @@ export const TabFamilyType = Symbol("TabFamily");
|
|||
*/
|
||||
export interface TabButtonOptions extends VueFeatureOptions {
|
||||
/** The tab to display when this button is clicked. */
|
||||
tab: Tab | MaybeRefOrGetter<Renderable>;
|
||||
tab: Tab | MaybeGetter<Renderable>;
|
||||
/** The label on this button. */
|
||||
display: MaybeRefOrGetter<Renderable>;
|
||||
display: MaybeGetter<Renderable>;
|
||||
/** The color of the glow effect to display when this button is active. */
|
||||
glowColor?: MaybeRefOrGetter<string>;
|
||||
}
|
||||
|
@ -33,9 +33,9 @@ export interface TabButtonOptions extends VueFeatureOptions {
|
|||
*/
|
||||
export interface TabButton extends VueFeature {
|
||||
/** The tab to display when this button is clicked. */
|
||||
tab: Tab | MaybeRef<Renderable>;
|
||||
tab: Tab | MaybeGetter<Renderable>;
|
||||
/** The label on this button. */
|
||||
display: MaybeRef<Renderable>;
|
||||
display: MaybeGetter<Renderable>;
|
||||
/** The color of the glow effect to display when this button is active. */
|
||||
glowColor?: MaybeRef<string>;
|
||||
/** A symbol that helps identify features of the same type. */
|
||||
|
@ -64,7 +64,7 @@ export interface TabFamily extends VueFeature {
|
|||
/** All the tabs within this family. */
|
||||
tabs: Record<string, TabButton>;
|
||||
/** The currently active tab, if any. */
|
||||
activeTab: Ref<Tab | MaybeRef<Renderable> | null>;
|
||||
activeTab: Ref<Tab | MaybeGetter<Renderable> | null>;
|
||||
/** The name of the tab that is currently active. */
|
||||
selected: Persistent<string>;
|
||||
/** A symbol that helps identify features of the same type. */
|
||||
|
@ -106,16 +106,17 @@ export function createTabFamily<T extends TabFamilyOptions>(
|
|||
const tabButton = {
|
||||
type: TabButtonType,
|
||||
...(props as Omit<typeof props, keyof VueFeature | keyof TabButtonOptions>),
|
||||
...vueFeatureMixin("tabButton", options, () =>
|
||||
...vueFeatureMixin("tabButton", options, () => (
|
||||
<TabButton
|
||||
display={tabButton.display}
|
||||
glowColor={tabButton.glowColor}
|
||||
active={unref(tabButton.tab) === unref(tabFamily.activeTab)}
|
||||
onSelectTab={() => tabFamily.selected.value = tab}
|
||||
/>),
|
||||
tab: processGetter(buttonTab),
|
||||
onSelectTab={() => (tabFamily.selected.value = tab)}
|
||||
/>
|
||||
)),
|
||||
tab: buttonTab,
|
||||
glowColor: processGetter(glowColor),
|
||||
display: processGetter(display)
|
||||
display
|
||||
} satisfies TabButton;
|
||||
|
||||
parsedTabs[tab] = tabButton;
|
||||
|
@ -124,7 +125,7 @@ export function createTabFamily<T extends TabFamilyOptions>(
|
|||
buttonContainerClasses: processGetter(buttonContainerClasses),
|
||||
buttonContainerStyle: processGetter(buttonContainerStyle),
|
||||
selected,
|
||||
activeTab: computed((): Tab | MaybeRef<Renderable> | null => {
|
||||
activeTab: computed((): Tab | MaybeGetter<Renderable> | null => {
|
||||
if (
|
||||
selected.value in tabFamily.tabs &&
|
||||
isVisible(tabFamily.tabs[selected.value].visibility ?? true)
|
||||
|
|
|
@ -7,11 +7,11 @@ import TreeNode from "features/trees/TreeNode.vue";
|
|||
import { noPersist } from "game/persistence";
|
||||
import type { DecimalSource } from "util/bignum";
|
||||
import Decimal, { format, formatWhole } from "util/bignum";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
||||
import type { MaybeRef, MaybeRefOrGetter, Ref } from "vue";
|
||||
import { computed, ref, shallowRef, unref } from "vue";
|
||||
import { ref, shallowRef, unref } from "vue";
|
||||
|
||||
/** A symbol used to identify {@link TreeNode} features. */
|
||||
export const TreeNodeType = Symbol("TreeNode");
|
||||
|
@ -27,7 +27,7 @@ export interface TreeNodeOptions extends VueFeatureOptions {
|
|||
/** The background color for this node. */
|
||||
color?: MaybeRefOrGetter<string>;
|
||||
/** The label to display on this tree node. */
|
||||
display?: MaybeRefOrGetter<Renderable>;
|
||||
display?: MaybeGetter<Renderable>;
|
||||
/** The color of the glow effect shown to notify the user there's something to do with this node. */
|
||||
glowColor?: MaybeRefOrGetter<string>;
|
||||
/** A reset object attached to this node, used for propagating resets through the tree. */
|
||||
|
@ -47,7 +47,7 @@ export interface TreeNode extends VueFeature {
|
|||
/** The background color for this node. */
|
||||
color?: MaybeRef<string>;
|
||||
/** The label to display on this tree node. */
|
||||
display?: MaybeRef<Renderable>;
|
||||
display?: MaybeGetter<Renderable>;
|
||||
/** The color of the glow effect shown to notify the user there's something to do with this node. */
|
||||
glowColor?: MaybeRef<string>;
|
||||
/** A reset object attached to this node, used for propagating resets through the tree. */
|
||||
|
@ -84,7 +84,7 @@ export function createTreeNode<T extends TreeNodeOptions>(optionsFunc?: () => T)
|
|||
)),
|
||||
canClick: processGetter(canClick) ?? true,
|
||||
color: processGetter(color),
|
||||
display: processGetter(display),
|
||||
display,
|
||||
glowColor: processGetter(glowColor),
|
||||
onClick:
|
||||
onClick == null
|
||||
|
@ -265,9 +265,9 @@ export function createResourceTooltip(
|
|||
resource: Resource,
|
||||
requiredResource: Resource | null = null,
|
||||
requirement: MaybeRefOrGetter<DecimalSource> = 0
|
||||
): Ref<string> {
|
||||
): () => string {
|
||||
const req = processGetter(requirement);
|
||||
return computed(() => {
|
||||
return () => {
|
||||
if (requiredResource == null || Decimal.gte(resource.value, unref(req))) {
|
||||
return displayResource(resource) + " " + resource.displayName;
|
||||
}
|
||||
|
@ -280,5 +280,5 @@ export function createResourceTooltip(
|
|||
? formatWhole(requiredResource.value)
|
||||
: format(requiredResource.value, requiredResource.precision)
|
||||
})`;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -275,7 +275,18 @@ export function makeDraggable<T, S extends MakeDraggableOptions<T>>(
|
|||
const position = persistent<NodePosition>({ x: 0, y: 0 });
|
||||
const draggable = createLazyProxy(() => {
|
||||
const options = optionsFunc();
|
||||
const { id, nodeBeingDragged, hasDragged, dragDelta, startDrag, endDrag, onMouseDown, onMouseUp, initialPosition, ...props } = options;
|
||||
const {
|
||||
id,
|
||||
nodeBeingDragged,
|
||||
hasDragged,
|
||||
dragDelta,
|
||||
startDrag,
|
||||
endDrag,
|
||||
onMouseDown,
|
||||
onMouseUp,
|
||||
initialPosition,
|
||||
...props
|
||||
} = options;
|
||||
|
||||
position[DefaultValue] = initialPosition ?? position[DefaultValue];
|
||||
|
||||
|
|
|
@ -5,9 +5,9 @@ import { persistent } from "game/persistence";
|
|||
import player from "game/player";
|
||||
import type { Emitter } from "nanoevents";
|
||||
import { createNanoEvents } from "nanoevents";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { render, Renderable } from "util/vue";
|
||||
import { Renderable } from "util/vue";
|
||||
import {
|
||||
computed,
|
||||
type CSSProperties,
|
||||
|
@ -108,7 +108,7 @@ export interface LayerOptions {
|
|||
* The layout of this layer's features.
|
||||
* When the layer is open in {@link game/player.PlayerData.tabs}, this is the content that is displayed.
|
||||
*/
|
||||
display: MaybeRefOrGetter<Renderable>;
|
||||
display: MaybeGetter<Renderable>;
|
||||
/** An object of classes that should be applied to the display. */
|
||||
classes?: MaybeRefOrGetter<Record<string, boolean>>;
|
||||
/** Styles that should be applied to the display. */
|
||||
|
@ -127,7 +127,7 @@ export interface LayerOptions {
|
|||
* The layout of this layer's features.
|
||||
* When the layer is open in {@link game/player.PlayerData.tabs}, but the tab is {@link Layer.minimized} this is the content that is displayed.
|
||||
*/
|
||||
minimizedDisplay?: MaybeRefOrGetter<Renderable>;
|
||||
minimizedDisplay?: MaybeGetter<Renderable>;
|
||||
/**
|
||||
* Whether or not to force the go back button to be hidden.
|
||||
* If true, go back will be hidden regardless of {@link data/projInfo.allowGoBack}.
|
||||
|
@ -169,7 +169,7 @@ export interface Layer extends BaseLayer {
|
|||
* The layout of this layer's features.
|
||||
* When the layer is open in {@link game/player.PlayerData.tabs}, this is the content that is displayed.
|
||||
*/
|
||||
display: MaybeRef<Renderable>;
|
||||
display: MaybeGetter<Renderable>;
|
||||
/** An object of classes that should be applied to the display. */
|
||||
classes?: MaybeRef<Record<string, boolean>>;
|
||||
/** Styles that should be applied to the display. */
|
||||
|
@ -188,7 +188,7 @@ export interface Layer extends BaseLayer {
|
|||
* The layout of this layer's features.
|
||||
* When the layer is open in {@link game/player.PlayerData.tabs}, but the tab is {@link Layer.minimized} this is the content that is displayed.
|
||||
*/
|
||||
minimizedDisplay?: MaybeRef<Renderable>;
|
||||
minimizedDisplay?: MaybeGetter<Renderable>;
|
||||
/**
|
||||
* Whether or not to force the go back button to be hidden.
|
||||
* If true, go back will be hidden regardless of {@link data/projInfo.allowGoBack}.
|
||||
|
@ -261,7 +261,7 @@ export function createLayer<T extends LayerOptions>(
|
|||
...baseLayer,
|
||||
...(props as Omit<typeof props, keyof LayerOptions>),
|
||||
color: processGetter(color),
|
||||
display: processGetter(display),
|
||||
display,
|
||||
classes: processGetter(classes),
|
||||
style: computed((): CSSProperties => {
|
||||
let width = unref(layer.minWidth);
|
||||
|
@ -293,7 +293,7 @@ export function createLayer<T extends LayerOptions>(
|
|||
forceHideGoBack: processGetter(forceHideGoBack),
|
||||
minWidth: processGetter(minWidth) ?? 600,
|
||||
minimizable: processGetter(minimizable) ?? true,
|
||||
minimizedDisplay: processGetter(minimizedDisplay)
|
||||
minimizedDisplay
|
||||
} satisfies Layer;
|
||||
|
||||
return layer;
|
||||
|
@ -370,21 +370,21 @@ export function reloadLayer(layer: Layer): void {
|
|||
*/
|
||||
export function setupLayerModal(layer: Layer): {
|
||||
openModal: VoidFunction;
|
||||
modal: Ref<JSX.Element>;
|
||||
modal: () => JSX.Element;
|
||||
} {
|
||||
const showModal = ref(false);
|
||||
return {
|
||||
openModal: () => (showModal.value = true),
|
||||
modal: computed(() => (
|
||||
modal: () => (
|
||||
<Modal
|
||||
modelValue={showModal.value}
|
||||
onUpdate:modelValue={value => (showModal.value = value)}
|
||||
v-slots={{
|
||||
header: () => <h2>{unref(layer.name)}</h2>,
|
||||
body: () => render(layer.display)
|
||||
body: typeof layer.display ? layer.display : () => layer.display
|
||||
}}
|
||||
/>
|
||||
))
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import settings from "game/settings";
|
|||
import type { DecimalSource } from "util/bignum";
|
||||
import Decimal, { formatSmall } from "util/bignum";
|
||||
import type { RequiredKeys, WithRequired } from "util/common";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { render, Renderable } from "util/vue";
|
||||
import { computed, MaybeRef, MaybeRefOrGetter, unref } from "vue";
|
||||
|
@ -32,7 +32,7 @@ export interface Modifier {
|
|||
* A description of this modifier.
|
||||
* @see {@link createModifierSection}.
|
||||
*/
|
||||
description?: MaybeRef<Renderable>;
|
||||
description?: MaybeGetter<Renderable>;
|
||||
}
|
||||
|
||||
/** Utility type that represents the output of all modifiers that represent a single operation. */
|
||||
|
@ -46,7 +46,7 @@ export interface AdditiveModifierOptions {
|
|||
/** The amount to add to the input value. */
|
||||
addend: MaybeRefOrGetter<DecimalSource>;
|
||||
/** Description of what this modifier is doing. */
|
||||
description?: MaybeRefOrGetter<Renderable>;
|
||||
description?: MaybeGetter<Renderable>;
|
||||
/** A MaybeRefOrGetter that will be processed and passed directly into the returned modifier. */
|
||||
enabled?: MaybeRefOrGetter<boolean>;
|
||||
/** Determines if numbers larger or smaller than 0 should be displayed as red. */
|
||||
|
@ -64,7 +64,6 @@ export function createAdditiveModifier<T extends AdditiveModifierOptions, S = Op
|
|||
const { addend, description, enabled, smallerIsBetter } = optionsFunc();
|
||||
|
||||
const processedAddend = processGetter(addend);
|
||||
const processedDescription = processGetter(description);
|
||||
const processedEnabled = enabled == null ? undefined : processGetter(enabled);
|
||||
return {
|
||||
apply: (gain: DecimalSource) => Decimal.add(gain, unref(processedAddend)),
|
||||
|
@ -72,13 +71,11 @@ export function createAdditiveModifier<T extends AdditiveModifierOptions, S = Op
|
|||
getFormula: (gain: FormulaSource) => Formula.add(gain, processedAddend),
|
||||
enabled: processedEnabled,
|
||||
description:
|
||||
processedDescription == null
|
||||
description == null
|
||||
? undefined
|
||||
: computed(() => (
|
||||
: () => (
|
||||
<div class="modifier-container">
|
||||
<span class="modifier-description">
|
||||
{render(processedDescription)}
|
||||
</span>
|
||||
<span class="modifier-description">{render(description)}</span>
|
||||
<span
|
||||
class="modifier-amount"
|
||||
style={
|
||||
|
@ -95,7 +92,7 @@ export function createAdditiveModifier<T extends AdditiveModifierOptions, S = Op
|
|||
{formatSmall(unref(processedAddend))}
|
||||
</span>
|
||||
</div>
|
||||
))
|
||||
)
|
||||
};
|
||||
}) as S;
|
||||
}
|
||||
|
@ -105,7 +102,7 @@ export interface MultiplicativeModifierOptions {
|
|||
/** The amount to multiply the input value by. */
|
||||
multiplier: MaybeRefOrGetter<DecimalSource>;
|
||||
/** Description of what this modifier is doing. */
|
||||
description?: MaybeRefOrGetter<Renderable> | undefined;
|
||||
description?: MaybeGetter<Renderable> | undefined;
|
||||
/** A MaybeRefOrGetter that will be processed and passed directly into the returned modifier. */
|
||||
enabled?: MaybeRefOrGetter<boolean> | undefined;
|
||||
/** Determines if numbers larger or smaller than 1 should be displayed as red. */
|
||||
|
@ -124,7 +121,6 @@ export function createMultiplicativeModifier<
|
|||
const { multiplier, description, enabled, smallerIsBetter } = optionsFunc();
|
||||
|
||||
const processedMultiplier = processGetter(multiplier);
|
||||
const processedDescription = processGetter(description);
|
||||
const processedEnabled = enabled == null ? undefined : processGetter(enabled);
|
||||
return {
|
||||
apply: (gain: DecimalSource) => Decimal.times(gain, unref(processedMultiplier)),
|
||||
|
@ -132,13 +128,11 @@ export function createMultiplicativeModifier<
|
|||
getFormula: (gain: FormulaSource) => Formula.times(gain, processedMultiplier),
|
||||
enabled: processedEnabled,
|
||||
description:
|
||||
processedDescription == null
|
||||
description == null
|
||||
? undefined
|
||||
: computed(() => (
|
||||
: () => (
|
||||
<div class="modifier-container">
|
||||
<span class="modifier-description">
|
||||
{render(processedDescription)}
|
||||
</span>
|
||||
<span class="modifier-description">{render(description)}</span>
|
||||
<span
|
||||
class="modifier-amount"
|
||||
style={
|
||||
|
@ -154,7 +148,7 @@ export function createMultiplicativeModifier<
|
|||
×{formatSmall(unref(processedMultiplier))}
|
||||
</span>
|
||||
</div>
|
||||
))
|
||||
)
|
||||
};
|
||||
}) as S;
|
||||
}
|
||||
|
@ -164,7 +158,7 @@ export interface ExponentialModifierOptions {
|
|||
/** The amount to raise the input value to the power of. */
|
||||
exponent: MaybeRefOrGetter<DecimalSource>;
|
||||
/** Description of what this modifier is doing. */
|
||||
description?: MaybeRefOrGetter<Renderable> | undefined;
|
||||
description?: MaybeGetter<Renderable> | undefined;
|
||||
/** A MaybeRefOrGetter that will be processed and passed directly into the returned modifier. */
|
||||
enabled?: MaybeRefOrGetter<boolean> | undefined;
|
||||
/** Add 1 before calculating, then remove it afterwards. This prevents low numbers from becoming lower. */
|
||||
|
@ -186,7 +180,6 @@ export function createExponentialModifier<
|
|||
optionsFunc();
|
||||
|
||||
const processedExponent = processGetter(exponent);
|
||||
const processedDescription = processGetter(description);
|
||||
const processedEnabled = enabled == null ? undefined : processGetter(enabled);
|
||||
return {
|
||||
apply: (gain: DecimalSource) => {
|
||||
|
@ -217,12 +210,12 @@ export function createExponentialModifier<
|
|||
: Formula.pow(gain, processedExponent),
|
||||
enabled: processedEnabled,
|
||||
description:
|
||||
processedDescription == null
|
||||
description == null
|
||||
? undefined
|
||||
: computed(() => (
|
||||
: () => (
|
||||
<div class="modifier-container">
|
||||
<span class="modifier-description">
|
||||
{render(processedDescription)}
|
||||
{render(description)}
|
||||
{supportLowNumbers ? " (+1 effective)" : null}
|
||||
</span>
|
||||
<span
|
||||
|
@ -240,7 +233,7 @@ export function createExponentialModifier<
|
|||
^{formatSmall(unref(processedExponent))}
|
||||
</span>
|
||||
</div>
|
||||
))
|
||||
)
|
||||
};
|
||||
}) as S;
|
||||
}
|
||||
|
@ -286,14 +279,13 @@ export function createSequentialModifier<
|
|||
? computed(() => modifiers.filter(m => unref(m.enabled) !== false).length > 0)
|
||||
: undefined,
|
||||
description: modifiers.some(m => m.description != null)
|
||||
? computed(() =>
|
||||
? () =>
|
||||
(
|
||||
modifiers
|
||||
.filter(m => unref(m.enabled) !== false)
|
||||
.map(m => unref(m.description))
|
||||
.filter(d => d) as MaybeRef<Renderable>[]
|
||||
.filter(d => d) as MaybeGetter<Renderable>[]
|
||||
).map(m => render(m))
|
||||
)
|
||||
: undefined
|
||||
};
|
||||
}) as S;
|
||||
|
@ -312,7 +304,7 @@ export interface ModifierSectionOptions {
|
|||
/** The unit of the value being modified, if any. */
|
||||
unit?: string;
|
||||
/** The label to use for the base value. Defaults to "Base". */
|
||||
baseText?: MaybeRefOrGetter<Renderable>;
|
||||
baseText?: MaybeGetter<Renderable>;
|
||||
/** Determines if numbers larger or smaller than the base should be displayed as red. */
|
||||
smallerIsBetter?: boolean;
|
||||
}
|
||||
|
@ -332,7 +324,6 @@ export function createModifierSection({
|
|||
smallerIsBetter
|
||||
}: ModifierSectionOptions) {
|
||||
const total = modifier.apply(base ?? 1);
|
||||
const processedBaseText = processGetter(baseText);
|
||||
return (
|
||||
<div style={{ "--unit": settings.alignUnits && unit != null ? "'" + unit + "'" : "" }}>
|
||||
<h3>
|
||||
|
@ -341,7 +332,7 @@ export function createModifierSection({
|
|||
</h3>
|
||||
<br />
|
||||
<div class="modifier-container">
|
||||
<span class="modifier-description">{render(processedBaseText ?? "Base")}</span>
|
||||
<span class="modifier-description">{render(baseText ?? "Base")}</span>
|
||||
<span class="modifier-amount">
|
||||
{formatSmall(base ?? 1)}
|
||||
{unit}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { isVisible, Visibility } from "features/feature";
|
||||
import { displayResource, Resource } from "features/resources/resource";
|
||||
import Decimal, { DecimalSource } from "lib/break_eternity";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { joinJSX, render, Renderable } from "util/vue";
|
||||
import { joinJSX, Renderable } from "util/vue";
|
||||
import { computed, MaybeRef, MaybeRefOrGetter, unref } from "vue";
|
||||
import Formula, { calculateCost, calculateMaxAffordable } from "./formulas/formulas";
|
||||
import type { GenericFormula, InvertibleIntegralFormula } from "./formulas/types";
|
||||
|
@ -262,16 +262,16 @@ export function createVisibilityRequirement(
|
|||
*/
|
||||
export function createBooleanRequirement(
|
||||
requirement: MaybeRefOrGetter<boolean>,
|
||||
display?: MaybeRefOrGetter<Renderable>
|
||||
display?: MaybeGetter<Renderable>
|
||||
): Requirement {
|
||||
return createLazyProxy(() => {
|
||||
const processedDisplay = processGetter(display);
|
||||
const partialDisplay =
|
||||
display == null ? undefined : typeof display === "function" ? display : () => display;
|
||||
return {
|
||||
requirementMet: processGetter(requirement),
|
||||
partialDisplay: processedDisplay == null ? undefined : () => render(processedDisplay),
|
||||
display:
|
||||
processedDisplay == null ? undefined : () => <>Req: {render(processedDisplay)}</>,
|
||||
visibility: processedDisplay == null ? Visibility.None : Visibility.Visible,
|
||||
partialDisplay,
|
||||
display: display == null ? undefined : () => <>Req: {partialDisplay}</>,
|
||||
visibility: display == null ? Visibility.None : Visibility.Visible,
|
||||
requiresPay: false
|
||||
};
|
||||
});
|
||||
|
|
|
@ -2,10 +2,10 @@ import projInfo from "data/projInfo.json";
|
|||
import { Themes } from "data/themes";
|
||||
import { globalBus } from "game/events";
|
||||
import LZString from "lz-string";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter } from "util/computed";
|
||||
import { decodeSave, hardReset } from "util/save";
|
||||
import { Renderable } from "util/vue";
|
||||
import { MaybeRef, MaybeRefOrGetter, reactive, watch } from "vue";
|
||||
import { reactive, watch } from "vue";
|
||||
|
||||
/** The player's settings object. */
|
||||
export interface Settings {
|
||||
|
@ -101,22 +101,22 @@ export function loadSettings(): void {
|
|||
}
|
||||
|
||||
/** A list of fields to append to the settings modal. */
|
||||
export const settingFields: MaybeRef<Renderable>[] = reactive([]);
|
||||
export const settingFields: MaybeGetter<Renderable>[] = reactive([]);
|
||||
/** Register a field to be displayed in the settings modal. */
|
||||
export function registerSettingField(component: MaybeRefOrGetter<Renderable>) {
|
||||
settingFields.push(processGetter(component));
|
||||
export function registerSettingField(component: MaybeGetter<Renderable>) {
|
||||
settingFields.push(component);
|
||||
}
|
||||
|
||||
/** A list of components to show in the info modal. */
|
||||
export const infoComponents: MaybeRef<Renderable>[] = reactive([]);
|
||||
export const infoComponents: MaybeGetter<Renderable>[] = reactive([]);
|
||||
/** Register a component to be displayed in the info modal. */
|
||||
export function registerInfoComponent(component: MaybeRefOrGetter<Renderable>) {
|
||||
infoComponents.push(processGetter(component));
|
||||
export function registerInfoComponent(component: MaybeGetter<Renderable>) {
|
||||
infoComponents.push(component);
|
||||
}
|
||||
|
||||
/** A list of components to add to the root of the page. */
|
||||
export const gameComponents: MaybeRef<Renderable>[] = reactive([]);
|
||||
export const gameComponents: MaybeGetter<Renderable>[] = reactive([]);
|
||||
/** Register a component to be displayed at the root of the page. */
|
||||
export function registerGameComponent(component: MaybeRefOrGetter<Renderable>) {
|
||||
gameComponents.push(processGetter(component));
|
||||
export function registerGameComponent(component: MaybeGetter<Renderable>) {
|
||||
gameComponents.push(component);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ import { isFunction } from "util/common";
|
|||
import type { ComputedRef } from "vue";
|
||||
import { computed } from "vue";
|
||||
|
||||
export type MaybeGetter<T> = T | (() => T);
|
||||
|
||||
export function processGetter<T>(obj: T): T extends () => infer S ? ComputedRef<S> : T {
|
||||
if (isFunction(obj)) {
|
||||
return computed(obj) as ReturnType<typeof processGetter<T>>;
|
||||
|
|
|
@ -6,9 +6,9 @@ import Col from "components/layout/Column.vue";
|
|||
import Row from "components/layout/Row.vue";
|
||||
import { getUniqueID, Visibility } from "features/feature";
|
||||
import VueFeatureComponent from "features/VueFeature.vue";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import type { CSSProperties, MaybeRef, MaybeRefOrGetter, Ref } from "vue";
|
||||
import { isRef, onUnmounted, ref, unref } from "vue";
|
||||
import { isRef, onUnmounted, ref, toValue } from "vue";
|
||||
import { JSX } from "vue/jsx-runtime";
|
||||
import { camelToKebab } from "./common";
|
||||
|
||||
|
@ -35,7 +35,7 @@ export interface VueFeature {
|
|||
/** CSS to apply to this feature. */
|
||||
style?: MaybeRef<CSSProperties>;
|
||||
/** The components to render inside the vue feature */
|
||||
components: MaybeRef<Renderable>[];
|
||||
components: MaybeGetter<Renderable>[];
|
||||
/** The components to render wrapped around the vue feature */
|
||||
wrappers: ((el: () => Renderable) => Renderable)[];
|
||||
/** Used to identify Vue Features */
|
||||
|
@ -45,14 +45,14 @@ export interface VueFeature {
|
|||
export function vueFeatureMixin(
|
||||
featureName: string,
|
||||
options: VueFeatureOptions,
|
||||
component?: MaybeRefOrGetter<Renderable>
|
||||
component?: MaybeGetter<Renderable>
|
||||
) {
|
||||
return {
|
||||
id: getUniqueID(featureName),
|
||||
visibility: processGetter(options.visibility),
|
||||
classes: processGetter(options.classes),
|
||||
style: processGetter(options.style),
|
||||
components: component == null ? [] : [processGetter(component)],
|
||||
components: component == null ? [] : [component],
|
||||
wrappers: [] as ((el: () => Renderable) => Renderable)[],
|
||||
[VueFeature]: true
|
||||
} satisfies VueFeature;
|
||||
|
@ -60,15 +60,15 @@ export function vueFeatureMixin(
|
|||
|
||||
export function render(object: VueFeature, wrapper?: (el: Renderable) => Renderable): JSX.Element;
|
||||
export function render<T extends Renderable>(
|
||||
object: MaybeRef<Renderable>,
|
||||
object: MaybeGetter<Renderable>,
|
||||
wrapper?: (el: Renderable) => T
|
||||
): T;
|
||||
export function render(
|
||||
object: VueFeature | MaybeRef<Renderable>,
|
||||
object: VueFeature | MaybeGetter<Renderable>,
|
||||
wrapper?: (el: Renderable) => Renderable
|
||||
): Renderable;
|
||||
export function render(
|
||||
object: VueFeature | MaybeRef<Renderable>,
|
||||
object: VueFeature | MaybeGetter<Renderable>,
|
||||
wrapper?: (el: Renderable) => Renderable
|
||||
) {
|
||||
if (typeof object === "object" && VueFeature in object) {
|
||||
|
@ -85,20 +85,24 @@ export function render(
|
|||
);
|
||||
}
|
||||
|
||||
object = unref(object);
|
||||
object = toValue(object);
|
||||
return wrapper?.(object) ?? object;
|
||||
}
|
||||
|
||||
export function renderRow(...objects: (VueFeature | MaybeRef<Renderable>)[]): JSX.Element {
|
||||
export function renderRow(
|
||||
...objects: (VueFeature | MaybeGetter<Renderable>)[]
|
||||
): JSX.Element {
|
||||
return <Row>{objects.map(obj => render(obj))}</Row>;
|
||||
}
|
||||
|
||||
export function renderCol(...objects: (VueFeature | MaybeRef<Renderable>)[]): JSX.Element {
|
||||
export function renderCol(
|
||||
...objects: (VueFeature | MaybeGetter<Renderable>)[]
|
||||
): JSX.Element {
|
||||
return <Col>{objects.map(obj => render(obj))}</Col>;
|
||||
}
|
||||
|
||||
export function joinJSX(
|
||||
objects: (VueFeature | MaybeRef<Renderable>)[],
|
||||
objects: (VueFeature | MaybeGetter<Renderable>)[],
|
||||
joiner: JSX.Element
|
||||
): JSX.Element {
|
||||
return objects.reduce<JSX.Element>(
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { type OptionsFunc } from "features/feature";
|
||||
import { processGetter } from "util/computed";
|
||||
import { createLazyProxy, runAfterEvaluation } from "util/proxies";
|
||||
import type { VueFeature } from "util/vue";
|
||||
|
@ -23,9 +22,9 @@ export interface Mark {
|
|||
* @param element The renderable feature to display the tooltip on.
|
||||
* @param options Mark options.
|
||||
*/
|
||||
export function addMark<T extends MarkOptions>(
|
||||
export function addMark(
|
||||
element: VueFeature,
|
||||
optionsFunc: OptionsFunc<T, Mark>
|
||||
optionsFunc: () => MarkOptions
|
||||
): asserts element is VueFeature & { mark: Mark } {
|
||||
const mark = createLazyProxy(() => {
|
||||
const options = optionsFunc();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { isVisible, type OptionsFunc } from "features/feature";
|
||||
import { isVisible } from "features/feature";
|
||||
import { deletePersistent, persistent } from "game/persistence";
|
||||
import { Direction } from "util/common";
|
||||
import { processGetter } from "util/computed";
|
||||
import { MaybeGetter, processGetter } from "util/computed";
|
||||
import { createLazyProxy, runAfterEvaluation } from "util/proxies";
|
||||
import { Renderable, vueFeatureMixin, type VueFeature, type VueFeatureOptions } from "util/vue";
|
||||
import { MaybeRef, MaybeRefOrGetter, type Ref } from "vue";
|
||||
|
@ -21,7 +21,7 @@ export interface TooltipOptions extends VueFeatureOptions {
|
|||
/** Whether or not this tooltip can be pinned, meaning it'll stay visible even when not hovered. */
|
||||
pinnable?: boolean;
|
||||
/** The text to display inside the tooltip. */
|
||||
display: MaybeRefOrGetter<Renderable>;
|
||||
display: MaybeGetter<Renderable>;
|
||||
/** The direction in which to display the tooltip */
|
||||
direction?: MaybeRefOrGetter<Direction>;
|
||||
/** The x offset of the tooltip, in px. */
|
||||
|
@ -35,7 +35,7 @@ export interface Tooltip extends VueFeature {
|
|||
/** Whether or not this tooltip can be pinned, meaning it'll stay visible even when not hovered. */
|
||||
pinnable?: boolean;
|
||||
/** The text to display inside the tooltip. */
|
||||
display: MaybeRef<Renderable>;
|
||||
display: MaybeGetter<Renderable>;
|
||||
/** The direction in which to display the tooltip */
|
||||
direction?: MaybeRef<Direction>;
|
||||
/** The x offset of the tooltip, in px. */
|
||||
|
@ -51,9 +51,9 @@ export interface Tooltip extends VueFeature {
|
|||
* @param element The renderable feature to display the tooltip on.
|
||||
* @param options Tooltip options.
|
||||
*/
|
||||
export function addTooltip<T extends TooltipOptions>(
|
||||
export function addTooltip(
|
||||
element: VueFeature,
|
||||
optionsFunc: OptionsFunc<T, Tooltip>
|
||||
optionsFunc: () => TooltipOptions
|
||||
): asserts element is VueFeature & { tooltip: Tooltip } {
|
||||
const pinned = persistent<boolean>(false, false);
|
||||
const tooltip = createLazyProxy(() => {
|
||||
|
@ -69,7 +69,7 @@ export function addTooltip<T extends TooltipOptions>(
|
|||
...vueFeatureMixin("tooltip", options),
|
||||
pinnable: pinnable ?? true,
|
||||
pinned: pinnable === false ? undefined : pinned,
|
||||
display: processGetter(display),
|
||||
display,
|
||||
direction: processGetter(direction ?? Direction.Up),
|
||||
xoffset: processGetter(xoffset),
|
||||
yoffset: processGetter(yoffset)
|
||||
|
|
|
@ -9,15 +9,16 @@ import {
|
|||
} from "game/modifiers";
|
||||
import Decimal, { DecimalSource } from "util/bignum";
|
||||
import { WithRequired } from "util/common";
|
||||
import { MaybeGetter } from "util/computed";
|
||||
import { render, Renderable } from "util/vue";
|
||||
import { beforeAll, describe, expect, test } from "vitest";
|
||||
import { MaybeRefOrGetter, Ref, ref, unref } from "vue";
|
||||
import "../utils";
|
||||
import { render, Renderable } from "util/vue";
|
||||
|
||||
export type ModifierConstructorOptions = {
|
||||
[S in "addend" | "multiplier" | "exponent"]: MaybeRefOrGetter<DecimalSource>;
|
||||
} & {
|
||||
description?: MaybeRefOrGetter<Renderable>;
|
||||
description?: MaybeGetter<Renderable>;
|
||||
enabled?: MaybeRefOrGetter<boolean>;
|
||||
smallerIsBetter?: boolean;
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue