diff --git a/src/data/Scene.vue b/src/data/Scene.vue index 8dc001f..7e9bb24 100644 --- a/src/data/Scene.vue +++ b/src/data/Scene.vue @@ -19,6 +19,12 @@ class="scene-item" style="left: 26%; bottom: 12%; width: 40px; height: 40px" /> + +
( /** An option object for a modifier display as a single section. **/ export interface Section { /** The header for this modifier. **/ - title: string; + title: Computable; /** A subtitle for this modifier, e.g. to explain the context for the modifier. **/ - subtitle?: string; + subtitle?: Computable; /** The modifier to be displaying in this section. **/ modifier: WithRequired; /** The base value being modified. **/ @@ -276,6 +276,8 @@ export function createCollapsibleModifierSections( base: ProcessedComputable[]; baseText: ProcessedComputable[]; visible: ProcessedComputable[]; + title: ProcessedComputable[]; + subtitle: ProcessedComputable[]; } | Record = {}; let calculated = false; @@ -285,6 +287,8 @@ export function createCollapsibleModifierSections( processed.base = sections.map(s => convertComputable(s.base)); processed.baseText = sections.map(s => convertComputable(s.baseText)); processed.visible = sections.map(s => convertComputable(s.visible)); + processed.title = sections.map(s => convertComputable(s.title)); + processed.subtitle = sections.map(s => convertComputable(s.subtitle)); calculated = true; } return sections; @@ -307,8 +311,10 @@ export function createCollapsibleModifierSections( > ▼ - {s.title} - {s.subtitle != null ? ({s.subtitle}) : null} + {unref(processed.title[i])} + {unref(processed.subtitle[i]) != null ? ( + ({unref(processed.subtitle[i])}) + ) : null} ); diff --git a/src/data/layers/factory.tsx b/src/data/layers/factory.tsx index b8346bd..b6e8db7 100644 --- a/src/data/layers/factory.tsx +++ b/src/data/layers/factory.tsx @@ -101,9 +101,6 @@ const toyGoal = 750; const advancedToyGoal = 1500; const presentsGoal = 8e9; -// 20x20 block size -// TODO: unhardcode stuff - function roundDownTo(num: number, multiple: number) { return Math.floor((num + multiple / 2) / multiple) * multiple; } @@ -508,8 +505,7 @@ const factory = createLayer(id, () => { plank: { amount: computed(() => (upgrades[0][0].bought.value ? 2 : 1)) } - }, - visible: main.days[presentsDay - 1].opened + } } as FactoryComponentDeclaration, thread: { imageSrc: _threadMaker, @@ -671,7 +667,8 @@ const factory = createLayer(id, () => { box: { amount: 2 } - } + }, + visible: main.days[presentsDay - 1].opened } as FactoryComponentDeclaration, blocks: { imageSrc: _blockMaker, @@ -1641,6 +1638,7 @@ const factory = createLayer(id, () => { const compData = _compData as FactoryInternalProcessor; // factory part // PRODUCTION + data.ticksDone += factoryTicks; if (data.ticksDone >= factoryData.tick) { if (compData.canProduce.value) { const cyclesDone = Math.floor(data.ticksDone / factoryData.tick); @@ -1678,8 +1676,6 @@ const factory = createLayer(id, () => { if (compData.lastProdTimes.length > 10) compData.lastProdTimes.shift(); compData.lastFactoryProd = now; } - } else { - data.ticksDone += factoryTicks; } // now look at each component direction and see if it accepts items coming in // components are 1x1 so simple math for now @@ -2247,12 +2243,12 @@ const factory = createLayer(id, () => { style={{ color: (compInternalHovered.value as FactoryInternalProcessor) - .average.value! > 1 - ? "purple" + .average.value! >= 0.995 + ? "fuchsia" : ( compInternalHovered.value as FactoryInternalProcessor ).average.value! >= 0.9 - ? "green" + ? "lime" : ( compInternalHovered.value as FactoryInternalProcessor ).average.value! >= 0.5 diff --git a/src/data/layers/metal.tsx b/src/data/layers/metal.tsx index c6d338f..e74aeb1 100644 --- a/src/data/layers/metal.tsx +++ b/src/data/layers/metal.tsx @@ -38,6 +38,7 @@ import workshop from "./workshop"; import wrappingPaper from "./wrapping-paper"; import toys from "./toys"; import reindeer from "./reindeer"; +import sleigh from "./sleigh"; const id = "metal"; const day = 7; @@ -111,6 +112,11 @@ const layer = createLayer(id, function (this: BaseLayer) { exponent: 1.1, description: "Mary Level 2", enabled: management.elfTraining.heatedPlanterElfTraining.milestones[1].earned + })), + createExponentialModifier(() => ({ + exponent: 1.2, + description: "100% Sleigh Fixed", + enabled: sleigh.milestones.milestone8.earned })) ]); const computedOrePurity = computed(() => orePurity.apply(0.1)); @@ -182,6 +188,11 @@ const layer = createLayer(id, function (this: BaseLayer) { description: "Jazzy Wrapping Paper", enabled: computed(() => Decimal.gt(wrappingPaper.boosts.jazzy1.value, 1)) })), + createMultiplicativeModifier(() => ({ + multiplier: 2, + description: "30% Sleigh Fixed", + enabled: sleigh.milestones.milestone4.earned + })), createAdditiveModifier(() => ({ addend: () => Decimal.sub(lastOreGained.value, lastOreSmelted.value).max(0), description: "Metal Decoration", @@ -291,6 +302,11 @@ const layer = createLayer(id, function (this: BaseLayer) { description: "1000 Letters Processed", enabled: letters.milestones.miningMilestone.earned })), + createMultiplicativeModifier(() => ({ + multiplier: 2, + description: "30% Sleigh Fixed", + enabled: sleigh.milestones.milestone4.earned + })), createMultiplicativeModifier(() => ({ multiplier: () => Decimal.add(toys.clothes.value, 1), description: "Give elves clothes to wear", diff --git a/src/data/layers/plastic.tsx b/src/data/layers/plastic.tsx index 4061179..6023a10 100644 --- a/src/data/layers/plastic.tsx +++ b/src/data/layers/plastic.tsx @@ -38,6 +38,7 @@ import paper from "./paper"; import workshop from "./workshop"; import toys from "./toys"; import reindeer from "./reindeer"; +import sleigh from "./sleigh"; const id = "plastic"; const day = 10; @@ -320,6 +321,14 @@ const layer = createLayer(id, function (this: BaseLayer) { description: "Oil Refinery", enabled: () => Decimal.gt(activeRefinery.value, 0) })), + createAdditiveModifier(() => ({ + addend: () => + management.elfTraining.oilElfTraining.milestones[3].earned.value + ? Decimal.times(Decimal.div(sleigh.sleighProgress.value.value,2).floor(), 200) + : Decimal.times(activeRefinery.value, 40), + description: "75% Sleigh Fixed", + enabled: sleigh.milestones.milestone7.earned + })), createMultiplicativeModifier(() => ({ multiplier: 2, description: "Paper Elf Recruitment", @@ -385,6 +394,16 @@ const layer = createLayer(id, function (this: BaseLayer) { multiplier: () => dyes.boosts.white1.value, description: "White Dye Boost" })), + createMultiplicativeModifier(() => ({ + multiplier: () => Decimal.div(sleigh.sleighProgress.value.value, 5).floor().mul(0.05).add(1), + description: "20% Sleigh Fixed", + enabled: sleigh.milestones.milestone3.earned + })), + createMultiplicativeModifier(() => ({ + multiplier: 4, + description: "40% Sleigh Fixed", + enabled: sleigh.milestones.milestone5.earned + })), reindeer.reindeer.blitzen.modifier ]); const computedPlasticGain = computed(() => plasticGain.apply(0)); diff --git a/src/data/layers/reindeer.tsx b/src/data/layers/reindeer.tsx index 74795a4..1523ad0 100644 --- a/src/data/layers/reindeer.tsx +++ b/src/data/layers/reindeer.tsx @@ -74,6 +74,7 @@ const layer = createLayer(id, function (this: BaseLayer) { const computedMaxCooldown = computed(() => 10); function focus() { + currCooldown.value = Decimal.fromValue(computedMaxCooldown.value).toNumber(); let targetsSelected = 0; currTargets.value = {}; timeSinceFocus.value = 0; @@ -141,7 +142,6 @@ const layer = createLayer(id, function (this: BaseLayer) { }, canClick: () => Decimal.eq(currCooldown.value, 0), onClick() { - currCooldown.value = Decimal.fromValue(computedMaxCooldown.value).toNumber(); focus(); } })); @@ -488,7 +488,7 @@ const layer = createLayer(id, function (this: BaseLayer) { let auto = false; if (upgrade7.bought.value) { timeSinceFocus.value += diff; - if (timeSinceFocus.value > 10) { + if (timeSinceFocus.value > 20) { auto = true; } } diff --git a/src/data/layers/routing.tsx b/src/data/layers/routing.tsx new file mode 100644 index 0000000..887dbb1 --- /dev/null +++ b/src/data/layers/routing.tsx @@ -0,0 +1,870 @@ +/** + * @module + * @hidden + */ +import Spacer from "components/layout/Spacer.vue"; +import Modal from "components/Modal.vue"; +import { createCollapsibleMilestones, createCollapsibleModifierSections } from "data/common"; +import { main } from "data/projEntry"; +import { createBar, GenericBar } from "features/bars/bar"; +import { BoardNode, BoardNodeLink, createBoard, Shape } from "features/boards/board"; +import { createClickable } from "features/clickables/clickable"; +import { jsx, showIf } from "features/feature"; +import { createMilestone } from "features/milestones/milestone"; +import MainDisplay from "features/resources/MainDisplay.vue"; +import { createResource } from "features/resources/resource"; +import { globalBus } from "game/events"; +import { BaseLayer, createLayer } from "game/layers"; +import { + createAdditiveModifier, + createExponentialModifier, + createMultiplicativeModifier, + createSequentialModifier +} from "game/modifiers"; +import { persistent } from "game/persistence"; +import Decimal, { DecimalSource, format, formatWhole } from "util/bignum"; +import { Direction } from "util/common"; +import { render, renderRow } from "util/vue"; +import { computed, ComputedRef, ref, unref, watchEffect } from "vue"; +import "./styles/routing.css"; + +const alpha = [ + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z" +]; + +const id = "routing"; +const day = 23; +const layer = createLayer(id, function (this: BaseLayer) { + const name = "Routing"; + const color = "navajowhite"; + + const citiesGoal = 10; + + const citiesCompleted = createResource(0, "countries solved"); + const currentCity = persistent([]); + const routeIndex = persistent(0); + const checkRouteProgress = persistent(0); + const redundanciesRemoved = persistent(0); + + const currentRoutes = computed(() => { + // Manually check milestone req here due to calling generateCity() before milestones get earned + if (Decimal.gte(citiesCompleted.value, 7)) { + return Decimal.factorial(currentCity.value.length).div(2).toNumber(); + } + // Permutation code from https://stackoverflow.com/a/37580979 + const length = currentCity.value.length; + const permutation = new Array(length).fill(0).map((_, i) => i); + const result = [permutation.slice()]; + const c = new Array(length).fill(0); + let i = 1; + + while (i < length) { + if (c[i] < i) { + const k = i % 2 && c[i]; + const p = permutation[i]; + permutation[i] = permutation[k]; + permutation[k] = p; + ++c[i]; + i = 1; + result.push(permutation.slice()); + } else { + c[i] = 0; + ++i; + } + } + return result; + }); + const redundantRoutes = computed(() => { + const routes = currentRoutes.value; + if (typeof routes === "number") { + return []; + } + const redundancies = []; + for (let i = 0; i < routes.length; i++) { + if (routes[i][0] > routes[i][1]) { + redundancies.push(i); + } + } + return redundancies; + }); + const routesToSkip = persistent([]); + + const currentRoute: ComputedRef = computed(() => + typeof currentRoutes.value === "number" + ? currentCity.value.length + : currentRoutes.value[routeIndex.value] + ); + + const currentRouteDuration = computed(() => { + const route = currentRoute.value; + if (route == null) { + return 0; + } else if (typeof route === "number") { + return Decimal.times(route, computedMinWeight.value).floor().toNumber(); + } + let duration = 0; + for (let i = 0; i < route.length - 1; i++) { + duration += currentCity.value[route[i]][route[i + 1]]; + } + return duration; + }); + + globalBus.on("onLoad", () => { + if (currentCity.value.length === 0) { + generateCity(); + } + }); + + function stringifyRoute(route: number[]) { + return route + .map(h => (city.types.house.title as (node: BoardNode) => string)(city.nodes.value[h])) + .join("->"); + } + + function generateCity() { + const numHouses = new Decimal(computedHouses.value).clampMin(3).toNumber(); + const min = computedMinWeight.value; + const max = milestone6.earned.value ? min : computedMaxWeight.value; + const diff = Decimal.sub(max, min); + const city: number[][] = []; + for (let i = 0; i < numHouses; i++) { + const house: number[] = []; + for (let j = 0; j < numHouses; j++) { + if (i === j) { + house.push(0); + } else if (j < i) { + house.push(city[j][i]); + } else { + house.push(Decimal.times(diff, Math.random()).add(min).floor().toNumber()); + } + } + city.push(house); + } + currentCity.value = city; + routeIndex.value = 0; + redundanciesRemoved.value = Decimal.gte(citiesCompleted.value, 7) + ? Decimal.factorial(currentCity.value.length).div(2).toNumber() + : 0; + routesToSkip.value = []; + getNextRoute(); + } + + function getNextRoute() { + const numRoutes = + typeof currentRoutes.value === "number" + ? currentRoutes.value + : currentRoutes.value.length; + while (routeIndex.value <= numRoutes && routesToSkip.value.includes(routeIndex.value)) { + routeIndex.value++; + } + if (routeIndex.value >= numRoutes) { + citiesCompleted.value = Decimal.add(citiesCompleted.value, 1); + generateCity(); + } else { + if (redundantRoutes.value.includes(routeIndex.value)) { + routesToSkip.value = [...routesToSkip.value, routeIndex.value]; + } + checkRouteProgress.value = 0; + } + } + + const newCityProgress = persistent(0); + const newCityProgressBar = createBar(() => ({ + direction: Direction.Right, + width: 100, + height: 10, + style: "margin-top: 8px", + borderStyle: "border-color: black", + baseStyle: "margin-top: -1px", + fillStyle: "margin-top: -1px; transition-duration: 0s; background: black", + progress: () => Decimal.div(newCityProgress.value, 10) + })); + const getNewCity = createClickable(() => ({ + display: { + description: jsx(() => ( + <> + Generate New Country +
+ {render(newCityProgressBar)} + + )) + }, + style: { + minHeight: "40px", + "--layer-color": "var(--danger)" + }, + canClick: () => Decimal.gte(newCityProgress.value, 10), + onClick() { + if (!unref(getNewCity.canClick)) { + return; + } + generateCity(); + newCityProgress.value = 0; + } + })); + + const boostProgress = persistent(0); + const boostProgressBar = createBar(() => ({ + direction: Direction.Right, + width: 100, + height: 10, + style: "margin-top: 8px", + borderStyle: "border-color: black", + baseStyle: "margin-top: -1px", + fillStyle: "margin-top: -1px; transition-duration: 0s; background: black", + progress: () => Decimal.div(boostProgress.value, computedManualCooldown.value) + })); + const boost = createClickable(() => ({ + display: { + description: jsx(() => ( + <> + Perform {formatWhole(computedManualBoost.value)} units of work +
+ {render(boostProgressBar)} + + )) + }, + style: { + minHeight: "40px" + }, + canClick: () => Decimal.gte(boostProgress.value, computedManualCooldown.value), + onClick() { + if (!unref(boost.canClick)) { + return; + } + checkRouteProgress.value = Decimal.add( + checkRouteProgress.value, + computedManualBoost.value + ).toNumber(); + boostProgress.value = 0; + } + })); + + const redundantProgress = persistent(0); + const redundantProgressBar = createBar(() => ({ + direction: Direction.Right, + width: 100, + height: 10, + style: "margin-top: 8px", + borderStyle: "border-color: black", + baseStyle: "margin-top: -1px", + fillStyle: "margin-top: -1px; transition-duration: 0s; background: black", + progress: () => Decimal.div(redundantProgress.value, computedRedundantCooldown.value) + })); + const removeRedundantRoute = createClickable(() => ({ + display: { + description: jsx(() => ( + <> + Remove a redundant route from the list to check +
+ {render(redundantProgressBar)} + + )) + }, + style: { + minHeight: "40px" + }, + visibility: () => showIf(!milestone7.earned.value), + canClick: () => + Decimal.gte(redundantProgress.value, computedRedundantCooldown.value) && + routesToSkip.value.length < redundantRoutes.value.length, + onClick() { + if (!unref(removeRedundantRoute.canClick)) { + return; + } + routesToSkip.value = [ + ...routesToSkip.value, + redundantRoutes.value[routesToSkip.value.length] + ]; + redundantProgress.value = 0; + redundanciesRemoved.value++; + } + })); + + const city = createBoard(() => ({ + startNodes: () => [], + types: { + house: { + shape: Shape.Circle, + fillColor: "var(--highlighted)", + outlineColor: "var(--accent1)", + size: 20, + title(node) { + let letter = node.state as number; + let name = ""; + while (true) { + if (letter < 26) { + name += alpha[letter]; + break; + } + let thisLetter = letter; + let iterations = 0; + while (Math.floor(thisLetter / 26) - 1 >= 0) { + thisLetter = Math.floor(thisLetter / 26) - 1; + iterations++; + } + name += alpha[thisLetter]; + + let amountToDecrement = thisLetter + 1; + for (let i = 0; i < iterations; i++) { + amountToDecrement *= 26; + } + letter -= amountToDecrement; + } + return name; + } + } + }, + width: "600px", + height: "600px", + style: { + background: "var(--raised-background)", + borderRadius: "var(--border-radius) var(--border-radius) 0 0", + boxShadow: "0 2px 10px rgb(0 0 0 / 50%)" + }, + state: computed(() => { + const nodes: BoardNode[] = []; + const city = currentCity.value; + const rows = Math.ceil(Math.sqrt(city.length)); + const cols = Math.ceil(city.length / rows); + for (let i = 0; i < city.length; i++) { + const row = Math.floor(i / rows); + const col = Math.floor(i % rows); + const randomOffsetIndex = i + new Decimal(citiesCompleted.value).toNumber(); + nodes.push({ + id: i, + position: { + x: 160 * (-(cols - 1) / 2 + col) + Math.cos(randomOffsetIndex) * 40, + y: 160 * (-(rows - 1) / 2 + row) + Math.sin(randomOffsetIndex) * 40 + }, + type: "house", + state: i + }); + } + return { + nodes, + selectedNode: null, + selectedAction: null + }; + }), + links() { + const links: BoardNodeLink[] = []; + const route = currentRoute.value; + if (route == null) { + return links; + } + const citySize = currentCity.value.length; + let completedLegs = 0; + let progress = 0; + let partialLeg = 0; + if (typeof route !== "number") { + for (let i = 0; i < route.length - 1; i++) { + const weight = progress + currentCity.value[route[i]][route[i + 1]]; + if (checkRouteProgress.value > weight) { + completedLegs++; + progress = weight; + } else { + break; + } + } + partialLeg = + (checkRouteProgress.value - progress) / + currentCity.value[route[completedLegs]][route[completedLegs + 1]]; + } + for (let i = 0; i < citySize; i++) { + for (let j = 0; j < citySize; j++) { + if (i !== j) { + let progress = 0; + if (typeof route !== "number") { + const iIndex = route.indexOf(i); + const jIndex = route.indexOf(j); + if ( + iIndex >= 0 && + jIndex >= 0 && + (route[iIndex + 1] === j || route[jIndex + 1] === i) + ) { + if (jIndex < completedLegs || iIndex < completedLegs) { + progress = 1; + } else if (jIndex === completedLegs || iIndex === completedLegs) { + progress = partialLeg; + } + } + } + links.push({ + style: "transition-duration: 0s", + startNode: city.nodes.value[i], + endNode: city.nodes.value[j], + stroke: `rgb(${progress * 255}, 0, 0)`, + "stroke-width": 2 * progress + 2, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + weight: i < j ? null : currentCity.value[i][j] + }); + } + } + } + return links; + } + })); + + const checkRouteProgressBar = createBar(() => ({ + direction: Direction.Right, + width: 597, + height: 24, + style: { + borderRadius: "0 0 var(--border-radius) var(--border-radius)", + background: "var(--raised-background)", + marginTop: "-24px" + }, + borderStyle: { + borderRadius: "0 0 var(--border-radius) var(--border-radius)", + borderColor: "var(--outline)", + marginTop: "unset" + }, + fillStyle: { + background: "black", + marginTop: "unset" + }, + progress() { + return Decimal.div(checkRouteProgress.value, currentRouteDuration.value); + }, + display: jsx(() => ( + <> + {Math.floor(checkRouteProgress.value)}/{currentRouteDuration.value} + + )) + })); + + const milestone1 = createMilestone(() => ({ + display: { + requirement: "1 Country Solved", + effectDisplay: "Each country solved doubles manual and auto processing speed" + }, + shouldEarn() { + return Decimal.gte(citiesCompleted.value, 1); + } + })); + const milestone2 = createMilestone(() => ({ + display: { + requirement: "2 Countries Solved", + effectDisplay: + "Manually checking routes does additional work based on number of routes checked in this country" + }, + shouldEarn() { + return Decimal.gte(citiesCompleted.value, 2); + }, + visibility: () => showIf(milestone1.earned.value) + })); + const milestone3 = createMilestone(() => ({ + display: { + requirement: "3 Countries Solved", + effectDisplay: + "Each country solved makes the cooldown for removing a redundant route 25% shorter" + }, + shouldEarn() { + return Decimal.gte(citiesCompleted.value, 3); + }, + visibility: () => showIf(milestone2.earned.value) + })); + const milestone4 = createMilestone(() => ({ + display: { + requirement: "4 Countries Solved", + effectDisplay: + "Automatic processing speed is multiplied by the amount of redundant routes removed from this country" + }, + shouldEarn() { + return Decimal.gte(citiesCompleted.value, 4); + }, + visibility: () => showIf(milestone3.earned.value) + })); + const milestone5 = createMilestone(() => ({ + display: { + requirement: "5 Countries Solved", + effectDisplay: "Remove 1 city" + }, + shouldEarn() { + return Decimal.gte(citiesCompleted.value, 5); + }, + onComplete() { + generateCity(); + }, + visibility: () => showIf(milestone4.earned.value) + })); + const milestone6 = createMilestone(() => ({ + display: { + requirement: "6 Countries Solved", + effectDisplay: + "Lower max weight to the min weight, and uncap amount of routes that can be checked per tick" + }, + shouldEarn() { + return Decimal.gte(citiesCompleted.value, 6); + }, + visibility: () => showIf(milestone5.earned.value) + })); + const milestone7 = createMilestone(() => ({ + display: { + requirement: "7 Countries Solved", + effectDisplay: "All redundancies are removed" + }, + shouldEarn() { + return Decimal.gte(citiesCompleted.value, 7); + }, + visibility: () => showIf(milestone6.earned.value) + })); + const milestones = { + milestone1, + milestone2, + milestone3, + milestone4, + milestone5, + milestone6, + milestone7 + }; + const { collapseMilestones, display: milestonesDisplay } = + createCollapsibleMilestones(milestones); + + const houses = createSequentialModifier(() => [ + createAdditiveModifier(() => ({ + addend: citiesCompleted, + description: "Countries Completed" + })), + createAdditiveModifier(() => ({ + addend: 1, + description: "5 Countries Completed", + enabled: milestone5.earned + })) + ]); + const computedHouses = computed(() => houses.apply(3)); + const maxWeight = createSequentialModifier(() => [ + createAdditiveModifier(() => ({ + addend: () => Decimal.pow(citiesCompleted.value, 1.1), + description: "Countries Completed" + })) + ]); + const computedMaxWeight = computed(() => maxWeight.apply(10)); + const minWeight = createSequentialModifier(() => [ + createAdditiveModifier(() => ({ + addend: citiesCompleted, + description: "Countries Completed" + })), + createExponentialModifier(() => ({ + exponent: 3, + description: "Countries Completed", + enabled: milestone7.earned + })) + ]); + const computedMinWeight = computed(() => minWeight.apply(2)); + const manualBoost = createSequentialModifier(() => [ + createAdditiveModifier(() => ({ + addend: () => Decimal.add(routeIndex.value, 1).sqrt(), + description: "2 Countries Solved", + enabled: milestone2.earned + })) + ]); + const computedManualBoost = computed(() => manualBoost.apply(1)); + const manualCooldown = createSequentialModifier(() => [ + createMultiplicativeModifier(() => ({ + multiplier: () => Decimal.pow(0.5, citiesCompleted.value), + description: "1 Country Solved", + enabled: milestone1.earned + })) + ]); + const computedManualCooldown = computed(() => manualCooldown.apply(1)); + const redundantCooldown = createSequentialModifier(() => [ + createMultiplicativeModifier(() => ({ + multiplier: () => Decimal.pow(0.75, citiesCompleted.value), + description: "3 Countries Solved", + enabled: milestone3.earned + })) + ]); + const computedRedundantCooldown = computed(() => redundantCooldown.apply(10)); + const autoProcessing = createSequentialModifier(() => [ + createMultiplicativeModifier(() => ({ + multiplier: () => Decimal.pow(2, citiesCompleted.value), + description: "1 Country Solved", + enabled: milestone1.earned + })), + createMultiplicativeModifier(() => ({ + multiplier: () => Decimal.add(redundanciesRemoved.value, 1), + description: "4 Countries Solved", + enabled: milestone4.earned + })) + ]); + const computedAutoProcessing = computed(() => autoProcessing.apply(1)); + + const [generalTab, generalTabCollapsed] = createCollapsibleModifierSections(() => [ + { + title: "Cities/country", + modifier: houses, + base: 3 + }, + { + title: () => (milestone6.earned.value ? "Weight" : "Minimum Weight"), + modifier: minWeight, + base: 2 + }, + { + title: "Maximum Weight", + modifier: maxWeight, + base: 10, + visible: () => !milestone6.earned.value + }, + { + title: "Manual Processing Amount", + modifier: manualBoost, + base: 1 + }, + { + title: "Manual Processing Cooldown", + modifier: manualCooldown, + base: 1, + unit: "s" + }, + { + title: "Remove Redundant Route Cooldown", + modifier: redundantCooldown, + base: 10, + unit: "s" + }, + { + title: "Auto Processing Speed", + modifier: autoProcessing, + base: 1, + unit: "/s" + } + ]); + const showModifiersModal = ref(false); + const modifiersModal = jsx(() => ( + (showModifiersModal.value = value)} + v-slots={{ + header: () =>

{name} Modifiers

, + body: generalTab + }} + /> + )); + + globalBus.on("update", diff => { + if (Decimal.lt(main.day.value, day)) { + return; + } + + if (Decimal.gte(newCityProgress.value, 10)) { + newCityProgress.value = 10; + } else { + newCityProgress.value = Decimal.add(newCityProgress.value, diff); + if (getNewCity.isHolding.value) { + getNewCity.onClick(); + } + } + + if (Decimal.gte(boostProgress.value, computedManualCooldown.value)) { + boostProgress.value = computedManualCooldown.value; + } else { + boostProgress.value = Decimal.add(boostProgress.value, diff); + if (boost.isHolding.value) { + boost.onClick(); + } + } + + if (Decimal.gte(redundantProgress.value, computedRedundantCooldown.value)) { + redundantProgress.value = computedRedundantCooldown.value; + } else { + redundantProgress.value = Decimal.add(redundantProgress.value, diff); + if (removeRedundantRoute.isHolding.value) { + removeRedundantRoute.onClick(); + } + } + + checkRouteProgress.value = Decimal.times(diff, computedAutoProcessing.value) + .add(checkRouteProgress.value) + .toNumber(); + if (checkRouteProgress.value > currentRouteDuration.value) { + const overflow = checkRouteProgress.value - currentRouteDuration.value; + routeIndex.value++; + if (milestone6.earned.value && currentRoute.value != null) { + const length = + typeof currentRoute.value === "number" + ? currentRoute.value + : currentRoute.value.length; + const extraRoutes = Decimal.div( + overflow, + Decimal.times(length, computedMinWeight.value) + ) + .floor() + .toNumber(); + routeIndex.value += extraRoutes; + } + getNextRoute(); + } + }); + + const dayProgress = createBar(() => ({ + direction: Direction.Right, + width: 600, + height: 25, + fillStyle: `backgroundColor: ${color}`, + textStyle: { + color: "var(--feature-foreground)" + }, + progress: () => + main.day.value === day ? Decimal.div(citiesCompleted.value, citiesGoal) : 1, + display: jsx(() => + main.day.value === day ? ( + <> + {formatWhole(citiesCompleted.value)}/{formatWhole(citiesGoal)} + + ) : ( + "" + ) + ) + })) as GenericBar; + + watchEffect(() => { + if (main.day.value === day && Decimal.gte(citiesCompleted.value, citiesGoal)) { + main.completeDay(); + } + }); + + function displayRoutes() { + if (currentRoute.value == null) { + return ""; + } + if (typeof currentRoutes.value === "number") { + return ( +
+ {routeIndex.value > 0 ? ( +
{formatWhole(routeIndex.value)} already checked
+ ) : null} +
+ {formatWhole(currentRoutes.value - routeIndex.value)} routes left to check +
+
+ ); + } + if (typeof currentRoutes.value === "number") { + console.error("Something went horribly wrong"); + return; + } + const routes = currentRoutes.value.slice(); + let showPrevious = false; + let showNext = 0; + if (routes.length > 6) { + routes.splice(0, routeIndex.value); + showPrevious = true; + if (routes.length > 6) { + showNext = routes.length - 5; + routes.splice(5); + } + } + return ( +
+ {showPrevious && routeIndex.value > 0 ? ( +
{formatWhole(routeIndex.value)} already checked
+ ) : null} + {routes.map((route, i) => { + const index = i + (showPrevious ? routeIndex.value : 0); + return ( +
route[1], + checked: routeIndex.value > index, + processing: routeIndex.value === index, + skipped: + routeIndex.value < index && routesToSkip.value.includes(index) + }} + > + {stringifyRoute(route)} +
+ ); + })} + + {showNext > 0 ?
+ {formatWhole(showNext)} more
: null} +
+ ); + } + + return { + name, + day, + color, + citiesCompleted, + currentCity, + routeIndex, + checkRouteProgress, + newCityProgress, + boostProgress, + redundantProgress, + generalTabCollapsed, + currentRoutes, + redundantRoutes, + routesToSkip, + redundanciesRemoved, + city, + milestones, + collapseMilestones, + minWidth: 700, + display: jsx(() => ( + <> +
+ {main.day.value === day + ? `Solve ${formatWhole(citiesGoal)} countries to complete the day` + : `${name} Complete!`}{" "} + -{" "} + +
+ {render(dayProgress)} + {render(modifiersModal)} + + + {renderRow(getNewCity, boost, removeRedundantRoute)} + {render(city)} + {render(checkRouteProgressBar)} + +

Checking Routes...

+ {displayRoutes()} + + {milestonesDisplay()} + + )), + minimizedDisplay: jsx(() => ( +
+ {name} {format(citiesCompleted.value)} countries solved +
+ )) + }; +}); + +export default layer; diff --git a/src/data/layers/sleigh.tsx b/src/data/layers/sleigh.tsx index 93247e9..35bfc71 100644 --- a/src/data/layers/sleigh.tsx +++ b/src/data/layers/sleigh.tsx @@ -43,9 +43,9 @@ const layer = createLayer(id, function (this: BaseLayer) { const sleighCost = computed(() => { let v = sleighProgress.value.value; return { - wood: Decimal.mul(1e60, Decimal.pow(1.2, v)), - metal: Decimal.mul(1e40, Decimal.pow(1.1, v)), - plastic: Decimal.mul(1e10, Decimal.pow(1.05, v)) + wood: Decimal.mul(1e97, Decimal.pow(1.2, v)), + metal: Decimal.mul(1e67, Decimal.pow(1.1, v)), + plastic: Decimal.mul(1e22, Decimal.pow(1.05, v)) }; }); const sleigh = createBuyable(() => ({ @@ -55,7 +55,7 @@ const layer = createLayer(id, function (this: BaseLayer) {

- Cost: {displayCost(trees.logs, sleighCost.value.wood, "logs")}, + Requires: {displayCost(trees.logs, sleighCost.value.wood, "logs")}, {displayCost(metal.metal, sleighCost.value.metal, "metal")}, {displayCost(plastic.plastic, sleighCost.value.plastic, "plastic")} @@ -111,7 +111,7 @@ const layer = createLayer(id, function (this: BaseLayer) { const milestone5 = createMilestone(() => ({ display: { requirement: "40% Sleigh Fixed", - effectDisplay: "Plastic gain is doubled" + effectDisplay: "Plastic gain is quadrupled" }, shouldEarn: () => Decimal.gte(sleighProgress.value.value, 40), showPopups: shouldShowPopups @@ -127,7 +127,7 @@ const layer = createLayer(id, function (this: BaseLayer) { const milestone7 = createMilestone(() => ({ display: { requirement: "75% Sleigh Fixed", - effectDisplay: "Gain 10 extra refineries for every 2% of sleigh fixed" + effectDisplay: "Gain 40 extra refineries for every 2% of sleigh fixed" }, shouldEarn: () => Decimal.gte(sleighProgress.value.value, 75), showPopups: shouldShowPopups diff --git a/src/data/layers/styles/routing.css b/src/data/layers/styles/routing.css new file mode 100644 index 0000000..bf7b2a3 --- /dev/null +++ b/src/data/layers/styles/routing.css @@ -0,0 +1,20 @@ +.routes-list .checked { + color: var(--bought); +} + +.routes-list .processing { + color: var(--layer-color); +} + +.routes-list .redundant:not(.checked):not(.processing) { + opacity: 0.5; +} + +.routes-list .skipped { + text-decoration: line-through; + text-decoration-thickness: 5px; +} + +.routes-list > * { + flex: 1 1 33%; +} diff --git a/src/data/layers/trees.tsx b/src/data/layers/trees.tsx index ef68f14..a0cdfed 100644 --- a/src/data/layers/trees.tsx +++ b/src/data/layers/trees.tsx @@ -41,6 +41,7 @@ import wrappingPaper from "./wrapping-paper"; import toys from "./toys"; import factory from "./factory"; import reindeer from "./reindeer"; +import sleigh from "./sleigh"; const id = "trees"; const day = 1; @@ -558,6 +559,16 @@ const layer = createLayer(id, function (this: BaseLayer) { description: "Haul wood in trucks", enabled: factory.upgrades[0][2].bought })), + createMultiplicativeModifier(() => ({ + multiplier: () => Decimal.div(sleigh.sleighProgress.value.value, 5).floor().mul(0.05).add(1), + description: "10% Sleigh Fixed", + enabled: sleigh.milestones.milestone2.earned + })), + createMultiplicativeModifier(() => ({ + multiplier: 10, + description: "50% Sleigh Fixed", + enabled: sleigh.milestones.milestone6.earned + })), reindeer.reindeer.dasher.modifier, createExponentialModifier(() => ({ exponent: 1.2, diff --git a/src/data/projEntry.tsx b/src/data/projEntry.tsx index f4d75f3..59ab98a 100644 --- a/src/data/projEntry.tsx +++ b/src/data/projEntry.tsx @@ -34,6 +34,7 @@ import paper from "./layers/paper"; import plastic from "./layers/plastic"; import reindeer from "./layers/reindeer"; import ribbon from "./layers/ribbon"; +import routing from "./layers/routing"; import toys from "./layers/toys"; import trees from "./layers/trees"; import workshop from "./layers/workshop"; @@ -64,6 +65,8 @@ import snowflakeSymbol from "./symbols/snowflake.svg"; import presentSymbol from "./layers/factory-components/present.svg"; import { createParticles } from "features/particles/particles"; import { credits } from "./credits"; +import sleighSymbol from "./symbols/sleigh.png"; +import routingSymbol from "./symbols/gps.png"; export interface Day extends VueFeature { day: number; @@ -620,19 +623,21 @@ export const main = createLayer("main", function (this: BaseLayer) { createDay(() => ({ day: 22, shouldNotify: false, - layer: "sleigh", // "sleigh" - symbol: "", - story: "default body", - completedStory: "", + layer: "sleigh", + symbol: sleighSymbol, + story: "You realize you haven't noticed a very important object since you've started working here. Where's the sleigh? You bring it up to Santa and he immediately becomes visibly stressed, mentioning it's been in disrepair and he completely forgot! You promise you'll get it back in shape in no time!", + completedStory: + "Crisis averted! The sleigh has been returned to it's full splendor. Santa is incredibly appreciative. Good Job!", masteredStory: "" })), createDay(() => ({ day: 23, shouldNotify: false, - layer: null, // "distribution route planning" - symbol: "", - story: "", - completedStory: "", + layer: "routing", + symbol: routingSymbol, + story: "You're almost ready for the big day! The next step is to find an optimal route to ensure you can get all the presents delivered before kids start waking up! This is like the travelling salesman problem on steroids. Good Luck!", + completedStory: + "Take that, math majors! Optimal route planned with time to spare. Good Job!", masteredStory: "" })), createDay(() => ({ @@ -796,7 +801,8 @@ export const getInitialLayers = ( toys, factory, reindeer, - sleigh + sleigh, + routing ]; /** diff --git a/src/data/symbols/gps.png b/src/data/symbols/gps.png new file mode 100644 index 0000000..154b28b Binary files /dev/null and b/src/data/symbols/gps.png differ diff --git a/src/data/symbols/sleigh.png b/src/data/symbols/sleigh.png new file mode 100644 index 0000000..644cf63 Binary files /dev/null and b/src/data/symbols/sleigh.png differ diff --git a/src/features/boards/BoardLink.vue b/src/features/boards/BoardLink.vue index cb61914..0f15200 100644 --- a/src/features/boards/BoardLink.vue +++ b/src/features/boards/BoardLink.vue @@ -8,6 +8,13 @@ :x2="endPosition.x" :y2="endPosition.y" /> + {{ link.weight }}