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">
|
<script setup lang="tsx">
|
||||||
import "components/common/fields.css";
|
import "components/common/fields.css";
|
||||||
|
import { MaybeGetter } from "util/computed";
|
||||||
import { render, Renderable } from "util/vue";
|
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 VueNextSelect from "vue-next-select";
|
||||||
import "vue-next-select/dist/index.css";
|
import "vue-next-select/dist/index.css";
|
||||||
|
|
||||||
export type SelectOption = { label: string; value: unknown };
|
export type SelectOption = { label: string; value: unknown };
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
title?: MaybeRef<Renderable>;
|
title?: MaybeGetter<Renderable>;
|
||||||
modelValue?: unknown;
|
modelValue?: unknown;
|
||||||
options: SelectOption[];
|
options: SelectOption[];
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
|
|
@ -27,12 +27,13 @@
|
||||||
|
|
||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import "components/common/fields.css";
|
import "components/common/fields.css";
|
||||||
|
import { MaybeGetter } from "util/computed";
|
||||||
import { render, Renderable } from "util/vue";
|
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";
|
import VueTextareaAutosize from "vue-textarea-autosize";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
title?: MaybeRef<Renderable>;
|
title?: MaybeGetter<Renderable>;
|
||||||
modelValue?: string;
|
modelValue?: string;
|
||||||
textArea?: boolean;
|
textArea?: boolean;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
|
|
@ -7,11 +7,12 @@
|
||||||
|
|
||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import "components/common/fields.css";
|
import "components/common/fields.css";
|
||||||
|
import { MaybeGetter } from "util/computed";
|
||||||
import { render, Renderable } from "util/vue";
|
import { render, Renderable } from "util/vue";
|
||||||
import { computed, MaybeRef } from "vue";
|
import { computed } from "vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
title?: MaybeRef<Renderable>;
|
title?: MaybeGetter<Renderable>;
|
||||||
modelValue?: boolean;
|
modelValue?: boolean;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|
|
@ -8,14 +8,15 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { MaybeGetter } from "util/computed";
|
||||||
import { render, Renderable } from "util/vue";
|
import { render, Renderable } from "util/vue";
|
||||||
import type { MaybeRef, Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
import Col from "./Column.vue";
|
import Col from "./Column.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
collapsed: Ref<boolean>;
|
collapsed: Ref<boolean>;
|
||||||
display: MaybeRef<Renderable>;
|
display: MaybeGetter<Renderable>;
|
||||||
content: MaybeRef<Renderable>;
|
content: MaybeGetter<Renderable>;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const Display = () => render(props.display);
|
const Display = () => render(props.display);
|
||||||
|
|
|
@ -17,7 +17,7 @@ import settings from "game/settings";
|
||||||
import type { DecimalSource } from "util/bignum";
|
import type { DecimalSource } from "util/bignum";
|
||||||
import Decimal, { format, formatSmall, formatTime } from "util/bignum";
|
import Decimal, { format, formatSmall, formatTime } from "util/bignum";
|
||||||
import { WithRequired } from "util/common";
|
import { WithRequired } from "util/common";
|
||||||
import { processGetter } from "util/computed";
|
import { MaybeGetter, processGetter } from "util/computed";
|
||||||
import { render, Renderable, renderCol } from "util/vue";
|
import { render, Renderable, renderCol } from "util/vue";
|
||||||
import type { ComputedRef, MaybeRef, MaybeRefOrGetter } from "vue";
|
import type { ComputedRef, MaybeRef, MaybeRefOrGetter } from "vue";
|
||||||
import { computed, ref, unref } from "vue";
|
import { computed, ref, unref } from "vue";
|
||||||
|
@ -43,7 +43,7 @@ export interface ResetButtonOptions extends ClickableOptions {
|
||||||
* The content to display on the button.
|
* The content to display on the button.
|
||||||
* By default, this includes the reset description, and amount of currency to be gained.
|
* 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.
|
* Whether or not this button can currently be clicked.
|
||||||
* Defaults to checking the current gain amount is greater than {@link minimumGain}
|
* 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))
|
Decimal.gte(unref(conversion.actualGain), unref(resetButton.minimumGain))
|
||||||
),
|
),
|
||||||
display:
|
display:
|
||||||
processGetter(display) ??
|
display ??
|
||||||
computed(
|
((): JSX.Element => (
|
||||||
(): JSX.Element => (
|
|
||||||
<span>
|
<span>
|
||||||
{unref(resetButton.resetDescription)}
|
{unref(resetButton.resetDescription)}
|
||||||
<b>
|
<b>
|
||||||
|
@ -156,8 +155,7 @@ export function createResetButton<T extends ClickableOptions & ResetButtonOption
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</span>
|
</span>
|
||||||
)
|
)),
|
||||||
),
|
|
||||||
onClick: function (e?: MouseEvent | TouchEvent) {
|
onClick: function (e?: MouseEvent | TouchEvent) {
|
||||||
if (unref(resetButton.canClick) === false) {
|
if (unref(resetButton.canClick) === false) {
|
||||||
return;
|
return;
|
||||||
|
@ -211,7 +209,7 @@ export function createLayerTreeNode<T extends LayerTreeNodeOptions>(optionsFunc:
|
||||||
return {
|
return {
|
||||||
...(props as Omit<typeof props, keyof LayerTreeNodeOptions>),
|
...(props as Omit<typeof props, keyof LayerTreeNodeOptions>),
|
||||||
layerID,
|
layerID,
|
||||||
display: processGetter(display) ?? layerID,
|
display: display ?? layerID,
|
||||||
append: processGetter(append) ?? true,
|
append: processGetter(append) ?? true,
|
||||||
onClick() {
|
onClick() {
|
||||||
if (unref<boolean>(layerTreeNode.append)) {
|
if (unref<boolean>(layerTreeNode.append)) {
|
||||||
|
@ -244,7 +242,7 @@ export interface Section {
|
||||||
/** The unit of measurement for the base. **/
|
/** The unit of measurement for the base. **/
|
||||||
unit?: string;
|
unit?: string;
|
||||||
/** The label to call the base amount. Defaults to "Base". **/
|
/** 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. **/
|
/** Whether or not this section should be currently visible to the player. **/
|
||||||
visible?: MaybeRefOrGetter<boolean>;
|
visible?: MaybeRefOrGetter<boolean>;
|
||||||
/** Determines if numbers larger or smaller than the base should be displayed as red. */
|
/** Determines if numbers larger or smaller than the base should be displayed as red. */
|
||||||
|
@ -258,12 +256,12 @@ export interface Section {
|
||||||
*/
|
*/
|
||||||
export function createCollapsibleModifierSections(
|
export function createCollapsibleModifierSections(
|
||||||
sectionsFunc: () => Section[]
|
sectionsFunc: () => Section[]
|
||||||
): [MaybeRef<Renderable>, Persistent<Record<number, boolean>>] {
|
): [() => Renderable, Persistent<Record<number, boolean>>] {
|
||||||
const sections: Section[] = [];
|
const sections: Section[] = [];
|
||||||
const processed:
|
const processed:
|
||||||
| {
|
| {
|
||||||
base: MaybeRef<DecimalSource | undefined>[];
|
base: MaybeRef<DecimalSource | undefined>[];
|
||||||
baseText: (MaybeRef<Renderable> | undefined)[];
|
baseText: (MaybeGetter<Renderable> | undefined)[];
|
||||||
visible: MaybeRef<boolean | undefined>[];
|
visible: MaybeRef<boolean | undefined>[];
|
||||||
title: MaybeRef<string | undefined>[];
|
title: MaybeRef<string | undefined>[];
|
||||||
subtitle: MaybeRef<string | undefined>[];
|
subtitle: MaybeRef<string | undefined>[];
|
||||||
|
@ -274,7 +272,7 @@ export function createCollapsibleModifierSections(
|
||||||
if (!calculated) {
|
if (!calculated) {
|
||||||
sections.push(...sectionsFunc());
|
sections.push(...sectionsFunc());
|
||||||
processed.base = sections.map(s => processGetter(s.base));
|
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.visible = sections.map(s => processGetter(s.visible));
|
||||||
processed.title = sections.map(s => processGetter(s.title));
|
processed.title = sections.map(s => processGetter(s.title));
|
||||||
processed.subtitle = sections.map(s => processGetter(s.subtitle));
|
processed.subtitle = sections.map(s => processGetter(s.subtitle));
|
||||||
|
@ -284,7 +282,7 @@ export function createCollapsibleModifierSections(
|
||||||
}
|
}
|
||||||
|
|
||||||
const collapsed = persistent<Record<number, boolean>>({}, false);
|
const collapsed = persistent<Record<number, boolean>>({}, false);
|
||||||
const jsxFunc = computed(() => {
|
const jsxFunc = () => {
|
||||||
const sections = calculateSections();
|
const sections = calculateSections();
|
||||||
|
|
||||||
let firstVisibleSection = true;
|
let firstVisibleSection = true;
|
||||||
|
@ -364,7 +362,7 @@ export function createCollapsibleModifierSections(
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return <>{sectionJSX}</>;
|
return <>{sectionJSX}</>;
|
||||||
});
|
};
|
||||||
return [jsxFunc, collapsed];
|
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 Node from "components/Node.vue";
|
||||||
import type { Visibility } from "features/feature";
|
import type { Visibility } from "features/feature";
|
||||||
import { isHidden, isVisible } from "features/feature";
|
import { isHidden, isVisible } from "features/feature";
|
||||||
|
import { MaybeGetter } from "util/computed";
|
||||||
import { render, Renderable } from "util/vue";
|
import { render, Renderable } from "util/vue";
|
||||||
import { MaybeRef, unref, type CSSProperties } from "vue";
|
import { MaybeRef, unref, type CSSProperties } from "vue";
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
id: string;
|
id: string;
|
||||||
components: MaybeRef<Renderable>[];
|
components: MaybeGetter<Renderable>[];
|
||||||
wrappers: ((el: () => Renderable) => Renderable)[];
|
wrappers: ((el: () => Renderable) => Renderable)[];
|
||||||
visibility?: MaybeRef<Visibility | boolean>;
|
visibility?: MaybeRef<Visibility | boolean>;
|
||||||
style?: MaybeRef<CSSProperties>;
|
style?: MaybeRef<CSSProperties>;
|
||||||
|
|
|
@ -29,35 +29,7 @@ const props = defineProps<{
|
||||||
small: Achievement["small"];
|
small: Achievement["small"];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const Component = () => {
|
const Component = () => props.display == null ? <></> : render(props.display);
|
||||||
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>);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -14,7 +14,7 @@ import {
|
||||||
} from "game/requirements";
|
} from "game/requirements";
|
||||||
import settings, { registerSettingField } from "game/settings";
|
import settings, { registerSettingField } from "game/settings";
|
||||||
import { camelToTitle } from "util/common";
|
import { camelToTitle } from "util/common";
|
||||||
import { processGetter } from "util/computed";
|
import { MaybeGetter, processGetter } from "util/computed";
|
||||||
import { createLazyProxy } from "util/proxies";
|
import { createLazyProxy } from "util/proxies";
|
||||||
import {
|
import {
|
||||||
isJSXElement,
|
isJSXElement,
|
||||||
|
@ -24,7 +24,7 @@ import {
|
||||||
vueFeatureMixin,
|
vueFeatureMixin,
|
||||||
VueFeatureOptions
|
VueFeatureOptions
|
||||||
} from "util/vue";
|
} 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 { useToast } from "vue-toastification";
|
||||||
import Achievement from "./Achievement.vue";
|
import Achievement from "./Achievement.vue";
|
||||||
|
|
||||||
|
@ -50,14 +50,15 @@ export interface AchievementOptions extends VueFeatureOptions {
|
||||||
requirements?: Requirements;
|
requirements?: Requirements;
|
||||||
/** The display to use for this achievement. */
|
/** The display to use for this achievement. */
|
||||||
display?:
|
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}. */
|
/** 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. */
|
/** 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. */
|
/** 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. */
|
/** Toggles a smaller design for the feature. */
|
||||||
small?: MaybeRefOrGetter<boolean>;
|
small?: MaybeRefOrGetter<boolean>;
|
||||||
|
@ -76,13 +77,7 @@ export interface Achievement extends VueFeature {
|
||||||
/** A function that is called when the achievement is completed. */
|
/** A function that is called when the achievement is completed. */
|
||||||
onComplete?: VoidFunction;
|
onComplete?: VoidFunction;
|
||||||
/** The display to use for this achievement. */
|
/** The display to use for this achievement. */
|
||||||
display?:
|
display?: MaybeGetter<Renderable>;
|
||||||
| MaybeRef<Renderable>
|
|
||||||
| {
|
|
||||||
requirement?: MaybeRef<Renderable>;
|
|
||||||
effectDisplay?: MaybeRef<Renderable>;
|
|
||||||
optionsDisplay?: MaybeRef<Renderable>;
|
|
||||||
};
|
|
||||||
/** Toggles a smaller design for the feature. */
|
/** Toggles a smaller design for the feature. */
|
||||||
small?: MaybeRef<boolean>;
|
small?: MaybeRef<boolean>;
|
||||||
/** An image to display as the background for this achievement. */
|
/** 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);
|
const earned = persistent<boolean>(false, false);
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const options = optionsFunc?.() ?? ({} as T);
|
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, () => (
|
const vueFeature = vueFeatureMixin("achievement", options, () => (
|
||||||
<Achievement
|
<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 = {
|
const achievement = {
|
||||||
type: AchievementType,
|
type: AchievementType,
|
||||||
...(props as Omit<typeof props, keyof VueFeature | keyof AchievementOptions>),
|
...(props as Omit<typeof props, keyof VueFeature | keyof AchievementOptions>),
|
||||||
...vueFeature,
|
...vueFeature,
|
||||||
visibility: computed(() => {
|
visibility: computed(() => {
|
||||||
const display = unref((achievement as Achievement).display);
|
|
||||||
switch (settings.msDisplay) {
|
switch (settings.msDisplay) {
|
||||||
default:
|
default:
|
||||||
case AchievementDisplay.All:
|
case AchievementDisplay.All:
|
||||||
|
@ -131,9 +157,9 @@ export function createAchievement<T extends AchievementOptions>(optionsFunc?: ()
|
||||||
if (
|
if (
|
||||||
unref(earned) &&
|
unref(earned) &&
|
||||||
!(
|
!(
|
||||||
display != null &&
|
_display != null &&
|
||||||
typeof display === "object" &&
|
typeof _display === "object" &&
|
||||||
"optionsDisplay" in display
|
!isJSXElement(_display)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return Visibility.None;
|
return Visibility.None;
|
||||||
|
@ -153,19 +179,7 @@ export function createAchievement<T extends AchievementOptions>(optionsFunc?: ()
|
||||||
small: processGetter(small),
|
small: processGetter(small),
|
||||||
image: processGetter(image),
|
image: processGetter(image),
|
||||||
showPopups: processGetter(showPopups) ?? true,
|
showPopups: processGetter(showPopups) ?? true,
|
||||||
display:
|
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)
|
|
||||||
},
|
|
||||||
requirements:
|
requirements:
|
||||||
requirements == null
|
requirements == null
|
||||||
? undefined
|
? undefined
|
||||||
|
@ -181,19 +195,18 @@ export function createAchievement<T extends AchievementOptions>(optionsFunc?: ()
|
||||||
earned.value = true;
|
earned.value = true;
|
||||||
achievement.onComplete?.();
|
achievement.onComplete?.();
|
||||||
if (achievement.display != null && unref(achievement.showPopups) === true) {
|
if (achievement.display != null && unref(achievement.showPopups) === true) {
|
||||||
const display = achievement.display;
|
let display = achievement.display;
|
||||||
let Display;
|
if (typeof _display === "object" && !isJSXElement(_display)) {
|
||||||
if (isRef(display) || typeof display === "string" || isJSXElement(display)) {
|
if (_display.requirement != null) {
|
||||||
Display = () => render(display);
|
display = _display.requirement;
|
||||||
} else if (display.requirement != null) {
|
|
||||||
Display = () => render(display.requirement!);
|
|
||||||
} else {
|
} else {
|
||||||
Display = () => displayRequirements(achievement.requirements ?? []);
|
display = displayRequirements(requirements ?? []);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
toast.info(
|
toast.info(
|
||||||
<div>
|
<div>
|
||||||
<h3>Achievement earned!</h3>
|
<h3>Achievement earned!</h3>
|
||||||
<div>{Display()}</div>
|
<div>{render(display)}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import Bar from "features/bars/Bar.vue";
|
import Bar from "features/bars/Bar.vue";
|
||||||
import type { DecimalSource } from "util/bignum";
|
import type { DecimalSource } from "util/bignum";
|
||||||
import { Direction } from "util/common";
|
import { Direction } from "util/common";
|
||||||
import { processGetter } from "util/computed";
|
import { MaybeGetter, processGetter } from "util/computed";
|
||||||
import { createLazyProxy } from "util/proxies";
|
import { createLazyProxy } from "util/proxies";
|
||||||
import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
||||||
import { CSSProperties, MaybeRef, MaybeRefOrGetter } from "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. */
|
/** The progress value of the bar, from 0 to 1. */
|
||||||
progress: MaybeRefOrGetter<DecimalSource>;
|
progress: MaybeRefOrGetter<DecimalSource>;
|
||||||
/** The display to use for this bar. */
|
/** 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. */
|
/** 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. */
|
/** The progress value of the bar, from 0 to 1. */
|
||||||
progress: MaybeRef<DecimalSource>;
|
progress: MaybeRef<DecimalSource>;
|
||||||
/** The display to use for this bar. */
|
/** The display to use for this bar. */
|
||||||
display?: MaybeRef<Renderable>;
|
display?: MaybeGetter<Renderable>;
|
||||||
/** A symbol that helps identify features of the same type. */
|
/** A symbol that helps identify features of the same type. */
|
||||||
type: typeof BarType;
|
type: typeof BarType;
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,7 @@ export function createBar<T extends BarOptions>(optionsFunc: () => T) {
|
||||||
textStyle: processGetter(textStyle),
|
textStyle: processGetter(textStyle),
|
||||||
fillStyle: processGetter(fillStyle),
|
fillStyle: processGetter(fillStyle),
|
||||||
progress: processGetter(progress),
|
progress: processGetter(progress),
|
||||||
display: processGetter(display)
|
display
|
||||||
} satisfies Bar;
|
} satisfies Bar;
|
||||||
|
|
||||||
return bar;
|
return bar;
|
||||||
|
|
|
@ -22,7 +22,6 @@
|
||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import "components/common/features.css";
|
import "components/common/features.css";
|
||||||
import { getHighNotifyStyle, getNotifyStyle } from "game/notifications";
|
import { getHighNotifyStyle, getNotifyStyle } from "game/notifications";
|
||||||
import { displayRequirements } from "game/requirements";
|
|
||||||
import { render } from "util/vue";
|
import { render } from "util/vue";
|
||||||
import type { Component } from "vue";
|
import type { Component } from "vue";
|
||||||
import { computed, unref } from "vue";
|
import { computed, unref } from "vue";
|
||||||
|
@ -61,34 +60,7 @@ const notifyStyle = computed(() => {
|
||||||
return {};
|
return {};
|
||||||
});
|
});
|
||||||
|
|
||||||
const Component = () => {
|
const Component = () => props.display == null ? <></> : render(props.display);
|
||||||
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);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -4,13 +4,20 @@ import type { Reset } from "features/reset";
|
||||||
import { globalBus } from "game/events";
|
import { globalBus } from "game/events";
|
||||||
import type { Persistent } from "game/persistence";
|
import type { Persistent } from "game/persistence";
|
||||||
import { 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 settings, { registerSettingField } from "game/settings";
|
||||||
import type { DecimalSource } from "util/bignum";
|
import type { DecimalSource } from "util/bignum";
|
||||||
import Decimal 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 { 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 type { MaybeRef, MaybeRefOrGetter, Ref, WatchStopHandle } from "vue";
|
||||||
import { computed, unref, watch } from "vue";
|
import { computed, unref, watch } from "vue";
|
||||||
import Challenge from "./Challenge.vue";
|
import Challenge from "./Challenge.vue";
|
||||||
|
@ -32,18 +39,19 @@ export interface ChallengeOptions extends VueFeatureOptions {
|
||||||
completionLimit?: MaybeRefOrGetter<DecimalSource>;
|
completionLimit?: MaybeRefOrGetter<DecimalSource>;
|
||||||
/** The display to use for this challenge. */
|
/** The display to use for this challenge. */
|
||||||
display?:
|
display?:
|
||||||
| MaybeRefOrGetter<Renderable>
|
| Renderable
|
||||||
|
| (() => Renderable)
|
||||||
| {
|
| {
|
||||||
/** A header to appear at the top of the display. */
|
/** A header to appear at the top of the display. */
|
||||||
title?: MaybeRefOrGetter<Renderable>;
|
title?: MaybeGetter<Renderable>;
|
||||||
/** The main text that appears in the display. */
|
/** 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}. */
|
/** 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. */
|
/** 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. */
|
/** 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. */
|
/** A function that is called when the challenge is completed. */
|
||||||
onComplete?: VoidFunction;
|
onComplete?: VoidFunction;
|
||||||
|
@ -70,20 +78,7 @@ export interface Challenge extends VueFeature {
|
||||||
/** The maximum number of times the challenge can be completed. */
|
/** The maximum number of times the challenge can be completed. */
|
||||||
completionLimit?: MaybeRef<DecimalSource>;
|
completionLimit?: MaybeRef<DecimalSource>;
|
||||||
/** The display to use for this challenge. */
|
/** The display to use for this challenge. */
|
||||||
display?:
|
display?: MaybeGetter<Renderable>;
|
||||||
| 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>;
|
|
||||||
};
|
|
||||||
/** The current amount of times this challenge can be completed. */
|
/** The current amount of times this challenge can be completed. */
|
||||||
canComplete: Ref<DecimalSource>;
|
canComplete: Ref<DecimalSource>;
|
||||||
/** The current number of times this challenge has been completed. */
|
/** The current number of times this challenge has been completed. */
|
||||||
|
@ -118,7 +113,7 @@ export function createChallenge<T extends ChallengeOptions>(optionsFunc: () => T
|
||||||
requirements,
|
requirements,
|
||||||
canStart,
|
canStart,
|
||||||
completionLimit,
|
completionLimit,
|
||||||
display,
|
display: _display,
|
||||||
reset,
|
reset,
|
||||||
onComplete,
|
onComplete,
|
||||||
onEnter,
|
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 = {
|
const challenge = {
|
||||||
type: ChallengeType,
|
type: ChallengeType,
|
||||||
...(props as Omit<typeof props, keyof VueFeature | keyof ChallengeOptions>),
|
...(props as Omit<typeof props, keyof VueFeature | keyof ChallengeOptions>),
|
||||||
|
@ -157,18 +187,7 @@ export function createChallenge<T extends ChallengeOptions>(optionsFunc: () => T
|
||||||
onComplete,
|
onComplete,
|
||||||
onEnter,
|
onEnter,
|
||||||
onExit,
|
onExit,
|
||||||
display:
|
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),
|
|
||||||
toggle: function () {
|
toggle: function () {
|
||||||
if (active.value) {
|
if (active.value) {
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -5,11 +5,10 @@ import { persistent } from "game/persistence";
|
||||||
import Decimal, { DecimalSource } from "lib/break_eternity";
|
import Decimal, { DecimalSource } from "lib/break_eternity";
|
||||||
import { Unsubscribe } from "nanoevents";
|
import { Unsubscribe } from "nanoevents";
|
||||||
import { Direction } from "util/common";
|
import { Direction } from "util/common";
|
||||||
import { processGetter } from "util/computed";
|
import { MaybeGetter, processGetter } from "util/computed";
|
||||||
import { createLazyProxy } from "util/proxies";
|
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 { computed, MaybeRef, MaybeRefOrGetter, Ref, ref, unref } from "vue";
|
||||||
import { JSX } from "vue/jsx-runtime";
|
|
||||||
import { Bar, BarOptions, createBar } from "../bars/bar";
|
import { Bar, BarOptions, createBar } from "../bars/bar";
|
||||||
import { type Clickable, ClickableOptions } from "./clickable";
|
import { type Clickable, ClickableOptions } from "./clickable";
|
||||||
|
|
||||||
|
@ -39,7 +38,7 @@ export interface Action extends VueFeature {
|
||||||
/** Whether or not the action may be performed. */
|
/** Whether or not the action may be performed. */
|
||||||
canClick: MaybeRef<boolean>;
|
canClick: MaybeRef<boolean>;
|
||||||
/** The display to use for this action. */
|
/** The display to use for this action. */
|
||||||
display?: MaybeRef<Renderable>;
|
display?: MaybeGetter<Renderable>;
|
||||||
/** A function that is called when the action is clicked. */
|
/** A function that is called when the action is clicked. */
|
||||||
onClick: (amount: DecimalSource) => void;
|
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. */
|
/** 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);
|
const progress = persistent<DecimalSource>(0);
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const options = optionsFunc?.() ?? ({} as T);
|
const options = optionsFunc?.() ?? ({} as T);
|
||||||
const { style, duration, canClick, autoStart, display, barOptions, onClick, ...props } =
|
const {
|
||||||
options;
|
style,
|
||||||
|
duration,
|
||||||
|
canClick,
|
||||||
|
autoStart,
|
||||||
|
display: _display,
|
||||||
|
barOptions,
|
||||||
|
onClick,
|
||||||
|
...props
|
||||||
|
} = options;
|
||||||
|
|
||||||
const processedCanClick = processGetter(canClick) ?? true;
|
const processedCanClick = processGetter(canClick) ?? true;
|
||||||
const processedStyle = processGetter(style);
|
const processedStyle = processGetter(style);
|
||||||
|
@ -78,29 +85,24 @@ export function createAction<T extends ActionOptions>(optionsFunc?: () => T) {
|
||||||
...(barOptions as Omit<typeof barOptions, keyof VueFeature>)
|
...(barOptions as Omit<typeof barOptions, keyof VueFeature>)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let Component: () => JSX.Element;
|
let display: MaybeGetter<Renderable>;
|
||||||
if (typeof display === "object" && "description" in display) {
|
if (typeof _display === "object" && !isJSXElement(_display)) {
|
||||||
const title = processGetter(display.title);
|
display = () => (
|
||||||
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 (
|
|
||||||
<span>
|
<span>
|
||||||
{title != null ? (
|
{_display.title != null ? (
|
||||||
<div>
|
<div>
|
||||||
<Title />
|
{render(_display.title, el => (
|
||||||
|
<h3>{el}</h3>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<Description />
|
{render(_display.description, el => (
|
||||||
|
<div>{el}</div>
|
||||||
|
))}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
} else if (_display != null) {
|
||||||
} else if (display != null) {
|
display = _display;
|
||||||
const processedDisplay = processGetter(display);
|
|
||||||
Component = () => render(processedDisplay);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const action = {
|
const action = {
|
||||||
|
@ -135,14 +137,14 @@ export function createAction<T extends ActionOptions>(optionsFunc?: () => T) {
|
||||||
unref(processedCanClick) && Decimal.gte(progress.value, unref(action.duration))
|
unref(processedCanClick) && Decimal.gte(progress.value, unref(action.duration))
|
||||||
),
|
),
|
||||||
autoStart: processGetter(autoStart) ?? false,
|
autoStart: processGetter(autoStart) ?? false,
|
||||||
display: computed(() => (
|
display: () => (
|
||||||
<>
|
<>
|
||||||
<div style="flex-grow: 1" />
|
<div style="flex-grow: 1" />
|
||||||
{display == null ? null : <Component />}
|
{display == null ? null : render(display)}
|
||||||
<div style="flex-grow: 1" />
|
<div style="flex-grow: 1" />
|
||||||
{render(progressBar)}
|
{render(progressBar)}
|
||||||
</>
|
</>
|
||||||
)),
|
),
|
||||||
progressBar,
|
progressBar,
|
||||||
onClick: function () {
|
onClick: function () {
|
||||||
if (unref(action.canClick) === false) {
|
if (unref(action.canClick) === false) {
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
import Clickable from "features/clickables/Clickable.vue";
|
import Clickable from "features/clickables/Clickable.vue";
|
||||||
import type { BaseLayer } from "game/layers";
|
import type { BaseLayer } from "game/layers";
|
||||||
import type { Unsubscribe } from "nanoevents";
|
import type { Unsubscribe } from "nanoevents";
|
||||||
import { processGetter } from "util/computed";
|
import { MaybeGetter, processGetter } from "util/computed";
|
||||||
import { createLazyProxy } from "util/proxies";
|
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";
|
import { computed, MaybeRef, MaybeRefOrGetter, unref } from "vue";
|
||||||
|
|
||||||
/** A symbol used to identify {@link Clickable} features. */
|
/** A symbol used to identify {@link Clickable} features. */
|
||||||
|
@ -17,12 +24,13 @@ export interface ClickableOptions extends VueFeatureOptions {
|
||||||
canClick?: MaybeRefOrGetter<boolean>;
|
canClick?: MaybeRefOrGetter<boolean>;
|
||||||
/** The display to use for this clickable. */
|
/** The display to use for this clickable. */
|
||||||
display?:
|
display?:
|
||||||
| MaybeRefOrGetter<Renderable>
|
| Renderable
|
||||||
|
| (() => Renderable)
|
||||||
| {
|
| {
|
||||||
/** A header to appear at the top of the display. */
|
/** A header to appear at the top of the display. */
|
||||||
title?: MaybeRefOrGetter<Renderable>;
|
title?: MaybeGetter<Renderable>;
|
||||||
/** The main text that appears in the display. */
|
/** The main text that appears in the display. */
|
||||||
description: MaybeRefOrGetter<Renderable>;
|
description: MaybeGetter<Renderable>;
|
||||||
};
|
};
|
||||||
/** A function that is called when the clickable is clicked. */
|
/** A function that is called when the clickable is clicked. */
|
||||||
onClick?: (e?: MouseEvent | TouchEvent) => void;
|
onClick?: (e?: MouseEvent | TouchEvent) => void;
|
||||||
|
@ -39,7 +47,7 @@ export interface Clickable extends VueFeature {
|
||||||
/** Whether or not the clickable may be clicked. */
|
/** Whether or not the clickable may be clicked. */
|
||||||
canClick: MaybeRef<boolean>;
|
canClick: MaybeRef<boolean>;
|
||||||
/** The display to use for this clickable. */
|
/** The display to use for this clickable. */
|
||||||
display?: MaybeRef<Renderable>;
|
display?: MaybeGetter<Renderable>;
|
||||||
/** A symbol that helps identify features of the same type. */
|
/** A symbol that helps identify features of the same type. */
|
||||||
type: typeof ClickableType;
|
type: typeof ClickableType;
|
||||||
}
|
}
|
||||||
|
@ -53,26 +61,20 @@ export function createClickable<T extends ClickableOptions>(optionsFunc?: () =>
|
||||||
const options = optionsFunc?.() ?? ({} as T);
|
const options = optionsFunc?.() ?? ({} as T);
|
||||||
const { canClick, display: _display, onClick: onClick, onHold: onHold, ...props } = options;
|
const { canClick, display: _display, onClick: onClick, onHold: onHold, ...props } = options;
|
||||||
|
|
||||||
let display: MaybeRef<Renderable> | undefined = undefined;
|
let display: MaybeGetter<Renderable> | undefined = undefined;
|
||||||
if (typeof _display === "object" && "description" in _display) {
|
if (typeof _display === "object" && !isJSXElement(_display)) {
|
||||||
const title = processGetter(_display.title);
|
display = () => (
|
||||||
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(() => (
|
|
||||||
<span>
|
<span>
|
||||||
{title != null ? (
|
{_display.title != null ? (
|
||||||
<div>
|
<div>
|
||||||
<Title />
|
{render(_display.title, el => <h3>{el}</h3>)}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<Description />
|
{render(_display.description, el => <div>{el}</div>)}
|
||||||
</span>
|
</span>
|
||||||
));
|
);
|
||||||
} else if (_display != null) {
|
} else if (_display != null) {
|
||||||
display = processGetter(_display);
|
display = _display;
|
||||||
}
|
}
|
||||||
|
|
||||||
const clickable = {
|
const clickable = {
|
||||||
|
|
|
@ -11,11 +11,11 @@ import {
|
||||||
} from "game/requirements";
|
} from "game/requirements";
|
||||||
import type { DecimalSource } from "util/bignum";
|
import type { DecimalSource } from "util/bignum";
|
||||||
import Decimal, { formatWhole } 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 { createLazyProxy } from "util/proxies";
|
||||||
import { isJSXElement, render, Renderable, VueFeature, vueFeatureMixin } from "util/vue";
|
import { isJSXElement, render, Renderable, VueFeature, vueFeatureMixin } from "util/vue";
|
||||||
import type { MaybeRef, MaybeRefOrGetter, Ref } from "vue";
|
import type { MaybeRef, MaybeRefOrGetter, Ref } from "vue";
|
||||||
import { computed, isRef, unref } from "vue";
|
import { computed, unref } from "vue";
|
||||||
import { ClickableOptions } from "./clickable";
|
import { ClickableOptions } from "./clickable";
|
||||||
|
|
||||||
/** A symbol used to identify {@link Repeatable} features. */
|
/** A symbol used to identify {@link Repeatable} features. */
|
||||||
|
@ -31,14 +31,15 @@ export interface RepeatableOptions extends ClickableOptions {
|
||||||
initialAmount?: DecimalSource;
|
initialAmount?: DecimalSource;
|
||||||
/** The display to use for this repeatable. */
|
/** The display to use for this repeatable. */
|
||||||
display?:
|
display?:
|
||||||
| MaybeRefOrGetter<Renderable>
|
| Renderable
|
||||||
|
| (() => Renderable)
|
||||||
| {
|
| {
|
||||||
/** A header to appear at the top of the display. */
|
/** A header to appear at the top of the display. */
|
||||||
title?: MaybeRefOrGetter<Renderable>;
|
title?: MaybeGetter<Renderable>;
|
||||||
/** The main text that appears in the display. */
|
/** 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. */
|
/** 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. */
|
/** Whether or not to show the current amount of this repeatable at the bottom of the display. */
|
||||||
showAmount?: boolean;
|
showAmount?: boolean;
|
||||||
};
|
};
|
||||||
|
@ -53,7 +54,7 @@ export interface Repeatable extends VueFeature {
|
||||||
/** The initial amount this repeatable has on a new save / after reset. */
|
/** The initial amount this repeatable has on a new save / after reset. */
|
||||||
initialAmount?: DecimalSource;
|
initialAmount?: DecimalSource;
|
||||||
/** The display to use for this repeatable. */
|
/** The display to use for this repeatable. */
|
||||||
display?: MaybeRef<Renderable>;
|
display?: MaybeGetter<Renderable>;
|
||||||
/** Whether or not the repeatable may be clicked. */
|
/** Whether or not the repeatable may be clicked. */
|
||||||
canClick: Ref<boolean>;
|
canClick: Ref<boolean>;
|
||||||
/** A function that is called when the repeatable is clicked. */
|
/** A function that is called when the repeatable is clicked. */
|
||||||
|
@ -119,25 +120,19 @@ export function createRepeatable<T extends RepeatableOptions>(optionsFunc: () =>
|
||||||
}
|
}
|
||||||
|
|
||||||
let display;
|
let display;
|
||||||
if (typeof _display === "object" && !isRef(_display) && !isJSXElement(_display)) {
|
if (typeof _display === "object" && !isJSXElement(_display)) {
|
||||||
const title = processGetter(_display.title);
|
const { title, description, effectDisplay, showAmount } = _display;
|
||||||
const description = processGetter(_display.description);
|
|
||||||
const effectDisplay = processGetter(_display.effectDisplay);
|
|
||||||
const showAmount = processGetter(_display.showAmount);
|
|
||||||
|
|
||||||
const Title = title == null ? null : () => render(title, el => <h3>{el}</h3>);
|
display = () => (
|
||||||
const Description = () => render(description, el => <>{el}</>);
|
|
||||||
const EffectDisplay =
|
|
||||||
effectDisplay == null ? null : () => render(effectDisplay, el => <>{el}</>);
|
|
||||||
|
|
||||||
display = computed(() => (
|
|
||||||
<span>
|
<span>
|
||||||
{Title == null ? null : (
|
{title == null ? null : (
|
||||||
<div>
|
<div>
|
||||||
<Title />
|
{render(title, el => (
|
||||||
|
<h3>{el}</h3>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Description />
|
{render(description)}
|
||||||
{showAmount === false ? null : (
|
{showAmount === false ? null : (
|
||||||
<div>
|
<div>
|
||||||
<br />
|
<br />
|
||||||
|
@ -147,10 +142,10 @@ export function createRepeatable<T extends RepeatableOptions>(optionsFunc: () =>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{EffectDisplay == null ? null : (
|
{effectDisplay == null ? null : (
|
||||||
<div>
|
<div>
|
||||||
<br />
|
<br />
|
||||||
Currently: <EffectDisplay />
|
Currently: {render(effectDisplay)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{unref(repeatable.maxed) ? null : (
|
{unref(repeatable.maxed) ? null : (
|
||||||
|
@ -160,12 +155,9 @@ export function createRepeatable<T extends RepeatableOptions>(optionsFunc: () =>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
));
|
);
|
||||||
} else if (_display != null) {
|
} else if (_display != null) {
|
||||||
const processedDisplay = processGetter(_display);
|
display = _display;
|
||||||
display = computed(() => render(processedDisplay));
|
|
||||||
} else {
|
|
||||||
display = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
amount[DefaultValue] = initialAmount ?? 0;
|
amount[DefaultValue] = initialAmount ?? 0;
|
||||||
|
|
|
@ -10,9 +10,16 @@ import {
|
||||||
requirementsMet
|
requirementsMet
|
||||||
} from "game/requirements";
|
} from "game/requirements";
|
||||||
import { isFunction } from "util/common";
|
import { isFunction } from "util/common";
|
||||||
import { processGetter } from "util/computed";
|
import { MaybeGetter, processGetter } from "util/computed";
|
||||||
import { createLazyProxy } from "util/proxies";
|
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 type { MaybeRef, MaybeRefOrGetter, Ref } from "vue";
|
||||||
import { computed, unref } from "vue";
|
import { computed, unref } from "vue";
|
||||||
import Clickable from "./Clickable.vue";
|
import Clickable from "./Clickable.vue";
|
||||||
|
@ -27,14 +34,15 @@ export const UpgradeType = Symbol("Upgrade");
|
||||||
export interface UpgradeOptions extends VueFeatureOptions, ClickableOptions {
|
export interface UpgradeOptions extends VueFeatureOptions, ClickableOptions {
|
||||||
/** The display to use for this upgrade. */
|
/** The display to use for this upgrade. */
|
||||||
display?:
|
display?:
|
||||||
| MaybeRefOrGetter<Renderable>
|
| Renderable
|
||||||
|
| (() => Renderable)
|
||||||
| {
|
| {
|
||||||
/** A header to appear at the top of the display. */
|
/** A header to appear at the top of the display. */
|
||||||
title?: MaybeRefOrGetter<Renderable>;
|
title?: MaybeGetter<Renderable>;
|
||||||
/** The main text that appears in the display. */
|
/** 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. */
|
/** 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. */
|
/** The requirements to purchase this upgrade. */
|
||||||
requirements: Requirements;
|
requirements: Requirements;
|
||||||
|
@ -47,7 +55,7 @@ export interface Upgrade extends VueFeature {
|
||||||
/** The requirements to purchase this upgrade. */
|
/** The requirements to purchase this upgrade. */
|
||||||
requirements: Requirements;
|
requirements: Requirements;
|
||||||
/** The display to use for this upgrade. */
|
/** The display to use for this upgrade. */
|
||||||
display?: MaybeRef<Renderable>;
|
display?: MaybeGetter<Renderable>;
|
||||||
/** Whether or not this upgrade has been purchased. */
|
/** Whether or not this upgrade has been purchased. */
|
||||||
bought: Persistent<boolean>;
|
bought: Persistent<boolean>;
|
||||||
/** Whether or not the upgrade can currently be purchased. */
|
/** 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));
|
requirements.push(createVisibilityRequirement(vueFeature.visibility));
|
||||||
}
|
}
|
||||||
|
|
||||||
let display: MaybeRef<Renderable> | undefined = undefined;
|
let display;
|
||||||
if (typeof _display === "object" && "description" in _display) {
|
if (typeof _display === "object" && !isJSXElement(_display)) {
|
||||||
const title = processGetter(_display.title);
|
const { title, description, effectDisplay } = _display;
|
||||||
const description = processGetter(_display.description);
|
|
||||||
const effectDisplay = processGetter(_display.effectDisplay);
|
|
||||||
|
|
||||||
const Title = () => (title == null ? <></> : render(title, el => <h3>{el}</h3>));
|
display = () => (
|
||||||
const Description = () => render(description, el => <div>{el}</div>);
|
|
||||||
const EffectDisplay = () =>
|
|
||||||
effectDisplay == null ? <></> : render(effectDisplay, el => <>{el}</>);
|
|
||||||
|
|
||||||
display = computed(() => (
|
|
||||||
<span>
|
<span>
|
||||||
{title != null ? (
|
{title != null ? (
|
||||||
<div>
|
<div>
|
||||||
<Title />
|
{render(title, el => (
|
||||||
</div>
|
<h3>{el}</h3>
|
||||||
) : null}
|
))}
|
||||||
<Description />
|
|
||||||
{effectDisplay != null ? (
|
|
||||||
<div>
|
|
||||||
Currently: <EffectDisplay />
|
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
{render(description, el => (
|
||||||
|
<div>{el}</div>
|
||||||
|
))}
|
||||||
|
{effectDisplay != null ? <div>Currently: {render(effectDisplay)}</div> : null}
|
||||||
{bought.value ? null : (
|
{bought.value ? null : (
|
||||||
<>
|
<>
|
||||||
<br />
|
<br />
|
||||||
|
@ -123,9 +124,9 @@ export function createUpgrade<T extends UpgradeOptions>(optionsFunc: () => T) {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
));
|
);
|
||||||
} else if (_display != null) {
|
} else if (_display != null) {
|
||||||
display = processGetter(_display);
|
display = _display;
|
||||||
}
|
}
|
||||||
|
|
||||||
const upgrade = {
|
const upgrade = {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import type { BaseLayer } from "game/layers";
|
||||||
import { createBooleanRequirement } from "game/requirements";
|
import { createBooleanRequirement } from "game/requirements";
|
||||||
import type { DecimalSource } from "util/bignum";
|
import type { DecimalSource } from "util/bignum";
|
||||||
import Decimal 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 { createLazyProxy } from "util/proxies";
|
||||||
import { Renderable } from "util/vue";
|
import { Renderable } from "util/vue";
|
||||||
import { computed, MaybeRef, MaybeRefOrGetter, unref } from "vue";
|
import { computed, MaybeRef, MaybeRefOrGetter, unref } from "vue";
|
||||||
|
@ -310,7 +310,7 @@ export function setupPassiveGeneration(
|
||||||
export function createCanConvertRequirement(
|
export function createCanConvertRequirement(
|
||||||
conversion: Conversion,
|
conversion: Conversion,
|
||||||
minGainAmount: MaybeRefOrGetter<DecimalSource> = 1,
|
minGainAmount: MaybeRefOrGetter<DecimalSource> = 1,
|
||||||
display?: MaybeRefOrGetter<Renderable>
|
display?: MaybeGetter<Renderable>
|
||||||
) {
|
) {
|
||||||
const computedMinGainAmount = processGetter(minGainAmount);
|
const computedMinGainAmount = processGetter(minGainAmount);
|
||||||
return createBooleanRequirement(
|
return createBooleanRequirement(
|
||||||
|
|
|
@ -47,9 +47,12 @@ export function findFeatures(obj: object, ...types: symbol[]): unknown[] {
|
||||||
const handleObject = (obj: object) => {
|
const handleObject = (obj: object) => {
|
||||||
Object.keys(obj).forEach(key => {
|
Object.keys(obj).forEach(key => {
|
||||||
const value: unknown = obj[key as keyof typeof obj];
|
const value: unknown = obj[key as keyof typeof obj];
|
||||||
if (value != null && typeof value === "object") {
|
if (
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
value != null &&
|
||||||
if (types.includes((value as Record<string, any>).type)) {
|
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);
|
objects.push(value);
|
||||||
} else if (!(value instanceof Decimal) && !isRef(value)) {
|
} else if (!(value instanceof Decimal) && !isRef(value)) {
|
||||||
handleObject(value as Record<string, unknown>);
|
handleObject(value as Record<string, unknown>);
|
||||||
|
@ -66,7 +69,7 @@ export function getFirstFeature<T extends VueFeature>(
|
||||||
filter: (feature: T) => boolean
|
filter: (feature: T) => boolean
|
||||||
): {
|
): {
|
||||||
firstFeature: Ref<T | undefined>;
|
firstFeature: Ref<T | undefined>;
|
||||||
collapsedContent: MaybeRef<Renderable>;
|
collapsedContent: () => Renderable;
|
||||||
hasCollapsedContent: Ref<boolean>;
|
hasCollapsedContent: Ref<boolean>;
|
||||||
} {
|
} {
|
||||||
const filteredFeatures = computed(() =>
|
const filteredFeatures = computed(() =>
|
||||||
|
@ -74,7 +77,7 @@ export function getFirstFeature<T extends VueFeature>(
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
firstFeature: computed(() => filteredFeatures.value[0]),
|
firstFeature: computed(() => filteredFeatures.value[0]),
|
||||||
collapsedContent: computed(() => renderCol(...filteredFeatures.value.slice(1))),
|
collapsedContent: () => renderCol(...filteredFeatures.value.slice(1)),
|
||||||
hasCollapsedContent: computed(() => filteredFeatures.value.length > 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>) => {
|
const handleObject = (obj: Record<string, unknown>) => {
|
||||||
Object.keys(obj).forEach(key => {
|
Object.keys(obj).forEach(key => {
|
||||||
const value = obj[key];
|
const value = obj[key];
|
||||||
if (value != null && typeof value === "object") {
|
|
||||||
if (
|
if (
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
value != null &&
|
||||||
typeof (value as Record<string, any>).type === "symbol" &&
|
typeof value === "object" &&
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
(value as Record<string, unknown>).__v_isVNode !== true
|
||||||
!types.includes((value as Record<string, any>).type)
|
|
||||||
) {
|
) {
|
||||||
|
const type = (value as Record<string, unknown>).type;
|
||||||
|
if (typeof type === "symbol" && !types.includes(type)) {
|
||||||
objects.push(value);
|
objects.push(value);
|
||||||
} else if (!(value instanceof Decimal) && !isRef(value)) {
|
} else if (!(value instanceof Decimal) && !isRef(value)) {
|
||||||
handleObject(value as Record<string, unknown>);
|
handleObject(value as Record<string, unknown>);
|
||||||
|
|
|
@ -1,16 +1,23 @@
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* 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 { getUniqueID, Visibility } from "features/feature";
|
||||||
import type { Persistent, State } from "game/persistence";
|
import type { Persistent, State } from "game/persistence";
|
||||||
import { persistent } from "game/persistence";
|
import { persistent } from "game/persistence";
|
||||||
import { isFunction } from "util/common";
|
import { isFunction } from "util/common";
|
||||||
import { processGetter } from "util/computed";
|
import { MaybeGetter, processGetter } from "util/computed";
|
||||||
import { createLazyProxy } from "util/proxies";
|
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 type { CSSProperties, MaybeRef, MaybeRefOrGetter, Ref } from "vue";
|
||||||
import { computed, isRef, unref } from "vue";
|
import { computed, unref } from "vue";
|
||||||
import Column from "components/layout/Column.vue";
|
|
||||||
import Row from "components/layout/Row.vue";
|
|
||||||
import Clickable from "features/clickables/Clickable.vue";
|
|
||||||
|
|
||||||
/** A symbol used to identify {@link Grid} features. */
|
/** A symbol used to identify {@link Grid} features. */
|
||||||
export const GridType = Symbol("Grid");
|
export const GridType = Symbol("Grid");
|
||||||
|
@ -39,7 +46,7 @@ export interface GridCell extends VueFeature {
|
||||||
/** The persistent state of this cell. */
|
/** The persistent state of this cell. */
|
||||||
state: State;
|
state: State;
|
||||||
/** The main text that appears in the display. */
|
/** The main text that appears in the display. */
|
||||||
display: MaybeRef<Renderable>;
|
display: MaybeGetter<Renderable>;
|
||||||
/** A function that is called when the cell is clicked. */
|
/** A function that is called when the cell is clicked. */
|
||||||
onClick?: (e?: MouseEvent | TouchEvent) => void;
|
onClick?: (e?: MouseEvent | TouchEvent) => void;
|
||||||
/** A function that is called when the cell is held down. */
|
/** 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. */
|
/** A getter for the CSS classes for a cell. */
|
||||||
getClasses?: CellMaybeRefOrGetter<Record<string, boolean>>;
|
getClasses?: CellMaybeRefOrGetter<Record<string, boolean>>;
|
||||||
/** A getter for the display component for a cell. */
|
/** A getter for the display component for a cell. */
|
||||||
getDisplay: CellMaybeRefOrGetter<Renderable> | {
|
getDisplay:
|
||||||
getTitle?: CellMaybeRefOrGetter<Renderable>;
|
| Renderable
|
||||||
getDescription: CellMaybeRefOrGetter<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. */
|
/** A function that is called when a cell is clicked. */
|
||||||
onClick?: (row: number, col: number, state: State, e?: MouseEvent | TouchEvent) => void;
|
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. */
|
/** A getter for the CSS classes for a cell. */
|
||||||
getClasses?: ProcessedCellRefOrGetter<Record<string, boolean>>;
|
getClasses?: ProcessedCellRefOrGetter<Record<string, boolean>>;
|
||||||
/** A getter for the display component for a cell. */
|
/** 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. */
|
/** 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;
|
getID: (row: number, col: number, state: State) => string;
|
||||||
/** Get the persistent state of the given cell. */
|
/** 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);
|
return grid.getState(row, col);
|
||||||
}
|
}
|
||||||
case "id":
|
case "id":
|
||||||
return target.id = target.id ?? getUniqueID("gridcell");
|
return (target.id = target.id ?? getUniqueID("gridcell"));
|
||||||
case "components":
|
case "components":
|
||||||
return [
|
return [
|
||||||
computed(() => (
|
computed(() => (
|
||||||
|
@ -264,12 +274,10 @@ function getCellHandler(grid: Grid, row: number, col: number): GridCell {
|
||||||
return (grid as any)[key];
|
return (grid as any)[key];
|
||||||
},
|
},
|
||||||
set(target, key, value) {
|
set(target, key, value) {
|
||||||
console.log("!!?", key, value)
|
|
||||||
if (typeof key !== "string") {
|
if (typeof key !== "string") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
key = `set${key.slice(0, 1).toUpperCase() + key.slice(1)}`;
|
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) {
|
if (key in grid && isFunction((grid as any)[key]) && (grid as any)[key].length <= 3) {
|
||||||
(grid as any)[key].call(grid, row, col, value);
|
(grid as any)[key].call(grid, row, col, value);
|
||||||
return true;
|
return true;
|
||||||
|
@ -334,20 +342,23 @@ export function createGrid<T extends GridOptions>(optionsFunc: () => T) {
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
let getDisplay;
|
let getDisplay;
|
||||||
if (typeof _getDisplay === "object" && !isRef(_getDisplay) && !isJSXElement(_getDisplay)) {
|
if (typeof _getDisplay === "object" && !isJSXElement(_getDisplay)) {
|
||||||
const { getTitle, getDescription } = _getDisplay;
|
const { getTitle, getDescription } = _getDisplay;
|
||||||
const getProcessedTitle = convertCellMaybeRefOrGetter(getTitle);
|
|
||||||
const getProcessedDescription = convertCellMaybeRefOrGetter(getDescription);
|
|
||||||
getDisplay = function (row: number, col: number, state: State) {
|
getDisplay = function (row: number, col: number, state: State) {
|
||||||
const title = typeof getProcessedTitle === "function" ? getProcessedTitle(row, col, state) : unref(getProcessedTitle);
|
const title = typeof getTitle === "function" ? getTitle(row, col, state) : getTitle;
|
||||||
const description = typeof getProcessedDescription === "function" ? getProcessedDescription(row, col, state) : unref(getProcessedDescription);
|
const description =
|
||||||
return <>
|
typeof getDescription === "function"
|
||||||
|
? getDescription(row, col, state)
|
||||||
|
: getDescription;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
{title}
|
{title}
|
||||||
{description}
|
{description}
|
||||||
</>;
|
</>
|
||||||
}
|
);
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
getDisplay = convertCellMaybeRefOrGetter(_getDisplay);
|
getDisplay = _getDisplay;
|
||||||
}
|
}
|
||||||
|
|
||||||
const grid = {
|
const grid = {
|
||||||
|
@ -357,10 +368,13 @@ export function createGrid<T extends GridOptions>(optionsFunc: () => T) {
|
||||||
<Column>
|
<Column>
|
||||||
{new Array(unref(grid.rows)).fill(0).map((_, row) => (
|
{new Array(unref(grid.rows)).fill(0).map((_, row) => (
|
||||||
<Row>
|
<Row>
|
||||||
{new Array(unref(grid.cols)).fill(0).map((_, col) =>
|
{new Array(unref(grid.cols))
|
||||||
render(grid.cells[row][col]))}
|
.fill(0)
|
||||||
</Row>))}
|
.map((_, col) => render(grid.cells[row][col]))}
|
||||||
</Column>)),
|
</Row>
|
||||||
|
))}
|
||||||
|
</Column>
|
||||||
|
)),
|
||||||
cellState,
|
cellState,
|
||||||
cells: new Proxy({} as GridCell[][], {
|
cells: new Proxy({} as GridCell[][], {
|
||||||
get(target, key: PropertyKey) {
|
get(target, key: PropertyKey) {
|
||||||
|
@ -422,8 +436,10 @@ export function createGrid<T extends GridOptions>(optionsFunc: () => T) {
|
||||||
cols: processGetter(cols),
|
cols: processGetter(cols),
|
||||||
getVisibility: convertCellMaybeRefOrGetter(getVisibility ?? true),
|
getVisibility: convertCellMaybeRefOrGetter(getVisibility ?? true),
|
||||||
getCanClick: convertCellMaybeRefOrGetter(getCanClick ?? true),
|
getCanClick: convertCellMaybeRefOrGetter(getCanClick ?? true),
|
||||||
getStartState: typeof getStartState === "function" && getStartState.length > 0 ?
|
getStartState:
|
||||||
getStartState : processGetter(getStartState),
|
typeof getStartState === "function" && getStartState.length > 0
|
||||||
|
? getStartState
|
||||||
|
: processGetter(getStartState),
|
||||||
getStyle: convertCellMaybeRefOrGetter(getStyle),
|
getStyle: convertCellMaybeRefOrGetter(getStyle),
|
||||||
getClasses: convertCellMaybeRefOrGetter(getClasses),
|
getClasses: convertCellMaybeRefOrGetter(getClasses),
|
||||||
getDisplay,
|
getDisplay,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import Infobox from "features/infoboxes/Infobox.vue";
|
import Infobox from "features/infoboxes/Infobox.vue";
|
||||||
import type { Persistent } from "game/persistence";
|
import type { Persistent } from "game/persistence";
|
||||||
import { 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 { createLazyProxy } from "util/proxies";
|
||||||
import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
||||||
import { CSSProperties, MaybeRef, MaybeRefOrGetter } from "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. */
|
/** CSS to apply to the body of the infobox. */
|
||||||
bodyStyle?: MaybeRefOrGetter<CSSProperties>;
|
bodyStyle?: MaybeRefOrGetter<CSSProperties>;
|
||||||
/** A header to appear at the top of the display. */
|
/** A header to appear at the top of the display. */
|
||||||
title: MaybeRefOrGetter<Renderable>;
|
title: MaybeGetter<Renderable>;
|
||||||
/** The main text that appears in the display. */
|
/** 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. */
|
/** 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. */
|
/** CSS to apply to the body of the infobox. */
|
||||||
bodyStyle?: MaybeRef<CSSProperties>;
|
bodyStyle?: MaybeRef<CSSProperties>;
|
||||||
/** A header to appear at the top of the display. */
|
/** A header to appear at the top of the display. */
|
||||||
title: MaybeRef<Renderable>;
|
title: MaybeGetter<Renderable>;
|
||||||
/** The main text that appears in the display. */
|
/** The main text that appears in the display. */
|
||||||
display: MaybeRef<Renderable>;
|
display: MaybeGetter<Renderable>;
|
||||||
/** Whether or not this infobox is collapsed. */
|
/** Whether or not this infobox is collapsed. */
|
||||||
collapsed: Persistent<boolean>;
|
collapsed: Persistent<boolean>;
|
||||||
/** A symbol that helps identify features of the same type. */
|
/** 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",
|
color: processGetter(color) ?? "--layer-color",
|
||||||
titleStyle: processGetter(titleStyle),
|
titleStyle: processGetter(titleStyle),
|
||||||
bodyStyle: processGetter(bodyStyle),
|
bodyStyle: processGetter(bodyStyle),
|
||||||
title: processGetter(title),
|
title,
|
||||||
display: processGetter(display)
|
display
|
||||||
} satisfies Infobox;
|
} satisfies Infobox;
|
||||||
|
|
||||||
return infobox;
|
return infobox;
|
||||||
|
|
|
@ -41,6 +41,7 @@ onMounted(() => {
|
||||||
});
|
});
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
app.value?.destroy();
|
app.value?.destroy();
|
||||||
|
app.value = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
let isDirty = true;
|
let isDirty = true;
|
||||||
|
|
|
@ -3,8 +3,9 @@ import type { EmitterConfigV3 } from "@pixi/particle-emitter";
|
||||||
import { Emitter, upgradeConfig } from "@pixi/particle-emitter";
|
import { Emitter, upgradeConfig } from "@pixi/particle-emitter";
|
||||||
import { createLazyProxy } from "util/proxies";
|
import { createLazyProxy } from "util/proxies";
|
||||||
import { VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
import { VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
||||||
import { Ref, shallowRef } from "vue";
|
import { Ref, shallowRef, unref } from "vue";
|
||||||
import Particles from "./Particles.vue";
|
import Particles from "./Particles.vue";
|
||||||
|
import { processGetter } from "util/computed";
|
||||||
|
|
||||||
/** A symbol used to identify {@link Particles} features. */
|
/** A symbol used to identify {@link Particles} features. */
|
||||||
export const ParticlesType = Symbol("Particles");
|
export const ParticlesType = Symbol("Particles");
|
||||||
|
@ -47,7 +48,10 @@ export interface Particles extends VueFeature {
|
||||||
export function createParticles<T extends ParticlesOptions>(optionsFunc?: () => T) {
|
export function createParticles<T extends ParticlesOptions>(optionsFunc?: () => T) {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const options = optionsFunc?.() ?? ({} as T);
|
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: {
|
let emittersToAdd: {
|
||||||
resolve: (value: Emitter | PromiseLike<Emitter>) => void;
|
resolve: (value: Emitter | PromiseLike<Emitter>) => void;
|
||||||
|
@ -57,6 +61,7 @@ export function createParticles<T extends ParticlesOptions>(optionsFunc?: () =>
|
||||||
function onInit(app: Application) {
|
function onInit(app: Application) {
|
||||||
emittersToAdd.forEach(({ resolve, config }) => resolve(new Emitter(app.stage, config)));
|
emittersToAdd.forEach(({ resolve, config }) => resolve(new Emitter(app.stage, config)));
|
||||||
emittersToAdd = [];
|
emittersToAdd = [];
|
||||||
|
particles.app.value = app;
|
||||||
}
|
}
|
||||||
|
|
||||||
const particles = {
|
const particles = {
|
||||||
|
|
|
@ -19,15 +19,16 @@ import Sticky from "components/layout/Sticky.vue";
|
||||||
import type { Resource } from "features/resources/resource";
|
import type { Resource } from "features/resources/resource";
|
||||||
import ResourceVue from "features/resources/Resource.vue";
|
import ResourceVue from "features/resources/Resource.vue";
|
||||||
import Decimal from "util/bignum";
|
import Decimal from "util/bignum";
|
||||||
|
import { MaybeGetter } from "util/computed";
|
||||||
import { Renderable } from "util/vue";
|
import { Renderable } from "util/vue";
|
||||||
import { computed, MaybeRefOrGetter, ref, StyleValue, toValue } from "vue";
|
import { computed, ref, StyleValue, toValue } from "vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
resource: Resource;
|
resource: Resource;
|
||||||
color?: string;
|
color?: string;
|
||||||
classes?: Record<string, boolean>;
|
classes?: Record<string, boolean>;
|
||||||
style?: StyleValue;
|
style?: StyleValue;
|
||||||
effectDisplay?: MaybeRefOrGetter<Renderable>;
|
effectDisplay?: MaybeGetter<Renderable>;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const displayRef = ref<Element | null>(null);
|
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 { createLazyProxy } from "util/proxies";
|
||||||
import { render, Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
||||||
import { MaybeRef, MaybeRefOrGetter } from "vue";
|
|
||||||
import { JSX } from "vue/jsx-runtime";
|
|
||||||
|
|
||||||
/** A symbol used to identify {@link Tab} features. */
|
/** A symbol used to identify {@link Tab} features. */
|
||||||
export const TabType = Symbol("Tab");
|
export const TabType = Symbol("Tab");
|
||||||
|
@ -12,7 +10,7 @@ export const TabType = Symbol("Tab");
|
||||||
*/
|
*/
|
||||||
export interface TabOptions extends VueFeatureOptions {
|
export interface TabOptions extends VueFeatureOptions {
|
||||||
/** The display to use for this tab. */
|
/** 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 {
|
export interface Tab extends VueFeature {
|
||||||
/** The display to use for this tab. */
|
/** The display to use for this tab. */
|
||||||
display: MaybeRef<Renderable>;
|
display: MaybeGetter<Renderable>;
|
||||||
/** A symbol that helps identify features of the same type. */
|
/** A symbol that helps identify features of the same type. */
|
||||||
type: typeof TabType;
|
type: typeof TabType;
|
||||||
}
|
}
|
||||||
|
@ -38,8 +36,8 @@ export function createTab<T extends TabOptions>(optionsFunc: () => T) {
|
||||||
const tab = {
|
const tab = {
|
||||||
type: TabType,
|
type: TabType,
|
||||||
...(props as Omit<typeof props, keyof VueFeature | keyof TabOptions>),
|
...(props as Omit<typeof props, keyof VueFeature | keyof TabOptions>),
|
||||||
...vueFeatureMixin("tab", options, (): JSX.Element => render(tab.display)),
|
...vueFeatureMixin("tab", options, display),
|
||||||
display: processGetter(display)
|
display
|
||||||
} satisfies Tab;
|
} satisfies Tab;
|
||||||
|
|
||||||
return tab;
|
return tab;
|
||||||
|
|
|
@ -4,7 +4,7 @@ import TabButton from "features/tabs/TabButton.vue";
|
||||||
import TabFamily from "features/tabs/TabFamily.vue";
|
import TabFamily from "features/tabs/TabFamily.vue";
|
||||||
import type { Persistent } from "game/persistence";
|
import type { Persistent } from "game/persistence";
|
||||||
import { 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 { createLazyProxy } from "util/proxies";
|
||||||
import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
||||||
import type { CSSProperties, MaybeRef, MaybeRefOrGetter, Ref } from "vue";
|
import type { CSSProperties, MaybeRef, MaybeRefOrGetter, Ref } from "vue";
|
||||||
|
@ -20,9 +20,9 @@ export const TabFamilyType = Symbol("TabFamily");
|
||||||
*/
|
*/
|
||||||
export interface TabButtonOptions extends VueFeatureOptions {
|
export interface TabButtonOptions extends VueFeatureOptions {
|
||||||
/** The tab to display when this button is clicked. */
|
/** The tab to display when this button is clicked. */
|
||||||
tab: Tab | MaybeRefOrGetter<Renderable>;
|
tab: Tab | MaybeGetter<Renderable>;
|
||||||
/** The label on this button. */
|
/** The label on this button. */
|
||||||
display: MaybeRefOrGetter<Renderable>;
|
display: MaybeGetter<Renderable>;
|
||||||
/** The color of the glow effect to display when this button is active. */
|
/** The color of the glow effect to display when this button is active. */
|
||||||
glowColor?: MaybeRefOrGetter<string>;
|
glowColor?: MaybeRefOrGetter<string>;
|
||||||
}
|
}
|
||||||
|
@ -33,9 +33,9 @@ export interface TabButtonOptions extends VueFeatureOptions {
|
||||||
*/
|
*/
|
||||||
export interface TabButton extends VueFeature {
|
export interface TabButton extends VueFeature {
|
||||||
/** The tab to display when this button is clicked. */
|
/** The tab to display when this button is clicked. */
|
||||||
tab: Tab | MaybeRef<Renderable>;
|
tab: Tab | MaybeGetter<Renderable>;
|
||||||
/** The label on this button. */
|
/** The label on this button. */
|
||||||
display: MaybeRef<Renderable>;
|
display: MaybeGetter<Renderable>;
|
||||||
/** The color of the glow effect to display when this button is active. */
|
/** The color of the glow effect to display when this button is active. */
|
||||||
glowColor?: MaybeRef<string>;
|
glowColor?: MaybeRef<string>;
|
||||||
/** A symbol that helps identify features of the same type. */
|
/** 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. */
|
/** All the tabs within this family. */
|
||||||
tabs: Record<string, TabButton>;
|
tabs: Record<string, TabButton>;
|
||||||
/** The currently active tab, if any. */
|
/** 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. */
|
/** The name of the tab that is currently active. */
|
||||||
selected: Persistent<string>;
|
selected: Persistent<string>;
|
||||||
/** A symbol that helps identify features of the same type. */
|
/** A symbol that helps identify features of the same type. */
|
||||||
|
@ -106,16 +106,17 @@ export function createTabFamily<T extends TabFamilyOptions>(
|
||||||
const tabButton = {
|
const tabButton = {
|
||||||
type: TabButtonType,
|
type: TabButtonType,
|
||||||
...(props as Omit<typeof props, keyof VueFeature | keyof TabButtonOptions>),
|
...(props as Omit<typeof props, keyof VueFeature | keyof TabButtonOptions>),
|
||||||
...vueFeatureMixin("tabButton", options, () =>
|
...vueFeatureMixin("tabButton", options, () => (
|
||||||
<TabButton
|
<TabButton
|
||||||
display={tabButton.display}
|
display={tabButton.display}
|
||||||
glowColor={tabButton.glowColor}
|
glowColor={tabButton.glowColor}
|
||||||
active={unref(tabButton.tab) === unref(tabFamily.activeTab)}
|
active={unref(tabButton.tab) === unref(tabFamily.activeTab)}
|
||||||
onSelectTab={() => tabFamily.selected.value = tab}
|
onSelectTab={() => (tabFamily.selected.value = tab)}
|
||||||
/>),
|
/>
|
||||||
tab: processGetter(buttonTab),
|
)),
|
||||||
|
tab: buttonTab,
|
||||||
glowColor: processGetter(glowColor),
|
glowColor: processGetter(glowColor),
|
||||||
display: processGetter(display)
|
display
|
||||||
} satisfies TabButton;
|
} satisfies TabButton;
|
||||||
|
|
||||||
parsedTabs[tab] = tabButton;
|
parsedTabs[tab] = tabButton;
|
||||||
|
@ -124,7 +125,7 @@ export function createTabFamily<T extends TabFamilyOptions>(
|
||||||
buttonContainerClasses: processGetter(buttonContainerClasses),
|
buttonContainerClasses: processGetter(buttonContainerClasses),
|
||||||
buttonContainerStyle: processGetter(buttonContainerStyle),
|
buttonContainerStyle: processGetter(buttonContainerStyle),
|
||||||
selected,
|
selected,
|
||||||
activeTab: computed((): Tab | MaybeRef<Renderable> | null => {
|
activeTab: computed((): Tab | MaybeGetter<Renderable> | null => {
|
||||||
if (
|
if (
|
||||||
selected.value in tabFamily.tabs &&
|
selected.value in tabFamily.tabs &&
|
||||||
isVisible(tabFamily.tabs[selected.value].visibility ?? true)
|
isVisible(tabFamily.tabs[selected.value].visibility ?? true)
|
||||||
|
|
|
@ -7,11 +7,11 @@ import TreeNode from "features/trees/TreeNode.vue";
|
||||||
import { noPersist } from "game/persistence";
|
import { noPersist } from "game/persistence";
|
||||||
import type { DecimalSource } from "util/bignum";
|
import type { DecimalSource } from "util/bignum";
|
||||||
import Decimal, { format, formatWhole } 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 { createLazyProxy } from "util/proxies";
|
||||||
import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
|
||||||
import type { MaybeRef, MaybeRefOrGetter, Ref } from "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. */
|
/** A symbol used to identify {@link TreeNode} features. */
|
||||||
export const TreeNodeType = Symbol("TreeNode");
|
export const TreeNodeType = Symbol("TreeNode");
|
||||||
|
@ -27,7 +27,7 @@ export interface TreeNodeOptions extends VueFeatureOptions {
|
||||||
/** The background color for this node. */
|
/** The background color for this node. */
|
||||||
color?: MaybeRefOrGetter<string>;
|
color?: MaybeRefOrGetter<string>;
|
||||||
/** The label to display on this tree node. */
|
/** 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. */
|
/** The color of the glow effect shown to notify the user there's something to do with this node. */
|
||||||
glowColor?: MaybeRefOrGetter<string>;
|
glowColor?: MaybeRefOrGetter<string>;
|
||||||
/** A reset object attached to this node, used for propagating resets through the tree. */
|
/** 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. */
|
/** The background color for this node. */
|
||||||
color?: MaybeRef<string>;
|
color?: MaybeRef<string>;
|
||||||
/** The label to display on this tree node. */
|
/** 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. */
|
/** The color of the glow effect shown to notify the user there's something to do with this node. */
|
||||||
glowColor?: MaybeRef<string>;
|
glowColor?: MaybeRef<string>;
|
||||||
/** A reset object attached to this node, used for propagating resets through the tree. */
|
/** 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,
|
canClick: processGetter(canClick) ?? true,
|
||||||
color: processGetter(color),
|
color: processGetter(color),
|
||||||
display: processGetter(display),
|
display,
|
||||||
glowColor: processGetter(glowColor),
|
glowColor: processGetter(glowColor),
|
||||||
onClick:
|
onClick:
|
||||||
onClick == null
|
onClick == null
|
||||||
|
@ -265,9 +265,9 @@ export function createResourceTooltip(
|
||||||
resource: Resource,
|
resource: Resource,
|
||||||
requiredResource: Resource | null = null,
|
requiredResource: Resource | null = null,
|
||||||
requirement: MaybeRefOrGetter<DecimalSource> = 0
|
requirement: MaybeRefOrGetter<DecimalSource> = 0
|
||||||
): Ref<string> {
|
): () => string {
|
||||||
const req = processGetter(requirement);
|
const req = processGetter(requirement);
|
||||||
return computed(() => {
|
return () => {
|
||||||
if (requiredResource == null || Decimal.gte(resource.value, unref(req))) {
|
if (requiredResource == null || Decimal.gte(resource.value, unref(req))) {
|
||||||
return displayResource(resource) + " " + resource.displayName;
|
return displayResource(resource) + " " + resource.displayName;
|
||||||
}
|
}
|
||||||
|
@ -280,5 +280,5 @@ export function createResourceTooltip(
|
||||||
? formatWhole(requiredResource.value)
|
? formatWhole(requiredResource.value)
|
||||||
: format(requiredResource.value, requiredResource.precision)
|
: 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 position = persistent<NodePosition>({ x: 0, y: 0 });
|
||||||
const draggable = createLazyProxy(() => {
|
const draggable = createLazyProxy(() => {
|
||||||
const options = optionsFunc();
|
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];
|
position[DefaultValue] = initialPosition ?? position[DefaultValue];
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,9 @@ import { persistent } from "game/persistence";
|
||||||
import player from "game/player";
|
import player from "game/player";
|
||||||
import type { Emitter } from "nanoevents";
|
import type { Emitter } from "nanoevents";
|
||||||
import { createNanoEvents } from "nanoevents";
|
import { createNanoEvents } from "nanoevents";
|
||||||
import { processGetter } from "util/computed";
|
import { MaybeGetter, processGetter } from "util/computed";
|
||||||
import { createLazyProxy } from "util/proxies";
|
import { createLazyProxy } from "util/proxies";
|
||||||
import { render, Renderable } from "util/vue";
|
import { Renderable } from "util/vue";
|
||||||
import {
|
import {
|
||||||
computed,
|
computed,
|
||||||
type CSSProperties,
|
type CSSProperties,
|
||||||
|
@ -108,7 +108,7 @@ export interface LayerOptions {
|
||||||
* The layout of this layer's features.
|
* 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.
|
* 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. */
|
/** An object of classes that should be applied to the display. */
|
||||||
classes?: MaybeRefOrGetter<Record<string, boolean>>;
|
classes?: MaybeRefOrGetter<Record<string, boolean>>;
|
||||||
/** Styles that should be applied to the display. */
|
/** Styles that should be applied to the display. */
|
||||||
|
@ -127,7 +127,7 @@ export interface LayerOptions {
|
||||||
* The layout of this layer's features.
|
* 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.
|
* 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.
|
* 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}.
|
* 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.
|
* 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.
|
* 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. */
|
/** An object of classes that should be applied to the display. */
|
||||||
classes?: MaybeRef<Record<string, boolean>>;
|
classes?: MaybeRef<Record<string, boolean>>;
|
||||||
/** Styles that should be applied to the display. */
|
/** Styles that should be applied to the display. */
|
||||||
|
@ -188,7 +188,7 @@ export interface Layer extends BaseLayer {
|
||||||
* The layout of this layer's features.
|
* 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.
|
* 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.
|
* 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}.
|
* If true, go back will be hidden regardless of {@link data/projInfo.allowGoBack}.
|
||||||
|
@ -261,7 +261,7 @@ export function createLayer<T extends LayerOptions>(
|
||||||
...baseLayer,
|
...baseLayer,
|
||||||
...(props as Omit<typeof props, keyof LayerOptions>),
|
...(props as Omit<typeof props, keyof LayerOptions>),
|
||||||
color: processGetter(color),
|
color: processGetter(color),
|
||||||
display: processGetter(display),
|
display,
|
||||||
classes: processGetter(classes),
|
classes: processGetter(classes),
|
||||||
style: computed((): CSSProperties => {
|
style: computed((): CSSProperties => {
|
||||||
let width = unref(layer.minWidth);
|
let width = unref(layer.minWidth);
|
||||||
|
@ -293,7 +293,7 @@ export function createLayer<T extends LayerOptions>(
|
||||||
forceHideGoBack: processGetter(forceHideGoBack),
|
forceHideGoBack: processGetter(forceHideGoBack),
|
||||||
minWidth: processGetter(minWidth) ?? 600,
|
minWidth: processGetter(minWidth) ?? 600,
|
||||||
minimizable: processGetter(minimizable) ?? true,
|
minimizable: processGetter(minimizable) ?? true,
|
||||||
minimizedDisplay: processGetter(minimizedDisplay)
|
minimizedDisplay
|
||||||
} satisfies Layer;
|
} satisfies Layer;
|
||||||
|
|
||||||
return layer;
|
return layer;
|
||||||
|
@ -370,21 +370,21 @@ export function reloadLayer(layer: Layer): void {
|
||||||
*/
|
*/
|
||||||
export function setupLayerModal(layer: Layer): {
|
export function setupLayerModal(layer: Layer): {
|
||||||
openModal: VoidFunction;
|
openModal: VoidFunction;
|
||||||
modal: Ref<JSX.Element>;
|
modal: () => JSX.Element;
|
||||||
} {
|
} {
|
||||||
const showModal = ref(false);
|
const showModal = ref(false);
|
||||||
return {
|
return {
|
||||||
openModal: () => (showModal.value = true),
|
openModal: () => (showModal.value = true),
|
||||||
modal: computed(() => (
|
modal: () => (
|
||||||
<Modal
|
<Modal
|
||||||
modelValue={showModal.value}
|
modelValue={showModal.value}
|
||||||
onUpdate:modelValue={value => (showModal.value = value)}
|
onUpdate:modelValue={value => (showModal.value = value)}
|
||||||
v-slots={{
|
v-slots={{
|
||||||
header: () => <h2>{unref(layer.name)}</h2>,
|
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 type { DecimalSource } from "util/bignum";
|
||||||
import Decimal, { formatSmall } from "util/bignum";
|
import Decimal, { formatSmall } from "util/bignum";
|
||||||
import type { RequiredKeys, WithRequired } from "util/common";
|
import type { RequiredKeys, WithRequired } from "util/common";
|
||||||
import { processGetter } from "util/computed";
|
import { MaybeGetter, processGetter } from "util/computed";
|
||||||
import { createLazyProxy } from "util/proxies";
|
import { createLazyProxy } from "util/proxies";
|
||||||
import { render, Renderable } from "util/vue";
|
import { render, Renderable } from "util/vue";
|
||||||
import { computed, MaybeRef, MaybeRefOrGetter, unref } from "vue";
|
import { computed, MaybeRef, MaybeRefOrGetter, unref } from "vue";
|
||||||
|
@ -32,7 +32,7 @@ export interface Modifier {
|
||||||
* A description of this modifier.
|
* A description of this modifier.
|
||||||
* @see {@link createModifierSection}.
|
* @see {@link createModifierSection}.
|
||||||
*/
|
*/
|
||||||
description?: MaybeRef<Renderable>;
|
description?: MaybeGetter<Renderable>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Utility type that represents the output of all modifiers that represent a single operation. */
|
/** 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. */
|
/** The amount to add to the input value. */
|
||||||
addend: MaybeRefOrGetter<DecimalSource>;
|
addend: MaybeRefOrGetter<DecimalSource>;
|
||||||
/** Description of what this modifier is doing. */
|
/** 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. */
|
/** A MaybeRefOrGetter that will be processed and passed directly into the returned modifier. */
|
||||||
enabled?: MaybeRefOrGetter<boolean>;
|
enabled?: MaybeRefOrGetter<boolean>;
|
||||||
/** Determines if numbers larger or smaller than 0 should be displayed as red. */
|
/** 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 { addend, description, enabled, smallerIsBetter } = optionsFunc();
|
||||||
|
|
||||||
const processedAddend = processGetter(addend);
|
const processedAddend = processGetter(addend);
|
||||||
const processedDescription = processGetter(description);
|
|
||||||
const processedEnabled = enabled == null ? undefined : processGetter(enabled);
|
const processedEnabled = enabled == null ? undefined : processGetter(enabled);
|
||||||
return {
|
return {
|
||||||
apply: (gain: DecimalSource) => Decimal.add(gain, unref(processedAddend)),
|
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),
|
getFormula: (gain: FormulaSource) => Formula.add(gain, processedAddend),
|
||||||
enabled: processedEnabled,
|
enabled: processedEnabled,
|
||||||
description:
|
description:
|
||||||
processedDescription == null
|
description == null
|
||||||
? undefined
|
? undefined
|
||||||
: computed(() => (
|
: () => (
|
||||||
<div class="modifier-container">
|
<div class="modifier-container">
|
||||||
<span class="modifier-description">
|
<span class="modifier-description">{render(description)}</span>
|
||||||
{render(processedDescription)}
|
|
||||||
</span>
|
|
||||||
<span
|
<span
|
||||||
class="modifier-amount"
|
class="modifier-amount"
|
||||||
style={
|
style={
|
||||||
|
@ -95,7 +92,7 @@ export function createAdditiveModifier<T extends AdditiveModifierOptions, S = Op
|
||||||
{formatSmall(unref(processedAddend))}
|
{formatSmall(unref(processedAddend))}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
))
|
)
|
||||||
};
|
};
|
||||||
}) as S;
|
}) as S;
|
||||||
}
|
}
|
||||||
|
@ -105,7 +102,7 @@ export interface MultiplicativeModifierOptions {
|
||||||
/** The amount to multiply the input value by. */
|
/** The amount to multiply the input value by. */
|
||||||
multiplier: MaybeRefOrGetter<DecimalSource>;
|
multiplier: MaybeRefOrGetter<DecimalSource>;
|
||||||
/** Description of what this modifier is doing. */
|
/** 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. */
|
/** A MaybeRefOrGetter that will be processed and passed directly into the returned modifier. */
|
||||||
enabled?: MaybeRefOrGetter<boolean> | undefined;
|
enabled?: MaybeRefOrGetter<boolean> | undefined;
|
||||||
/** Determines if numbers larger or smaller than 1 should be displayed as red. */
|
/** 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 { multiplier, description, enabled, smallerIsBetter } = optionsFunc();
|
||||||
|
|
||||||
const processedMultiplier = processGetter(multiplier);
|
const processedMultiplier = processGetter(multiplier);
|
||||||
const processedDescription = processGetter(description);
|
|
||||||
const processedEnabled = enabled == null ? undefined : processGetter(enabled);
|
const processedEnabled = enabled == null ? undefined : processGetter(enabled);
|
||||||
return {
|
return {
|
||||||
apply: (gain: DecimalSource) => Decimal.times(gain, unref(processedMultiplier)),
|
apply: (gain: DecimalSource) => Decimal.times(gain, unref(processedMultiplier)),
|
||||||
|
@ -132,13 +128,11 @@ export function createMultiplicativeModifier<
|
||||||
getFormula: (gain: FormulaSource) => Formula.times(gain, processedMultiplier),
|
getFormula: (gain: FormulaSource) => Formula.times(gain, processedMultiplier),
|
||||||
enabled: processedEnabled,
|
enabled: processedEnabled,
|
||||||
description:
|
description:
|
||||||
processedDescription == null
|
description == null
|
||||||
? undefined
|
? undefined
|
||||||
: computed(() => (
|
: () => (
|
||||||
<div class="modifier-container">
|
<div class="modifier-container">
|
||||||
<span class="modifier-description">
|
<span class="modifier-description">{render(description)}</span>
|
||||||
{render(processedDescription)}
|
|
||||||
</span>
|
|
||||||
<span
|
<span
|
||||||
class="modifier-amount"
|
class="modifier-amount"
|
||||||
style={
|
style={
|
||||||
|
@ -154,7 +148,7 @@ export function createMultiplicativeModifier<
|
||||||
×{formatSmall(unref(processedMultiplier))}
|
×{formatSmall(unref(processedMultiplier))}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
))
|
)
|
||||||
};
|
};
|
||||||
}) as S;
|
}) as S;
|
||||||
}
|
}
|
||||||
|
@ -164,7 +158,7 @@ export interface ExponentialModifierOptions {
|
||||||
/** The amount to raise the input value to the power of. */
|
/** The amount to raise the input value to the power of. */
|
||||||
exponent: MaybeRefOrGetter<DecimalSource>;
|
exponent: MaybeRefOrGetter<DecimalSource>;
|
||||||
/** Description of what this modifier is doing. */
|
/** 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. */
|
/** A MaybeRefOrGetter that will be processed and passed directly into the returned modifier. */
|
||||||
enabled?: MaybeRefOrGetter<boolean> | undefined;
|
enabled?: MaybeRefOrGetter<boolean> | undefined;
|
||||||
/** Add 1 before calculating, then remove it afterwards. This prevents low numbers from becoming lower. */
|
/** Add 1 before calculating, then remove it afterwards. This prevents low numbers from becoming lower. */
|
||||||
|
@ -186,7 +180,6 @@ export function createExponentialModifier<
|
||||||
optionsFunc();
|
optionsFunc();
|
||||||
|
|
||||||
const processedExponent = processGetter(exponent);
|
const processedExponent = processGetter(exponent);
|
||||||
const processedDescription = processGetter(description);
|
|
||||||
const processedEnabled = enabled == null ? undefined : processGetter(enabled);
|
const processedEnabled = enabled == null ? undefined : processGetter(enabled);
|
||||||
return {
|
return {
|
||||||
apply: (gain: DecimalSource) => {
|
apply: (gain: DecimalSource) => {
|
||||||
|
@ -217,12 +210,12 @@ export function createExponentialModifier<
|
||||||
: Formula.pow(gain, processedExponent),
|
: Formula.pow(gain, processedExponent),
|
||||||
enabled: processedEnabled,
|
enabled: processedEnabled,
|
||||||
description:
|
description:
|
||||||
processedDescription == null
|
description == null
|
||||||
? undefined
|
? undefined
|
||||||
: computed(() => (
|
: () => (
|
||||||
<div class="modifier-container">
|
<div class="modifier-container">
|
||||||
<span class="modifier-description">
|
<span class="modifier-description">
|
||||||
{render(processedDescription)}
|
{render(description)}
|
||||||
{supportLowNumbers ? " (+1 effective)" : null}
|
{supportLowNumbers ? " (+1 effective)" : null}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
|
@ -240,7 +233,7 @@ export function createExponentialModifier<
|
||||||
^{formatSmall(unref(processedExponent))}
|
^{formatSmall(unref(processedExponent))}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
))
|
)
|
||||||
};
|
};
|
||||||
}) as S;
|
}) as S;
|
||||||
}
|
}
|
||||||
|
@ -286,14 +279,13 @@ export function createSequentialModifier<
|
||||||
? computed(() => modifiers.filter(m => unref(m.enabled) !== false).length > 0)
|
? computed(() => modifiers.filter(m => unref(m.enabled) !== false).length > 0)
|
||||||
: undefined,
|
: undefined,
|
||||||
description: modifiers.some(m => m.description != null)
|
description: modifiers.some(m => m.description != null)
|
||||||
? computed(() =>
|
? () =>
|
||||||
(
|
(
|
||||||
modifiers
|
modifiers
|
||||||
.filter(m => unref(m.enabled) !== false)
|
.filter(m => unref(m.enabled) !== false)
|
||||||
.map(m => unref(m.description))
|
.map(m => unref(m.description))
|
||||||
.filter(d => d) as MaybeRef<Renderable>[]
|
.filter(d => d) as MaybeGetter<Renderable>[]
|
||||||
).map(m => render(m))
|
).map(m => render(m))
|
||||||
)
|
|
||||||
: undefined
|
: undefined
|
||||||
};
|
};
|
||||||
}) as S;
|
}) as S;
|
||||||
|
@ -312,7 +304,7 @@ export interface ModifierSectionOptions {
|
||||||
/** The unit of the value being modified, if any. */
|
/** The unit of the value being modified, if any. */
|
||||||
unit?: string;
|
unit?: string;
|
||||||
/** The label to use for the base value. Defaults to "Base". */
|
/** 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. */
|
/** Determines if numbers larger or smaller than the base should be displayed as red. */
|
||||||
smallerIsBetter?: boolean;
|
smallerIsBetter?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -332,7 +324,6 @@ export function createModifierSection({
|
||||||
smallerIsBetter
|
smallerIsBetter
|
||||||
}: ModifierSectionOptions) {
|
}: ModifierSectionOptions) {
|
||||||
const total = modifier.apply(base ?? 1);
|
const total = modifier.apply(base ?? 1);
|
||||||
const processedBaseText = processGetter(baseText);
|
|
||||||
return (
|
return (
|
||||||
<div style={{ "--unit": settings.alignUnits && unit != null ? "'" + unit + "'" : "" }}>
|
<div style={{ "--unit": settings.alignUnits && unit != null ? "'" + unit + "'" : "" }}>
|
||||||
<h3>
|
<h3>
|
||||||
|
@ -341,7 +332,7 @@ export function createModifierSection({
|
||||||
</h3>
|
</h3>
|
||||||
<br />
|
<br />
|
||||||
<div class="modifier-container">
|
<div class="modifier-container">
|
||||||
<span class="modifier-description">{render(processedBaseText ?? "Base")}</span>
|
<span class="modifier-description">{render(baseText ?? "Base")}</span>
|
||||||
<span class="modifier-amount">
|
<span class="modifier-amount">
|
||||||
{formatSmall(base ?? 1)}
|
{formatSmall(base ?? 1)}
|
||||||
{unit}
|
{unit}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { isVisible, Visibility } from "features/feature";
|
import { isVisible, Visibility } from "features/feature";
|
||||||
import { displayResource, Resource } from "features/resources/resource";
|
import { displayResource, Resource } from "features/resources/resource";
|
||||||
import Decimal, { DecimalSource } from "lib/break_eternity";
|
import Decimal, { DecimalSource } from "lib/break_eternity";
|
||||||
import { processGetter } from "util/computed";
|
import { MaybeGetter, processGetter } from "util/computed";
|
||||||
import { createLazyProxy } from "util/proxies";
|
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 { computed, MaybeRef, MaybeRefOrGetter, unref } from "vue";
|
||||||
import Formula, { calculateCost, calculateMaxAffordable } from "./formulas/formulas";
|
import Formula, { calculateCost, calculateMaxAffordable } from "./formulas/formulas";
|
||||||
import type { GenericFormula, InvertibleIntegralFormula } from "./formulas/types";
|
import type { GenericFormula, InvertibleIntegralFormula } from "./formulas/types";
|
||||||
|
@ -262,16 +262,16 @@ export function createVisibilityRequirement(
|
||||||
*/
|
*/
|
||||||
export function createBooleanRequirement(
|
export function createBooleanRequirement(
|
||||||
requirement: MaybeRefOrGetter<boolean>,
|
requirement: MaybeRefOrGetter<boolean>,
|
||||||
display?: MaybeRefOrGetter<Renderable>
|
display?: MaybeGetter<Renderable>
|
||||||
): Requirement {
|
): Requirement {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const processedDisplay = processGetter(display);
|
const partialDisplay =
|
||||||
|
display == null ? undefined : typeof display === "function" ? display : () => display;
|
||||||
return {
|
return {
|
||||||
requirementMet: processGetter(requirement),
|
requirementMet: processGetter(requirement),
|
||||||
partialDisplay: processedDisplay == null ? undefined : () => render(processedDisplay),
|
partialDisplay,
|
||||||
display:
|
display: display == null ? undefined : () => <>Req: {partialDisplay}</>,
|
||||||
processedDisplay == null ? undefined : () => <>Req: {render(processedDisplay)}</>,
|
visibility: display == null ? Visibility.None : Visibility.Visible,
|
||||||
visibility: processedDisplay == null ? Visibility.None : Visibility.Visible,
|
|
||||||
requiresPay: false
|
requiresPay: false
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,10 +2,10 @@ import projInfo from "data/projInfo.json";
|
||||||
import { Themes } from "data/themes";
|
import { Themes } from "data/themes";
|
||||||
import { globalBus } from "game/events";
|
import { globalBus } from "game/events";
|
||||||
import LZString from "lz-string";
|
import LZString from "lz-string";
|
||||||
import { processGetter } from "util/computed";
|
import { MaybeGetter } from "util/computed";
|
||||||
import { decodeSave, hardReset } from "util/save";
|
import { decodeSave, hardReset } from "util/save";
|
||||||
import { Renderable } from "util/vue";
|
import { Renderable } from "util/vue";
|
||||||
import { MaybeRef, MaybeRefOrGetter, reactive, watch } from "vue";
|
import { reactive, watch } from "vue";
|
||||||
|
|
||||||
/** The player's settings object. */
|
/** The player's settings object. */
|
||||||
export interface Settings {
|
export interface Settings {
|
||||||
|
@ -101,22 +101,22 @@ export function loadSettings(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A list of fields to append to the settings modal. */
|
/** 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. */
|
/** Register a field to be displayed in the settings modal. */
|
||||||
export function registerSettingField(component: MaybeRefOrGetter<Renderable>) {
|
export function registerSettingField(component: MaybeGetter<Renderable>) {
|
||||||
settingFields.push(processGetter(component));
|
settingFields.push(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A list of components to show in the info modal. */
|
/** 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. */
|
/** Register a component to be displayed in the info modal. */
|
||||||
export function registerInfoComponent(component: MaybeRefOrGetter<Renderable>) {
|
export function registerInfoComponent(component: MaybeGetter<Renderable>) {
|
||||||
infoComponents.push(processGetter(component));
|
infoComponents.push(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A list of components to add to the root of the page. */
|
/** 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. */
|
/** Register a component to be displayed at the root of the page. */
|
||||||
export function registerGameComponent(component: MaybeRefOrGetter<Renderable>) {
|
export function registerGameComponent(component: MaybeGetter<Renderable>) {
|
||||||
gameComponents.push(processGetter(component));
|
gameComponents.push(component);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ import { isFunction } from "util/common";
|
||||||
import type { ComputedRef } from "vue";
|
import type { ComputedRef } from "vue";
|
||||||
import { computed } 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 {
|
export function processGetter<T>(obj: T): T extends () => infer S ? ComputedRef<S> : T {
|
||||||
if (isFunction(obj)) {
|
if (isFunction(obj)) {
|
||||||
return computed(obj) as ReturnType<typeof processGetter<T>>;
|
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 Row from "components/layout/Row.vue";
|
||||||
import { getUniqueID, Visibility } from "features/feature";
|
import { getUniqueID, Visibility } from "features/feature";
|
||||||
import VueFeatureComponent from "features/VueFeature.vue";
|
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 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 { JSX } from "vue/jsx-runtime";
|
||||||
import { camelToKebab } from "./common";
|
import { camelToKebab } from "./common";
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ export interface VueFeature {
|
||||||
/** CSS to apply to this feature. */
|
/** CSS to apply to this feature. */
|
||||||
style?: MaybeRef<CSSProperties>;
|
style?: MaybeRef<CSSProperties>;
|
||||||
/** The components to render inside the vue feature */
|
/** The components to render inside the vue feature */
|
||||||
components: MaybeRef<Renderable>[];
|
components: MaybeGetter<Renderable>[];
|
||||||
/** The components to render wrapped around the vue feature */
|
/** The components to render wrapped around the vue feature */
|
||||||
wrappers: ((el: () => Renderable) => Renderable)[];
|
wrappers: ((el: () => Renderable) => Renderable)[];
|
||||||
/** Used to identify Vue Features */
|
/** Used to identify Vue Features */
|
||||||
|
@ -45,14 +45,14 @@ export interface VueFeature {
|
||||||
export function vueFeatureMixin(
|
export function vueFeatureMixin(
|
||||||
featureName: string,
|
featureName: string,
|
||||||
options: VueFeatureOptions,
|
options: VueFeatureOptions,
|
||||||
component?: MaybeRefOrGetter<Renderable>
|
component?: MaybeGetter<Renderable>
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
id: getUniqueID(featureName),
|
id: getUniqueID(featureName),
|
||||||
visibility: processGetter(options.visibility),
|
visibility: processGetter(options.visibility),
|
||||||
classes: processGetter(options.classes),
|
classes: processGetter(options.classes),
|
||||||
style: processGetter(options.style),
|
style: processGetter(options.style),
|
||||||
components: component == null ? [] : [processGetter(component)],
|
components: component == null ? [] : [component],
|
||||||
wrappers: [] as ((el: () => Renderable) => Renderable)[],
|
wrappers: [] as ((el: () => Renderable) => Renderable)[],
|
||||||
[VueFeature]: true
|
[VueFeature]: true
|
||||||
} satisfies VueFeature;
|
} satisfies VueFeature;
|
||||||
|
@ -60,15 +60,15 @@ export function vueFeatureMixin(
|
||||||
|
|
||||||
export function render(object: VueFeature, wrapper?: (el: Renderable) => Renderable): JSX.Element;
|
export function render(object: VueFeature, wrapper?: (el: Renderable) => Renderable): JSX.Element;
|
||||||
export function render<T extends Renderable>(
|
export function render<T extends Renderable>(
|
||||||
object: MaybeRef<Renderable>,
|
object: MaybeGetter<Renderable>,
|
||||||
wrapper?: (el: Renderable) => T
|
wrapper?: (el: Renderable) => T
|
||||||
): T;
|
): T;
|
||||||
export function render(
|
export function render(
|
||||||
object: VueFeature | MaybeRef<Renderable>,
|
object: VueFeature | MaybeGetter<Renderable>,
|
||||||
wrapper?: (el: Renderable) => Renderable
|
wrapper?: (el: Renderable) => Renderable
|
||||||
): Renderable;
|
): Renderable;
|
||||||
export function render(
|
export function render(
|
||||||
object: VueFeature | MaybeRef<Renderable>,
|
object: VueFeature | MaybeGetter<Renderable>,
|
||||||
wrapper?: (el: Renderable) => Renderable
|
wrapper?: (el: Renderable) => Renderable
|
||||||
) {
|
) {
|
||||||
if (typeof object === "object" && VueFeature in object) {
|
if (typeof object === "object" && VueFeature in object) {
|
||||||
|
@ -85,20 +85,24 @@ export function render(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
object = unref(object);
|
object = toValue(object);
|
||||||
return wrapper?.(object) ?? 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>;
|
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>;
|
return <Col>{objects.map(obj => render(obj))}</Col>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function joinJSX(
|
export function joinJSX(
|
||||||
objects: (VueFeature | MaybeRef<Renderable>)[],
|
objects: (VueFeature | MaybeGetter<Renderable>)[],
|
||||||
joiner: JSX.Element
|
joiner: JSX.Element
|
||||||
): JSX.Element {
|
): JSX.Element {
|
||||||
return objects.reduce<JSX.Element>(
|
return objects.reduce<JSX.Element>(
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { type OptionsFunc } from "features/feature";
|
|
||||||
import { processGetter } from "util/computed";
|
import { processGetter } from "util/computed";
|
||||||
import { createLazyProxy, runAfterEvaluation } from "util/proxies";
|
import { createLazyProxy, runAfterEvaluation } from "util/proxies";
|
||||||
import type { VueFeature } from "util/vue";
|
import type { VueFeature } from "util/vue";
|
||||||
|
@ -23,9 +22,9 @@ export interface Mark {
|
||||||
* @param element The renderable feature to display the tooltip on.
|
* @param element The renderable feature to display the tooltip on.
|
||||||
* @param options Mark options.
|
* @param options Mark options.
|
||||||
*/
|
*/
|
||||||
export function addMark<T extends MarkOptions>(
|
export function addMark(
|
||||||
element: VueFeature,
|
element: VueFeature,
|
||||||
optionsFunc: OptionsFunc<T, Mark>
|
optionsFunc: () => MarkOptions
|
||||||
): asserts element is VueFeature & { mark: Mark } {
|
): asserts element is VueFeature & { mark: Mark } {
|
||||||
const mark = createLazyProxy(() => {
|
const mark = createLazyProxy(() => {
|
||||||
const options = optionsFunc();
|
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 { deletePersistent, persistent } from "game/persistence";
|
||||||
import { Direction } from "util/common";
|
import { Direction } from "util/common";
|
||||||
import { processGetter } from "util/computed";
|
import { MaybeGetter, processGetter } from "util/computed";
|
||||||
import { createLazyProxy, runAfterEvaluation } from "util/proxies";
|
import { createLazyProxy, runAfterEvaluation } from "util/proxies";
|
||||||
import { Renderable, vueFeatureMixin, type VueFeature, type VueFeatureOptions } from "util/vue";
|
import { Renderable, vueFeatureMixin, type VueFeature, type VueFeatureOptions } from "util/vue";
|
||||||
import { MaybeRef, MaybeRefOrGetter, type Ref } from "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. */
|
/** Whether or not this tooltip can be pinned, meaning it'll stay visible even when not hovered. */
|
||||||
pinnable?: boolean;
|
pinnable?: boolean;
|
||||||
/** The text to display inside the tooltip. */
|
/** The text to display inside the tooltip. */
|
||||||
display: MaybeRefOrGetter<Renderable>;
|
display: MaybeGetter<Renderable>;
|
||||||
/** The direction in which to display the tooltip */
|
/** The direction in which to display the tooltip */
|
||||||
direction?: MaybeRefOrGetter<Direction>;
|
direction?: MaybeRefOrGetter<Direction>;
|
||||||
/** The x offset of the tooltip, in px. */
|
/** 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. */
|
/** Whether or not this tooltip can be pinned, meaning it'll stay visible even when not hovered. */
|
||||||
pinnable?: boolean;
|
pinnable?: boolean;
|
||||||
/** The text to display inside the tooltip. */
|
/** The text to display inside the tooltip. */
|
||||||
display: MaybeRef<Renderable>;
|
display: MaybeGetter<Renderable>;
|
||||||
/** The direction in which to display the tooltip */
|
/** The direction in which to display the tooltip */
|
||||||
direction?: MaybeRef<Direction>;
|
direction?: MaybeRef<Direction>;
|
||||||
/** The x offset of the tooltip, in px. */
|
/** 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 element The renderable feature to display the tooltip on.
|
||||||
* @param options Tooltip options.
|
* @param options Tooltip options.
|
||||||
*/
|
*/
|
||||||
export function addTooltip<T extends TooltipOptions>(
|
export function addTooltip(
|
||||||
element: VueFeature,
|
element: VueFeature,
|
||||||
optionsFunc: OptionsFunc<T, Tooltip>
|
optionsFunc: () => TooltipOptions
|
||||||
): asserts element is VueFeature & { tooltip: Tooltip } {
|
): asserts element is VueFeature & { tooltip: Tooltip } {
|
||||||
const pinned = persistent<boolean>(false, false);
|
const pinned = persistent<boolean>(false, false);
|
||||||
const tooltip = createLazyProxy(() => {
|
const tooltip = createLazyProxy(() => {
|
||||||
|
@ -69,7 +69,7 @@ export function addTooltip<T extends TooltipOptions>(
|
||||||
...vueFeatureMixin("tooltip", options),
|
...vueFeatureMixin("tooltip", options),
|
||||||
pinnable: pinnable ?? true,
|
pinnable: pinnable ?? true,
|
||||||
pinned: pinnable === false ? undefined : pinned,
|
pinned: pinnable === false ? undefined : pinned,
|
||||||
display: processGetter(display),
|
display,
|
||||||
direction: processGetter(direction ?? Direction.Up),
|
direction: processGetter(direction ?? Direction.Up),
|
||||||
xoffset: processGetter(xoffset),
|
xoffset: processGetter(xoffset),
|
||||||
yoffset: processGetter(yoffset)
|
yoffset: processGetter(yoffset)
|
||||||
|
|
|
@ -9,15 +9,16 @@ import {
|
||||||
} from "game/modifiers";
|
} from "game/modifiers";
|
||||||
import Decimal, { DecimalSource } from "util/bignum";
|
import Decimal, { DecimalSource } from "util/bignum";
|
||||||
import { WithRequired } from "util/common";
|
import { WithRequired } from "util/common";
|
||||||
|
import { MaybeGetter } from "util/computed";
|
||||||
|
import { render, Renderable } from "util/vue";
|
||||||
import { beforeAll, describe, expect, test } from "vitest";
|
import { beforeAll, describe, expect, test } from "vitest";
|
||||||
import { MaybeRefOrGetter, Ref, ref, unref } from "vue";
|
import { MaybeRefOrGetter, Ref, ref, unref } from "vue";
|
||||||
import "../utils";
|
import "../utils";
|
||||||
import { render, Renderable } from "util/vue";
|
|
||||||
|
|
||||||
export type ModifierConstructorOptions = {
|
export type ModifierConstructorOptions = {
|
||||||
[S in "addend" | "multiplier" | "exponent"]: MaybeRefOrGetter<DecimalSource>;
|
[S in "addend" | "multiplier" | "exponent"]: MaybeRefOrGetter<DecimalSource>;
|
||||||
} & {
|
} & {
|
||||||
description?: MaybeRefOrGetter<Renderable>;
|
description?: MaybeGetter<Renderable>;
|
||||||
enabled?: MaybeRefOrGetter<boolean>;
|
enabled?: MaybeRefOrGetter<boolean>;
|
||||||
smallerIsBetter?: boolean;
|
smallerIsBetter?: boolean;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue