diff --git a/src/data/boardUtils.tsx b/src/data/boardUtils.tsx new file mode 100644 index 0000000..09724c7 --- /dev/null +++ b/src/data/boardUtils.tsx @@ -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( + bonusConnections: DecimalSource, + node: ComputedRef, + 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() + ) + }; + } + } +} diff --git a/src/data/data.tsx b/src/data/data.tsx new file mode 100644 index 0000000..2c9a30e --- /dev/null +++ b/src/data/data.tsx @@ -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; + +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 }>; + +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(); diff --git a/src/data/nodeTypes.tsx b/src/data/nodeTypes.tsx new file mode 100644 index 0000000..b5d3c21 --- /dev/null +++ b/src/data/nodeTypes.tsx @@ -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; diff --git a/src/data/planes.tsx b/src/data/planes.tsx index 2781930..91a6983 100644 --- a/src/data/planes.tsx +++ b/src/data/planes.tsx @@ -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 })) ]); diff --git a/src/data/projEntry.tsx b/src/data/projEntry.tsx index ec0b894..0dead91 100644 --- a/src/data/projEntry.tsx +++ b/src/data/projEntry.tsx @@ -4,9 +4,6 @@ import { BoardNode, BoardNodeLink, GenericBoard, - NodeLabel, - ProgressDisplay, - Shape, createBoard, getUniqueNodeID } from "features/boards/board"; @@ -14,8 +11,8 @@ import { jsx } from "features/feature"; import { createResource } from "features/resources/resource"; import { createTabFamily } from "features/tabs/tabFamily"; import Formula, { calculateCost } from "game/formulas/formulas"; -import { GenericFormula, InvertibleIntegralFormula } from "game/formulas/types"; -import { BaseLayer, GenericLayer, addLayer, createLayer, layers } from "game/layers"; +import { GenericFormula } from "game/formulas/types"; +import { BaseLayer, GenericLayer, createLayer, layers } from "game/layers"; import { Modifier, createAdditiveModifier, @@ -26,426 +23,69 @@ import { State } from "game/persistence"; import type { LayerData, Player } from "game/player"; import player from "game/player"; import settings from "game/settings"; -import Decimal, { DecimalSource } from "lib/break_eternity"; -import { format, formatWhole } from "util/bignum"; +import Decimal, { DecimalSource, format, formatWhole } from "util/bignum"; import { WithRequired, camelToTitle } from "util/common"; import { render } from "util/vue"; import { ComputedRef, computed, nextTick, reactive, ref, watch } from "vue"; import { useToast } from "vue-toastification"; +import { + checkConnections, + isEmpowered, + isPowered, + resourceLevelFormula, + togglePoweredAction +} from "./boardUtils"; import { Section, createCollapsibleModifierSections, createFormulaPreview } from "./common"; +import { + BoosterState, + DowsingState, + EmpowererState, + InfluenceState, + Influences, + MineState, + Passives, + PortalGeneratorState, + PortalState, + QuarryState, + ResourceState, + Resources, + mineLootTable, + resourceNames, + tools +} from "./data"; import "./main.css"; +import { + booster, + brokenFactory, + dowsing, + empowerer, + factory, + influence, + mine, + passive, + portal, + portalGenerator, + quarry, + resource +} from "./nodeTypes"; import { GenericPlane, createPlane } from "./planes"; const toast = useToast(); -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[]; - -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: { planes: [], maxConnections: 1, powered: false, level: 1 } - }, - emerald: { - cost: 1e19, - name: "Artificial Intelligence", - type: "passive", - state: "emerald" - }, - platinum: { - cost: 1e24, - name: "Upgrader", - type: "upgrader", - state: { planes: [], maxConnections: 1, powered: false } - }, - diamond: { - cost: 1e30, - name: "Machine Learning", - type: "passive", - state: "diamond" - }, - berylium: { - cost: 1e37, - name: "Automator", - type: "automator", - state: { planes: [], maxConnections: 1, powered: false } - }, - unobtainium: { - cost: 1e45, - name: "National Grid", - type: "passive", - state: "unobtainium" - }, - ultimatum: { - cost: 1e54, - name: "Investments", - type: "investments", - state: { planes: [], 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; - -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 }>; - -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; - -const increaseBoostFormula = Formula.variable(0).add(8).times(2).pow10(); +const types = { + mine, + brokenFactory, + factory, + resource, + passive, + dowsing, + quarry, + empowerer, + portalGenerator, + portal, + influence, + booster +}; /** * @hidden @@ -453,8 +93,6 @@ const increaseBoostFormula = Formula.variable(0).add(8).times(2).pow10(); export const main = createLayer("main", function (this: BaseLayer) { const energy = createResource(0, "energy"); - const resourceLevelFormula = Formula.variable(0).add(1); - const resourceNodes: ComputedRef> = computed(() => board.types.resource.nodes.value.reduce((acc, curr) => { acc[(curr.state as unknown as ResourceState).type] = curr; @@ -481,14 +119,6 @@ export const main = createLayer("main", function (this: BaseLayer) { }, {} as Record) })); - function isEmpowered(passive: Passives) { - return ( - empowerer.value != null && - isPowered(empowerer.value) && - (empowerer.value.state as unknown as EmpowererState).tools.includes(passive) - ); - } - const resourceLevels = computed(() => resourceNames.reduce((acc, curr) => { const amount = @@ -512,22 +142,6 @@ export const main = createLayer("main", function (this: BaseLayer) { return acc; }, {} as Record) ); - function getResourceLevelProgress(resource: Resources) { - const amount = - (resourceNodes.value[resource]?.state as unknown as ResourceState | undefined) - ?.amount ?? 0; - const currentLevel = 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(); - } const resourceMinedCooldown: Partial> = reactive({}); const resourceQuarriedCooldown: Partial> = reactive({}); @@ -587,951 +201,12 @@ export const main = createLayer("main", function (this: BaseLayer) { ); }); - 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: [] }; - } - board.selectedAction.value = null; - 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; - } - }; - - const togglePoweredAction = { - id: "toggle", - icon: "bolt", - tooltip: (node: BoardNode) => ({ - text: (node.state as { powered: boolean }).powered - ? "Turn Off" - : `Turn On - Always runs for ${formatWhole(nextPowerCost.value)} energy/s` - }), - onClick(node: BoardNode) { - node.state = { - ...(node.state as object), - powered: !(node.state as { powered: boolean }).powered - }; - board.selectedAction.value = null; - }, - fillColor: (node: BoardNode) => - (node.state as { powered: boolean }).powered ? "var(--accent1)" : "var(--locked)" - }; - - 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) => - Decimal.gte( - 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(energy.value, cost)) { - energy.value = Decimal.sub(energy.value, cost); - } - node.state = { - ...(node.state as object), - maxConnections: Decimal.add( - (node.state as { maxConnections: number }).maxConnections, - 1 - ) - }; - board.selectedAction.value = null; - }, - visibility: (node: BoardNode) => - Decimal.add( - (node.state as { maxConnections: number }).maxConnections, - computedBonusConnectionsModifier.value - ).lt(maxConnections) - }; - } - - function labelForAcceptingResource( - node: BoardNode, - description: (resource: Resources) => string - ): NodeLabel | null { - if ((board as GenericBoard).draggingNode.value?.type === "resource") { - const resource = ( - (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, computedBonusConnectionsModifier.value).lte( - resources.length - ) - ) { - return { text: "Max connections", color: "var(--danger)" }; - } - return { - text: description(resource), - color: "var(--accent2)" - }; - } - return null; - } - - function labelForAcceptingTool( - node: BoardNode, - description: (passive: Passives) => string - ): NodeLabel | null { - if ((board as GenericBoard).draggingNode.value?.type === "passive") { - const passive = (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, computedBonusConnectionsModifier.value).lte( - tools.length - ) - ) { - return { text: "Max connections", color: "var(--danger)" }; - } - return { - text: description(passive), - color: "var(--accent2)" - }; - } - return null; - } - - function labelForAcceptingPortal( - node: BoardNode, - description: (portal: string) => string - ): NodeLabel | null { - if ((board as GenericBoard).draggingNode.value?.type === "portal") { - const portal = ( - (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, computedBonusConnectionsModifier.value).lte( - portals.length - ) - ) { - return { text: "Max connections", color: "var(--danger)" }; - } - return { - text: description(portal), - color: "var(--accent2)" - }; - } - return null; - } - - 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, computedBonusConnectionsModifier.value).lte( - resources.length - ) - ) { - return false; - } - return true; - } - - 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] - }; - } - board.selectedNode.value = node; - } - - 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, computedBonusConnectionsModifier.value).lte(tools.length)) { - return false; - } - return true; - } - - 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] - }; - } - board.selectedNode.value = node; - } - - 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, computedBonusConnectionsModifier.value).lte(portals.length) - ) { - return false; - } - return true; - } - - 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] - }; - } - board.selectedNode.value = node; - } - const board = createBoard(board => ({ startNodes: () => [ { position: { x: 0, y: 0 }, type: "mine", state: { progress: 0, powered: false } }, { position: { x: 0, y: -200 }, type: "brokenFactory" } ], - types: { - mine: { - shape: Shape.Diamond, - size: 50, - title: "đŸĒ¨", - label: node => - node === board.selectedNode.value - ? { text: "Mining" } - : Object.keys(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 - }, - brokenFactory: { - shape: Shape.Diamond, - size: 50, - title: "🛠ī¸", - label: node => - node === 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(energy.value, 100)) { - node.type = "factory"; - energy.value = Decimal.sub(energy.value, 100); - } - }, - confirmationLabel: () => - Decimal.gte(energy.value, 1000) - ? { text: "Tap again to confirm" } - : { text: "Cannot afford", color: "var(--danger)" } - } - ], - draggable: true - }, - factory: { - shape: Shape.Diamond, - size: 50, - title: "🛠ī¸", - label: node => { - if (node === board.selectedNode.value) { - return { - text: - node.state == null - ? "Forge - Drag a resource to me!" - : `Forging ${tools[node.state as Resources].name}` - }; - } - if ((board as GenericBoard).draggingNode.value?.type === "resource") { - const resource = ( - (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(energy.value, tools[resource].cost) && - 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; - board.selectedAction.value = null; - 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(energy.value, tool.cost) && - toolNodes.value[node.state as Resources] == null - ) { - energy.value = Decimal.sub(energy.value, tool.cost); - const newNode = { - id: getUniqueNodeID(board as GenericBoard), - position: { ...node.position }, - type: tool.type, - state: "state" in tool ? tool.state : undefined - }; - board.placeInAvailableSpace(newNode); - board.nodes.value.push(newNode); - board.selectedAction.value = null; - board.selectedNode.value = null; - node.state = undefined; - } - }, - fillColor: node => - Decimal.gte(energy.value, tools[node.state as Resources].cost) && - toolNodes.value[node.state as Resources] == null - ? "var(--accent2)" - : "var(--danger)", - visibility: node => node.state != null, - confirmationLabel: node => - Decimal.gte(energy.value, tools[node.state as Resources].cost) - ? 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 || toolNodes.value[node.state as Resources] != null - ? 0 - : Decimal.div(energy.value, tools[node.state as Resources].cost) - .clampMax(1) - .toNumber(), - progressDisplay: ProgressDisplay.Fill, - progressColor: node => - node.state != null && - Decimal.gte(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; - } - board.selectedNode.value = node; - }, - draggable: true - }, - 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": - (dowsing.value != null && - isPowered(dowsing.value) && - (dowsing.value.state as unknown as DowsingState).resources.includes( - (node.state as unknown as ResourceState).type - )) || - Decimal.neq( - planarMultis.value[(node.state as unknown as ResourceState).type] ?? 1, - 1 - ) - }), - draggable: true - }, - passive: { - shape: Shape.Circle, - size: 50, - title: node => tools[node.state as Resources].name, - label: node => - node === 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 - }, - dowsing: { - shape: Shape.Diamond, - size: 50, - title: "đŸĨĸ", - label: node => { - if (node === 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, - 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 - }, - quarry: { - shape: Shape.Diamond, - size: 50, - title: "⛏ī¸", - label: node => { - if (node === 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, - computedBonusConnectionsModifier.value - )})` - }; - } - return labelForAcceptingResource( - node, - resource => - `Gather ${format( - Decimal.div(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(quarryProgressRequired.value, 0) - ? 0 - : new Decimal((node.state as unknown as QuarryState).progress) - .div(quarryProgressRequired.value) - .toNumber() - : 0, - progressDisplay: ProgressDisplay.Outline, - progressColor: "var(--accent2)", - canAccept: canAcceptResource, - onDrop: onDropResource, - classes: node => ({ - running: isPowered(node) - }), - draggable: true - }, - empowerer: { - shape: Shape.Diamond, - size: 50, - title: "🔌", - label: node => { - if (node === 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, - 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 - }, - portalGenerator: { - shape: Shape.Diamond, - size: 50, - title: "⛩ī¸", - label: node => { - if (node === 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 = (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: [] - }; - board.selectedAction.value = null; - 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 => - influenceNodes.value[influence] - .state as unknown as InfluenceState - ) - ), - player - ); - const newNode = { - id: getUniqueNodeID(board as GenericBoard), - position: { ...node.position }, - type: "portal", - state: { id: `portal-${id}`, powered: false } - }; - board.placeInAvailableSpace(newNode); - board.nodes.value.push(newNode); - board.selectedAction.value = null; - 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] - }; - } - } - board.selectedNode.value = node; - }, - draggable: true - }, - portal: { - shape: Shape.Diamond, - size: 50, - title: "🌀", - label: node => - node === 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 - }, - 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 === 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 = (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] }; - } - board.selectedNode.value = node; - }, - outlineColor: "var(--danger)", - draggable: true - }, - booster: { - shape: Shape.Diamond, - size: 50, - title: "⌛", - label: node => { - if (node === 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, - 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: [] }; - board.selectedAction.value = null; - 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( - 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(energy.value, cost)) { - energy.value = Decimal.sub(energy.value, cost); - } - node.state = { - ...(node.state as object), - level: Decimal.add((node.state as unknown as BoosterState).level, 1) - }; - board.selectedAction.value = null; - } - }, - togglePoweredAction - ], - canAccept: canAcceptPortal, - onDrop: onDropPortal, - classes: node => ({ - running: isPowered(node) - }), - draggable: true - } - }, + types, style: { position: "absolute", top: 0, @@ -1685,10 +360,6 @@ export const main = createLayer("main", function (this: BaseLayer) { } })); - function isPowered(node: BoardNode): boolean { - return node === board.selectedNode.value || (node.state as { powered: boolean }).powered; - } - const mine: ComputedRef = computed(() => board.types.mine.nodes.value[0]); const factory: ComputedRef = computed( () => board.types.factory.nodes.value[0] @@ -2105,78 +776,10 @@ export const main = createLayer("main", function (this: BaseLayer) { watch(computedBonusConnectionsModifier, (curr, prev) => { if (Decimal.lt(curr, prev)) { - if (dowsing.value) { - const maxConnections = (dowsing.value.state as unknown as DowsingState) - .maxConnections; - if ( - Decimal.lt( - (dowsing.value.state as unknown as DowsingState).resources.length, - Decimal.add(maxConnections, curr) - ) - ) { - dowsing.value.state = { - ...(dowsing.value.state as object), - resources: (dowsing.value.state as unknown as DowsingState).resources.slice( - 0, - Decimal.add(maxConnections, curr).toNumber() - ) - }; - } - } - if (quarry.value) { - const maxConnections = (quarry.value.state as unknown as QuarryState) - .maxConnections; - if ( - Decimal.lt( - (quarry.value.state as unknown as QuarryState).resources.length, - Decimal.add(maxConnections, curr) - ) - ) { - quarry.value.state = { - ...(quarry.value.state as object), - resources: (quarry.value.state as unknown as QuarryState).resources.slice( - 0, - Decimal.add(maxConnections, curr).toNumber() - ) - }; - } - } - if (empowerer.value) { - const maxConnections = (empowerer.value.state as unknown as EmpowererState) - .maxConnections; - if ( - Decimal.lt( - (empowerer.value.state as unknown as EmpowererState).tools.length, - Decimal.add(maxConnections, curr) - ) - ) { - empowerer.value.state = { - ...(empowerer.value.state as object), - resources: (empowerer.value.state as unknown as EmpowererState).tools.slice( - 0, - Decimal.add(maxConnections, curr).toNumber() - ) - }; - } - } - if (booster.value) { - const maxConnections = (booster.value.state as unknown as BoosterState) - .maxConnections; - if ( - Decimal.lt( - (booster.value.state as unknown as BoosterState).portals.length, - Decimal.add(maxConnections, curr) - ) - ) { - booster.value.state = { - ...(booster.value.state as object), - resources: (booster.value.state as unknown as BoosterState).portals.slice( - 0, - Decimal.add(maxConnections, curr).toNumber() - ) - }; - } - } + checkConnections(curr, dowsing, "resources"); + checkConnections(curr, quarry, "resources"); + checkConnections(curr, empowerer, "tools"); + checkConnections(curr, booster, "portals"); } }); @@ -2185,15 +788,20 @@ export const main = createLayer("main", function (this: BaseLayer) { board, energy, modifierTabs, - mineLootTable, - tools, - passives, resourceNodes, toolNodes, influenceNodes, grantResource, activePortals, isEmpowered, + nextPowerCost, + computedBonusConnectionsModifier, + quarryProgressRequired, + dropRates, + dowsing, + empowerer, + resourceLevels, + planarMultis, display: jsx(() => ( <>