Project cleanup

This commit is contained in:
thepaperpilot 2023-05-08 20:57:27 -05:00
parent 003d304668
commit 204b6788b0
5 changed files with 1533 additions and 1474 deletions

360
src/data/boardUtils.tsx Normal file
View file

@ -0,0 +1,360 @@
import { BoardNode, GenericBoard, GenericBoardNodeAction, NodeLabel } from "features/boards/board";
import Formula, { calculateCost } from "game/formulas/formulas";
import { GenericFormula, InvertibleIntegralFormula } from "game/formulas/types";
import Decimal, { formatWhole } from "util/bignum";
import {
BoosterState,
DowsingState,
EmpowererState,
InfluenceState,
Passives,
PortalState,
ResourceState,
Resources
} from "./data";
import { main } from "./projEntry";
import { DecimalSource } from "lib/break_eternity";
import { ComputedRef } from "vue";
export const resourceLevelFormula = Formula.variable(0).add(1);
export const deselectAllAction = {
id: "deselect",
icon: "close",
tooltip: (node: BoardNode) => ({
text: "tools" in (node.state as object) ? "Disconnect tools" : "Disconnect resources"
}),
onClick(node: BoardNode) {
if (Array.isArray((node.state as unknown as InfluenceState)?.data)) {
node.state = { ...(node.state as object), data: [] };
} else if ("resources" in (node.state as object)) {
node.state = { ...(node.state as object), resources: [] };
} else if ("tools" in (node.state as object)) {
node.state = { ...(node.state as object), tools: [] };
}
main.board.selectedAction.value = null;
main.board.selectedNode.value = null;
},
visibility: (node: BoardNode) => {
if (Array.isArray((node.state as unknown as InfluenceState)?.data)) {
return ((node.state as unknown as InfluenceState).data as string[]).length > 0;
}
if ("resources" in (node.state as object)) {
return (node.state as { resources: Resources[] }).resources.length > 0;
}
if ("tools" in (node.state as object)) {
return (node.state as { tools: Passives[] }).tools.length > 0;
}
return false;
}
};
export const togglePoweredAction = {
id: "toggle",
icon: "bolt",
tooltip: (node: BoardNode): NodeLabel => ({
text: (node.state as { powered: boolean }).powered
? "Turn Off"
: `Turn On - Always runs for ${formatWhole(main.nextPowerCost.value)} energy/s`
}),
onClick(node: BoardNode) {
node.state = {
...(node.state as object),
powered: !(node.state as { powered: boolean }).powered
};
main.board.selectedAction.value = null;
},
fillColor: (node: BoardNode) =>
(node.state as { powered: boolean }).powered ? "var(--accent1)" : "var(--locked)"
} as GenericBoardNodeAction;
export function getIncreaseConnectionsAction(
cost: (x: InvertibleIntegralFormula) => GenericFormula,
maxConnections = Infinity
) {
const formula = cost(Formula.variable(0));
return {
id: "moreConnections",
icon: "hub",
formula,
tooltip(node: BoardNode) {
return {
text: `Increase Connections - ${formatWhole(
formula.evaluate((node.state as { maxConnections: number }).maxConnections)
)} energy`
};
},
confirmationLabel: (node: BoardNode): NodeLabel =>
Decimal.gte(
main.energy.value,
formula.evaluate((node.state as { maxConnections: number }).maxConnections)
)
? { text: "Tap again to confirm" }
: { text: "Cannot afford", color: "var(--danger)" },
onClick(node: BoardNode) {
const cost = formula.evaluate(
(node.state as { maxConnections: number }).maxConnections
);
if (Decimal.gte(main.energy.value, cost)) {
main.energy.value = Decimal.sub(main.energy.value, cost);
}
node.state = {
...(node.state as object),
maxConnections: Decimal.add(
(node.state as { maxConnections: number }).maxConnections,
1
)
};
main.board.selectedAction.value = null;
},
visibility: (node: BoardNode): boolean =>
Decimal.add(
(node.state as { maxConnections: number }).maxConnections,
main.computedBonusConnectionsModifier.value
).lt(maxConnections)
};
}
export function labelForAcceptingResource(
node: BoardNode,
description: (resource: Resources) => string
): NodeLabel | null {
if ((main.board as GenericBoard).draggingNode.value?.type === "resource") {
const resource = (
(main.board as GenericBoard).draggingNode.value?.state as unknown as ResourceState
).type;
const { maxConnections, resources } = node.state as unknown as DowsingState;
if (resources.includes(resource)) {
return { text: "Disconnect", color: "var(--accent2)" };
}
if (
Decimal.add(maxConnections, main.computedBonusConnectionsModifier.value).lte(
resources.length
)
) {
return { text: "Max connections", color: "var(--danger)" };
}
return {
text: description(resource),
color: "var(--accent2)"
};
}
return null;
}
export function labelForAcceptingTool(
node: BoardNode,
description: (passive: Passives) => string
): NodeLabel | null {
if ((main.board as GenericBoard).draggingNode.value?.type === "passive") {
const passive = (main.board as GenericBoard).draggingNode.value?.state as Passives;
const { maxConnections, tools } = node.state as unknown as EmpowererState;
if (tools.includes(passive)) {
return { text: "Disconnect", color: "var(--accent2)" };
}
if (
Decimal.add(maxConnections, main.computedBonusConnectionsModifier.value).lte(
tools.length
)
) {
return { text: "Max connections", color: "var(--danger)" };
}
return {
text: description(passive),
color: "var(--accent2)"
};
}
return null;
}
export function labelForAcceptingPortal(
node: BoardNode,
description: (portal: string) => string
): NodeLabel | null {
if ((main.board as GenericBoard).draggingNode.value?.type === "portal") {
const portal = (
(main.board as GenericBoard).draggingNode.value?.state as unknown as PortalState
).id;
const { maxConnections, portals } = node.state as unknown as BoosterState;
if (portals.includes(portal)) {
return { text: "Disconnect", color: "var(--accent2)" };
}
if (
Decimal.add(maxConnections, main.computedBonusConnectionsModifier.value).lte(
portals.length
)
) {
return { text: "Max connections", color: "var(--danger)" };
}
return {
text: description(portal),
color: "var(--accent2)"
};
}
return null;
}
export function canAcceptResource(node: BoardNode, otherNode: BoardNode) {
if (otherNode.type !== "resource") {
return false;
}
const resource = (otherNode.state as unknown as ResourceState).type;
const { maxConnections, resources } = node.state as unknown as DowsingState;
if (resources.includes(resource)) {
return true;
}
if (
Decimal.add(maxConnections, main.computedBonusConnectionsModifier.value).lte(
resources.length
)
) {
return false;
}
return true;
}
export function onDropResource(node: BoardNode, otherNode: BoardNode) {
if (otherNode.type !== "resource") {
return;
}
const resource = (otherNode.state as unknown as ResourceState).type;
const resources = (node.state as unknown as { resources: Resources[] }).resources;
if (resources.includes(resource)) {
node.state = {
...(node.state as object),
resources: resources.filter(r => r !== resource)
};
} else {
node.state = {
...(node.state as object),
resources: [...resources, resource]
};
}
main.board.selectedNode.value = node;
}
export function canAcceptTool(node: BoardNode, otherNode: BoardNode) {
if (otherNode.type !== "passive") {
return false;
}
const passive = otherNode.state as Passives;
const { maxConnections, tools } = node.state as unknown as EmpowererState;
if (tools.includes(passive)) {
return true;
}
if (
Decimal.add(maxConnections, main.computedBonusConnectionsModifier.value).lte(tools.length)
) {
return false;
}
return true;
}
export function onDropTool(node: BoardNode, otherNode: BoardNode) {
if (otherNode.type !== "passive") {
return;
}
const passive = otherNode.state as Passives;
const tools = (node.state as unknown as { tools: Passives[] }).tools;
if (tools.includes(passive)) {
node.state = {
...(node.state as object),
tools: tools.filter(r => r !== passive)
};
} else {
node.state = {
...(node.state as object),
tools: [...tools, passive]
};
}
main.board.selectedNode.value = node;
}
export function canAcceptPortal(node: BoardNode, otherNode: BoardNode) {
if (otherNode.type !== "portal") {
return false;
}
const portal = (otherNode.state as unknown as PortalState).id;
const { maxConnections, portals } = node.state as unknown as BoosterState;
if (portals.includes(portal)) {
return true;
}
if (
Decimal.add(maxConnections, main.computedBonusConnectionsModifier.value).lte(portals.length)
) {
return false;
}
return true;
}
export function onDropPortal(node: BoardNode, otherNode: BoardNode) {
if (otherNode.type !== "portal") {
return;
}
const portal = (otherNode.state as unknown as PortalState).id;
const { portals } = node.state as unknown as BoosterState;
if (portals.includes(portal)) {
node.state = {
...(node.state as object),
tools: portals.filter(r => r !== portal)
};
} else {
node.state = {
...(node.state as object),
tools: [...portals, portal]
};
}
main.board.selectedNode.value = node;
}
export function isPowered(node: BoardNode): boolean {
return node === main.board.selectedNode.value || (node.state as { powered: boolean }).powered;
}
export function isEmpowered(passive: Passives): boolean {
return (
main.empowerer.value != null &&
isPowered(main.empowerer.value) &&
(main.empowerer.value.state as unknown as EmpowererState).tools.includes(passive)
);
}
export function getResourceLevelProgress(resource: Resources): number {
const amount =
(main.resourceNodes.value[resource]?.state as unknown as ResourceState | undefined)
?.amount ?? 0;
const currentLevel = main.resourceLevels.value[resource];
const requiredForCurrentLevel = calculateCost(resourceLevelFormula, currentLevel, true);
const requiredForNextLevel = calculateCost(
resourceLevelFormula,
Decimal.add(currentLevel, 1),
true
);
return Decimal.sub(amount, requiredForCurrentLevel)
.max(0)
.div(Decimal.sub(requiredForNextLevel, requiredForCurrentLevel))
.toNumber();
}
export function checkConnections<T extends string>(
bonusConnections: DecimalSource,
node: ComputedRef<BoardNode | undefined>,
connectionsName: T
) {
if (node.value) {
const state = node.value.state as unknown as { [K in T]: string[] } & {
maxConnections: DecimalSource;
};
const currentConnections = state[connectionsName];
const maxConnections = state.maxConnections;
if (Decimal.lt(currentConnections.length, Decimal.add(maxConnections, bonusConnections))) {
node.value.state = {
...(node.value.state as object),
[connectionsName]: currentConnections.slice(
0,
Decimal.add(maxConnections, bonusConnections).toNumber()
)
};
}
}
}

