forked from profectus/Profectus
Revert changes in /data. Check the board-example branch for them instead
This commit is contained in:
parent
99227a2cb2
commit
f9c59f7636
2 changed files with 142 additions and 405 deletions
73
src/data/layers/prestige.tsx
Normal file
73
src/data/layers/prestige.tsx
Normal file
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* @module
|
||||
* @hidden
|
||||
*/
|
||||
import { main } from "data/projEntry";
|
||||
import { createCumulativeConversion } from "features/conversion";
|
||||
import { jsx } from "features/feature";
|
||||
import { createHotkey } from "features/hotkey";
|
||||
import { createReset } from "features/reset";
|
||||
import MainDisplay from "features/resources/MainDisplay.vue";
|
||||
import { createResource } from "features/resources/resource";
|
||||
import { addTooltip } from "features/tooltips/tooltip";
|
||||
import { createResourceTooltip } from "features/trees/tree";
|
||||
import { BaseLayer, createLayer } from "game/layers";
|
||||
import type { DecimalSource } from "util/bignum";
|
||||
import { render } from "util/vue";
|
||||
import { createLayerTreeNode, createResetButton } from "../common";
|
||||
|
||||
const id = "p";
|
||||
const layer = createLayer(id, function (this: BaseLayer) {
|
||||
const name = "Prestige";
|
||||
const color = "#4BDC13";
|
||||
const points = createResource<DecimalSource>(0, "prestige points");
|
||||
|
||||
const conversion = createCumulativeConversion(() => ({
|
||||
formula: x => x.div(10).sqrt(),
|
||||
baseResource: main.points,
|
||||
gainResource: points
|
||||
}));
|
||||
|
||||
const reset = createReset(() => ({
|
||||
thingsToReset: (): Record<string, unknown>[] => [layer]
|
||||
}));
|
||||
|
||||
const treeNode = createLayerTreeNode(() => ({
|
||||
layerID: id,
|
||||
color,
|
||||
reset
|
||||
}));
|
||||
const tooltip = addTooltip(treeNode, {
|
||||
display: createResourceTooltip(points),
|
||||
pinnable: true
|
||||
});
|
||||
|
||||
const resetButton = createResetButton(() => ({
|
||||
conversion,
|
||||
tree: main.tree,
|
||||
treeNode
|
||||
}));
|
||||
|
||||
const hotkey = createHotkey(() => ({
|
||||
description: "Reset for prestige points",
|
||||
key: "p",
|
||||
onPress: resetButton.onClick
|
||||
}));
|
||||
|
||||
return {
|
||||
name,
|
||||
color,
|
||||
points,
|
||||
tooltip,
|
||||
display: jsx(() => (
|
||||
<>
|
||||
<MainDisplay resource={points} color={color} />
|
||||
{render(resetButton)}
|
||||
</>
|
||||
)),
|
||||
treeNode,
|
||||
hotkey
|
||||
};
|
||||
});
|
||||
|
||||
export default layer;
|
|
@ -1,428 +1,92 @@
|
|||
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 {
|
||||
NodePosition,
|
||||
makeDraggable,
|
||||
placeInAvailableSpace,
|
||||
setupActions,
|
||||
setupDraggableNode,
|
||||
setupUniqueIds
|
||||
} from "game/boards/board";
|
||||
import Node from "components/Node.vue";
|
||||
import Spacer from "components/layout/Spacer.vue";
|
||||
import { jsx } from "features/feature";
|
||||
import { createResource } from "features/resources/resource";
|
||||
import { createUpgrade } from "features/upgrades/upgrade";
|
||||
import { createResource, trackBest, trackOOMPS, trackTotal } from "features/resources/resource";
|
||||
import type { GenericTree } from "features/trees/tree";
|
||||
import { branchedResetPropagation, createTree } from "features/trees/tree";
|
||||
import { globalBus } from "game/events";
|
||||
import type { BaseLayer, GenericLayer } from "game/layers";
|
||||
import { createLayer } from "game/layers";
|
||||
import { Persistent, persistent } from "game/persistence";
|
||||
import type { Player } from "game/player";
|
||||
import { createCostRequirement } from "game/requirements";
|
||||
import player from "game/player";
|
||||
import type { DecimalSource } from "util/bignum";
|
||||
import Decimal, { format, formatTime } from "util/bignum";
|
||||
import { render } from "util/vue";
|
||||
import { ComponentPublicInstance, computed, ref, watch } from "vue";
|
||||
import { setupSelectable } from "./common";
|
||||
import "./common.css";
|
||||
import { computed, toRaw } from "vue";
|
||||
import prestige from "./layers/prestige";
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
export const main = createLayer("main", 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 & { position: Persistent<NodePosition> };
|
||||
type NodeTypes = ANode | BNode;
|
||||
const points = createResource<DecimalSource>(10);
|
||||
const best = trackBest(points);
|
||||
const total = trackTotal(points);
|
||||
|
||||
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 pointGain = computed(() => {
|
||||
// eslint-disable-next-line prefer-const
|
||||
let gain = new Decimal(1);
|
||||
return gain;
|
||||
});
|
||||
globalBus.on("update", diff => {
|
||||
points.value = Decimal.add(points.value, Decimal.times(pointGain.value, diff));
|
||||
});
|
||||
const oomps = trackOOMPS(points, pointGain);
|
||||
|
||||
const {
|
||||
startDrag,
|
||||
endDrag,
|
||||
drag,
|
||||
nodeBeingDragged,
|
||||
hasDragged,
|
||||
receivingNodes,
|
||||
receivingNode,
|
||||
dragDelta
|
||||
} = setupDraggableNode<number | "cnode">({
|
||||
board,
|
||||
getPosition(id) {
|
||||
return nodesById.value[id] ?? (cNode as CNode).position.value;
|
||||
const tree = createTree(() => ({
|
||||
nodes: [[prestige.treeNode]],
|
||||
branches: [],
|
||||
onReset() {
|
||||
points.value = toRaw(this.resettingNode.value) === toRaw(prestige.treeNode) ? 0 : 10;
|
||||
best.value = points.value;
|
||||
total.value = points.value;
|
||||
},
|
||||
setPosition(id, position) {
|
||||
const node = nodesById.value[id] ?? (cNode as CNode).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",
|
||||
pointerEvents: "none"
|
||||
}
|
||||
}));
|
||||
makeDraggable(cNode, {
|
||||
id: "cnode",
|
||||
endDrag,
|
||||
startDrag,
|
||||
hasDragged,
|
||||
nodeBeingDragged,
|
||||
dragDelta,
|
||||
onMouseUp() {
|
||||
if (!hasDragged.value) {
|
||||
cNode.purchase();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const dNodesPerAxis = 50;
|
||||
const dNodes = jsx(() => (
|
||||
<>
|
||||
{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 = jsx(() => (
|
||||
<>
|
||||
{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);
|
||||
}
|
||||
}
|
||||
resetPropagation: branchedResetPropagation
|
||||
})) as GenericTree;
|
||||
|
||||
return {
|
||||
name: "Tree",
|
||||
color: "var(--accent1)",
|
||||
links: tree.links,
|
||||
display: jsx(() => (
|
||||
<>
|
||||
<Board
|
||||
onDrag={drag}
|
||||
onMouseDown={deselect}
|
||||
onMouseUp={endDrag}
|
||||
onMouseLeave={endDrag}
|
||||
ref={board}
|
||||
>
|
||||
<SVGNode>
|
||||
{dNodes()}
|
||||
{links()}
|
||||
</SVGNode>
|
||||
{nodes.value.map(renderNode)}
|
||||
{render(cNode)}
|
||||
<SVGNode>
|
||||
{aActions()}
|
||||
{bActions()}
|
||||
</SVGNode>
|
||||
</Board>
|
||||
{player.devSpeed === 0 ? (
|
||||
<div>
|
||||
Game Paused
|
||||
<Node id="paused" />
|
||||
</div>
|
||||
) : null}
|
||||
{player.devSpeed != null && player.devSpeed !== 0 && player.devSpeed !== 1 ? (
|
||||
<div>
|
||||
Dev Speed: {format(player.devSpeed)}x
|
||||
<Node id="devspeed" />
|
||||
</div>
|
||||
) : null}
|
||||
{player.offlineTime != null && player.offlineTime !== 0 ? (
|
||||
<div>
|
||||
Offline Time: {formatTime(player.offlineTime)}
|
||||
<Node id="offline" />
|
||||
</div>
|
||||
) : null}
|
||||
<div>
|
||||
{Decimal.lt(points.value, "1e1000") ? <span>You have </span> : null}
|
||||
<h2>{format(points.value)}</h2>
|
||||
{Decimal.lt(points.value, "1e1e6") ? <span> points</span> : null}
|
||||
</div>
|
||||
{Decimal.gt(pointGain.value, 0) ? (
|
||||
<div>
|
||||
({oomps.value})
|
||||
<Node id="oomps" />
|
||||
</div>
|
||||
) : null}
|
||||
<Spacer />
|
||||
{render(tree)}
|
||||
</>
|
||||
)),
|
||||
boardNodes: nodes,
|
||||
cNode,
|
||||
selected: persistent(selected)
|
||||
points,
|
||||
best,
|
||||
total,
|
||||
oomps,
|
||||
tree
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -433,7 +97,7 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
|||
export const getInitialLayers = (
|
||||
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
||||
player: Partial<Player>
|
||||
): Array<GenericLayer> => [main];
|
||||
): Array<GenericLayer> => [main, prestige];
|
||||
|
||||
/**
|
||||
* A computed ref whose value is true whenever the game is over.
|
||||
|
|
Loading…
Reference in a new issue