Merge branch 'main' into day-25-credits

This commit is contained in:
thepaperpilot 2022-12-24 00:52:05 -06:00
commit f3b3d3b6ae
17 changed files with 1055 additions and 63 deletions

View file

@ -19,6 +19,12 @@
class="scene-item"
style="left: 26%; bottom: 12%; width: 40px; height: 40px"
/>
<img
v-if="day >= 21"
:src="sleigh"
class="scene-item"
style="left: 10%; bottom: 56%; transform: rotate(24deg); width: 100px; height: 100px"
/>
<img
v-if="day >= 12"
:src="advManagement"
@ -52,6 +58,12 @@
class="scene-item"
style="left: 72%; bottom: 8%; width: 40px; height: 40px"
/>
<img
v-if="day >= 22"
:src="routing"
class="scene-item"
style="left: 76%; bottom: 4%; width: 40px; height: 40px"
/>
<img v-if="day >= 8" :src="oil" class="scene-item" style="left: 80%; bottom: 6%" />
<div
v-if="day >= 4"
@ -102,6 +114,8 @@ import factory from "./symbols/gears.png";
import advFactory from "./symbols/teddyBear.png";
import presents from "./symbols/presents.png";
import reindeer from "./symbols/reindeer.png";
import sleigh from "./symbols/sleigh.png";
import routing from "./symbols/gps.png";
defineProps<{
day: number;

View file

@ -247,9 +247,9 @@ export function createLayerTreeNode<T extends LayerTreeNodeOptions>(
/** An option object for a modifier display as a single section. **/
export interface Section {
/** The header for this modifier. **/
title: string;
title: Computable<string>;
/** A subtitle for this modifier, e.g. to explain the context for the modifier. **/
subtitle?: string;
subtitle?: Computable<string>;
/** The modifier to be displaying in this section. **/
modifier: WithRequired<Modifier, "description">;
/** The base value being modified. **/
@ -276,6 +276,8 @@ export function createCollapsibleModifierSections(
base: ProcessedComputable<DecimalSource | undefined>[];
baseText: ProcessedComputable<CoercableComponent | undefined>[];
visible: ProcessedComputable<boolean | undefined>[];
title: ProcessedComputable<string | undefined>[];
subtitle: ProcessedComputable<string | undefined>[];
}
| Record<string, never> = {};
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(
>
</span>
{s.title}
{s.subtitle != null ? <span class="subtitle"> ({s.subtitle})</span> : null}
{unref(processed.title[i])}
{unref(processed.subtitle[i]) != null ? (
<span class="subtitle"> ({unref(processed.subtitle[i])})</span>
) : null}
</h3>
);

View file

@ -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

View file

@ -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",

View file

@ -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));

View file

@ -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;
}
}

870
src/data/layers/routing.tsx Normal file
View file