412
src/data/data.tsx Normal file
View file

@ -0,0 +1,412 @@
import Formula from "game/formulas/formulas";
import { State } from "game/persistence";
import { DecimalSource } from "util/bignum";
export interface MineState {
progress: DecimalSource;
powered: boolean;
}
export interface ResourceState {
type: Resources;
amount: DecimalSource;
}
export interface DowsingState {
resources: Resources[];
maxConnections: number;
powered: boolean;
}
export interface QuarryState extends DowsingState {
progress: DecimalSource;
}
export interface EmpowererState {
tools: Passives[];
maxConnections: number;
powered: boolean;
}
export interface PortalGeneratorState {
tier: Resources | undefined;
influences: Influences[];
}
export interface PortalState {
id: string;
powered: boolean;
}
export interface InfluenceState {
type: Influences;
data: State;
}
export interface BoosterState {
portals: string[];
maxConnections: number;
powered: boolean;
level: DecimalSource;
}
export const mineLootTable = {
dirt: 120,
sand: 60,
gravel: 40,
wood: 30,
stone: 24,
coal: 20,
copper: 15,
iron: 12,
silver: 10,
gold: 8,
emerald: 6,
platinum: 5,
diamond: 4,
berylium: 3,
unobtainium: 2,
ultimatum: 1
} as const;
export type Resources = keyof typeof mineLootTable;
export const resourceNames = Object.keys(mineLootTable) as Resources[];
export const tools = {
dirt: {
cost: 1000,
name: "Pickaxe",
type: "passive",
state: "dirt"
},
sand: {
cost: 1e4,
name: "Dowsing Rod",
type: "dowsing",
state: { resources: [], maxConnections: 1, powered: false }
},
gravel: {
cost: 1e5,
name: "Ore Processor",
type: "passive",
state: "gravel"
},
wood: {
cost: 1e6,
name: "Quarry",
type: "quarry",
state: { resources: [], maxConnections: 1, powered: false, progress: 0 }
},
stone: {
cost: 1e7,
name: "Energizer",
type: "passive",
state: "stone"
},
coal: {
cost: 1e8,
name: "Tool Empowerer",
type: "empowerer",
state: { tools: [], maxConnections: 1, powered: false }
},
copper: {
cost: 1e9,
name: "Book",
type: "passive",
state: "copper"
},
iron: {
cost: 1e10,
name: "Portal Generator",
type: "portalGenerator",
state: { tier: undefined, influences: [] }
},
silver: {
cost: 1e12,
name: "Robotics",
type: "passive",
state: "silver"
},
gold: {
cost: 1e15,
name: "Booster",
type: "booster",
state: { portals: [], maxConnections: 1, powered: false, level: 1 }
},
emerald: {
cost: 1e19,
name: "Artificial Intelligence",
type: "passive",
state: "emerald"
},
platinum: {
cost: 1e24,
name: "Upgrader",
type: "upgrader",
state: { portals: [], maxConnections: 1, powered: false }
},
diamond: {
cost: 1e30,
name: "Machine Learning",
type: "passive",
state: "diamond"
},
berylium: {
cost: 1e37,
name: "Automator",
type: "automator",
state: { portals: [], maxConnections: 1, powered: false }
},
unobtainium: {
cost: 1e45,
name: "National Grid",
type: "passive",
state: "unobtainium"
},
ultimatum: {
cost: 1e54,
name: "Investments",
type: "investments",
state: { portals: [], maxConnections: 1, powered: false }
}
} as const satisfies Record<
Resources,
{
cost: DecimalSource;
name: string;
type: string;
state?: State;
}
>;
export const relics = {
dirt: "Replicator",
sand: "Metal Detector",
gravel: "Neural Networks",
wood: "Mining Laser",
stone: "BOGO Coupon",
coal: "Planar Intelligence",
copper: "Efficient Code",
iron: "Trade Agreements",
silver: "Machine Synergizer",
gold: "XP Market",
emerald: "Efficient Portals",
platinum: "Time Dilation",
diamond: "Paypal",
berylium: "Tiered Mining",
unobtainium: "Overclocked Portals",
ultimatum: "Rebates"
} as const satisfies Record<Resources, string>;
export const passives = {
dirt: {
description: (empowered: boolean) =>
empowered ? "Quadruples mining speed" : "Doubles mining speed"
},
gravel: {
description: (empowered: boolean) =>
empowered ? "Quadruples mine ore drops" : "Doubles mine ore drops"
},
stone: {
description: (empowered: boolean) =>
empowered ? "Quadruples energy gain" : "Doubles energy gain"
},
copper: {
description: (empowered: boolean) =>
empowered ? "Material level is 20% stronger" : "Material level is 10% stronger"
},
silver: {
description: (empowered: boolean) =>
empowered
? "Doubles each plane's resource gain"
: "Quadruples each plane's resource gain"
},
diamond: {
description: (empowered: boolean) =>
empowered
? "+20% plane's resource gain per upgrade bought"
: "+10% plane's resource gain per upgrade bought"
},
emerald: {
description: (empowered: boolean) =>
empowered
? "+2% plane's resource gain per minute active"
: "+1% plane's resource gain per minute active"
},
unobtainium: {
description: (empowered: boolean) =>
empowered ? "+2 max connections per machine" : "+1 max connections per machine"
},
dirtRelic: {
description: (empowered: boolean) =>
empowered ? "Upgrades apply thrice" : "Upgrades apply twice"
},
sandRelic: {
description: (empowered: boolean) =>
empowered ? "Treasure's 2 tiers stronger" : "Treasure's 1 tier stronger"
},
gravelRelic: {
description: (empowered: boolean) =>
empowered
? "+2% plane's resource gain per repeatable purchase"
: "+1% plane's resource gain per repeatable purchase"
},
woodRelic: {
description: (empowered: boolean) =>
empowered ? "(Relics)^2 boost mine speed" : "Relics boost mine speed"
},
stoneRelic: {
description: (empowered: boolean) =>
empowered ? "2 free levels for repeatables" : "1 free level for repeatables"
},
coalRelic: {
description: (empowered: boolean) =>
empowered ? "(Treasures)^2 boost planar speed" : "Treasures boost planar speed"
},
copperRelic: {
description: (empowered: boolean) =>
empowered ? "Power 2 machines free" : "Power 1 machine free"
},
ironRelic: {
description: (empowered: boolean) =>
empowered ? "Conversions give triple output" : "Conversions give double output"
},
silverRelic: {
description: (empowered: boolean) =>
empowered ? "(Power machines)^2 boost ore dropped" : "Power machines boost ore dropped"
},
goldRelic: {
description: (empowered: boolean) =>
empowered ? "Each treasure quadruples XP gain" : "Each treasure doubles XP gain"
},
emeraldRelic: {
description: (empowered: boolean) =>
empowered
? "Creating portals costs a third the energy"
: "Creating portals costs half the energy"
},
platinumRelic: {
description: (empowered: boolean) =>
empowered ? "Triple dimensions' tick rate" : "Double dimensions' tick rate"
},
diamondRelic: {
description: (empowered: boolean) =>
empowered ? "Repeatables/dimensions buy max at once" : "Repeatables buy max at once"
},
beryliumRelic: {
description: (empowered: boolean) =>
empowered ? "ln(energy) boosts planar speed" : "log(energy) boosts planar speed"
},
unobtainiumRelic: {
description: (empowered: boolean) =>
empowered
? "Upgrades/repeatables/dimensions/prestige no longer spend on purchase"
: "Upgrades/repeatables no longer spend on purchase"
}
} as const satisfies Record<string, { description: (empowered: boolean) => string }>;
export type Passives = keyof typeof passives;
export const influences = {
increaseResources: {
display: "+resource",
description: (state: InfluenceState) => {
const resources = state.data as Resources[];
if (resources.length === 0) {
return "Increase resource odds - Drag a resource to me!";
}
if (resources.length === 1) {
return `Increase ${resources[0]}'s odds`;
}
return `Increase ${resources.length} resources' odds`;
},
cost: 2,
initialData: []
},
decreaseResources: {
display: "-resource",
description: (state: InfluenceState) => {
const resources = state.data as Resources[];
if (resources.length === 0) {
return "Decrease resource odds - Drag a resource to me!";
}
if (resources.length === 1) {
return `Decrease ${resources[0]}'s odds`;
}
return `Decrease ${resources.length} resources' odds`;
},
cost: 2,
initialData: []
},
increaseLength: {
display: "+length",
description: "Increase length",
cost: 100,
initialData: undefined
},
increaseCaches: {
display: "+caches",
description: "Increase caches odds",
cost: 10,
initialData: undefined
},
increaseGens: {
display: "+gens",
description: "Increase generators odds",
cost: 10,
initialData: undefined
},
increaseInfluences: {
display: "+influences",
description: "Increase influences odds",
cost: 10,
initialData: undefined
},
increaseEnergyMults: {
display: "+energy mults",
description: "Increase energy mults odds",
cost: 10,
initialData: undefined
},
increaseResourceMults: {
display: "+resource mults",
description: "Increase resource mults odds",
cost: 10,
initialData: undefined
},
increaseDiff: {
display: "+diff",
description: "Increase difficulty/rewards odds",
cost: 10,
initialData: undefined
},
decreaseDiff: {
display: "-diff",
description: "Decrease difficulty/rewards odds",
cost: 10,
initialData: undefined
},
increaseRewards: {
display: "+rewards",
description: "Increase rewards level",
cost: 1e4,
initialData: undefined
},
relic: {
display: "+relic",
description: "Max length/difficulty, add tier-unique relic",
cost: 1e6,
initialData: undefined
}
} as const satisfies Record<
string,
{
display: string;
description: string | ((state: InfluenceState) => string);
cost: DecimalSource;
initialData?: State;
}
>;
export type Influences = keyof typeof influences;
export const increaseBoostFormula = Formula.variable(0).add(8).times(2).pow10();

