Merge branch 'main' into day-24-packing

This commit is contained in:
Seth Posner 2022-12-23 23:10:42 -08:00
commit 8d3dd4f339
41 changed files with 2589 additions and 179 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -66,9 +66,8 @@ const useHeader = projInfo.useHeader;
const loreBody = computeOptionalComponent(main.loreBody);
function gatherLayerProps(layer: GenericLayer) {
const { display, minimized, minWidth, name, color, minimizable, nodes, minimizedDisplay } =
layer;
return { display, minimized, minWidth, name, color, minimizable, nodes, minimizedDisplay };
const { display, minimized, name, color, minimizable, nodes, minimizedDisplay } = layer;
return { display, minimized, name, color, minimizable, nodes, minimizedDisplay };
}
</script>

View file

@ -2,7 +2,7 @@
<div class="layer-container" :style="{ '--layer-color': unref(color) }">
<button v-if="showGoBack" class="goBack" @click="goBack"></button>
<button class="layer-tab minimized" v-if="minimized" @click="setMinimized(false)">
<button class="layer-tab minimized" v-if="unref(minimized)" @click="setMinimized(false)">
<component v-if="minimizedComponent" :is="minimizedComponent" />
<div v-else>{{ unref(name) }}</div>
</button>
@ -34,10 +34,6 @@ export default defineComponent({
type: Number,
required: true
},
tab: {
type: Function as PropType<() => HTMLElement | undefined>,
required: true
},
display: {
type: processedPropType<CoercableComponent>(Object, String, Function),
required: true
@ -47,10 +43,6 @@ export default defineComponent({
type: Object as PropType<Persistent<boolean>>,
required: true
},
minWidth: {
type: processedPropType<number | string>(Number, String),
required: true
},
name: {
type: processedPropType<string>(String),
required: true
@ -63,7 +55,7 @@ export default defineComponent({
}
},
setup(props) {
const { display, index, minimized, minWidth, tab, minimizedDisplay, name } = toRefs(props);
const { display, index, minimized, minimizedDisplay } = toRefs(props);
const component = computeComponent(display);
const minimizedComponent = computeOptionalComponent(minimizedDisplay);
@ -79,39 +71,10 @@ export default defineComponent({
minimized.value = min;
}
nextTick(() => updateTab(minimized.value, unref(minWidth.value)));
watch([name, minimized, wrapRef(minWidth)], ([name, minimized, minWidth]) => {
updateTab(minimized, minWidth);
});
function updateNodes(nodes: Record<string, FeatureNode | undefined>) {
props.nodes.value = nodes;
}
function updateTab(min: boolean, minWidth: number | string) {
minimized.value = min;
const width =
typeof minWidth === "number" || Number.isNaN(parseInt(minWidth))
? minWidth + "px"
: minWidth;
const tabValue = tab.value();
if (tabValue != undefined) {
if (min) {
tabValue.style.flexGrow = "0";
tabValue.style.flexShrink = "0";
tabValue.style.width = "60px";
tabValue.style.minWidth = tabValue.style.flexBasis = "";
tabValue.style.margin = "0";
} else {
tabValue.style.flexGrow = "";
tabValue.style.flexShrink = "";
tabValue.style.width = "";
tabValue.style.minWidth = tabValue.style.flexBasis = width;
tabValue.style.margin = "";
}
}
}
return {
component,
minimizedComponent,
@ -119,16 +82,13 @@ export default defineComponent({
updateNodes,
unref,
goBack,
setMinimized,
minimized,
minWidth
setMinimized
};
}
});
</script>
<style scoped>
.layer-tab:not(.minimized) {
padding-top: 20px;
padding-bottom: 20px;

View file

@ -6,13 +6,25 @@
class="scene-item"
style="left: 4%; bottom: 3%; width: 40px; height: 40px"
/>
<img v-if="day >= 0" :src="tree" class="scene-item" style="left: 10%; bottom: 10%" />
<img
v-if="day >= 0"
:src="tree"
class="scene-item"
style="left: 6%; bottom: 10%; width: 120px; height: 120px"
/>
<img v-if="day >= 20" :src="reindeer" class="scene-item" style="left: 13%; bottom: 8%" />
<img
v-if="day >= 13"
:src="letters"
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"
@ -46,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"
@ -69,6 +87,7 @@
<div v-if="day >= 4" class="scene-bubble left" style="left: 64%; bottom: 37%">
<img v-if="day >= 17" :src="toys" class="scene-item" />
<img v-if="day >= 18" :src="advFactory" class="scene-item" />
<img v-if="day >= 19" :src="presents" class="scene-item" />
</div>
</div>
</template>
@ -93,6 +112,10 @@ import ribbons from "./symbols/ribbons.png";
import toys from "./symbols/truck.png";
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

@ -28,9 +28,11 @@ import { render, renderGrid } from "util/vue";
import { computed, ComputedRef, ref, unref } from "vue";
import dyes from "./dyes";
import elves, { ElfBuyable } from "./elves";
import factory from "./factory";
import management from "./management";
import paper from "./paper";
import plastic from "./plastic";
import reindeer from "./reindeer";
import trees from "./trees";
import workshop from "./workshop";
import wrappingPaper from "./wrapping-paper";
@ -55,6 +57,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
description: "1000% Foundation Completed",
enabled: workshop.milestones.extraExpansionMilestone5.earned
})),
reindeer.reindeer.vixen.modifier,
createExponentialModifier(() => ({
exponent: 1.1,
description: "Bell Level 2",
@ -587,7 +590,36 @@ const layer = createLayer(id, function (this: BaseLayer) {
Decimal.add(plasticBoxesBuyable.amount.value, plasticBoxesBuyable.freeLevels.value)
)
})) as BoxesBuyable;
const presentBuyable = createBuyable(() => ({
display: {
title: "Carry presents in boxes",
description: jsx(() => (
<>
Use boxes to carry presents, boosting its gain
<br />
<br />
<div>Amount: {formatWhole(presentBuyable.amount.value)} boxes</div>
</>
)),
effectDisplay: jsx(() => (
<>{format(Decimal.div(presentBuyable.amount.value, 10).add(1).pow(2))}x</>
)),
showAmount: false
},
resource: noPersist(boxes),
cost() {
return Decimal.pow(2, presentBuyable.amount.value).mul(1e87);
},
inverseCost(x: DecimalSource) {
const amt = Decimal.div(x, 1e87).log2();
return Decimal.isNaN(amt) ? Decimal.dZero : amt.floor().max(0);
},
freeLevels: computed(() => 0),
totalAmount: computed(() => presentBuyable.amount.value),
visibility: () => showIf(factory.upgrades[3][3].bought.value)
})) as BoxesBuyable;
const buyables2 = { oreBoxesBuyable, metalBoxesBuyable, plasticBoxesBuyable };
const buyables3 = { presentBuyable };
globalBus.on("update", diff => {
if (Decimal.lt(main.day.value, day)) {
return;
@ -677,6 +709,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
row3Upgrades,
buyables,
buyables2,
buyables3,
minWidth: 700,
generalTabCollapsed,
display: jsx(() => (
@ -703,7 +736,11 @@ const layer = createLayer(id, function (this: BaseLayer) {
Object.values(row3Upgrades)
)}
<Spacer />
{renderGrid(Object.values(buyables), Object.values(buyables2))}
{renderGrid(
Object.values(buyables),
Object.values(buyables2),
Object.values(buyables3)
)}
</>
)),
minimizedDisplay: jsx(() => (

View file

@ -21,12 +21,13 @@ import { BaseLayer, createLayer } from "game/layers";
import {
createAdditiveModifier,
createMultiplicativeModifier,
createSequentialModifier
createSequentialModifier,
Modifier
} from "game/modifiers";
import { noPersist, persistent } from "game/persistence";
import Decimal, { DecimalSource, format } from "util/bignum";
import { formatWhole } from "util/break_eternity";
import { Direction } from "util/common";
import { Direction, WithRequired } from "util/common";
import { render, renderCol, renderRow } from "util/vue";
import { computed, ref, unref } from "vue";
import boxes from "./boxes";
@ -36,6 +37,7 @@ import management from "./management";
import metal from "./metal";
import paper from "./paper";
import plastic from "./plastic";
import reindeer from "./reindeer";
import trees from "./trees";
import workshop from "./workshop";
@ -449,8 +451,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
multiplier: dyes.boosts.yellow2,
description: "Yellow Dye",
enabled: dyes.masteryEffectActive
}))
]);
})),
reindeer.reindeer.cupid.modifier
]) as WithRequired<Modifier, "description" | "revert">;
const computedSheepGain = computed(() => sheepGain.apply(1));
const breedingCooldown = createSequentialModifier(() => []);
const computedBreedingCooldown = computed(() => breedingCooldown.apply(1));
@ -494,8 +497,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
multiplier: dyes.boosts.yellow2,
description: "Yellow Dye",
enabled: dyes.masteryEffectActive
}))
]);
})),
reindeer.reindeer.cupid.modifier
]) as WithRequired<Modifier, "description" | "revert">;
const computedShearingAmount = computed(() => shearingAmount.apply(1));
const shearingCooldown = createSequentialModifier(() => []);
const computedShearingCooldown = computed(() => shearingCooldown.apply(1));
@ -539,8 +543,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
multiplier: dyes.boosts.yellow2,
description: "Yellow Dye",
enabled: dyes.masteryEffectActive
}))
]);
})),
reindeer.reindeer.cupid.modifier
]) as WithRequired<Modifier, "description" | "revert">;
const computedSpinningAmount = computed(() => spinningAmount.apply(1));
const spinningCooldown = createSequentialModifier(() => []);
const computedSpinningCooldown = computed(() => spinningCooldown.apply(1));