@ -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<DecimalSource>(0, "countries solved");
const currentCity = persistent<number[][]>([]);
const routeIndex = persistent<number>(0);
const checkRouteProgress = persistent<number>(0);
const redundanciesRemoved = persistent<number>(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<number[]>([]);
const currentRoute: ComputedRef<number[] | number | undefined> = 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<DecimalSource>(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
<br />
{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<DecimalSource>(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
<br />
{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<DecimalSource>(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
<br />
{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(() => (
<Modal
modelValue={showModifiersModal.value}
onUpdate:modelValue={(value: boolean) => (showModifiersModal.value = value)}
v-slots={{
header: () => <h2>{name} Modifiers</h2>,
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 (
<div class="routes-list">
{routeIndex.value > 0 ? (
<div class="checked">{formatWhole(routeIndex.value)} already checked</div>
) : null}
<div>
{formatWhole(currentRoutes.value - routeIndex.value)} routes left to check
</div>
</div>
);
}
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 (
<div class="routes-list">
{showPrevious && routeIndex.value > 0 ? (
<div class="checked">{formatWhole(routeIndex.value)} already checked</div>
) : null}
{routes.map((route, i) => {
const index = i + (showPrevious ? routeIndex.value : 0);
return (
<div
class={{
redundant: route[0] > route[1],
checked: routeIndex.value > index,
processing: routeIndex.value === index,
skipped:
routeIndex.value < index && routesToSkip.value.includes(index)
}}
>
{stringifyRoute(route)}
</div>
);
})}
{showNext > 0 ? <div>+ {formatWhole(showNext)} more</div> : null}
</div>
);
}
return {
name,
day,
color,
citiesCompleted,
currentCity,
routeIndex,
checkRouteProgress,
newCityProgress,
boostProgress,
redundantProgress,
generalTabCollapsed,
currentRoutes,
redundantRoutes,
routesToSkip,
redundanciesRemoved,
city,
milestones,
collapseMilestones,
minWidth: 700,
display: jsx(() => (
<>
<div>
{main.day.value === day
? `Solve ${formatWhole(citiesGoal)} countries to complete the day`
: `${name} Complete!`}{" "}
-{" "}
<button
class="button"
style="display: inline-block;"
onClick={() => (showModifiersModal.value = true)}
>
Check Modifiers
</button>
</div>
{render(dayProgress)}
{render(modifiersModal)}
<Spacer />
<MainDisplay resource={citiesCompleted} color={color} />
{renderRow(getNewCity, boost, removeRedundantRoute)}
{render(city)}
{render(checkRouteProgressBar)}
<Spacer />
<h3>Checking Routes...</h3>
{displayRoutes()}
<Spacer />
{milestonesDisplay()}
</>
)),
minimizedDisplay: jsx(() => (
<div>
{name} <span class="desc">{format(citiesCompleted.value)} countries solved</span>
</div>
))
};
});
export default layer;

View file

@ -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) {
<br />
<br />
<span style="font-size: large">
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")}
</span>
@ -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

View file

@ -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%;
}

View file

@ -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,

View file

@ -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
];
/**

BIN
src/data/symbols/gps.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

BIN
src/data/symbols/sleigh.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

View file

@ -8,6 +8,13 @@
:x2="endPosition.x"
:y2="endPosition.y"
/>
<text
v-if="link.weight"
:x="(startPosition.x + endPosition.x) / 2"
:y="(startPosition.y + endPosition.y) / 2 + 4"
style="text-anchor: middle; fill: var(--foreground)"
>{{ link.weight }}</text
>
</template>
<script setup lang="ts">
@ -15,7 +22,7 @@ import type { BoardNodeLink } from "features/boards/board";
import { computed, toRefs, unref } from "vue";
const _props = defineProps<{
link: BoardNodeLink;
link: BoardNodeLink & { weight?: number };
}>();
const props = toRefs(_props);

View file

@ -9,7 +9,7 @@ import {
Visibility
} from "features/feature";
import { globalBus } from "game/events";
import type { Persistent, State } from "game/persistence";
import { DefaultValue, deletePersistent, Persistent, State } from "game/persistence";
import { persistent } from "game/persistence";
import type { Unsubscribe } from "nanoevents";
import { isFunction } from "util/common";
@ -167,12 +167,12 @@ export interface BoardOptions {
style?: Computable<StyleValue>;
startNodes: () => Omit<BoardNode, "id">[];
types: Record<string, NodeTypeOptions>;
state?: Computable<BoardData>;
links?: Computable<BoardNodeLink[] | null>;
}
export interface BaseBoard {
id: string;
state: Persistent<BoardData>;
links: Ref<BoardNodeLink[] | null>;
nodes: Ref<BoardNode[]>;
selectedNode: Ref<BoardNode | null>;
selectedAction: Ref<GenericBoardNodeAction | null>;
@ -191,6 +191,8 @@ export type Board<T extends BoardOptions> = Replace<
width: GetComputableType<T["width"]>;
classes: GetComputableType<T["classes"]>;
style: GetComputableType<T["style"]>;
state: GetComputableTypeWithDefault<T["state"], Persistent<BoardData>>;
links: GetComputableTypeWithDefault<T["links"], Ref<BoardNodeLink[] | null>>;
}
>;
@ -198,31 +200,46 @@ export type GenericBoard = Replace<
Board<BoardOptions>,
{
visibility: ProcessedComputable<Visibility>;
state: ProcessedComputable<BoardData>;
links: ProcessedComputable<BoardNodeLink[] | null>;
}
>;
export function createBoard<T extends BoardOptions>(
optionsFunc: OptionsFunc<T, BaseBoard, GenericBoard>
): Board<T> {
const state = persistent<BoardData>({
nodes: [],
selectedNode: null,
selectedAction: null
});
return createLazyProxy(() => {
const board = optionsFunc();
board.id = getUniqueID("board-");
board.type = BoardType;
board[Component] = BoardComponent;
board.state = persistent<BoardData>({
nodes: board.startNodes().map((n, i) => {
(n as BoardNode).id = i;
return n as BoardNode;
}),
selectedNode: null,
selectedAction: null
});
board.nodes = computed(() => processedBoard.state.value.nodes);
if (board.state) {
deletePersistent(state);
processComputable(board as T, "state");
} else {
state[DefaultValue] = {
nodes: board.startNodes().map((n, i) => {
(n as BoardNode).id = i;
return n as BoardNode;
}),
selectedNode: null,
selectedAction: null
};
board.state = state;
}
board.nodes = computed(() => unref(processedBoard.state).nodes);
board.selectedNode = computed(
() =>
processedBoard.nodes.value.find(
node => node.id === processedBoard.state.value.selectedNode
node => node.id === unref(processedBoard.state).selectedNode
) || null
);
board.selectedAction = computed(() => {
@ -236,23 +253,30 @@ export function createBoard<T extends BoardOptions>(
}
return (
type.actions.find(
action => action.id === processedBoard.state.value.selectedAction
action => action.id === unref(processedBoard.state).selectedAction
) || null
);
});
board.mousePosition = ref(null);
board.links = computed(() => {
if (processedBoard.selectedAction.value == null) {
return null;
}
if (processedBoard.selectedAction.value.links && processedBoard.selectedNode.value) {
return getNodeProperty(
processedBoard.selectedAction.value.links,
if (board.links) {
processComputable(board as T, "links");
} else {
board.links = computed(() => {
if (processedBoard.selectedAction.value == null) {
return null;
}
if (
processedBoard.selectedAction.value.links &&
processedBoard.selectedNode.value
);
}
return null;
});
) {
return getNodeProperty(
processedBoard.selectedAction.value.links,
processedBoard.selectedNode.value
);
}
return null;
});
}
processComputable(board as T, "visibility");
setDefault(board, "visibility", Visibility.Visible);
processComputable(board as T, "width");
@ -286,10 +310,10 @@ export function createBoard<T extends BoardOptions>(
processComputable(nodeType as NodeTypeOptions, "actionDistance");
setDefault(nodeType, "actionDistance", Math.PI / 6);
nodeType.nodes = computed(() =>
processedBoard.state.value.nodes.filter(node => node.type === type)
unref(processedBoard.state).nodes.filter(node => node.type === type)
);
setDefault(nodeType, "onClick", function (node: BoardNode) {
processedBoard.state.value.selectedNode = node.id;
unref(processedBoard.state).selectedNode = node.id;
});
if (nodeType.actions) {

View file

@ -156,7 +156,7 @@ export default defineComponent({
}
.tab-family-container > :nth-child(2) {
margin-top: 20px;
margin-top: 50px;
}
.modal-body > .tab-family-container > :nth-child(2) {
@ -222,8 +222,8 @@ export default defineComponent({
}
.showGoBack
> .tab-family-container
> .tab-buttons-container:not(.floating):first-child
> .tab-family-container:first-child
> .tab-buttons-container:not(.floating)
.tab-buttons {
padding-left: 70px;
}

View file

@ -268,6 +268,9 @@ globalBus.on("addLayer", (layer: GenericLayer, saveData: Record<string, unknown>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
handleObject((layer as any)[ProxyState]);
persistentRefs[layer.id].forEach(persistent => {
if (persistent[Deleted]) {
return;
}
console.error(
`Created persistent ref in ${layer.id} without registering it to the layer! Make sure to include everything persistent in the returned object`,
persistent,