681
src/data/nodeTypes.tsx Normal file
View file

@ -0,0 +1,681 @@
import {
BoardNode,
GenericBoard,
NodeTypeOptions,
ProgressDisplay,
Shape,
getUniqueNodeID
} from "features/boards/board";
import { addLayer, layers } from "game/layers";
import player from "game/player";
import Decimal from "lib/break_eternity";
import { format, formatWhole } from "util/break_eternity";
import { camelToTitle } from "util/common";
import {
canAcceptPortal,
canAcceptResource,
canAcceptTool,
deselectAllAction,
getIncreaseConnectionsAction,
getResourceLevelProgress,
isEmpowered,
isPowered,
labelForAcceptingPortal,
labelForAcceptingResource,
labelForAcceptingTool,
onDropPortal,
onDropResource,
onDropTool,
togglePoweredAction
} from "./boardUtils";
import {
BoosterState,
DowsingState,
EmpowererState,
InfluenceState,
MineState,
Passives,
PortalGeneratorState,
PortalState,
QuarryState,
ResourceState,
Resources,
increaseBoostFormula,
influences,
passives,
relics,
tools
} from "./data";
import { GenericPlane, createPlane } from "./planes";
import { main } from "./projEntry";
export const mine = {
shape: Shape.Diamond,
size: 50,
title: "🪨",
label: node =>
node === main.board.selectedNode.value
? { text: "Mining" }
: Object.keys(main.resourceNodes.value).length === 0
? { text: "Click me!" }
: null,
actionDistance: Math.PI / 4,
actions: [togglePoweredAction],
progress: node =>
isPowered(node) ? new Decimal((node.state as unknown as MineState).progress).toNumber() : 0,
progressDisplay: ProgressDisplay.Outline,
progressColor: "var(--accent2)",
classes: node => ({
running: isPowered(node)
}),
draggable: true
} as NodeTypeOptions;
export const brokenFactory = {
shape: Shape.Diamond,
size: 50,
title: "🛠️",
label: node => (node === main.board.selectedNode.value ? { text: "Broken Forge" } : null),
actionDistance: Math.PI / 4,
actions: [
{
id: "repair",
icon: "build",
tooltip: { text: "Repair - 100 energy" },
onClick(node) {
if (Decimal.gte(main.energy.value, 100)) {
node.type = "factory";
main.energy.value = Decimal.sub(main.energy.value, 100);
}
},
confirmationLabel: () =>
Decimal.gte(main.energy.value, 1000)
? { text: "Tap again to confirm" }
: { text: "Cannot afford", color: "var(--danger)" }
}
],
draggable: true
} as NodeTypeOptions;
export const factory = {
shape: Shape.Diamond,
size: 50,
title: "🛠️",
label: node => {
if (node === main.board.selectedNode.value) {
return {
text:
node.state == null
? "Forge - Drag a resource to me!"
: `Forging ${tools[node.state as Resources].name}`
};
}
if ((main.board as GenericBoard).draggingNode.value?.type === "resource") {
const resource = (
(main.board as GenericBoard).draggingNode.value?.state as unknown as ResourceState
).type;
const text = node.state === resource ? "Disconnect" : tools[resource].name;
const color =
node.state === resource ||
(Decimal.gte(main.energy.value, tools[resource].cost) &&
main.toolNodes.value[resource] == null)
? "var(--accent2)"
: "var(--danger)";
return {
text,
color
};
}
return null;
},
actionDistance: Math.PI / 4,
actions: [
{
id: "deselect",
icon: "close",
tooltip: { text: "Disconnect resource" },
onClick(node) {
node.state = undefined;
main.board.selectedAction.value = null;
main.board.selectedNode.value = null;
},
visibility: node => node.state != null
},
{
id: "craft",
icon: "done",
tooltip: node => ({
text: `Forge ${tools[node.state as Resources].name} - ${formatWhole(
tools[node.state as Resources].cost
)} energy`
}),
onClick(node) {
const tool = tools[node.state as Resources];
if (
Decimal.gte(main.energy.value, tool.cost) &&
main.toolNodes.value[node.state as Resources] == null
) {
main.energy.value = Decimal.sub(main.energy.value, tool.cost);
const newNode = {
id: getUniqueNodeID(main.board as GenericBoard),
position: { ...node.position },
type: tool.type,
state: "state" in tool ? tool.state : undefined
};
main.board.placeInAvailableSpace(newNode);
main.board.nodes.value.push(newNode);
main.board.selectedAction.value = null;
main.board.selectedNode.value = null;
node.state = undefined;
}
},
fillColor: node =>
Decimal.gte(main.energy.value, tools[node.state as Resources].cost) &&
main.toolNodes.value[node.state as Resources] == null
? "var(--accent2)"
: "var(--danger)",
visibility: node => node.state != null,
confirmationLabel: node =>
Decimal.gte(main.energy.value, tools[node.state as Resources].cost)
? main.toolNodes.value[node.state as Resources] == null
? { text: "Tap again to confirm" }
: { text: "Already crafted", color: "var(--danger)" }
: { text: "Cannot afford", color: "var(--danger)" }
}
],
progress: node =>
node.state == null || main.toolNodes.value[node.state as Resources] != null
? 0
: Decimal.div(main.energy.value, tools[node.state as Resources].cost)
.clampMax(1)
.toNumber(),
progressDisplay: ProgressDisplay.Fill,
progressColor: node =>
node.state != null && Decimal.gte(main.energy.value, tools[node.state as Resources].cost)
? "var(--accent2)"
: "var(--foreground)",
canAccept(node, otherNode) {
return otherNode.type === "resource";
},
onDrop(node, otherNode) {
const droppedType = (otherNode.state as unknown as ResourceState).type;
if (node.state === droppedType) {
node.state = undefined;
} else {
node.state = droppedType;
}
main.board.selectedNode.value = node;
},
draggable: true
} as NodeTypeOptions;
export const resource = {
shape: Shape.Circle,
size: 50,
title: node => camelToTitle((node.state as unknown as ResourceState).type),
subtitle: node => formatWhole((node.state as unknown as ResourceState).amount),
progress: node => getResourceLevelProgress((node.state as unknown as ResourceState).type),
// Make clicking resources a no-op so they can't be selected
// eslint-disable-next-line @typescript-eslint/no-empty-function
onClick() {},
progressDisplay: ProgressDisplay.Outline,
progressColor: "var(--accent3)",
classes: node => ({
"affected-node":
(main.dowsing.value != null &&
isPowered(main.dowsing.value) &&
(main.dowsing.value.state as unknown as DowsingState).resources.includes(
(node.state as unknown as ResourceState).type
)) ||
Decimal.neq(
main.planarMultis.value[(node.state as unknown as ResourceState).type] ?? 1,
1
)
}),
draggable: true
} as NodeTypeOptions;
export const passive = {
shape: Shape.Circle,
size: 50,
title: node => tools[node.state as Resources].name,
label: node =>
node === main.board.selectedNode.value
? {
text: passives[node.state as Passives].description(
isEmpowered(node.state as Passives)
)
}
: null,
outlineColor: "var(--bought)",
classes: node => ({
"affected-node": isEmpowered(node.state as Passives)
}),
draggable: true
} as NodeTypeOptions;
export const dowsing = {
shape: Shape.Diamond,
size: 50,
title: "🥢",
label: node => {
if (node === main.board.selectedNode.value) {
return {
text:
(node.state as unknown as DowsingState).resources.length === 0
? "Dowsing - Drag a resource to me!"
: `Dowsing (${
(node.state as { resources: Resources[] }).resources.length
}/${Decimal.add(
(node.state as { maxConnections: number }).maxConnections,
main.computedBonusConnectionsModifier.value
)})`
};
}
return labelForAcceptingResource(node, resource => `Double ${resource} odds`);
},
actionDistance: Math.PI / 4,
actions: [
deselectAllAction,
getIncreaseConnectionsAction(x => x.add(2).pow_base(100), 16),
togglePoweredAction
],
classes: node => ({
running: isPowered(node)
}),
canAccept: canAcceptResource,
onDrop: onDropResource,
draggable: true
} as NodeTypeOptions;
export const quarry = {
shape: Shape.Diamond,
size: 50,
title: "⛏️",
label: node => {
if (node === main.board.selectedNode.value) {
return {
text:
(node.state as unknown as DowsingState).resources.length === 0
? "Quarry - Drag a resource to me!"
: `Quarrying (${
(node.state as { resources: Resources[] }).resources.length
}/${Decimal.add(
(node.state as { maxConnections: number }).maxConnections,
main.computedBonusConnectionsModifier.value
)})`
};
}
return labelForAcceptingResource(
node,
resource =>
`Gather ${format(
Decimal.div(main.dropRates[resource].computedModifier.value, 100)
)} ${resource}/s`
);
},
actionDistance: Math.PI / 4,
actions: [
deselectAllAction,
getIncreaseConnectionsAction(x => x.add(2).pow_base(10000), 16),
togglePoweredAction
],
progress: node =>
isPowered(node)
? Decimal.eq(main.quarryProgressRequired.value, 0)
? 0
: new Decimal((node.state as unknown as QuarryState).progress)
.div(main.quarryProgressRequired.value)
.toNumber()
: 0,
progressDisplay: ProgressDisplay.Outline,
progressColor: "var(--accent2)",
canAccept: canAcceptResource,
onDrop: onDropResource,
classes: node => ({
running: isPowered(node)
}),
draggable: true
} as NodeTypeOptions;
export const empowerer = {
shape: Shape.Diamond,
size: 50,
title: "🔌",
label: node => {
if (node === main.board.selectedNode.value) {
return {
text:
(node.state as unknown as EmpowererState).tools.length === 0
? "Empowerer - Drag a tool to me!"
: `Empowering (${
(node.state as { tools: Passives[] }).tools.length
}/${Decimal.add(
(node.state as { maxConnections: number }).maxConnections,
main.computedBonusConnectionsModifier.value
)})`
};
}
return labelForAcceptingTool(node, passive => {
if (passive.includes("Relic")) {
return `Double ${relics[passive.slice(0, -5) as Resources]}'s effect`;
}
return `Double ${tools[passive as Resources].name}'s effect`;
});
},
actionDistance: Math.PI / 4,
actions: [
deselectAllAction,
getIncreaseConnectionsAction(x => x.add(3).pow_base(1000), 16),
togglePoweredAction
],
canAccept: canAcceptTool,
onDrop: onDropTool,
classes: node => ({
running: isPowered(node)
}),
draggable: true
} as NodeTypeOptions;
export const portalGenerator = {
shape: Shape.Diamond,
size: 50,
title: "⛩️",
label: node => {
if (node === main.board.selectedNode.value) {
return {
text:
(node.state as unknown as PortalGeneratorState).tier == null
? "Portal Spawner - Drag a resource to me!"
: `Spawning ${
(node.state as unknown as PortalGeneratorState).tier
}-tier portal`
};
}
const draggingNode = (main.board as GenericBoard).draggingNode.value;
if (draggingNode?.type === "resource") {
const resource = (draggingNode.state as unknown as ResourceState).type;
const text =
(node.state as unknown as PortalGeneratorState).tier === resource
? "Disconnect"
: `${camelToTitle(resource)}-tier Portal`;
return {
text,
color: "var(--accent2)"
};
} else if (draggingNode?.type === "influence") {
const influence = (draggingNode.state as unknown as InfluenceState).type;
const { influences } = node.state as unknown as PortalGeneratorState;
if (influences.includes(influence)) {
return { text: "Disconnect", color: "var(--accent2)" };
}
return {
text: "Add influence",
color: "var(--accent2)"
};
}
return null;
},
actionDistance: Math.PI / 4,
actions: [
{
id: "deselect",
icon: "close",
tooltip: { text: "Disconnect all" },
onClick(node: BoardNode) {
node.state = {
...(node.state as object),
tier: undefined,
influences: []
};
main.board.selectedAction.value = null;
main.board.selectedNode.value = null;
},
visibility: (node: BoardNode) => {
const { tier, influences } = node.state as unknown as PortalGeneratorState;
return tier != null || influences.length > 0;
}
},
{
id: "makePortal",
icon: "done",
tooltip: node => ({
text: `Spawn ${(node.state as unknown as PortalGeneratorState).tier}-tier portal`
}),
onClick(node) {
let id = 0;
while (`portal-${id}` in layers) {
id++;
}
const { tier, influences } = node.state as unknown as PortalGeneratorState;
addLayer(
createPlane(
`portal-${id}`,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
tier!,
Math.floor(Math.random() * 4294967296),
influences.map(
influence =>
main.influenceNodes.value[influence]
.state as unknown as InfluenceState
)
),
player
);
const newNode = {
id: getUniqueNodeID(main.board as GenericBoard),
position: { ...node.position },
type: "portal",
state: { id: `portal-${id}`, powered: false }
};
main.board.placeInAvailableSpace(newNode);
main.board.nodes.value.push(newNode);
main.board.selectedAction.value = null;
main.board.selectedNode.value = null;
node.state = { tier: undefined, influences: [] };
},
visibility: node => (node.state as unknown as PortalGeneratorState).tier != null
}
],
canAccept(node, otherNode) {
return otherNode.type === "resource" || otherNode.type === "influence";
},
onDrop(node, otherNode) {
if (otherNode.type === "resource") {
const droppedType = (otherNode.state as unknown as ResourceState).type;
const currentType = (node.state as unknown as PortalGeneratorState).tier;
node.state = {
...(node.state as object),
tier: droppedType === currentType ? undefined : droppedType
};
} else if (otherNode.type === "influence") {
const droppedInfluence = (otherNode.state as unknown as InfluenceState).type;
const currentInfluences = (node.state as unknown as PortalGeneratorState).influences;
if (currentInfluences.includes(droppedInfluence)) {
node.state = {
...(node.state as object),
influences: currentInfluences.filter(i => i !== droppedInfluence)
};
} else {
node.state = {
...(node.state as object),
influences: [...currentInfluences, droppedInfluence]
};
}
}
main.board.selectedNode.value = node;
},
draggable: true
} as NodeTypeOptions;
export const portal = {
shape: Shape.Diamond,
size: 50,
title: "🌀",
label: node =>
node === main.board.selectedNode.value
? {
text: `Portal to ${
(layers[(node.state as unknown as PortalState).id] as GenericPlane).name
}`,
color: (layers[(node.state as unknown as PortalState).id] as GenericPlane).color
}
: null,
actionDistance: Math.PI / 4,
actions: [togglePoweredAction],
classes: node => ({
running: isPowered(node),
showNotif: (layers[(node.state as unknown as PortalState).id] as GenericPlane).showNotif
.value
}),
outlineColor: node =>
(layers[(node.state as unknown as PortalState).id] as GenericPlane).background,
draggable: true
} as NodeTypeOptions;
export const influence = {
shape: node =>
(node.state as unknown as InfluenceState).type === "increaseResources" ||
(node.state as unknown as InfluenceState).type === "decreaseResources"
? Shape.Diamond
: Shape.Circle,
size: 50,
title: node => influences[(node.state as unknown as InfluenceState).type].display,
label: node => {
if (node === main.board.selectedNode.value) {
const state = node.state as unknown as InfluenceState;
const desc = influences[state.type].description;
return { text: typeof desc === "function" ? desc(state) : desc };
}
const draggingNode = (main.board as GenericBoard).draggingNode.value;
if (draggingNode?.type === "resource") {
const resource = (draggingNode.state as unknown as ResourceState).type;
const { type, data } = node.state as unknown as InfluenceState;
let text;
if (Array.isArray(data) && data.includes(resource)) {
text = "Disconnect";
} else if (type === "increaseResources") {
text = `Increase ${camelToTitle(resource)} odds`;
} else if (type === "decreaseResources") {
text = `Decrease ${camelToTitle(resource)} odds`;
} else {
return null;
}
return {
text,
color: "var(--accent2)"
};
}
return null;
},
actionDistance: Math.PI / 4,
actions: [deselectAllAction],
canAccept: (node, otherNode) => {
if (otherNode.type !== "resource") {
return false;
}
return Array.isArray((node.state as unknown as InfluenceState).data);
},
onDrop: (node, otherNode) => {
if (otherNode.type !== "resource") {
return;
}
const resource = (otherNode.state as unknown as ResourceState).type;
const resources = (node.state as unknown as InfluenceState).data as Resources[] | undefined;
if (resources == null) {
return;
}
if (resources.includes(resource)) {
node.state = {
...(node.state as object),
data: resources.filter(r => r !== resource)
};
} else {
node.state = { ...(node.state as object), data: [...resources, resource] };
}
main.board.selectedNode.value = node;
},
outlineColor: "var(--danger)",
draggable: true
} as NodeTypeOptions;
export const booster = {
shape: Shape.Diamond,
size: 50,
title: "⌛",
label: node => {
if (node === main.board.selectedNode.value) {
return {
text:
(node.state as unknown as BoosterState).portals.length === 0
? "Booster - Drag a portal to me!"
: `Boosting by ${formatWhole(
Decimal.add(1, (node.state as unknown as BoosterState).level)
)}x (${(node.state as { tools: Passives[] }).tools.length}/${Decimal.add(
(node.state as { maxConnections: number }).maxConnections,
main.computedBonusConnectionsModifier.value
)})`
};
}
return labelForAcceptingPortal(node, portal => {
return `Boost ${(layers[portal] as GenericPlane).name}'s speed`;
});
},
actionDistance: Math.PI / 4,
actions: [
{
id: "deselect",
icon: "close",
tooltip: {
text: "Disconnect portals"
},
onClick(node: BoardNode) {
node.state = { ...(node.state as object), portals: [] };
main.board.selectedAction.value = null;
main.board.selectedNode.value = null;
},
visibility: (node: BoardNode) =>
(node.state as unknown as BoosterState)?.portals.length ?? 0 > 0
},
getIncreaseConnectionsAction(x => x.add(6).pow_base(1000)),
{
id: "increaseBoost",
icon: "arrow_upward",
tooltip(node: BoardNode) {
return {
text: `Increase boost - ${formatWhole(
increaseBoostFormula.evaluate((node.state as unknown as BoosterState).level)
)} energy`
};
},
confirmationLabel(node: BoardNode) {
return Decimal.gte(
main.energy.value,
increaseBoostFormula.evaluate((node.state as unknown as BoosterState).level)
)
? { text: "Tap again to confirm" }
: { text: "Cannot afford", color: "var(--danger)" };
},
onClick(node: BoardNode) {
const cost = increaseBoostFormula.evaluate(
(node.state as unknown as BoosterState).level
);
if (Decimal.gte(main.energy.value, cost)) {
main.energy.value = Decimal.sub(main.energy.value, cost);
}
node.state = {
...(node.state as object),
level: Decimal.add((node.state as unknown as BoosterState).level, 1)
};
main.board.selectedAction.value = null;
}
},
togglePoweredAction
],
canAccept: canAcceptPortal,
onDrop: onDropPortal,
classes: node => ({
running: isPowered(node)
}),
draggable: true
} as NodeTypeOptions;

