diff --git a/src/data/boardUtils.tsx b/src/data/boardUtils.tsx index d515b3f..aedd7b2 100644 --- a/src/data/boardUtils.tsx +++ b/src/data/boardUtils.tsx @@ -347,7 +347,7 @@ export function checkConnections( }; const currentConnections = state[connectionsName]; const maxConnections = state.maxConnections; - if (Decimal.lt(currentConnections.length, Decimal.add(maxConnections, bonusConnections))) { + if (Decimal.gt(currentConnections.length, Decimal.add(maxConnections, bonusConnections))) { node.value.state = { ...(node.value.state as object), [connectionsName]: currentConnections.slice( diff --git a/src/data/data.tsx b/src/data/data.tsx index d3560be..3c84c9b 100644 --- a/src/data/data.tsx +++ b/src/data/data.tsx @@ -56,6 +56,12 @@ export interface UpgraderState { powered: boolean; } +export interface AutomatorState { + portals: string[]; + maxConnections: number; + powered: boolean; +} + export const mineLootTable = { dirt: 120, sand: 60, @@ -224,8 +230,8 @@ export const passives = { silver: { description: (empowered: boolean) => empowered - ? "Doubles each plane's resource gain" - : "Quadruples each plane's resource gain" + ? "Quadruples each plane's resource gain" + : "Doubles each plane's resource gain" }, diamond: { description: (empowered: boolean) => diff --git a/src/data/nodeTypes.tsx b/src/data/nodeTypes.tsx index 0683ffb..6c4073e 100644 --- a/src/data/nodeTypes.tsx +++ b/src/data/nodeTypes.tsx @@ -29,6 +29,7 @@ import { togglePoweredAction } from "./boardUtils"; import { + AutomatorState, BoosterState, DowsingState, EmpowererState, @@ -239,7 +240,13 @@ export const resource = { export const passive = { shape: Shape.Circle, size: 50, - title: node => tools[node.state as Resources].name, + title: node => { + const passive = node.state as Passives; + if (passive.includes("Relic")) { + return relics[passive.slice(0, -5) as Resources]; + } + return tools[passive as Resources].name; + }, label: node => node === main.board.selectedNode.value ? { @@ -708,7 +715,7 @@ export const upgrader = { }; } return labelForAcceptingPortal(node, portal => { - return `Auto-buy ${(layers[portal] as GenericPlane).name}'s upgrades`; + return `Auto-buy ${(layers[portal] as GenericPlane).name}'s upgrades and prestiges`; }); }, actionDistance: Math.PI / 4, @@ -737,3 +744,52 @@ export const upgrader = { }), draggable: true } as NodeTypeOptions; + +export const automator = { + shape: Shape.Diamond, + size: 50, + title: "🦾", + label: node => { + if (node === main.board.selectedNode.value) { + return { + text: + (node.state as unknown as AutomatorState).portals.length === 0 + ? "Automator - Drag a portal to me!" + : `Automatating (${ + (node.state as unknown as AutomatorState).portals.length + }/${Decimal.add( + (node.state as unknown as AutomatorState).maxConnections, + main.computedBonusConnectionsModifier.value + )})` + }; + } + return labelForAcceptingPortal(node, portal => { + return `Auto-buy ${(layers[portal] as GenericPlane).name}'s repeatables and dimensions`; + }); + }, + 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 AutomatorState)?.portals.length ?? 0 > 0 + }, + getIncreaseConnectionsAction(x => x.add(4).pow_base(1e6)), + 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 f7b3d1e..a4bd3b6 100644 --- a/src/data/planes.tsx +++ b/src/data/planes.tsx @@ -4,7 +4,7 @@ import StickyVue from "components/layout/Sticky.vue"; import { GenericAchievement, createAchievement } from "features/achievements/achievement"; import { createBar } from "features/bars/bar"; import { BoardNode, getUniqueNodeID } from "features/boards/board"; -import { createClickable } from "features/clickables/clickable"; +import { GenericClickable, createClickable, setupAutoClick } from "features/clickables/clickable"; import { createCumulativeConversion } from "features/conversion"; import { CoercableComponent, findFeatures, isVisible, jsx } from "features/feature"; import { GenericRepeatable, RepeatableType, createRepeatable } from "features/repeatable"; @@ -43,12 +43,14 @@ import { useToast } from "vue-toastification"; import { isPowered } from "./boardUtils"; import { createCollapsibleModifierSections, createFormulaPreview, estimateTime } from "./common"; import { + AutomatorState, BoosterState, InfluenceState, Influences, PortalState, ResourceState, Resources, + UpgraderState, influences as influenceTypes, mineLootTable, relics, @@ -130,7 +132,9 @@ export function createPlane( })), createMultiplicativeModifier(() => ({ multiplier: () => - Decimal.div(timeActive.value, 6000).times(main.isEmpowered("emerald") ? 2 : 1), + Decimal.div(timeActive.value, 6000) + .times(main.isEmpowered("emerald") ? 2 : 1) + .add(1), description: () => (main.isEmpowered("emerald") ? "Empowered " : "") + tools.emerald.name, enabled: () => main.toolNodes.value.emerald != null @@ -1072,10 +1076,24 @@ export function createPlane( setupAutoPurchase( this as GenericLayer, - () => main.upgrader.value != null && isPowered(main.upgrader.value), + () => + earnedTreasures.value.length < length && + main.upgrader.value != null && + isPowered(main.upgrader.value) && + (main.upgrader.value.state as unknown as UpgraderState).portals.includes(id), upgrades ); + setupAutoClick( + this as GenericLayer, + () => + earnedTreasures.value.length < length && + main.automator.value != null && + isPowered(main.automator.value) && + (main.automator.value.state as unknown as AutomatorState).portals.includes(id), + repeatables as unknown as GenericClickable[] + ); + const resourceChange = computed(() => { const preview = previews.find(p => p.shouldShowPreview.value); if (preview) { @@ -1174,6 +1192,21 @@ export function createPlane( ) ); + const renderableFeatures = computed(() => { + const lastIndex = features.findIndex( + (row, i) => i > 0 && i % 2 === 0 && !(features[i - 1][0] as Treasure).earned.value + ); + let featuresToRender; + if (lastIndex === -1) { + featuresToRender = features; + } else { + featuresToRender = features.slice(0, lastIndex); + } + return featuresToRender.map((row, i) => + i in displays ? render(displays[i]) : renderRow(...row) + ); + }); + return { tier: persistent(tier), seed: persistent(seed), @@ -1254,17 +1287,7 @@ export function createPlane( ) : null} - {features - .slice( - 0, - features.findIndex( - (row, i) => - i > 0 && - i % 2 === 0 && - !(features[i - 1][0] as Treasure).earned.value - ) - ) - .map((row, i) => (i in displays ? render(displays[i]) : renderRow(...row)))} + {renderableFeatures.value} {render(modifiersModal)} )), diff --git a/src/data/projEntry.tsx b/src/data/projEntry.tsx index ba0558c..feddc9d 100644 --- a/src/data/projEntry.tsx +++ b/src/data/projEntry.tsx @@ -37,6 +37,7 @@ import { } from "./boardUtils"; import { Section, createCollapsibleModifierSections, createFormulaPreview } from "./common"; import { + AutomatorState, BoosterState, DowsingState, EmpowererState, @@ -68,7 +69,8 @@ import { portalGenerator, quarry, resource, - upgrader + upgrader, + automator } from "./nodeTypes"; import { GenericPlane, createPlane } from "./planes"; @@ -87,7 +89,8 @@ const types = { portal, influence, booster, - upgrader + upgrader, + automator }; /** @@ -113,7 +116,8 @@ export const main = createLayer("main", function (this: BaseLayer) { coal: board.types.empowerer.nodes.value[0], iron: board.types.portalGenerator.nodes.value[0], gold: board.types.booster.nodes.value[0], - platinum: board.types.upgrader.nodes.value[0] + platinum: board.types.upgrader.nodes.value[0], + berylium: board.types.automator.nodes.value[0] })); const influenceNodes: ComputedRef> = computed(() => ({ @@ -123,6 +127,13 @@ export const main = createLayer("main", function (this: BaseLayer) { }, {} as Record) })); + const portalNodes: ComputedRef> = computed(() => ({ + ...board.types.portal.nodes.value.reduce((acc, curr) => { + acc[(curr.state as unknown as PortalState).id] = curr; + return acc; + }, {} as Record) + })); + const resourceLevels = computed(() => resourceNames.reduce((acc, curr) => { const amount = @@ -337,10 +348,7 @@ export const main = createLayer("main", function (this: BaseLayer) { links.push({ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion startNode: booster.value!, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - endNode: (board as GenericBoard).types.portal.nodes.value.find( - node => (node.state as unknown as PortalState).id === portal - )!, + endNode: portalNodes.value[portal], // eslint-disable-next-line @typescript-eslint/no-non-null-assertion stroke: isPowered(booster.value!) ? "var(--accent1)" : "var(--foreground)", strokeWidth: 4 @@ -353,9 +361,19 @@ export const main = createLayer("main", function (this: BaseLayer) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion startNode: upgrader.value!, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - endNode: (board as GenericBoard).types.portal.nodes.value.find( - node => (node.state as unknown as PortalState).id === portal - )!, + endNode: portalNodes.value[portal], + stroke: "var(--foreground)", + strokeWidth: 4 + }); + }); + } + if (automator.value != null) { + (automator.value.state as unknown as AutomatorState).portals.forEach(portal => { + links.push({ + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + startNode: automator.value!, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + endNode: portalNodes.value[portal], stroke: "var(--foreground)", strokeWidth: 4 }); @@ -390,7 +408,8 @@ export const main = createLayer("main", function (this: BaseLayer) { ); const booster: ComputedRef = computed(() => toolNodes.value.gold); const upgrader: ComputedRef = computed(() => toolNodes.value.platinum); - const poweredMachines = [mine, dowsing, quarry, empowerer, booster, upgrader]; + const automator: ComputedRef = computed(() => toolNodes.value.berylium); + const poweredMachines = [mine, dowsing, quarry, empowerer, booster, upgrader, automator]; function grantResource(type: Resources, amount: DecimalSource) { let node = resourceNodes.value[type]; @@ -800,6 +819,7 @@ export const main = createLayer("main", function (this: BaseLayer) { checkConnections(curr, empowerer, "tools"); checkConnections(curr, booster, "portals"); checkConnections(curr, upgrader, "portals"); + checkConnections(curr, automator, "portals"); } }); @@ -822,6 +842,7 @@ export const main = createLayer("main", function (this: BaseLayer) { empowerer, booster, upgrader, + automator, resourceLevels, planarMultis, display: jsx(() => ( diff --git a/src/features/clickables/clickable.ts b/src/features/clickables/clickable.ts index cfd578e..442ba4d 100644 --- a/src/features/clickables/clickable.ts +++ b/src/features/clickables/clickable.ts @@ -1,14 +1,15 @@ import ClickableComponent from "features/clickables/Clickable.vue"; import { GenericDecorator } from "features/decorators/common"; -import type { +import { CoercableComponent, GenericComponent, OptionsFunc, Replace, - StyleValue + StyleValue, + findFeatures } from "features/feature"; import { Component, GatherProps, Visibility, getUniqueID, setDefault } from "features/feature"; -import type { BaseLayer } from "game/layers"; +import type { BaseLayer, GenericLayer } from "game/layers"; import type { Unsubscribe } from "nanoevents"; import type { Computable, @@ -190,15 +191,19 @@ export function createClickable( * @param autoActive Whether or not the clickable should currently be auto-clicking */ export function setupAutoClick( - layer: BaseLayer, - clickable: GenericClickable, - autoActive: Computable = true + layer: GenericLayer, + autoActive: Computable, + clickables: GenericClickable[] = [] ): Unsubscribe { + clickables = + clickables.length === 0 + ? (findFeatures(layer, ClickableType) as GenericClickable[]) + : clickables; const isActive: ProcessedComputable = typeof autoActive === "function" ? computed(autoActive) : autoActive; return layer.on("update", () => { - if (unref(isActive) && unref(clickable.canClick)) { - clickable.onClick?.(); + if (unref(isActive)) { + clickables.filter(c => unref(c.canClick)).forEach(c => c.onClick?.()); } }); }