View file

@ -29,6 +29,7 @@ import {
import { noPersist, persistent } from "game/persistence";
import Decimal, { DecimalSource, format, formatWhole } from "util/bignum";
import { WithRequired } from "util/common";
import { Computable } from "util/computed";
import { render, renderGrid, renderRow } from "util/vue";
import { computed, ref, unref } from "vue";
import boxes from "./boxes";
@ -40,6 +41,7 @@ import metal from "./metal";
import oil from "./oil";
import paper from "./paper";
import plastic from "./plastic";
import reindeer from "./reindeer";
import trees from "./trees";
import wrappingPaper from "./wrapping-paper";
@ -47,7 +49,7 @@ interface BetterFertilizerUpgOptions {
canAfford: () => boolean;
onPurchase: VoidFunction;
display: JSXFunction;
style: StyleValue;
style: Computable<StyleValue>;
visibility: () => Visibility;
}
interface UnlockKilnUpgOptions {
@ -57,7 +59,7 @@ interface UnlockKilnUpgOptions {
title: string;
description: string;
};
style: StyleValue;
style: Computable<StyleValue>;
visibility: () => Visibility;
}
interface EfficientSmeltherUpgOptions {
@ -67,7 +69,7 @@ interface EfficientSmeltherUpgOptions {
title: string;
description: string;
};
style: StyleValue;
style: Computable<StyleValue>;
visibility: () => Visibility;
}
@ -360,7 +362,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
title: "Warmer Cutters",
description: "Cut down twice as many trees/s"
},
style: { color: colorText }
style() {
return this.bought.value ? "" : { color: colorText };
}
}));
const warmerPlanters = createUpgrade(() => ({
resource: noPersist(coal),
@ -369,7 +373,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
title: "Warmer Planters",
description: "Plant twice as many trees/s"
},
style: { color: colorText }
style() {
return this.bought.value ? "" : { color: colorText };
}
}));
const basicFertilizer = createUpgrade(() => ({
resource: noPersist(ash),
@ -378,7 +384,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
title: "Ashy Soil",
description: "Trees give 25% more logs"
},
style: { color: colorText }
style() {
return this.bought.value ? "" : { color: colorText };
}
}));
const unlockBonfire = createUpgrade(() => ({
resource: fireResource,
@ -388,9 +396,11 @@ const layer = createLayer(id, function (this: BaseLayer) {
description: "Put all those fires together into a larger blaze"
},
onPurchase() {
fireResource.value = Decimal.add(fireResource.value, this.cost);
fireResource.value = Decimal.add(fireResource.value, this.cost as number);
},
style: { color: colorText }
style() {
return this.bought.value ? "" : { color: colorText };
}
}));
const row1upgrades = [warmerCutters, warmerPlanters, basicFertilizer, unlockBonfire];
@ -401,7 +411,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
title: "Dedicated Cutter Heaters",
description: "Double the bonus from Heated Cutters"
},
style: { color: colorText },
style() {
return this.bought.value ? "" : { color: colorText };
},
visibility: () => showIf(unlockBonfire.bought.value)
}));
const dedicatedPlanters = createUpgrade(() => ({
@ -411,7 +423,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
title: "Dedicated Planter Heaters",
description: "Double the bonus from Heated Planters"
},
style: { color: colorText },
style() {
return this.bought.value ? "" : { color: colorText };
},
visibility: () => showIf(unlockBonfire.bought.value)
}));
const betterFertilizer: Upgrade<BetterFertilizerUpgOptions> = createUpgrade(() => ({
@ -434,7 +448,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
{formatWhole(1e5)} {ash.displayName}
</>
)),
style: { color: colorText },
style() {
return this.bought.value ? "" : { color: colorText };
},
visibility: () => showIf(unlockBonfire.bought.value)
}));
@ -445,7 +461,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
title: "Efficient Fires",
description: "Move the fires underground to keep the coal from turning to ash"
},
style: { color: colorText },
style() {
return this.bought.value ? "" : { color: colorText };
},
visibility: () => showIf(unlockBonfire.bought.value)
}));
const row2upgrades = [dedicatedCutters, dedicatedPlanters, betterFertilizer, unlockKiln];
@ -457,7 +475,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
title: "Efficient Crucibles",
description: "Double auto smelting speed and triple metal gain from auto smelting"
},
style: { color: colorText },
style() {
return this.bought.value ? "" : { color: colorText };
},
visibility: () => showIf(oil.depthMilestones[4].earned.value)
}));
const arsonistAssistance = createUpgrade(() => ({
@ -467,7 +487,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
title: "Arsonist Assistance",
description: "Every elf at or above level 5 doubles ash gain"
},
style: { color: colorText },
style() {
return this.bought.value ? "" : { color: colorText };
},
visibility: () =>
showIf(management.elfTraining.coalDrillElfTraining.milestones[3].earned.value)
}));
@ -478,7 +500,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
title: "Refined Coal",
description: "Refineries boost coal gain"
},
style: { color: colorText },
style() {
return this.bought.value ? "" : { color: colorText };
},
visibility: () =>
showIf(management.elfTraining.coalDrillElfTraining.milestones[3].earned.value)
}));
@ -489,7 +513,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
title: "Colored Fire",
description: "Green dye also affects small fire synergy"
},
style: { color: colorText },
style() {
return this.bought.value ? "" : { color: colorText };
},
visibility: () =>
showIf(management.elfTraining.coalDrillElfTraining.milestones[3].earned.value)
}));
@ -785,6 +811,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
description: "Refined Coal",
enabled: refinedCoal.bought
})),
reindeer.reindeer.dancer.modifier,
createExponentialModifier(() => ({
exponent: 1.05,
description: "Jack Level 2",

View file

@ -37,6 +37,7 @@ import paper from "./paper";
import trees from "./trees";
import toys from "./toys";
import factory from "./factory";
import reindeer from "./reindeer";
interface Dye {
name: string;
@ -201,6 +202,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
enabled: boxes.row3Upgrades.dyeUpgrade.bought
}))
);
modifiers.push(reindeer.reindeer.rudolph.modifier);
return modifiers;
}) as WithRequired<Modifier, "description" | "revert">;
const computedToGenerate = computed(() => toGenerate.apply(0));

View file

@ -876,7 +876,11 @@ const layer = createLayer(id, function (this: BaseLayer) {
name: "Bell",
description:
"Bell will automatically purchase all box buyables you can afford, without actually spending any boxes.",
buyable: [...Object.values(boxes.buyables), ...Object.values(boxes.buyables2)],
buyable: [
...Object.values(boxes.buyables),
...Object.values(boxes.buyables2),
...Object.values(boxes.buyables3)
],
cooldownModifier: boxCooldown,
visibility: () => showIf(plastic.elfUpgrades.boxElf.bought.value)
});

View file

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="50"
height="50"
viewBox="0 0 13.229166 13.229167"
version="1.1"
id="svg5"
sodipodi:docname="advent.svg"
inkscape:version="1.2.1 (9c6d41e4, 2022-07-14)"
inkscape:export-filename="advent/boxmaker.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xml:space="preserve"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="3.0909964"
inkscape:cx="3.0734426"
inkscape:cy="59.527731"
inkscape:window-width="1309"
inkscape:window-height="804"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="0"
inkscape:current-layer="layer1" /><defs
id="defs2" /><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"><rect
style="fill:#e6e6e6;fill-opacity:1;stroke:#838390;stroke-width:0.764057;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:6;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill"
id="rect28545-6"
width="12.46511"
height="12.46511"
x="0.38202849"
y="0.38202882"
rx="1.246511" /><image
width="11.600364"
height="11.600364"
preserveAspectRatio="none"
xlink:href="data:image/png;base64,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
"
id="image727"
x="0.74370372"
y="0.35876995" /></g></svg>

After

Width:  |  Height:  |  Size: 6.9 KiB

View file