View file

@ -37,23 +37,21 @@ import { VueFeature, render, renderRow, trackHover } from "util/vue";
import { ComputedRef, Ref, computed, ref, unref } from "vue";
import { useToast } from "vue-toastification";
import { createCollapsibleModifierSections, createFormulaPreview, estimateTime } from "./common";
import type {
BoosterState,
import { getColor, getName, sfc32 } from "./utils";
import {
Resources,
InfluenceState,
resourceNames,
Influences,
PortalState,
ResourceState,
Resources
} from "./projEntry";
import {
hasWon,
influences as influenceTypes,
main,
mineLootTable,
relics,
resourceNames
} from "./projEntry";
import { getColor, getName, sfc32 } from "./utils";
BoosterState,
tools,
influences as influenceTypes
} from "./data";
import { main, hasWon } from "./projEntry";
const toast = useToast();
@ -108,7 +106,7 @@ export function createPlane(
createMultiplicativeModifier(() => ({
multiplier: () => (main.isEmpowered("silver") ? 4 : 2),
description: () =>
(main.isEmpowered("silver") ? "Empowered " : "") + main.tools.silver.name,
(main.isEmpowered("silver") ? "Empowered " : "") + tools.silver.name,
enabled: () => main.toolNodes.value.silver != null
})),
createMultiplicativeModifier(() => ({
@ -117,14 +115,14 @@ export function createPlane(
upgrades.filter(u => u.bought.value).length) /
10,
description: () =>
(main.isEmpowered("diamond") ? "Empowered " : "") + main.tools.diamond.name,
(main.isEmpowered("diamond") ? "Empowered " : "") + tools.diamond.name,
enabled: () => main.toolNodes.value.diamond != null
})),
createMultiplicativeModifier(() => ({
multiplier: () =>
Decimal.div(timeActive.value, 6000).times(main.isEmpowered("emerald") ? 2 : 1),
description: () =>
(main.isEmpowered("emerald") ? "Empowered " : "") + main.tools.emerald.name,
(main.isEmpowered("emerald") ? "Empowered " : "") + tools.emerald.name,
enabled: () => main.toolNodes.value.emerald != null
}))
]);

File diff suppressed because it is too large Load diff