@ -40,9 +40,11 @@ import _plastic from "../symbols/plastic.png";
import boxes from "./boxes";
import coal from "./coal";
import dyes from "./dyes";
import _box from "../symbols/cardboardBox.png";
import _bear from "./factory-components/bear.svg";
import _bearMaker from "./factory-components/bearmaker.svg";
import _block from "./factory-components/block.svg";
import _boxMaker from "./factory-components/boxmaker.svg";
import _blockMaker from "./factory-components/blockmaker.svg";
import _bucket from "./factory-components/bucket.svg";
import _bucketMaker from "./factory-components/bucketmaker.svg";
@ -75,6 +77,8 @@ import _truck from "./factory-components/truck.svg";
import _truckMaker from "./factory-components/truckmaker.svg";
import _wheel from "./factory-components/wheel.svg";
import _wheelMaker from "./factory-components/wheelmaker.svg";
import _present from "./factory-components/present.svg";
import _presentMaker from "./factory-components/presentmaker.svg";
import Factory from "./Factory.vue";
import metal from "./metal";
import oil from "./oil";
@ -85,6 +89,7 @@ import Toy from "./Toy.vue";
import toys from "./toys";
import trees from "./trees";
import workshop from "./workshop";
import ribbon from "./ribbon";
const id = "factory";
@ -94,9 +99,7 @@ const presentsDay = 20;
const toyGoal = 750;
const advancedToyGoal = 1500;
// 20x20 block size
// TODO: unhardcode stuff
const presentsGoal = 8e9;
function roundDownTo(num: number, multiple: number) {
return Math.floor((num + multiple / 2) / multiple) * multiple;
@ -138,6 +141,16 @@ const factory = createLayer(id, () => {
const bears = createResource<DecimalSource>(0, "teddy bears");
const bucketAndShovels = createResource<DecimalSource>(0, "shovel and pails");
const consoles = createResource<DecimalSource>(0, "consoles");
const presents = createResource<DecimalSource>(0, "presents");
const allToys = {
clothes: toys.clothes,
woodenBlocks: toys.woodenBlocks,
trucks: toys.trucks,
bears,
bucketAndShovels,
consoles
};
function getRelativeCoords(e: MouseEvent) {
const rect = (e.target as HTMLElement).getBoundingClientRect();
@ -172,6 +185,11 @@ const factory = createLayer(id, () => {
multiplier: 1.4,
description: "6000 toys",
enabled: toys.milestones.milestone6.earned
})),
createMultiplicativeModifier(() => ({
multiplier: () => Decimal.log10(trees.logs.value).div(100).add(1),
description: "Burn some logs",
enabled: betterLighting.bought
}))
]) as WithRequired<Modifier, "revert" | "description">;
const computedEnergy = computed(() => energy.apply(0));
@ -227,9 +245,33 @@ const factory = createLayer(id, () => {
addend: expandFactory.amount,
description: "Expand Factory",
enabled: () => Decimal.gt(expandFactory.amount.value, 0)
})),
createAdditiveModifier(() => ({
addend: 5,
description: "Factory eXPerience",
enabled: betterFactory.bought
}))
]);
const computedFactorySize = computed(() => new Decimal(factorySize.apply(7)).toNumber());
const presentMultipliers = createSequentialModifier(() => [
createMultiplicativeModifier(() => ({
multiplier: computedToyMultiplier,
description: "Tickspeed overflow",
enabled: () => computedToyMultiplier.value.gt(1)
})),
createMultiplicativeModifier(() => ({
multiplier: () =>
Decimal.div(boxes.buyables3.presentBuyable.amount.value, 10).add(1).pow(2),
description: "Carry presents in boxes",
enabled: carryPresents.bought
})),
createMultiplicativeModifier(() => ({
multiplier: () => Decimal.add(ribbon.ribbon.value, 1),
description: "With a bow",
enabled: bowUpgrade.bought
}))
]);
const computedPresentMultipliers = computed(() => presentMultipliers.apply(1));
const energyBar = createBar(() => ({
width: 680,
@ -317,6 +359,10 @@ const factory = createLayer(id, () => {
return str;
}
// this keeps track of which toy the present factory has consumed
// it cycles around, so each toy is used evenly
let toysIndex = 0;
const FACTORY_COMPONENTS = {
cursor: {
imageSrc: _cursor,
@ -604,6 +650,26 @@ const factory = createLayer(id, () => {
},
visible: main.days[advancedDay - 1].opened
} as FactoryComponentDeclaration,
boxMaker: {
imageSrc: _boxMaker,
key: "shift+9",
name: "Box Maker",
type: "processor",
description: computed(() => generateComponentDescription(FACTORY_COMPONENTS.boxMaker)),
energyCost: 3,
tick: 1,
inputs: {
plank: {
amount: 2
}
},
outputs: {
box: {
amount: 2
}
},
visible: main.days[presentsDay - 1].opened
} as FactoryComponentDeclaration,
blocks: {
imageSrc: _blockMaker,
key: "ctrl+1",
@ -755,6 +821,93 @@ const factory = createLayer(id, () => {
}
},
visible: main.days[advancedDay - 1].opened
} as FactoryComponentDeclaration,
present: {
imageSrc: _presentMaker,
type: "processor",
// idk about this
key: "ctrl+7",
name: "Present Wrapper",
description: computed(
() =>
`Takes in 4 dye, 4 plastic, 1 cloth, 2 boxes, and ${formatWhole(
computedToyMultiplier.value
)} toys of any type (from storage) to produce ${formatWhole(
computedPresentMultipliers.value
)} presents every tick.` +
(catalysts.bought.value
? " You can feed it wheels, buttons, stuffing, and circuit boards to increase its output."
: "")
),
tick: 1,
energyCost: 50,
inputs: {
dye: {
amount: 4
},
plastic: {
amount: 4
},
cloth: {
amount: 1
},
box: {
amount: 2
}
},
catalysts: computed(() => {
if (!catalysts.bought.value) return [] as ResourceNames[];
return {
wheel: {
amount: 1
},
buttons: {
amount: 1
},
stuffing: {
amount: 1
},
circuitBoard: {
amount: 1
}
};
}),
canProduce: computed(() => {
return Object.values(allToys).some(i =>
Decimal.gte(i.value, computedToyMultiplier.value)
);
}),
onProduce(times, stock) {
const value = Object.values(allToys);
let sumCatalysts: DecimalSource = catalysts.bought.value
? (["wheel", "buttons", "stuffing", "circuitBoard"] as const)
.map(c => stock?.[c] ?? 0)
.reduce(Decimal.add, Decimal.dZero)
.add(1)
: 1;
if (stock) {
(["wheel", "buttons", "stuffing", "circuitBoard"] as const).forEach(
c => delete stock[c]
);
}
while (times > 0) {
while (Decimal.lt(value[toysIndex].value, computedToyMultiplier.value)) {
toysIndex = (toysIndex + 1) % value.length;
}
const toyToPick = Object.values(allToys)[toysIndex];
toysIndex = (toysIndex + 1) % value.length;
toyToPick.value = Decimal.sub(toyToPick.value, computedToyMultiplier.value);
times--;
presents.value = Decimal.add(
presents.value,
Decimal.times(computedPresentMultipliers.value, sumCatalysts)
);
sumCatalysts = 1;
}
},
visible: main.days[presentsDay - 1].opened
} as FactoryComponentDeclaration
} as Record<string, FactoryComponentDeclaration>;
const RESOURCES = {
@ -784,6 +937,10 @@ const factory = createLayer(id, () => {
name: "Planks",
imageSrc: _plank
},
box: {
name: "Boxes",
imageSrc: _box
},
thread: {
name: "Thread",
imageSrc: _thread
@ -901,9 +1058,13 @@ const factory = createLayer(id, () => {
inputs?: Stock;
/** amount it produces */
outputs?: Stock;
catalysts?: ProcessedComputable<Stock>;
/** on produce, do something */
onProduce?: (times: number) => void;
onProduce?: (
times: number,
stock: Partial<Record<ResourceNames, number>> | undefined
) => void;
/** can it produce? (in addtion to the stock check) */
canProduce?: ComputedRef<boolean>;
}
@ -956,10 +1117,21 @@ const factory = createLayer(id, () => {
// trained elves
const costCheapener = createSequentialModifier(() => [
createMultiplicativeModifier(() => ({
multiplier: () => Decimal.add(presents.value, 1).log10().add(1),
description: "Excitment Upgrade",
enabled: excitmentUpgrade.bought
}))
]);
const computedCostCheapeners = computed(() => costCheapener.apply(1));
const clothesBuyable = createBuyable(() => ({
resource: toys.clothes,
cost() {
return Decimal.pow(2, Decimal.add(this.amount.value, 5));
return Decimal.pow(2, Decimal.add(this.amount.value, 5)).div(
computedCostCheapeners.value
);
},
display: {
title: "Train elves to make clothes",
@ -970,7 +1142,9 @@ const factory = createLayer(id, () => {
const blocksBuyable = createBuyable(() => ({
resource: toys.woodenBlocks,
cost() {
return Decimal.pow(2, Decimal.add(this.amount.value, 5));
return Decimal.pow(2, Decimal.add(this.amount.value, 5)).div(
computedCostCheapeners.value
);
},
display: {
title: "Train elves to make wooden blocks",
@ -981,7 +1155,9 @@ const factory = createLayer(id, () => {
const trucksBuyable = createBuyable(() => ({
resource: toys.trucks,
cost() {
return Decimal.pow(2, Decimal.add(this.amount.value, 5));
return Decimal.pow(2, Decimal.add(this.amount.value, 5)).div(
computedCostCheapeners.value
);
},
display: {
title: "Train elves to make toy trucks",
@ -992,7 +1168,9 @@ const factory = createLayer(id, () => {
const bearsBuyable = createBuyable(() => ({
resource: noPersist(bears),
cost() {
return Decimal.pow(2, Decimal.add(this.amount.value, 5));
return Decimal.pow(2, Decimal.add(this.amount.value, 5)).div(
computedCostCheapeners.value
);
},
display: {
title: "Train elves to make bears",
@ -1004,7 +1182,9 @@ const factory = createLayer(id, () => {
const bucketBuyable = createBuyable(() => ({
resource: noPersist(bucketAndShovels),
cost() {
return Decimal.pow(2, Decimal.add(this.amount.value, 5));
return Decimal.pow(2, Decimal.add(this.amount.value, 5)).div(
computedCostCheapeners.value
);
},
display: {
title: "Train elves to make shovel and pails",
@ -1016,7 +1196,9 @@ const factory = createLayer(id, () => {
const consolesBuyable = createBuyable(() => ({
resource: noPersist(consoles),
cost() {
return Decimal.pow(2, Decimal.add(this.amount.value, 5));
return Decimal.pow(2, Decimal.add(this.amount.value, 5)).div(
computedCostCheapeners.value
);
},
display: {
title: "Train elves to make consoles",
@ -1089,7 +1271,88 @@ const factory = createLayer(id, () => {
style: "width: 200px",
visibility: () => showIf(main.days[advancedDay - 1].opened.value)
})) as GenericBuyable;
const betterFactory = createUpgrade(() => ({
resource: noPersist(presents),
cost: 100,
display: {
title: "Factory eXPerience",
description: "Factory size is increased by 5."
},
visibility: () => showIf(main.days[presentsDay - 1].opened.value)
}));
const betterLighting = createUpgrade(() => ({
resource: noPersist(presents),
cost: 300,
display: {
title: "Burn some logs",
description: "More energy needed? Let's burn some logs! Logs boosts maximum energy.",
effectDisplay: jsx(() => (
<>x{format(Decimal.log10(trees.logs.value).div(100).add(1))}</>
))
},
visibility: () => showIf(betterFactory.bought.value)
}));
const excitmentUpgrade = createUpgrade(() => ({
resource: noPersist(presents),
cost: 1000,
display: {
title: "Faster Elf Training",
description:
"Apparently elves like presents. Let's use it to train them to work on the factory faster! Presents divides the requirement for factory elf training.",
effectDisplay: jsx(() => <>/{format(Decimal.add(presents.value, 1).log10().add(1))}</>)
},
visibility: () => showIf(betterLighting.bought.value)
}));
const carryPresents = createUpgrade(() => ({
resource: noPersist(presents),
cost: 5000,
display: {
title: "Carrying more stuff in boxes",
description:
"Boxes seem really useful for carrying stuff. Why don't we use them to carry presents as well? Unlocks 2 new buyables (one of them is in the boxes layer)."
},
visibility: () => showIf(excitmentUpgrade.bought.value)
}));
const carryBoxes = createBuyable(() => ({
resource: noPersist(presents),
cost() {
return Decimal.add(carryBoxes.amount.value, 1)
.pow(1.5)
.mul(Decimal.pow(2, carryBoxes.amount.value))
.mul(1000);
},
style: "width: 400px",
display: {
title: "Carry boxes in... presents?",
description:
"Presents are made out of boxes, so shouldn't they be able to hold boxes as well? Apparently it makes the boxes more durable. Each level multiplies boxes gain by 1.5.",
effectDisplay: jsx(() => <>x{format(Decimal.pow(1.5, carryBoxes.amount.value))}</>)
},
visibility: () => showIf(carryPresents.bought.value)
})) as GenericBuyable;
const catalysts = createUpgrade(() => ({
resource: noPersist(presents),
cost: 10000,
display: {
title: "Better Presents",
description:
"Instead of trying to make more presents, how about we make the ones we make better? Unlocks catalysts for the present maker."
},
visibility: () => showIf(carryPresents.bought.value)
}));
const bowUpgrade = createUpgrade(() => ({
resource: noPersist(presents),
cost: 1e7,
display: {
title: "With a bow",
description:
"These presents need ribbon to make the bows, right? Multiply present gain by the amount of ribbon you have"
},
visibility: () => showIf(catalysts.bought.value)
}));
const factoryBuyables = { expandFactory, oilFuel, carryToys };
const factoryBuyables2 = { carryBoxes };
const upgrades = [
[
createUpgrade(() => ({
@ -1205,7 +1468,9 @@ const factory = createLayer(id, () => {
},
visibility: () => showIf(main.days[advancedDay - 1].opened.value)
}))
]
],
[betterFactory, betterLighting, excitmentUpgrade, carryPresents],
[catalysts, bowUpgrade]
];
// pixi
@ -1314,8 +1579,6 @@ const factory = createLayer(id, () => {
const factoryTicks = Decimal.times(computedActualTickRate.value, diff).toNumber();
//debugger
// make them produce
for (const id in components.value) {
const [x, y] = id.split("x").map(p => +p);
const _data = components.value[id];
@ -1375,10 +1638,11 @@ 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);
factoryData.onProduce?.(cyclesDone);
factoryData.onProduce?.(cyclesDone, data.inputStock);
if (factoryData.inputs !== undefined) {
if (data.inputStock === undefined) data.inputStock = {};
for (const [key, val] of Object.entries(factoryData.inputs)) {
@ -1412,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
@ -1825,6 +2087,20 @@ const factory = createLayer(id, () => {
function togglePaused() {
paused.value = !paused.value;
}
function handleDrag(drag: DragEvent, name: FactoryCompNames) {
drag.dataTransfer!.setData("name", name);
}
function handleDrop(drag: DragEvent) {
drag.preventDefault();
const { tx, ty } = spriteContainer.localTransform;
let { x, y } = getRelativeCoords(drag);
x = roundDownTo(x - tx, blockSize) / blockSize;
y = roundDownTo(y - ty, blockSize) / blockSize;
const name = drag.dataTransfer!.getData("name");
if (components.value[x + "x" + y] == null) {
addFactoryComp(x, y, { type: name });
}
}
// ------------------------------------------------------------------------------- Tabs
@ -1850,6 +2126,8 @@ const factory = createLayer(id, () => {
src={item.imageSrc}
class={{ selected: compSelected.value === key }}
onClick={() => onCompClick(key)}
draggable="true"
onDragstart={drag => handleDrag(drag, key)}
/>
{item.extraImage == null ? null : (
<img src={item.extraImage} class="producedItem" />
@ -1885,7 +2163,7 @@ const factory = createLayer(id, () => {
});
function showStockAmount(
stocks: Partial<Record<ResourceNames, number>> | undefined,
stocks: Partial<Record<ResourceNames, number>> | undefined | Record<string, never>,
stockData: Stock | undefined,
title: string,
showAmount = true
@ -1944,7 +2222,11 @@ const factory = createLayer(id, () => {
<>
{showStockAmount(
(compHovered.value as FactoryComponentProcessor).inputStock,
FACTORY_COMPONENTS[compHovered.value.type].inputs,
{
...(FACTORY_COMPONENTS[compHovered.value.type].inputs ?? {}),
...(unref(FACTORY_COMPONENTS[compHovered.value.type].catalysts) ??
{})
},
"Inputs:"
)}
{showStockAmount(
@ -1961,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
@ -2002,6 +2284,8 @@ const factory = createLayer(id, () => {
? `Reach ${format(
advancedToyGoal
)} for each toy to complete the day`
: main.day.value === presentsDay
? `Reach ${format(presentsGoal)} presents`
: `${name} Complete!`}{" "}
-{" "}
<button
@ -2022,7 +2306,7 @@ const factory = createLayer(id, () => {
color="cornflowerblue"
/>
<Toy resource={toys.trucks} image={_truck} color="cadetblue" />
{main.days[advancedDay - 1].opened.value ? (
{main.days[advancedDay - 1].opened.value === true ? (
<>
<Toy resource={bears} image={_bear} color="teal" />
<Toy
@ -2037,6 +2321,11 @@ const factory = createLayer(id, () => {
/>
</>
) : null}
{main.days[presentsDay - 1].opened.value === true ? (
<>
<Toy resource={presents} image={_present} color="green" />
</>
) : undefined}
</Row>
<Spacer />
<MainDisplay
@ -2048,7 +2337,11 @@ const factory = createLayer(id, () => {
/>
{renderRow(...Object.values(elfBuyables))}
<Spacer />
{renderRow(...Object.values(factoryBuyables))}
{renderGrid(
Object.values(factoryBuyables),
Object.values(factoryBuyables2)
)}
<Spacer />
<Spacer />
{renderGrid(...(upgrades as VueFeature[][]))}
</>
@ -2069,6 +2362,8 @@ const factory = createLayer(id, () => {
onPointerenter={onFactoryMouseEnter}
onPointerleave={onFactoryMouseLeave}
onContextmenu={(e: MouseEvent) => e.preventDefault()}
onDrop={(e: DragEvent) => handleDrop(e)}
onDragover={(e: DragEvent) => e.preventDefault()}
/>
{componentsList()}
{hoveredComponent()}
@ -2095,6 +2390,11 @@ const factory = createLayer(id, () => {
modifier: tickRate,
base: 1,
unit: "/s"
},
{
title: "Present Multipliers",
modifier: presentMultipliers,
base: 1
}
]);
const showModifiersModal = ref(false);
@ -2111,7 +2411,10 @@ const factory = createLayer(id, () => {
<>
<br />
Note: the actual tick rate is capped at 5 TPS, but you'll gain extra
toys based on excessive tick rate as a compensation.
toys based on excessive tick rate as compensation.{" "}
{main.days[presentsDay - 1].opened.value === true
? "Present maker's toy requirement and production is also affected by tick overflow."
: undefined}
</>
) : (
""
@ -2127,6 +2430,7 @@ const factory = createLayer(id, () => {
width: 600,
height: 25,
fillStyle: `animation: 15s factory-bar linear infinite`,
textStyle: `color: var(--feature-foreground)`,
progress: () =>
main.day.value === day
? Decimal.div(toys.clothes.value, toyGoal)
@ -2139,6 +2443,8 @@ const factory = createLayer(id, () => {
.map(r => Decimal.div(r.value, advancedToyGoal).clampMax(1))
.reduce(Decimal.add, Decimal.dZero)
.div(6)
: main.day.value === presentsDay
? Decimal.div(presents.value, presentsGoal).clampMax(1)
: 1,
display: jsx(() =>
main.day.value === day ? (
@ -2164,6 +2470,10 @@ const factory = createLayer(id, () => {
}{" "}
/ 6
</>
) : main.day.value === presentsDay ? (
<>
{formatWhole(presents.value)}/{formatWhole(presentsGoal)} presents
</>
) : (
""
)
@ -2190,6 +2500,8 @@ const factory = createLayer(id, () => {
].filter(d => Decimal.gte(d.value, advancedToyGoal)).length >= 6
) {
main.completeDay();
} else if (main.day.value === presentsDay && Decimal.gte(presents.value, presentsGoal)) {
main.completeDay();
}
});
@ -2206,8 +2518,11 @@ const factory = createLayer(id, () => {
bears,
bucketAndShovels,
consoles,
presents,
tabs,
factoryBuyables,
factoryBuyables2,
carryBoxes,
generalTabCollapsed,
hotkeys,
upgrades,

View file

@ -76,10 +76,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
if (Decimal.lt(processingProgress.value, computedProcessingCooldown.value)) {
return;
}
const amount = Decimal.div(
processingProgress.value,
computedProcessingCooldown.value
).floor();
const amount = Decimal.div(processingProgress.value, computedProcessingCooldown.value)
.floor()
.max(1);
letters.value = Decimal.times(amount, computedLettersGain.value)
.add(letters.value)
.min(8e9);
@ -339,7 +338,6 @@ const layer = createLayer(id, function (this: BaseLayer) {
) : null}
<MainDisplay resource={letters} color={color} />
{render(process)}
If your letters are stuck, try holding L
{Decimal.lt(totalLetters.value, 8e9) ? (
<div>
The more letters you process, the more you'll improve at processing letters.

View file

@ -37,6 +37,8 @@ import plastic from "./plastic";
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;
@ -105,10 +107,16 @@ const layer = createLayer(id, function (this: BaseLayer) {
description: "Twinkle Level 1",
enabled: management.elfTraining.metalElfTraining.milestones[0].earned
})),
reindeer.reindeer.comet.modifier,
createExponentialModifier(() => ({
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));
@ -180,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",
@ -289,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

@ -43,6 +43,7 @@ import { WithRequired } from "util/common";
import { ElfBuyable } from "./elves";
import toys from "./toys";
import factory from "./factory";
import reindeer from "./reindeer";
const id = "oil";
const day = 9;
@ -625,7 +626,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
description: "Increase drill power by +4% per Coal Drill owned.",
effectDisplay: jsx(() => <>x{format(row1UpgradeEffects[0].value)}</>)
},
style: { color: colorText }
style() {
return this.bought.value ? "" : { color: colorText };
}
})),
createUpgrade(() => ({
resource: metal.metal,
@ -635,7 +638,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
description: "Increase drill power by +4% per Metal Drill owned.",
effectDisplay: jsx(() => <>x{format(row1UpgradeEffects[1].value)}</>)
},
style: { color: colorText }
style() {
return this.bought.value ? "" : { color: colorText };
}
})),
createUpgrade(() => ({
resource: coal.coal,
@ -645,7 +650,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
description: "Increase drill power by +6% per OoM of coal owned.",
effectDisplay: jsx(() => <>x{format(row1UpgradeEffects[2].value)}</>)
},
style: { color: colorText }
style() {
return this.bought.value ? "" : { color: colorText };
}
})),
createUpgrade(() => ({
resource: metal.metal,
@ -655,7 +662,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
description: "Increase drill power by +10% per OoM of metal ingot owned.",
effectDisplay: jsx(() => <>x{format(row1UpgradeEffects[3].value)}</>)
},
style: { color: colorText }
style() {
return this.bought.value ? "" : { color: colorText };
}
})),
createUpgrade(() => ({
resource: noPersist(oil),
@ -667,7 +676,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
<>+{format(Decimal.mul(row1UpgradeEffects[4].value, 100))}%</>
))
},
style: { color: colorText }
style() {
return this.bought.value ? "" : { color: colorText };
}
}))
];
const row1UpgradeEffects: ComputedRef<DecimalSource>[] = [
@ -706,7 +717,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
title: "Oil the Oil Pump",
description: "Double oil gain."
},
style: { color: colorText }
style() {
return this.bought.value ? "" : { color: colorText };
}
})),
createUpgrade(() => ({
resource: noPersist(oil),
@ -716,7 +729,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
description:
"Double ore mining speed and square the coal drill amount in its effect."
},
style: { color: colorText }
style() {
return this.bought.value ? "" : { color: colorText };
}
})),
createUpgrade(() => ({
resource: noPersist(oil),
@ -725,7 +740,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
title: "Blaster Burner",
description: "The Oil Burner can now increase your auto smelting multi."
},
style: { color: colorText }
style() {
return this.bought.value ? "" : { color: colorText };
}
})),
createUpgrade(() => ({
resource: noPersist(oil),
@ -734,7 +751,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
title: "Oil Integration",
description: "Reduce Oil Pump's coal consumption multipler from 5 to 4"
},
style: { color: colorText }
style() {
return this.bought.value ? "" : { color: colorText };
}
})),
createUpgrade(() => ({
resource: noPersist(oil),
@ -748,7 +767,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
</>
))
},
style: { color: colorText }
style() {
return this.bought.value ? "" : { color: colorText };
}
}))
];
const row3Upgrades = [
@ -761,7 +782,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
},
visibility: () =>
showIf(management.elfTraining.oilElfTraining.milestones[4].earned.value),
style: { color: colorText }
style() {
return this.bought.value ? "" : { color: colorText };
}
})),
createUpgrade(() => ({
resource: noPersist(oil),
@ -772,7 +795,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
},
visibility: () =>
showIf(management.elfTraining.oilElfTraining.milestones[4].earned.value),
style: { color: colorText }
style() {
return this.bought.value ? "" : { color: colorText };
}
})),
createUpgrade(() => ({
resource: noPersist(oil),
@ -788,7 +813,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
},
visibility: () =>
showIf(management.elfTraining.oilElfTraining.milestones[4].earned.value),
style: { color: colorText }
style() {
return this.bought.value ? "" : { color: colorText };
}
})),
createUpgrade(() => ({
resource: noPersist(oil),
@ -799,7 +826,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
},
visibility: () =>
showIf(management.elfTraining.oilElfTraining.milestones[4].earned.value),
style: { color: colorText }
style() {
return this.bought.value ? "" : { color: colorText };
}
})),
createUpgrade(() => ({
resource: noPersist(oil),
@ -810,7 +839,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
},
visibility: () =>
showIf(management.elfTraining.oilElfTraining.milestones[4].earned.value),
style: { color: colorText }
style() {
return this.bought.value ? "" : { color: colorText };
}
}))
];
const coalConsumption = createSequentialModifier(() => [
@ -964,7 +995,8 @@ const layer = createLayer(id, function (this: BaseLayer) {
multiplier: 50,
description: "350 toys",
enabled: toys.milestones.milestone4.earned
}))
})),
reindeer.reindeer.donner.modifier
]) as WithRequired<Modifier, "description" | "revert">;
const computedOilSpeed = computed(() => oilSpeed.apply(0));

View file

@ -27,6 +27,7 @@ import dyes from "./dyes";
import elves, { ElfBuyable } from "./elves";
import management from "./management";
import plastic from "./plastic";
import reindeer from "./reindeer";
import ribbon from "./ribbon";
import trees from "./trees";
import workshop from "./workshop";
@ -423,7 +424,8 @@ const layer = createLayer(id, function (this: BaseLayer) {
multiplier: wrappingPaper.boosts.sunshine1,
description: "Sunshine Wrapping Paper",
enabled: () => Decimal.gte(wrappingPaper.boosts.sunshine1.value, 2)
}))
})),
reindeer.reindeer.prancer.modifier
]) as WithRequired<Modifier, "description" | "revert">;
const ashCost = createSequentialModifier(() => [
createMultiplicativeModifier(() => ({

View file

@ -37,6 +37,8 @@ import oil from "./oil";
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;
@ -319,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",
@ -383,7 +393,18 @@ const layer = createLayer(id, function (this: BaseLayer) {
createMultiplicativeModifier(() => ({
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

@ -0,0 +1,592 @@
/**
* @module
* @hidden
*/
import HotkeyVue from "components/Hotkey.vue";
import Spacer from "components/layout/Spacer.vue";
import Modal from "components/Modal.vue";
import { createCollapsibleModifierSections } from "data/common";
import { main } from "data/projEntry";
import { createBar, GenericBar } from "features/bars/bar";
import { createClickable } from "features/clickables/clickable";
import { jsx } from "features/feature";
import { createHotkey, GenericHotkey } from "features/hotkey";
import { createUpgrade } from "features/upgrades/upgrade";
import { globalBus } from "game/events";
import { BaseLayer, createLayer } from "game/layers";
import {
createAdditiveModifier,
createMultiplicativeModifier,
createSequentialModifier
} from "game/modifiers";
import { persistent } from "game/persistence";
import Decimal, { DecimalSource, format, formatTime, formatWhole } from "util/bignum";
import { Direction } from "util/common";
import { render, renderGrid } from "util/vue";
import { computed, ref, unref, watchEffect } from "vue";
import boxes from "./boxes";
import cloth from "./cloth";
import coal from "./coal";
import dyes from "./dyes";
import metal from "./metal";
import oil from "./oil";
import paper from "./paper";
import plastic from "./plastic";
import "./styles/reindeer.css";
import trees from "./trees";
const id = "reindeer";
const day = 21;
const layer = createLayer(id, function (this: BaseLayer) {
const name = "Reindeer";
const color = "saddlebrown";
const feedGoal = 1.5e3;
const timeSinceFocus = persistent<number>(0);
const currMultiplier = persistent<DecimalSource>(1);
const currTargets = persistent<Record<string, boolean>>({});
const currCooldown = persistent<number>(0);
const crit = persistent<number>(0);
const maxMultiplier = createSequentialModifier(() => [
createMultiplicativeModifier(() => ({
multiplier: 2,
description: "Carry food in boxes",
enabled: upgrade4.bought
}))
]);
const computedMaxMultiplier = computed(() => maxMultiplier.apply(2));
const targetsCount = createSequentialModifier(() => [
createAdditiveModifier(() => ({
addend: 1,
description: "Guide to Reindeer Handling",
enabled: upgrade3.bought
})),
createAdditiveModifier(() => ({
addend: crit,
description: "Metal clapper",
enabled: upgrade5.bought
}))
]);
const computedTargetsCount = computed(() => targetsCount.apply(1));
const computedMaxCooldown = computed(() => 10);
function focus() {
currCooldown.value = Decimal.fromValue(computedMaxCooldown.value).toNumber();
let targetsSelected = 0;
currTargets.value = {};
timeSinceFocus.value = 0;
while (Decimal.gt(computedTargetsCount.value, targetsSelected)) {
const selectedReindeer =
Object.values(reindeer)[Math.floor(Math.random() * Object.values(reindeer).length)];
const roll = selectedReindeer?.name ?? "";
if (!currTargets.value[roll]) {
currTargets.value[roll] = true;
targetsSelected++;
if (upgrade8.bought.value) {
selectedReindeer.onClick();
}
}
}
}
const focusMeter = createBar(() => ({
direction: Direction.Right,
width: 476,
height: 50,
style: `border-radius: 0`,
borderStyle: `border-radius: 0`,
fillStyle: () => ({
background: currCooldown.value > 0 ? color : "#7f7f00",
animation: currCooldown.value > 0 ? "1s focused-eating-bar linear infinite" : "",
opacity: currCooldown.value > 0 ? currCooldown.value / 10 : 1,
transition: "none"
}),
progress: () =>
Decimal.sub(currMultiplier.value, 1)
.div(Decimal.sub(computedMaxMultiplier.value, 1))
.toNumber(),
display: jsx(() => (
<>
{format(currMultiplier.value)}x
{currCooldown.value > 0 ? (
<>
{" "}
to {Object.keys(currTargets.value).join(", ")} for{" "}
{formatTime(currCooldown.value)}
</>
) : (
""
)}
</>
))
})) as GenericBar;
const focusButton = createClickable(() => ({
display: {
title: "Focus",
description: jsx(() => (
<>
Motivate reindeer to eat, multiplying {formatWhole(computedTargetsCount.value)}{" "}
random reindeer's eating rate by up to {format(computedMaxMultiplier.value)}x
for {formatTime(computedMaxCooldown.value)}, equal to the focus bar's effect.
</>
))
},
style: {
width: "480px",
minHeight: "80px",
zIndex: 4
},
canClick: () => Decimal.eq(currCooldown.value, 0),
onClick() {
focus();
}
}));
const cooldown = createSequentialModifier(() => [
createMultiplicativeModifier(() => ({
multiplier: 0.5,
description: "Pile of coal",
enabled: upgrade2.bought
}))
]);
const computedCooldown = computed(() => cooldown.apply(10));
function createReindeer(options: {
name: string;
key: string;
boostDescription: string;
boostAmount: DecimalSource;
}) {
const timesFed = persistent<DecimalSource>(0);
const progress = persistent<DecimalSource>(0);
const hotkey = createHotkey(() => ({
key: "Numpad " + options.key,
description: "Feed " + options.name,
enabled: main.days[day - 1].opened,
onPress: clickable.onClick
})) as GenericHotkey;
const clickable = createClickable(() => {
const progressBar = createBar(() => ({
direction: Direction.Right,
width: 140,
height: 10,
style: "margin-top: 8px",
borderStyle: "border-color: black",
baseStyle: "margin-top: -1px",
fillStyle: () => ({
marginTop: "-1px",
transitionDuration: "0s",
background: "black",
animation:
currTargets.value[options.name] && currCooldown.value > 0
? ".5s focused-eating-bar linear infinite"
: ""
}),
progress: () => Decimal.div(progress.value, computedCooldown.value)
}));
const modifier = createMultiplicativeModifier(() => ({
multiplier: effect,
description: options.name,
enabled: () => Decimal.gt(timesFed.value, 0)
}));
const effect = computed(() =>
Decimal.times(options.boostAmount, timesFed.value)
.add(1)
.pow(upgrade9.bought.value ? 1.1 : 1)
);
return {
...options,
hotkey,
timesFed,
progress,
effect,
modifier,
display: {
title: jsx(() => (
<h3>
Feed {options.name} <HotkeyVue hotkey={hotkey} />
</h3>
)),
description: jsx(() => (
<>
<br />
Each time you feed {options.name} will increase your{" "}
{options.boostDescription} by +{format(options.boostAmount)}x
<Spacer />
Currently {format(effect.value)}x
<br />
{render(progressBar)}
</>
))
},
style: {
width: "160px",
height: "160px"
},
canClick() {
return Decimal.gte(progress.value, computedCooldown.value);
},
onClick() {
if (!unref(clickable.canClick)) {
return;
}
let amount = Decimal.div(progress.value, computedCooldown.value);
if (upgrade1.bought.value) {
amount = Decimal.times(amount, 2);
}
timesFed.value = Decimal.add(timesFed.value, amount);
progress.value = 0;
},
update(diff: number) {
if (Decimal.gte(progress.value, computedCooldown.value)) {
progress.value = computedCooldown.value;
} else {
let amount: DecimalSource = diff;
const isFocused = currTargets.value[options.name] && currCooldown.value > 0;
if (isFocused) {
amount = Decimal.times(amount, currMultiplier.value);
}
progress.value = Decimal.add(progress.value, amount);
if (clickable.isHolding.value || (upgrade8.bought.value && isFocused)) {
clickable.onClick();
}
}
}
};
});
return clickable;
}
const dasher = createReindeer({
name: "Dasher",
key: "7",
boostDescription: "log gain",
boostAmount: 1
});
const dancer = createReindeer({
name: "Dancer",
key: "8",
boostDescription: "coal gain",
boostAmount: 0.1
});
const prancer = createReindeer({
name: "Prancer",
key: "9",
boostDescription: "paper gain",
boostAmount: 0.1
});
const vixen = createReindeer({
name: "Vixen",
key: "4",
boostDescription: "boxes gain",
boostAmount: 0.1
});
const comet = createReindeer({
name: "Comet",
key: "5",
boostDescription: "metal gain",
boostAmount: 0.1
});
const cupid = createReindeer({
name: "Cupid",
key: "6",
boostDescription: "cloth actions",
boostAmount: 0.1
});
const donner = createReindeer({
name: "Donner",
key: "1",
boostDescription: "oil gain",
boostAmount: 0.01
});
const blitzen = createReindeer({
name: "Blitzen",
key: "2",
boostDescription: "plastic gain",
boostAmount: 0.1
});
const rudolph = createReindeer({
name: "Rudolph",
key: "3",
boostDescription: "dye gain",
boostAmount: 0.01
});
// order is designed so hotkeys appear 1-9, even though they're displayed in numpad order in the layer itself
const reindeer = { donner, blitzen, rudolph, vixen, comet, cupid, dasher, dancer, prancer };
const sumTimesFed = computed(() =>
Object.values(reindeer)
.map(r => r.timesFed.value)
.reduce(Decimal.add, Decimal.dZero)
);
const upgrade1 = createUpgrade(() => ({
resource: trees.logs,
cost: 1e97,
style: {
width: "160px"
},
display: {
title: "Sawdust?",
description:
"Adding some sawdust to the feed allows you to make more of it. Each feed action counts twice"
}
}));
const upgrade2 = createUpgrade(() => ({
resource: coal.coal,
cost: 1e167,
style: {
width: "160px"
},
display: {
title: "Pile of coal",
description:
"Building a threatening pile of coal encourages the reindeer to behave. Each reindeer eats twice as fast"
}
}));
const upgrade3 = createUpgrade(() => ({
resource: paper.paper,
cost: 1e117,
style: {
width: "160px"
},
display: {
title: "Guide to Reindeer Handling",
description:
"Written reindeer handling instructions allow you to help more focus at once. Increase focus targets by one"
}
}));
const upgrade4 = createUpgrade(() => ({
resource: boxes.boxes,
cost: 1e102,
style: {
width: "160px"
},
display: {
title: "Carry food in boxes",
description:
"Carrying reindeer food in boxes allows you to distribute it faster. Double the maximum focus multiplier"
}
}));
const upgrade5 = createUpgrade(() => ({
resource: metal.metal,
cost: 1e67,
style: {
width: "160px"
},
display: {
title: "Metal clapper",
description:
'Striking two rods of metal can help get more reindeer\'s attention when done right. "Critical" focuses now affect up to two additional reindeer'
}
}));
const upgrade6 = createUpgrade(() => ({
resource: cloth.cloth,
cost: 1e20,
style: {
width: "160px"
},
display: {
title: "Focus bar padding",
description:
"Adding padding to the focus bar lets you slow it down when it's closer to the max value"
}
}));
const upgrade7 = createUpgrade(() => ({
resource: oil.oil,
cost: 4e25,
style: {
width: "160px"
},
display: {
title: "Oil can do that?",
description:
"Using a lot of oil somehow let's reindeers focus themselves with a random value when left un-focused for 10s"
}
}));
const upgrade8 = createUpgrade(() => ({
resource: plastic.plastic,
cost: 1e22,
style: {
width: "160px"
},
display: {
title: "Automated feeder",
description: "An automated feeder lets focused reindeer eat automatically"
}
}));
const upgrade9 = createUpgrade(() => ({
resource: dyes.dyes.white.amount,
cost: 7.5e7,
style: {
width: "160px"
},
display: {
title: "Colorful food",
description:
"Adding some non-toxic dyes to the food makes them more powerful. Raise each reindeer's effect to the ^1.1"
}
}));
const upgrades = {
upgrade1,
upgrade2,
upgrade3,
upgrade4,
upgrade5,
upgrade6,
upgrade7,
upgrade8,
upgrade9
};
const [generalTab, generalTabCollapsed] = createCollapsibleModifierSections(() => [
{
title: "Max Focus Multiplier",
modifier: maxMultiplier,
base: 2
},
{
title: "Focus Targets",
modifier: targetsCount,
base: 1
},
{
title: "Eating duration",
modifier: cooldown,
base: 10
}
]);
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;
}
Object.values(reindeer).forEach(reindeer => reindeer.update(diff));
currCooldown.value = Math.max(currCooldown.value - diff, 0);
let auto = false;
if (upgrade7.bought.value) {
timeSinceFocus.value += diff;
if (timeSinceFocus.value > 20) {
auto = true;
}
}
if (Decimal.eq(currCooldown.value, 0)) {
let speed = 1000;
if (auto) {
speed = Math.random() * 1000;
}
let stoppedAt = 1 - Math.abs(Math.sin((Date.now() / speed) * 2));
if (upgrade6.bought.value) {
stoppedAt = 1 - (1 - stoppedAt) ** 2;
}
crit.value = stoppedAt > 0.975 ? 2 : stoppedAt > 0.9 ? 1 : 0;
currMultiplier.value = Decimal.pow(computedMaxMultiplier.value, stoppedAt);
if (auto) {
focus();
}
}
});
const dayProgress = createBar(() => ({
direction: Direction.Right,
width: 600,
height: 25,
fillStyle: `animation: 15s reindeer-bar linear infinite`,
progress: () => (main.day.value === day ? Decimal.div(sumTimesFed.value, feedGoal) : 1),
display: jsx(() =>
main.day.value === day ? (
<>
{formatWhole(sumTimesFed.value)}/{formatWhole(feedGoal)}
</>
) : (
""
)
)
})) as GenericBar;
watchEffect(() => {
if (main.day.value === day && Decimal.gte(sumTimesFed.value, feedGoal)) {
main.completeDay();
}
});
return {
name,
day,
color,
reindeer,
generalTabCollapsed,
timeSinceFocus,
currMultiplier,
currTargets,
currCooldown,
upgrades,
crit,
minWidth: 700,
display: jsx(() => (
<>
<div>
{main.day.value === day
? `Feed reindeer ${formatWhole(feedGoal)} times 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 />
<div>You have fed reindeer {formatWhole(sumTimesFed.value)} times</div>
<Spacer />
{renderGrid(
[focusButton],
[focusMeter],
[dasher, dancer, prancer],
[vixen, comet, cupid],
[donner, blitzen, rudolph]
)}
<Spacer />
{renderGrid(
[upgrade1, upgrade2, upgrade3],
[upgrade4, upgrade5, upgrade6],
[upgrade7, upgrade8, upgrade9]
)}
</>
)),
minimizedDisplay: jsx(() => (
<div>
{name} <span class="desc">{format(sumTimesFed.value)} times fed</span>
</div>
))
};
});
export default layer;

View file

@ -93,7 +93,6 @@ const layer = createLayer(id, () => {
if (!unref(makeRibbon.canClick)) {
return;
}
currentDyeType.value.amount.value = 0;
currentDyeType.value.buyable.amount.value = 0;
cloth.cloth.value = Decimal.sub(cloth.cloth.value, 1e9);
ribbon.value = Decimal.add(ribbon.value, 1);

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;

225
src/data/layers/sleigh.tsx Normal file
View file

@ -0,0 +1,225 @@
/**
* @module
* @hidden
*/
import Spacer from "components/layout/Spacer.vue";
import { createCollapsibleMilestones} from "data/common";
import { main } from "data/projEntry";
import { createBar } from "features/bars/bar";
import { jsx, showIf } from "features/feature";
import { createMilestone } from "features/milestones/milestone";
import { BaseLayer, createLayer } from "game/layers";
import Decimal, { DecimalSource, format, formatWhole } from "util/bignum";
import { Direction} from "util/common";
import { render } from "util/vue";
import { computed, watchEffect } from "vue";
import management from "./management";
import trees from "./trees";
import metal from "./metal";
import plastic from "./plastic"
import { createBuyable, GenericBuyable } from "features/buyable";
import { Resource } from "features/resources/resource";
import { isArray } from "@vue/shared";
const id = "sleigh";
const day = 22;
const layer = createLayer(id, function (this: BaseLayer) {
const name = "Sleigh";
const color = "#D71830";
const colorDark = "#A01020";
function displayCost(
res: Resource<DecimalSource> | Resource<DecimalSource>[],
cost: DecimalSource,
label: string
) {
const affordable = (isArray(res) ? res : [res]).every(res => Decimal.gte(res.value, cost));
return (
<span class={affordable ? "" : "unaffordable"}>
{format(cost)} {label}
</span>
);
}
const sleighProgress = computed(() => sleigh.amount)
const sleighCost = computed(() => {
let v = sleighProgress.value.value;
return {
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(() => ({
display: jsx(() => (
<>
<b style="font-size: x-large">Fix 1% of the sleigh</b>
<br />
<br />
<span style="font-size: large">
Requires: {displayCost(trees.logs, sleighCost.value.wood, "logs")},
{displayCost(metal.metal, sleighCost.value.metal, "metal")},
{displayCost(plastic.plastic, sleighCost.value.plastic, "plastic")}
</span>
</>
)),
canPurchase(): boolean {
return (
sleighCost.value.wood.lte(trees.logs.value) &&
sleighCost.value.metal.lte(metal.metal.value) &&
sleighCost.value.plastic.lte(plastic.plastic.value)
);
},
onPurchase() {
this.amount.value = Decimal.add(this.amount.value, 1);
},
visibility: () => showIf(Decimal.lt(sleighProgress.value.value, 100)),
style: "width: 600px"
})) as GenericBuyable;
const shouldShowPopups = computed(() => true);
const milestone1 = createMilestone(() => ({
display: {
requirement: "1% Sleigh Fixed",
effectDisplay: "Ore gives 5% more metal for each % of sleigh fixed"
},
shouldEarn: () => Decimal.gte(sleighProgress.value.value, 1),
showPopups: shouldShowPopups
}));
const milestone2 = createMilestone(() => ({
display: {
requirement: "10% Sleigh Fixed",
effectDisplay: "Gain an additional 5% more wood for each 5% of sleigh fixed"
},
shouldEarn: () => Decimal.gte(sleighProgress.value.value, 10),
showPopups: shouldShowPopups
}));
const milestone3 = createMilestone(() => ({
display: {
requirement: "20% Sleigh Fixed",
effectDisplay: "Gain an additional 5% more plastic for each 5% of sleigh fixed"
},
shouldEarn: () => Decimal.gte(sleighProgress.value.value, 20),
showPopups: shouldShowPopups
}));
const milestone4 = createMilestone(() => ({
display: {
requirement: "30% Sleigh Fixed",
effectDisplay: "All automatic metal actions are doubled"
},
shouldEarn: () => Decimal.gte(sleighProgress.value.value, 30),
showPopups: shouldShowPopups
}));
const milestone5 = createMilestone(() => ({
display: {
requirement: "40% Sleigh Fixed",
effectDisplay: "Plastic gain is quadrupled"
},
shouldEarn: () => Decimal.gte(sleighProgress.value.value, 40),
showPopups: shouldShowPopups
}));
const milestone6 = createMilestone(() => ({
display: {
requirement: "50% Sleigh Fixed",
effectDisplay: "Trees give 10x as many logs"
},
shouldEarn: () => Decimal.gte(sleighProgress.value.value, 50),
showPopups: shouldShowPopups
}));
const milestone7 = createMilestone(() => ({
display: {
requirement: "75% Sleigh Fixed",
effectDisplay: "Gain 40 extra refineries for every 2% of sleigh fixed"
},
shouldEarn: () => Decimal.gte(sleighProgress.value.value, 75),
showPopups: shouldShowPopups
}));
const milestone8 = createMilestone(() => ({
display: {
requirement: "100% Sleigh Fixed",
effectDisplay: "Metal per ore is raised to the 1.2th power"
},
shouldEarn: () => Decimal.gte(sleighProgress.value.value, 100),
showPopups: shouldShowPopups
}));
const milestones = {
milestone1,
milestone2,
milestone3,
milestone4,
milestone5,
milestone6,
milestone7,
milestone8
};
const { collapseMilestones, display: milestonesDisplay } =
createCollapsibleMilestones(milestones);
const dayProgress = createBar(() => ({
direction: Direction.Right,
width: 600,
height: 25,
fillStyle: `backgroundColor: ${colorDark}`,
progress: () =>
main.day.value === day || main.currentlyMastering.value?.name === name
? Decimal.div(sleighProgress.value.value, 100)
: 1,
display: jsx(() =>
main.day.value === day || main.currentlyMastering.value?.name === name ? (
<>{formatWhole(sleighProgress.value.value)}%</>
) : (
""
)
)
}));
watchEffect(() => {
if (main.day.value === day && Decimal.gte(sleighProgress.value.value, 100)) {
main.completeDay();
}
});
return {
name,
day,
color,
sleighProgress,
milestones,
collapseMilestones,
minWidth: 700,
sleigh,
display: jsx(() => (
<>
<div>
{main.day.value === day
? `Fix the sleigh to complete the day`
: `${name} Complete!`}
</div>
{render(dayProgress)}
<Spacer />
<div>
<span>The sleigh is </span>
<h2 style={`color: ${color}; text-shadow: 0 0 10px ${color}`}>
{formatWhole(sleighProgress.value.value)}
</h2>
% fixed
</div>
{Decimal.lt(sleighProgress.value.value, 100) ||
management.elfTraining.expandersElfTraining.milestones[2].earned.value ? (
<Spacer />
) : null}
{render(sleigh)}
<Spacer />
{milestonesDisplay()}
</>
)),
minimizedDisplay: jsx(() => (
<div>
{name}{" "}
<span class="desc">
{formatWhole(sleighProgress.value.value)}% sleigh
</span>
</div>
)),
};
});
export default layer;

View file

@ -94,4 +94,15 @@
rgb(76, 76, 255) 40px 50px, white 50px 60px
);
}
}
}
@keyframes reindeer-bar {
from {
background: 0 0 / 28px 28px repeat
repeating-linear-gradient(-45deg, brown 0 10px, saddlebrown 10px 20px);
}
to {
background: 28px 0px / 28px 28px repeat
repeating-linear-gradient(-45deg, brown 0 10px, saddlebrown 10px 20px);
}
}

View file

@ -151,8 +151,10 @@
justify-content: space-evenly;
align-items: flex-start;
align-content: flex-start;
justify-content: flex-start;
width: 148px;
direction: ltr;
text-align: left;
}
.comp-list::after {
@ -176,6 +178,11 @@
pointer-events: all;
}
.comp-list .comp > img:first-child {
width: 50px;
height: 50px;
}
.comp-list .comp:nth-child(3)::after,
.comp-list .comp:nth-child(4)::after {
content: "";
@ -192,7 +199,7 @@
.comp-list .comp:nth-child(4)::after {
left: -50px;
}
.comp-list .comp img.selected:not(.producedItem) {
.comp-list .comp img.selected, .comp-list .comp img.selected + img {
transform: translate(-5px, -5px);
filter: drop-shadow(2px 2px 0 var(--foreground)) drop-shadow(5px 5px 5px #0007);
}

View file

@ -0,0 +1,10 @@
@keyframes focused-eating-bar {
from {
background: 0 0 / 28px 28px repeat
repeating-linear-gradient(-45deg, brown 0 10px, saddlebrown 10px 20px);
}
to {
background: 28px 0px / 28px 28px repeat
repeating-linear-gradient(-45deg, brown 0 10px, saddlebrown 10px 20px);
}
}

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

@ -40,6 +40,8 @@ import workshop from "./workshop";
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;
@ -557,6 +559,17 @@ 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,
description: "100% Foundation Completed",

View file

@ -30,6 +30,7 @@ import {
import { noPersist, persistent } from "game/persistence";
import Decimal, { DecimalSource, formatWhole } from "util/bignum";
import { Direction, WithRequired } from "util/common";
import { loadingSave } from "util/save";
import { render } from "util/vue";
import { computed, ref, unref, watchEffect } from "vue";
import elves from "./elves";
@ -123,9 +124,6 @@ const layer = createLayer(id, function (this: BaseLayer) {
)),
visibility: () => showIf(Decimal.lt(foundationProgress.value, computedMaxFoundation.value)),
canClick: () => {
if (Decimal.gt(computedMaxFoundation.value, foundationProgress.value)){
foundationProgress.value = Decimal.min(0, computedMaxFoundation.value)
}
if (Decimal.lt(trees.logs.value, foundationConversion.nextAt.value)) {
return false;
}
@ -146,6 +144,15 @@ const layer = createLayer(id, function (this: BaseLayer) {
style: "width: 600px; min-height: unset"
}));
watchEffect(() => {
if (
!loadingSave.value &&
Decimal.lt(computedMaxFoundation.value, foundationProgress.value)
) {
foundationProgress.value = Decimal.min(0, computedMaxFoundation.value);
}
});
const buildFoundationHK = createHotkey(() => ({
key: "w",
description: "Build foundation",

View file

@ -31,10 +31,13 @@ import metal from "./layers/metal";
import oil from "./layers/oil";
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";
import sleigh from "./layers/sleigh";
import wrappingPaper from "./layers/wrapping-paper";
import boxesSymbol from "./symbols/cardboardBox.png";
import clothSymbol from "./symbols/cloth.png";
@ -48,14 +51,18 @@ import metalSymbol from "./symbols/metal.png";
import oilSymbol from "./symbols/oil.png";
import paperSymbol from "./symbols/paperStacks.png";
import plasticSymbol from "./symbols/plastic.png";
import presentsSymbol from "./symbols/presents.png";
import reindeerSymbol from "./symbols/reindeer.png";
import ribbonsSymbol from "./symbols/ribbons.png";
import workshopSymbol from "./symbols/sws.png";
import advFactorySymbol from "./symbols/teddyBear.png";
import treeSymbol from "./symbols/tree.png";
import toysSymbol from "./symbols/truck.png";
import advFactorySymbol from "./symbols/teddyBear.png";
import advManagementSymbol from "./symbols/workshopMansion.png";
import wrappingPaperSymbol from "./symbols/wrappingPaper.png";
import packing from "./layers/packing";
import sleighSymbol from "./symbols/sleigh.png";
import routingSymbol from "./symbols/gps.png";
export interface Day extends VueFeature {
day: number;
@ -465,37 +472,41 @@ export const main = createLayer("main", function (this: BaseLayer) {
createDay(() => ({
day: 20,
shouldNotify: false,
layer: null, // "presents"
symbol: wrappingPaperSymbol,
story: "",
completedStory: "",
layer: "factory",
symbol: presentsSymbol,
story: "Santa comes by again, and tells you that just toys may not be appealing enough. He tells you that you should probably wrap them in some wrapping paper so that it's more of a surprise. You try to argue that you've already done too much for him and deserve a day off, but Santa argues that it's for the benefit of everyone and that you'll get your vacation soon. Oh well, time to get back to the factory and expand it even more. Here we go again!",
completedStory:
"That was a lot of work, but it certainly felt worth actually using all those decorative supplies you'd previously made. One more sleepless night down, just a handful more to go. Good Job!",
masteredStory: ""
})),
createDay(() => ({
day: 21,
shouldNotify: false,
layer: null, // "reindeer"
symbol: "",
story: "",
completedStory: "",
layer: "reindeer",
symbol: reindeerSymbol,
story: "Now that the toys are being taken care of, it's time to make sure everything is prepped for the big night. One immediate concern is the reindeer, who are going to have to be in tip-top shape. Fortunately, Santa has a recipe to a very strong vitamin-filled kibble that'll get them pumped in no time!",
completedStory:
"Alright, now that the reindeer have been given all their ste- vitamins, I mean, they should be prepared for Christmas. Good Job!",
masteredStory: ""
})),
createDay(() => ({
day: 22,
shouldNotify: false,
layer: null, // "sleigh"
symbol: "",
story: "",
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(() => ({
@ -617,6 +628,9 @@ export const getInitialLayers = (
ribbon,
toys,
factory,
reindeer,
sleigh,
routing,
packing
];

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 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

@ -89,6 +89,9 @@ document.onkeydown = function (e) {
if (e.ctrlKey) {
key = "ctrl+" + key;
}
if (e.code.startsWith("Numpad") && `Numpad ${key}` in hotkeys) {
key = "Numpad " + key;
}
const hotkey = hotkeys[key];
if (hotkey && unref(hotkey.enabled)) {
e.preventDefault();

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

@ -21,7 +21,7 @@ import type {
} from "util/computed";
import { processComputable } from "util/computed";
import { createLazyProxy } from "util/proxies";
import type { InjectionKey, Ref } from "vue";
import { computed, InjectionKey, Ref } from "vue";
import { ref, shallowReactive, unref } from "vue";
/** A feature's node in the DOM that has its size tracked. */
@ -231,6 +231,8 @@ export function createLayer<T extends LayerOptions>(
processComputable(layer as T, "color");
processComputable(layer as T, "display");
processComputable(layer as T, "classes");
processComputable(layer as T, "style");
processComputable(layer as T, "name");
setDefault(layer, "name", layer.id);
processComputable(layer as T, "minWidth");
@ -239,6 +241,34 @@ export function createLayer<T extends LayerOptions>(
setDefault(layer, "minimizable", true);
processComputable(layer as T, "minimizedDisplay");
const style = layer.style as ProcessedComputable<StyleValue> | undefined;
layer.style = computed(() => {
let width = unref(layer.minWidth as ProcessedComputable<number | string>);
if (typeof width === "number" || !Number.isNaN(parseInt(width))) {
width = width + "px";
}
return [
unref(style) ?? "",
layer.minimized?.value
? {
flexGrow: "0",
flexShrink: "0",
width: "60px",
minWidth: "",
flexBasis: "",
margin: "0"
}
: {
flexGrow: "",
flexShrink: "",
width: "",
minWidth: width,
flexBasis: width,
margin: ""
}
];
}) as Ref<StyleValue>;
return layer as unknown as Layer<T>;
});
}

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,