Merge branch 'day-17-toys' into day-20-factory

This commit is contained in:
ducdat0507 2022-12-20 18:05:46 +07:00
commit 6b9a53685b
35 changed files with 3518 additions and 691 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

58
src/components/Hotkey.vue Normal file
View file

@ -0,0 +1,58 @@
<!-- Make eslint not whine about putting spaces before the +'s -->
<!-- eslint-disable prettier/prettier -->
<template>
<template v-if="isCtrl"
><div class="key">Ctrl</div
>+</template
><template v-if="isShift"
><div class="key">Shift</div
>+</template
>
<div class="key">{{ key }}</div>
</template>
<script setup lang="ts">
import { GenericHotkey } from "features/hotkey";
import { reactive } from "vue";
const props = defineProps<{
hotkey: GenericHotkey;
}>();
let key = reactive(props.hotkey).key;
let isCtrl = key.startsWith("ctrl+");
if (isCtrl) key = key.slice(5);
let isShift = key.startsWith("shift+");
if (isShift) key = key.slice(6);
let isAlpha = key.length == 1 && key.toLowerCase() != key.toUpperCase();
if (isAlpha) key = key.toUpperCase();
</script>
<style scoped>
.key {
display: inline-block;
height: 1.4em;
min-width: 1em;
margin-block: 0.1em;
padding-inline: 0.2em;
vertical-align: 0.1em;
background: var(--foreground);
color: var(--feature-foreground);
border: 1px solid #0007;
border-radius: 0.3em;
box-shadow: 0 0.1em #0007, 0 0.1em var(--foreground);
font-size: smaller;
text-align: center;
user-select: none;
transition: transform 0s, box-shadow 0s;
}
.key:active {
transform: translateY(0.1em);
box-shadow: none;
}
</style>

View file

@ -1,13 +1,26 @@
<template>
<div class="day feature dontMerge opened" v-if="opened.value">
<Tooltip :display="(layers[layer ?? '']?.name ?? '')" :direction="Direction.Up" yoffset="5px">
<Transition appear name="door">
<div
class="day feature dontMerge opened"
:class="{
mastered: unref(mastered),
masteryLock,
wallpaper: day < 8
}"
v-if="opened.value"
>
<div class="ribbon" v-if="day >= 8" />
<Tooltip :display="layers[layer ?? '']?.name ?? ''" :direction="Direction.Up" yoffset="5px">
<Transition appear :name="masteryLock ? 'door-close' : 'door'">
<div class="doors" @click="emit('openLayer')">
<div class="date">Dec<br />{{ day }}</div>
<div class="date">Dec<br />{{ day }}</div>
</div>
</Transition>
<div class="icon" @click="emit('openLayer')" :style="{ backgroundImage: `url(${symbol})` }"></div>
<div
class="icon"
@click="emit('openLayer')"
:style="{ backgroundImage: `url(${symbol})` }"
></div>
<div class="lore" @click="emit('openLore')">?</div>
<Notif v-if="unref(shouldNotify)" />
</Tooltip>
@ -15,14 +28,18 @@
<div
v-else
class="day feature dontMerge"
:class="{ can: canOpen, locked: !canOpen, canOpen }"
:class="{ can: canOpen, locked: !canOpen, canOpen, mastered: unref(mastered) }"
@click="tryUnlock"
>
<div class="doors"></div>
<div class="date">Dec<br />{{ day }}</div>
<div v-if="!canOpen" class="material-icons lock">lock</div>
<div v-if="main.day.value === day && !canOpen" class="timer">
{{ main.timeUntilNewDay.value < 0 ? "NYI, sorry" : formatTime(main.timeUntilNewDay.value, 0) }}
{{
main.timeUntilNewDay.value < 0
? "Not Ready"
: formatTime(main.timeUntilNewDay.value, 0)
}}
</div>
<Notif v-if="canOpen" />
</div>
@ -36,9 +53,11 @@ import { layers } from "game/layers";
import { Direction } from "util/common";
import { formatTime } from "util/break_eternity";
import { ProcessedComputable } from "util/computed";
import type { Ref } from "vue";
import { Ref, Transition } from "vue";
import { computed, unref } from "vue";
import { main } from "./projEntry";
import coal from "./layers/coal";
import dyes from "./layers/dyes";
const props = defineProps<{
day: number;
@ -47,6 +66,7 @@ const props = defineProps<{
opened: Ref<boolean>;
recentlyUpdated: Ref<boolean>;
shouldNotify: ProcessedComputable<boolean>;
mastered: Ref<boolean>;
}>();
const emit = defineEmits<{
@ -63,6 +83,17 @@ const canOpen = computed(
new Date().getDate() >= props.day
);
const isMastering = main.isMastery;
const includeMastery = computed(
() =>
props.mastered.value ||
main.currentlyMastering.value == layers[props.layer ?? ""] ||
["wrappingPaper", "ribbon"].includes(props.layer || "") ||
(coal.mastered.value && props.layer == "elves") ||
(dyes.mastered.value && props.layer == "elves")
);
const masteryLock = computed(() => isMastering.value && !includeMastery.value);
function tryUnlock() {
if (canOpen.value) {
emit("unlockLayer");
@ -80,59 +111,88 @@ function tryUnlock() {
margin: 5%;
}
.mastered.day.wallpaper {
box-shadow: rgb(0 0 0 / 25%) 0px 0px 0px 3px inset;
background: linear-gradient(
225deg,
rgb(255, 76, 76) 11.1%,
rgb(255, 255, 255) 11.1% 22.2%,
rgb(65, 255, 95) 22.2% 33.3%,
rgb(255, 255, 255) 33.3% 44.4%,
rgb(255, 76, 76) 44.4% 55.5%,
rgb(255, 255, 255) 55.5% 66.6%,
rgb(65, 255, 95) 66.6% 77.7%,
rgb(255, 255, 255) 77.7% 88.8%,
rgb(255, 76, 76) 88.8%
);
}
.door-enter-from::before,
.door-enter-from::after,
.door-leave-to::before,
.door-leave-to::after {
.door-close-enter-to::before,
.door-close-enter-to::after {
transform: perspective(150px) rotateY(0) !important;
}
.door-enter-from .date,
.door-leave-to .date {
.door-close-enter-to .date {
transform: translate(-50%, -50%) perspective(150px) rotateY(0) !important;
}
.door-enter-active::before,
.door-enter-active::after,
.door-leave-active::before,
.door-leave-active::after {
.door-close-enter-active::before,
.door-close-enter-active::after {
z-index: 2;
}
.door-enter-active .date,
.door-leave-active .date {
.door-close-enter-active .date {
z-index: 3;
}
.day.opened .doors::before,
.day.opened .doors::after,
.day.opened .doors .date {
.day .doors::before,
.day .doors::after,
.day .doors .date {
transition: 1s;
}
.day.opened .doors::before {
transform-origin: left;
transform: perspective(150px) rotateY(-135deg);
}
.day.opened .doors::after {
transform-origin: right;
}
.day.opened:not(.masteryLock) .doors::before {
transform: perspective(150px) rotateY(-135deg);
}
.day.opened:not(.masteryLock) .doors::after {
transform: perspective(150px) rotateY(135deg);
}
.day.opened .doors .date:first-child {
transform-origin: left;
transform: translate(-50%, -50%) perspective(150px) rotateY(-135deg);
clip-path: polygon(0 0, 50% 0, 50% 100%, 0 100%);
}
.day.opened .doors .date:last-child {
transform-origin: right;
transform: translate(-50%, -50%) perspective(150px) rotateY(135deg);
clip-path: polygon(100% 0, 50% 0, 50% 100%, 100% 100%);
}
.tooltip-container, .doors {
.day.opened:not(.masteryLock) .doors .date:first-child {
transform: translate(-50%, -50%) perspective(150px) rotateY(-135deg);
}
.day.opened:not(.masteryLock) .doors .date:last-child {
transform: translate(-50%, -50%) perspective(150px) rotateY(135deg);
}
.tooltip-container,
.doors {
position: absolute;
width: 100%;
height: 100%;
@ -152,6 +212,7 @@ function tryUnlock() {
width: 50%;
height: 100%;
pointer-events: none;
z-index: 1;
}
.doors::before {
@ -164,6 +225,73 @@ function tryUnlock() {
right: 0;
}
.masteryLock {
cursor: not-allowed;
}
.masteryLock > * {
pointer-events: none;
}
.masteryLock > * > :not(.doors) {
opacity: 0;
}
.masteryLock .icon {
transition-duration: 0.2s;
transition-delay: 0.8s;
}
.mastered.wallpaper .doors::before,
.mastered.wallpaper .doors::after {
background: linear-gradient(
225deg,
rgb(255, 76, 76) 11.1%,
rgb(255, 255, 255) 11.1% 22.2%,
rgb(65, 255, 95) 22.2% 33.3%,
rgb(255, 255, 255) 33.3% 44.4%,
rgb(255, 76, 76) 44.4% 55.5%,
rgb(255, 255, 255) 55.5% 66.6%,
rgb(65, 255, 95) 66.6% 77.7%,
rgb(255, 255, 255) 77.7% 88.8%,
rgb(255, 76, 76) 88.8%
);
}
.mastered .ribbon {
position: absolute;
top: -2px;
left: 0px;
width: calc(100% + 0px);
height: calc(100% + 4px);
overflow: hidden;
pointer-events: none;
user-select: none;
z-index: 11;
}
.mastered .ribbon::after {
content: "🎀";
color: red;
position: absolute;
top: -5px;
left: -5px;
font-size: xx-large;
transform: rotateZ(-45deg);
z-index: 1;
}
.mastered .ribbon::before {
content: "";
width: calc(100% - 24px);
height: 100%;
border: solid darkred 8px;
transform: rotateZ(45deg);
position: absolute;
top: 0;
left: 0;
border-top: none;
border-bottom: none;
z-index: 1;
}
.date {
position: absolute;
top: 50%;
@ -174,7 +302,7 @@ function tryUnlock() {
pointer-events: none;
user-select: none;
backface-visibility: hidden;
width: 100%;
width: calc(100% - 14px);
}
.timer {
@ -224,5 +352,6 @@ function tryUnlock() {
transform: translate(-50%, -50%);
opacity: 0.2;
font-size: 400%;
z-index: 2;
}
</style>

View file

@ -56,6 +56,8 @@
<img v-if="day >= 5" :src="boxes" class="scene-item" />
<img v-if="day >= 9" :src="plastic" class="scene-item" />
<img v-if="day >= 10" :src="dyes" class="scene-item" />
<img v-if="day >= 14" :src="wrappingPaper" class="scene-item" />
<img v-if="day >= 15" :src="ribbons" class="scene-item" />
</div>
</div>
</template>
@ -75,6 +77,8 @@ import dyes from "./symbols/dyes.png";
import management from "./symbols/elfManagement.png";
import advManagement from "./symbols/workshopMansion.png";
import letters from "./symbols/letterbox.png";
import wrappingPaper from "./symbols/wrappingPaper.png";
import ribbons from "./symbols/ribbons.png";
defineProps<{
day: number;

View file

@ -12,6 +12,13 @@
aspect-ratio: 3151 / 4190;
}
.advent.decorating {
filter: hue-rotate(-150deg);
}
.advent.decorating > * {
filter: hue-rotate(150deg);
}
.advent > .table {
width: 100%;
}

View file

@ -16,12 +16,13 @@ import { GenericMilestone } from "features/milestones/milestone";
import { displayResource, Resource, trackTotal } from "features/resources/resource";
import type { GenericTree, GenericTreeNode, TreeNode, TreeNodeOptions } from "features/trees/tree";
import { createTreeNode } from "features/trees/tree";
import { BaseLayer, Layer } from "game/layers";
import type { Modifier } from "game/modifiers";
import type { Persistent } from "game/persistence";
import { DefaultValue, persistent } from "game/persistence";
import player from "game/player";
import settings from "game/settings";
import type { DecimalSource } from "util/bignum";
import { DecimalSource, formatSmall } from "util/bignum";
import Decimal, { format } from "util/bignum";
import { formatWhole } from "util/break_eternity";
import { Direction, WithRequired } from "util/common";
@ -36,6 +37,7 @@ import { getFirstFeature, render, renderColJSX, renderJSX, VueFeature } from "ut
import { Ref, watchEffect } from "vue";
import { computed, unref } from "vue";
import "./common.css";
import "data/layers/styles/day-gradients.css";
import { main } from "./projEntry";
/** An object that configures a {@link ResetButton} */
@ -334,7 +336,11 @@ export function createCollapsibleModifierSections(
return (
<>
{hasPreviousSection ? <br /> : null}
<div style={{"--unit": settings.alignUnits && s.unit ? "'" + s.unit + "'" : ""}}>
<div
style={{
"--unit": settings.alignUnits && s.unit ? "'" + s.unit + "'" : ""
}}
>
{header}
<br />
{modifiers}
@ -342,7 +348,7 @@ export function createCollapsibleModifierSections(
<div class="modifier-container">
<span class="modifier-description">Total</span>
<span class="modifier-amount">
{format(s.modifier.apply(unref(processed.base[i]) ?? 1))}
{formatSmall(s.modifier.apply(unref(processed.base[i]) ?? 1))}
{s.unit}
</span>
</div>
@ -409,9 +415,15 @@ export function createCollapsibleMilestones(milestones: Record<string, GenericMi
export function setUpDailyProgressTracker(options: {
resource: Resource;
goal: DecimalSource;
masteryGoal?: DecimalSource;
name: string;
day: number;
color: string;
background:
| string
| {
gradient: string;
duration: string;
};
textColor?: string;
modal?: {
show: Ref<boolean>;
@ -422,9 +434,10 @@ export function setUpDailyProgressTracker(options: {
}) {
const total = options.ignoreTotal ? options.resource : trackTotal(options.resource);
const progressFunc = () => {
if (main.day.value !== options.day) return 1;
const isMastering = main.currentlyMastering.value?.name === options.name;
if (main.day.value !== options.day && !isMastering) return 1;
let progress = Decimal.add(total.value, 1);
let requirement = options.goal;
let requirement = isMastering ? options.masteryGoal ?? options.goal : options.goal;
if (options.usingLog?.value ?? settings.usingLog) {
progress = progress.log10();
requirement = Decimal.log10(requirement);
@ -435,13 +448,25 @@ export function setUpDailyProgressTracker(options: {
direction: Direction.Right,
width: 600,
height: 25,
fillStyle: { backgroundColor: options.color },
/* eslint-disable prettier/prettier */
fillStyle: typeof options.background == "string" ? {
backgroundColor: options.background,
} : {
animation: options.background.duration + " " + options.background.gradient + " linear infinite",
},
/* eslint-enable prettier/prettier */
textStyle: options.textColor ? { color: options.textColor } : undefined,
progress: progressFunc,
display: jsx(() =>
main.day.value === options.day ? (
main.day.value === options.day ||
main.currentlyMastering.value?.name === options.name ? (
<>
{formatWhole(total.value)}/{formatWhole(options.goal)}
{formatWhole(total.value)}/
{formatWhole(
main.currentlyMastering.value?.name === options.name
? options.masteryGoal ?? options.goal
: options.goal
)}
</>
) : (
""
@ -457,6 +482,12 @@ export function setUpDailyProgressTracker(options: {
Reach {formatWhole(options.goal)} {options.ignoreTotal ? "" : "total "}
{options.resource.displayName} to complete the day
</>
) : main.currentlyMastering.value?.name === options.name ? (
<>
Reach {formatWhole(options.masteryGoal ?? options.goal)}{" "}
{options.ignoreTotal ? "" : "total "}
{options.resource.displayName} to decorate the day
</>
) : (
<>{options.name} Complete!</>
)}
@ -482,6 +513,11 @@ export function setUpDailyProgressTracker(options: {
watchEffect(() => {
if (main.day.value === options.day && Decimal.gte(total.value, options.goal)) {
main.completeDay();
} else if (
main.currentlyMastering.value?.name === options.name &&
Decimal.gte(total.value, options.masteryGoal ?? options.goal)
) {
main.completeMastery();
}
});
@ -544,3 +580,11 @@ export function changeActiveBuyables(options: {
max
};
}
/* ugh
export function masteryHelper(layer: BaseLayer & {mastery: Partial<typeof layer>}, main: Layer<any> & { isMastery: Ref<boolean> }): ProxyHandler<typeof layer.mastery>{
return new Proxy({}, {
get (__, key: keyof typeof layer.mastery) {
return main.isMastery.value ? layer.mastery[key] : layer[key]
}
})
} */

View file

@ -6,7 +6,7 @@ import Spacer from "components/layout/Spacer.vue";
import Modal from "components/Modal.vue";
import { createCollapsibleModifierSections, setUpDailyProgressTracker } from "data/common";
import { main } from "data/projEntry";
import { createBuyable, GenericBuyable } from "features/buyable";
import { createBuyable } from "features/buyable";
import { createClickable } from "features/clickables/clickable";
import { createCumulativeConversion, createPolynomialScaling } from "features/conversion";
import { jsx, showIf } from "features/feature";
@ -21,13 +21,13 @@ import {
createSequentialModifier,
Modifier
} from "game/modifiers";
import { noPersist } from "game/persistence";
import { noPersist, persistent } from "game/persistence";
import Decimal, { DecimalSource, format, formatWhole } from "util/bignum";
import { WithRequired } from "util/common";
import { render, renderGrid, renderRow } from "util/vue";
import { render, renderGrid } from "util/vue";
import { computed, ComputedRef, ref, unref } from "vue";
import dyes from "./dyes";
import { ElfBuyable } from "./elves";
import elves, { ElfBuyable } from "./elves";
import management from "./management";
import paper from "./paper";
import plastic from "./plastic";
@ -98,7 +98,8 @@ const layer = createLayer(id, function (this: BaseLayer) {
}
boxesConversion.convert();
},
style: "width: 600px; min-height: unset"
style: "width: 600px; min-height: unset",
visibility: () => showIf(!main.isMastery.value || masteryEffectActive.value)
}));
const logsUpgrade = createUpgrade(() => ({
@ -107,6 +108,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
description: "Double log gain and unlock a new elf for training"
},
onPurchase() {
if (masteryEffectActive.value) {
elves.elves.smallFireElf.bought.value = true;
}
main.days[3].recentlyUpdated.value = true;
},
resource: noPersist(boxes),
@ -118,6 +122,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
description: "Double ash gain and unlock a new elf for training"
},
onPurchase() {
if (masteryEffectActive.value) {
elves.elves.bonfireElf.bought.value = true;
}
main.days[3].recentlyUpdated.value = true;
},
resource: noPersist(boxes),
@ -129,6 +136,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
description: "Double coal gain and unlock a new elf for training"
},
onPurchase() {
if (masteryEffectActive.value) {
elves.elves.kilnElf.bought.value = true;
}
main.days[3].recentlyUpdated.value = true;
},
resource: noPersist(boxes),
@ -179,13 +189,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
visibility: () => showIf(management.elfTraining.boxElfTraining.milestones[4].earned.value),
display: {
title: "Carry dye in boxes",
description: "Double all dye gain but reset all dyes"
},
onPurchase() {
(["red", "yellow", "blue", "orange", "green", "purple"] as const).forEach(dyeColor => {
dyes.dyes[dyeColor].amount.value = 0;
dyes.dyes[dyeColor].buyable.amount.value = 0;
});
description: "Double all dye gain"
}
})) as GenericUpgrade;
const xpUpgrade = createUpgrade(() => ({
@ -247,14 +251,21 @@ const layer = createLayer(id, function (this: BaseLayer) {
return Decimal.isNaN(v) ? Decimal.dZero : v.floor().max(0);
},
visibility: () => showIf(logsUpgrade.bought.value),
freeLevels: computed(() =>
management.elfTraining.boxElfTraining.milestones[0].earned.value
? Decimal.max(ashBoxesBuyable.amount.value, 1)
freeLevels: computed(() => {
let levels: DecimalSource = 0;
if (management.elfTraining.boxElfTraining.milestones[0].earned.value) {
levels = Decimal.max(ashBoxesBuyable.amount.value, 1)
.sqrt()
.floor()
.add(Decimal.max(coalBoxesBuyable.amount.value, 1).sqrt().floor())
: 0
),
.add(Decimal.max(coalBoxesBuyable.amount.value, 1).sqrt().floor());
}
if (masteryEffectActive.value) {
levels = Decimal.pow(logBoxesBuyable.amount.value, 2)
.sub(logBoxesBuyable.amount.value)
.add(levels);
}
return levels;
}),
totalAmount: computed(() =>
Decimal.add(logBoxesBuyable.amount.value, logBoxesBuyable.freeLevels.value)
)
@ -302,14 +313,21 @@ const layer = createLayer(id, function (this: BaseLayer) {
return Decimal.isNaN(v) ? Decimal.dZero : v.floor().max(0);
},
visibility: () => showIf(ashUpgrade.bought.value),
freeLevels: computed(() =>
management.elfTraining.boxElfTraining.milestones[0].earned.value
? Decimal.max(logBoxesBuyable.amount.value, 1)
freeLevels: computed(() => {
let levels: DecimalSource = 0;
if (management.elfTraining.boxElfTraining.milestones[0].earned.value) {
levels = Decimal.max(logBoxesBuyable.amount.value, 1)
.sqrt()
.floor()
.add(Decimal.max(coalBoxesBuyable.amount.value, 1).sqrt().floor())
: 0
),
.add(Decimal.max(coalBoxesBuyable.amount.value, 1).sqrt().floor());
}
if (masteryEffectActive.value) {
levels = Decimal.pow(ashBoxesBuyable.amount.value, 2)
.sub(ashBoxesBuyable.amount.value)
.add(levels);
}
return levels;
}),
totalAmount: computed(() =>
Decimal.add(ashBoxesBuyable.amount.value, ashBoxesBuyable.freeLevels.value)
)
@ -357,14 +375,21 @@ const layer = createLayer(id, function (this: BaseLayer) {
return Decimal.isNaN(v) ? Decimal.dZero : v.floor().max(0);
},
visibility: () => showIf(coalUpgrade.bought.value),
freeLevels: computed(() =>
management.elfTraining.boxElfTraining.milestones[0].earned.value
? Decimal.max(logBoxesBuyable.amount.value, 1)
freeLevels: computed(() => {
let levels: DecimalSource = 0;
if (management.elfTraining.boxElfTraining.milestones[0].earned.value) {
levels = Decimal.max(logBoxesBuyable.amount.value, 1)
.sqrt()
.floor()
.add(Decimal.max(ashBoxesBuyable.amount.value, 1).sqrt().floor())
: 0
),
.add(Decimal.max(ashBoxesBuyable.amount.value, 1).sqrt().floor());
}
if (masteryEffectActive.value) {
levels = Decimal.pow(coalBoxesBuyable.amount.value, 2)
.sub(coalBoxesBuyable.amount.value)
.add(levels);
}
return levels;
}),
totalAmount: computed(() =>
Decimal.add(coalBoxesBuyable.amount.value, coalBoxesBuyable.freeLevels.value)
)
@ -394,7 +419,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
resource: noPersist(boxes),
cost() {
let v = this.amount.value;
v = Decimal.pow(0.95, paper.books.boxBook.amount.value).times(v);
v = Decimal.pow(0.95, paper.books.boxBook.totalAmount.value).times(v);
let scaling = 10;
if (management.elfTraining.boxElfTraining.milestones[2].earned.value) {
scaling--;
@ -419,14 +444,21 @@ const layer = createLayer(id, function (this: BaseLayer) {
return Decimal.isNaN(v) ? Decimal.dZero : v.floor().max(0);
},
visibility: () => showIf(management.elfTraining.boxElfTraining.milestones[3].earned.value),
freeLevels: computed(() =>
management.elfTraining.boxElfTraining.milestones[0].earned.value
? Decimal.max(metalBoxesBuyable.amount.value, 1)
freeLevels: computed(() => {
let levels: DecimalSource = 0;
if (management.elfTraining.boxElfTraining.milestones[0].earned.value) {
levels = Decimal.max(metalBoxesBuyable.amount.value, 1)
.sqrt()
.floor()
.add(Decimal.max(plasticBoxesBuyable.amount.value, 1).sqrt().floor())
: 0
),
.add(Decimal.max(plasticBoxesBuyable.amount.value, 1).sqrt().floor());
}
if (masteryEffectActive.value) {
levels = Decimal.pow(oreBoxesBuyable.amount.value, 2)
.sub(oreBoxesBuyable.amount.value)
.add(levels);
}
return levels;
}),
totalAmount: computed(() =>
Decimal.add(oreBoxesBuyable.amount.value, oreBoxesBuyable.freeLevels.value)
)
@ -455,7 +487,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
resource: noPersist(boxes),
cost() {
let v = this.amount.value;
v = Decimal.pow(0.95, paper.books.boxBook.amount.value).times(v);
v = Decimal.pow(0.95, paper.books.boxBook.totalAmount.value).times(v);
let scaling = 15;
if (management.elfTraining.boxElfTraining.milestones[2].earned.value) {
scaling--;
@ -474,14 +506,21 @@ const layer = createLayer(id, function (this: BaseLayer) {
return Decimal.isNaN(v) ? Decimal.dZero : v.floor().max(0);
},
visibility: () => showIf(management.elfTraining.boxElfTraining.milestones[3].earned.value),
freeLevels: computed(() =>
management.elfTraining.boxElfTraining.milestones[0].earned.value
? Decimal.max(oreBoxesBuyable.amount.value, 1)
freeLevels: computed(() => {
let levels: DecimalSource = 0;
if (management.elfTraining.boxElfTraining.milestones[0].earned.value) {
levels = Decimal.max(oreBoxesBuyable.amount.value, 1)
.sqrt()
.floor()
.add(Decimal.max(plasticBoxesBuyable.amount.value, 1).sqrt().floor())
: 0
),
.add(Decimal.max(plasticBoxesBuyable.amount.value, 1).sqrt().floor());
}
if (masteryEffectActive.value) {
levels = Decimal.pow(metalBoxesBuyable.amount.value, 2)
.sub(metalBoxesBuyable.amount.value)
.add(levels);
}
return levels;
}),
totalAmount: computed(() =>
Decimal.add(metalBoxesBuyable.amount.value, metalBoxesBuyable.freeLevels.value)
)
@ -510,7 +549,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
resource: noPersist(boxes),
cost() {
let v = this.amount.value;
v = Decimal.pow(0.95, paper.books.boxBook.amount.value).times(v);
v = Decimal.pow(0.95, paper.books.boxBook.totalAmount.value).times(v);
let scaling = 20;
if (management.elfTraining.boxElfTraining.milestones[2].earned.value) {
scaling--;
@ -529,14 +568,21 @@ const layer = createLayer(id, function (this: BaseLayer) {
return Decimal.isNaN(v) ? Decimal.dZero : v.floor().max(0);
},
visibility: () => showIf(management.elfTraining.boxElfTraining.milestones[3].earned.value),
freeLevels: computed(() =>
management.elfTraining.boxElfTraining.milestones[0].earned.value
? Decimal.max(oreBoxesBuyable.amount.value, 1)
freeLevels: computed(() => {
let levels: DecimalSource = 0;
if (management.elfTraining.boxElfTraining.milestones[0].earned.value) {
levels = Decimal.max(oreBoxesBuyable.amount.value, 1)
.sqrt()
.floor()
.add(Decimal.max(metalBoxesBuyable.amount.value, 1).sqrt().floor())
: 0
),
.add(Decimal.max(metalBoxesBuyable.amount.value, 1).sqrt().floor());
}
if (masteryEffectActive.value) {
levels = Decimal.pow(plasticBoxesBuyable.amount.value, 2)
.sub(plasticBoxesBuyable.amount.value)
.add(levels);
}
return levels;
}),
totalAmount: computed(() =>
Decimal.add(plasticBoxesBuyable.amount.value, plasticBoxesBuyable.freeLevels.value)
)
@ -575,15 +621,50 @@ const layer = createLayer(id, function (this: BaseLayer) {
const { total: totalBoxes, trackerDisplay } = setUpDailyProgressTracker({
resource: boxes,
goal: 5e4,
masteryGoal: 5e5,
name,
day,
color,
background: color,
modal: {
display: modifiersModal,
show: showModifiersModal
}
});
const mastery = {
boxes: persistent<DecimalSource>(0),
totalBoxes: persistent<DecimalSource>(0),
upgrades: {
logsUpgrade: { bought: persistent<boolean>(false) },
ashUpgrade: { bought: persistent<boolean>(false) },
coalUpgrade: { bought: persistent<boolean>(false) }
},
row2Upgrades: {
oreUpgrade: { bought: persistent<boolean>(false) },
metalUpgrade: { bought: persistent<boolean>(false) },
plasticUpgrade: { bought: persistent<boolean>(false) }
},
row3Upgrades: {
clothUpgrade: { bought: persistent<boolean>(false) },
dyeUpgrade: { bought: persistent<boolean>(false) },
xpUpgrade: { bought: persistent<boolean>(false) }
},
buyables: {
logBoxesBuyable: { amount: persistent<DecimalSource>(0) },
ashBoxesBuyable: { amount: persistent<DecimalSource>(0) },
coalBoxesBuyable: { amount: persistent<DecimalSource>(0) }
},
buyables2: {
oreBoxesBuyable: { amount: persistent<DecimalSource>(0) },
metalBoxesBuyable: { amount: persistent<DecimalSource>(0) },
plasticBoxesBuyable: { amount: persistent<DecimalSource>(0) }
}
};
const mastered = persistent<boolean>(false);
const masteryEffectActive = computed(
() => mastered.value || main.currentlyMastering.value?.name === name
);
return {
name,
day,
@ -602,6 +683,16 @@ const layer = createLayer(id, function (this: BaseLayer) {
<>
{render(trackerDisplay)}
<Spacer />
{masteryEffectActive.value ? (
<>
<div class="decoration-effect">
Decoration effect:
<br />
Effective boxes buyables' levels are squared
</div>
<Spacer />
</>
) : null}
<MainDisplay resource={boxes} color={color} style="margin-bottom: 0" />
<Spacer />
{render(makeBoxes)}
@ -618,9 +709,13 @@ const layer = createLayer(id, function (this: BaseLayer) {
minimizedDisplay: jsx(() => (
<div>
{name}{" "}
<span class="desc">{format(boxes.value)} {boxes.displayName}</span>
<span class="desc">
{format(boxes.value)} {boxes.displayName}
</span>
</div>
)),
mastery,
mastered
};
});

View file

@ -2,13 +2,14 @@
* @module
* @hidden
*/
import HotkeyVue from "components/Hotkey.vue";
import Row from "components/layout/Row.vue";
import Spacer from "components/layout/Spacer.vue";
import Modal from "components/Modal.vue";
import { createCollapsibleModifierSections, setUpDailyProgressTracker } from "data/common";
import { main } from "data/projEntry";
import { createBar } from "features/bars/bar";
import { createBuyable, GenericBuyable } from "features/buyable";
import { createBuyable } from "features/buyable";
import { createClickable } from "features/clickables/clickable";
import { jsx, showIf } from "features/feature";
import { createHotkey } from "features/hotkey";
@ -27,7 +28,7 @@ import Decimal, { DecimalSource, format } from "util/bignum";
import { formatWhole } from "util/break_eternity";
import { Direction } from "util/common";
import { render, renderCol, renderRow } from "util/vue";
import { computed, ref } from "vue";
import { computed, ref, unref } from "vue";
import boxes from "./boxes";
import dyes from "./dyes";
import { ElfBuyable } from "./elves";
@ -61,7 +62,11 @@ const layer = createLayer(id, function (this: BaseLayer) {
}));
const breeding = createClickable(() => ({
display: {
title: "Breed sheep",
title: jsx(() => (
<h3>
Breed sheep <HotkeyVue hotkey={breedSheepHK} />
</h3>
)),
description: jsx(() => (
<>
Breed {formatWhole(Decimal.floor(computedSheepGain.value))} sheep
@ -73,14 +78,25 @@ const layer = createLayer(id, function (this: BaseLayer) {
style: {
minHeight: "80px"
},
canClick: () => Decimal.gte(breedingProgress.value, computedBreedingCooldown.value),
canClick: () =>
Decimal.gte(breedingProgress.value, computedBreedingCooldown.value) &&
(!main.isMastery.value || masteryEffectActive.value),
onClick() {
if (Decimal.lt(breedingProgress.value, computedBreedingCooldown.value)) {
if (!unref(breeding.canClick)) {
return;
}
// Breed
const amount = Decimal.floor(computedSheepGain.value);
sheep.value = Decimal.add(sheep.value, amount);
breedingProgress.value = 0;
if (masteryEffectActive.value) {
// Then shear
let amount = Decimal.min(sheep.value, computedShearingAmount.value).floor();
wool.value = Decimal.add(wool.value, amount);
// Then spin
amount = Decimal.min(wool.value, computedSpinningAmount.value).floor();
cloth.value = Decimal.add(cloth.value, amount);
}
}
}));
@ -97,7 +113,11 @@ const layer = createLayer(id, function (this: BaseLayer) {
}));
const shearing = createClickable(() => ({
display: {
title: "Shear sheep",
title: jsx(() => (
<h3>
Shear sheep <HotkeyVue hotkey={shearSheepHK} />
</h3>
)),
description: jsx(() => (
<>
Shear up to {formatWhole(Decimal.floor(computedShearingAmount.value))} sheep
@ -109,14 +129,27 @@ const layer = createLayer(id, function (this: BaseLayer) {
style: {
minHeight: "80px"
},
canClick: () => Decimal.gte(shearingProgress.value, computedShearingCooldown.value),
canClick: () =>
Decimal.gte(shearingProgress.value, computedShearingCooldown.value) &&
(!main.isMastery.value || masteryEffectActive.value),
onClick() {
if (Decimal.lt(shearingProgress.value, computedShearingCooldown.value)) {
if (!unref(shearing.canClick)) {
return;
}
if (masteryEffectActive.value) {
// Breed
const amount = Decimal.floor(computedSheepGain.value);
sheep.value = Decimal.add(sheep.value, amount);
}
// Then shear
const amount = Decimal.min(sheep.value, computedShearingAmount.value).floor();
wool.value = Decimal.add(wool.value, amount);
shearingProgress.value = 0;
if (masteryEffectActive.value) {
// Then spin
const amount = Decimal.min(wool.value, computedSpinningAmount.value).floor();
cloth.value = Decimal.add(cloth.value, amount);
}
}
}));
@ -133,7 +166,11 @@ const layer = createLayer(id, function (this: BaseLayer) {
}));
const spinning = createClickable(() => ({
display: {
title: "Spinning wool",
title: jsx(() => (
<h3>
Spin wool <HotkeyVue hotkey={spinWoolHK} />
</h3>
)),
description: jsx(() => (
<>
Spin {formatWhole(Decimal.floor(computedSpinningAmount.value))} wool
@ -145,40 +182,56 @@ const layer = createLayer(id, function (this: BaseLayer) {
style: {
minHeight: "80px"
},
canClick: () => Decimal.gte(spinningProgress.value, computedSpinningCooldown.value),
canClick: () =>
Decimal.gte(spinningProgress.value, computedSpinningCooldown.value) &&
(!main.isMastery.value || masteryEffectActive.value),
onClick() {
if (Decimal.lt(spinningProgress.value, computedSpinningCooldown.value)) {
if (!unref(spinning.canClick)) {
return;
}
if (masteryEffectActive.value) {
// Breed
let amount = Decimal.floor(computedSheepGain.value);
sheep.value = Decimal.add(sheep.value, amount);
// Then shear
amount = Decimal.min(sheep.value, computedShearingAmount.value).floor();
wool.value = Decimal.add(wool.value, amount);
}
// Then spin
const amount = Decimal.min(wool.value, computedSpinningAmount.value).floor();
cloth.value = Decimal.add(cloth.value, amount);
if (!masteryEffectActive.value) {
wool.value = Decimal.sub(wool.value, amount);
}
spinningProgress.value = 0;
}
}));
const breedSheepHK = createHotkey(() => ({
key: "b",
description: 'Press the "Breed Sheep" button',
description: "Breed sheep",
onPress: () => {
if (breeding.canClick.value) breeding.onClick();
}
},
enabled: main.days[day - 1].opened
}));
const shearSheepHK = createHotkey(() => ({
key: "h", // For some reason, "shift+s" doesn't work properly
description: 'Press the "Shear Sheep" button',
description: "Shear sheep",
onPress: () => {
if (shearing.canClick.value) shearing.onClick();
}
},
enabled: main.days[day - 1].opened
}));
const spinWoolHK = createHotkey(() => ({
key: "s",
description: 'Press the "Spin Wool" button',
description: "Spin wool",
onPress: () => {
if (spinning.canClick.value) spinning.onClick();
}
},
enabled: main.days[day - 1].opened
}));
const buildPens = createBuyable(() => ({
@ -391,6 +444,11 @@ const layer = createLayer(id, function (this: BaseLayer) {
multiplier: 2,
description: "Carry cloth in boxes",
enabled: boxes.row3Upgrades.clothUpgrade.bought
})),
createMultiplicativeModifier(() => ({
multiplier: dyes.boosts.yellow2,
description: "Yellow Dye",
enabled: dyes.masteryEffectActive
}))
]);
const computedSheepGain = computed(() => sheepGain.apply(1));
@ -431,6 +489,11 @@ const layer = createLayer(id, function (this: BaseLayer) {
multiplier: 2,
description: "Carry cloth in boxes",
enabled: boxes.row3Upgrades.clothUpgrade.bought
})),
createMultiplicativeModifier(() => ({
multiplier: dyes.boosts.yellow2,
description: "Yellow Dye",
enabled: dyes.masteryEffectActive
}))
]);
const computedShearingAmount = computed(() => shearingAmount.apply(1));
@ -471,6 +534,11 @@ const layer = createLayer(id, function (this: BaseLayer) {
multiplier: 2,
description: "Carry cloth in boxes",
enabled: boxes.row3Upgrades.clothUpgrade.bought
})),
createMultiplicativeModifier(() => ({
multiplier: dyes.boosts.yellow2,
description: "Yellow Dye",
enabled: dyes.masteryEffectActive
}))
]);
const computedSpinningAmount = computed(() => spinningAmount.apply(1));
@ -565,7 +633,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
goal: 1e3,
name,
day,
color,
background: color,
textColor: "var(--feature-foreground)",
modal: {
show: showModifiersModal,
@ -573,8 +641,41 @@ const layer = createLayer(id, function (this: BaseLayer) {
}
});
const mastery = {
cloth: persistent<DecimalSource>(0),
totalCloth: persistent<DecimalSource>(0),
wool: persistent<DecimalSource>(0),
sheep: persistent<DecimalSource>(0),
buildPens: { amount: persistent<DecimalSource>(0) },
betterShears: { amount: persistent<DecimalSource>(0) },
fasterSpinning: { amount: persistent<DecimalSource>(0) },
treesUpgrades: {
treesUpgrade1: { bought: persistent<boolean>(false) },
treesUpgrade2: { bought: persistent<boolean>(false) },
treesUpgrade3: { bought: persistent<boolean>(false) },
treesUpgrade4: { bought: persistent<boolean>(false) }
},
metalUpgrades: {
metalUpgrade1: { bought: persistent<boolean>(false) },
metalUpgrade2: { bought: persistent<boolean>(false) },
metalUpgrade3: { bought: persistent<boolean>(false) },
metalUpgrade4: { bought: persistent<boolean>(false) }
},
paperUpgrades: {
paperUpgrade1: { bought: persistent<boolean>(false) },
paperUpgrade2: { bought: persistent<boolean>(false) },
paperUpgrade3: { bought: persistent<boolean>(false) },
paperUpgrade4: { bought: persistent<boolean>(false) }
}
};
const mastered = persistent<boolean>(false);
const masteryEffectActive = computed(
() => mastered.value || main.currentlyMastering.value?.name === name
);
return {
name,
day,
color,
cloth,
totalCloth,
@ -598,6 +699,17 @@ const layer = createLayer(id, function (this: BaseLayer) {
<>
{render(trackerDisplay)}
<Spacer />
{masteryEffectActive.value ? (
<>
<div class="decoration-effect ribbon">
Decoration effect:
<br />
Performing any action performs all actions and spinning doesn't spend
wool
</div>
<Spacer />
</>
) : null}
<MainDisplay resource={cloth} style="margin-bottom: 0" />
<MainDisplay resource={wool} style="margin-bottom: 0" />
<MainDisplay resource={sheep} style="margin-bottom: 0" />
@ -614,9 +726,13 @@ const layer = createLayer(id, function (this: BaseLayer) {
minimizedDisplay: jsx(() => (
<div>
{name}{" "}
<span class="desc">{format(cloth.value)} {cloth.displayName}</span>
<span class="desc">
{format(cloth.value)} {cloth.displayName}
</span>
</div>
)),
mastery,
mastered
};
});

View file

@ -12,7 +12,7 @@ import {
setUpDailyProgressTracker
} from "data/common";
import { main } from "data/projEntry";
import { createBuyable, GenericBuyable } from "features/buyable";
import { createBuyable } from "features/buyable";
import { jsx, JSXFunction, showIf, StyleValue, Visibility } from "features/feature";
import MainDisplay from "features/resources/MainDisplay.vue";
import { createResource, Resource } from "features/resources/resource";
@ -33,15 +33,15 @@ import { render, renderGrid, renderRow } from "util/vue";
import { computed, ref, unref } from "vue";
import boxes from "./boxes";
import cloth from "./cloth";
import dyes from "./dyes";
import elves, { ElfBuyable } from "./elves";
import management from "./management";
import metal from "./metal";
import oil from "./oil";
import paper from "./paper";
import trees from "./trees";
import dyes from "./dyes";
import management from "./management";
import wrappingPaper from "./wrapping-paper";
import plastic from "./plastic";
import trees from "./trees";
import wrappingPaper from "./wrapping-paper";
interface BetterFertilizerUpgOptions {
canAfford: () => boolean;
@ -101,10 +101,10 @@ const layer = createLayer(id, function (this: BaseLayer) {
if (Decimal.gte(v, 100)) v = Decimal.pow(v, 2).div(100);
if (Decimal.gte(v, 10000)) v = Decimal.pow(v, 2).div(10000);
v = Decimal.pow(0.95, paper.books.smallFireBook.totalAmount.value).times(v);
return v.pow(1.5).times(1e4);
return v.pow(masteryEffectActive.value ? 1.1 : 1.5).times(1e4);
},
inverseCost(x: DecimalSource) {
let v = Decimal.div(x, 1e4).root(1.5);
let v = Decimal.div(x, 1e4).root(masteryEffectActive.value ? 1.1 : 1.5);
v = v.div(Decimal.pow(0.95, paper.books.smallFireBook.totalAmount.value));
if (Decimal.gte(v, 10000)) v = Decimal.mul(v, 10000).root(2);
if (Decimal.gte(v, 100)) v = Decimal.mul(v, 100).root(2);
@ -134,7 +134,8 @@ const layer = createLayer(id, function (this: BaseLayer) {
color: colorText,
width: "160px",
flexGrow: 1
}
},
visibility: () => showIf(!main.isMastery.value || masteryEffectActive.value)
})) as ElfBuyable & { resource: Resource };
const {
@ -617,7 +618,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
createMultiplicativeModifier(() => ({
multiplier: 2,
description: "Dedicated Cutter Heaters",
enabled: dedicatedCutters.bought
enabled: () => dedicatedCutters.bought.value
}))
]);
const computedHeatedCutterEffect = computed(() => heatedCutterEffect.apply(1));
@ -635,7 +636,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
createMultiplicativeModifier(() => ({
multiplier: 2,
description: "Dedicated Planter Heaters",
enabled: dedicatedPlanters.bought
enabled: () => dedicatedPlanters.bought.value
}))
]);
const computedHeatedPlanterEffect = computed(() => heatedPlanterEffect.apply(1));
@ -653,7 +654,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
createMultiplicativeModifier(() => ({
multiplier: 2,
description: "Mulched Soil",
enabled: betterFertilizer.bought
enabled: () => betterFertilizer.bought.value
}))
]);
const computedFertilizerEffect = computed(() => fertilizerEffect.apply(1));
@ -788,6 +789,11 @@ const layer = createLayer(id, function (this: BaseLayer) {
exponent: 1.05,
description: "Jack Level 2",
enabled: management.elfTraining.heatedCutterElfTraining.milestones[1].earned
})),
createAdditiveModifier(() => ({
addend: oil.burnerCoal,
description: "Oil Decoration",
enabled: oil.masteryEffectActive
}))
]) as WithRequired<Modifier, "description" | "revert">;
const computedCoalGain = computed(() => coalGain.apply(0));
@ -985,15 +991,49 @@ const layer = createLayer(id, function (this: BaseLayer) {
goal: 1e7,
name,
day,
color: colorCoal,
background: colorCoal,
modal: {
show: showModifiersModal,
display: modifiersModal
}
});
const mastery = {
coal: persistent<DecimalSource>(0),
totalCoal: persistent<DecimalSource>(0),
ash: persistent<DecimalSource>(0),
activeFires: persistent<DecimalSource>(0),
buildFire: { amount: persistent<DecimalSource>(0) },
activeBonfires: persistent<DecimalSource>(0),
buildBonfire: { amount: persistent<DecimalSource>(0) },
activeKilns: persistent<DecimalSource>(0),
buildKiln: { amount: persistent<DecimalSource>(0) },
activeDrills: persistent<DecimalSource>(0),
buildDrill: { amount: persistent<DecimalSource>(0) },
warmerCutters: { bought: persistent<boolean>(false) },
warmerPlanters: { bought: persistent<boolean>(false) },
basicFertilizer: { bought: persistent<boolean>(false) },
unlockBonfire: { bought: persistent<boolean>(false) },
dedicatedCutters: { bought: persistent<boolean>(false) },
dedicatedPlanters: { bought: persistent<boolean>(false) },
betterFertilizer: { bought: persistent<boolean>(false) },
unlockKiln: { bought: persistent<boolean>(false) },
efficientSmelther: { bought: persistent<boolean>(false) },
arsonistAssistance: { bought: persistent<boolean>(false) },
refinedCoal: { bought: persistent<boolean>(false) },
coloredFire: { bought: persistent<boolean>(false) },
heatedCutters: { amount: persistent<DecimalSource>(0) },
heatedPlanters: { amount: persistent<DecimalSource>(0) },
moreFertilizer: { amount: persistent<DecimalSource>(0) }
};
const mastered = persistent<boolean>(false);
const masteryEffectActive = computed(
() => mastered.value || main.currentlyMastering.value?.name === name
);
return {
name,
day,
color: colorCoal,
coal,
totalCoal,
@ -1031,6 +1071,16 @@ const layer = createLayer(id, function (this: BaseLayer) {
<>
{render(trackerDisplay)}
<Spacer />
{masteryEffectActive.value ? (
<>
<div class="decoration-effect">
Decoration effect:
<br />
Small fires' price increases drastically slower
</div>
<Spacer />
</>
) : null}
<MainDisplay
resource={coal}
color={colorCoal}
@ -1111,9 +1161,13 @@ const layer = createLayer(id, function (this: BaseLayer) {
minimizedDisplay: jsx(() => (
<div>
{name}{" "}
<span class="desc">{format(coal.value)} {coal.displayName}</span>
<span class="desc">
{format(coal.value)} {coal.displayName}
</span>
</div>
)),
mastery,
mastered
};
});

View file

@ -2,12 +2,14 @@
* @module
* @hidden
*/
import HotkeyVue from "components/Hotkey.vue";
import Spacer from "components/layout/Spacer.vue";
import Sqrt from "components/math/Sqrt.vue";
import Modal from "components/Modal.vue";
import { createCollapsibleModifierSections, setUpDailyProgressTracker } from "data/common";
import { BuyableOptions, createBuyable, GenericBuyable } from "features/buyable";
import { BuyableOptions, createBuyable } from "features/buyable";
import { jsx, JSXFunction, showIf, Visibility } from "features/feature";
import { createHotkey, GenericHotkey } from "features/hotkey";
import MainDisplay from "features/resources/MainDisplay.vue";
import { createResource, Resource } from "features/resources/resource";
import { createUpgrade, GenericUpgrade } from "features/upgrades/upgrade";
@ -18,30 +20,30 @@ import {
createSequentialModifier,
Modifier
} from "game/modifiers";
import { NonPersistent, noPersist, Persistent } from "game/persistence";
import { persistent } from "game/persistence";
import Decimal, { DecimalSource, format, formatWhole } from "util/bignum";
import { WithRequired } from "util/common";
import { Computable, convertComputable } from "util/computed";
import { render, renderCol, renderRow } from "util/vue";
import { computed, ComputedRef, ref, Ref, unref } from "vue";
import { main } from "../projEntry";
import boxes from "./boxes";
import cloth from "./cloth";
import coal from "./coal";
import { ElfBuyable } from "./elves";
import management from "./management";
import oil from "./oil";
import trees from "./trees";
import wrappingPaper from "./wrapping-paper";
import paper from "./paper";
import boxes from "./boxes";
import { ElfBuyable } from "./elves";
import trees from "./trees";
interface Dye {
name: string;
amount: Resource<DecimalSource> &
Persistent<DecimalSource> & { [NonPersistent]: Resource<DecimalSource> };
amount: Resource<DecimalSource>;
buyable: ElfBuyable;
toGenerate: WithRequired<Modifier, "description" | "revert">;
computedToGenerate: ComputedRef<DecimalSource>;
display: JSXFunction;
hotkey: GenericHotkey;
}
type DyeUpg =
@ -53,7 +55,7 @@ type DyeUpg =
| "blueDyeUpg2"
| "coalUpg";
export type enumColor = "red" | "green" | "blue" | "yellow" | "purple" | "orange";
export type enumColor = "red" | "green" | "blue" | "yellow" | "purple" | "orange" | "black";
const id = "dyes";
const day = 11;
@ -61,10 +63,16 @@ const layer = createLayer(id, function (this: BaseLayer) {
const name = "Dyes";
const color = "#D4D4F4";
const masteryEffectActive = computed(
() => mastered.value || main.currentlyMastering.value?.name === name
);
function createDye(
options: {
name: string;
color: string;
shadowColor?: string;
key: string;
costs: Computable<
{
base: Ref<DecimalSource> | DecimalSource;
@ -82,7 +90,15 @@ const layer = createLayer(id, function (this: BaseLayer) {
}[];
} & Partial<BuyableOptions>
): Dye {
const amount = createResource<DecimalSource>(0, options.name);
const amount = createResource(
computed(() =>
Decimal.add(buyable.amount.value, 1)
.mul(buyable.amount.value)
.div(2)
.mul(computedToGenerate.value)
),
options.name
);
const toGenerate = createSequentialModifier(() => {
const modifiers = [
@ -91,27 +107,29 @@ const layer = createLayer(id, function (this: BaseLayer) {
description: `${options.name} Chambers`
}))
];
if (options.color === "yellow" && oil.row3Upgrades[0].bought.value) {
if (options.color === "yellow") {
modifiers.push(
createMultiplicativeModifier(() => ({
multiplier() {
return Decimal.add(dyes.red.amount.value, 1).log10().add(1).pow(0.75);
},
description: "Dye Synergy I"
description: "Dye Synergy I",
enabled: oil.row3Upgrades[0].bought
}))
);
}
if (options.color === "red" && oil.row3Upgrades[3].bought.value) {
if (options.color === "red") {
modifiers.push(
createMultiplicativeModifier(() => ({
multiplier() {
return Decimal.add(dyes.blue.amount.value, 1).log10();
return Decimal.add(dyes.blue.amount.value, 1).log10().add(1);
},
description: "Dye Synergy II"
description: "Dye Synergy II",
enabled: oil.row3Upgrades[3].bought
}))
);
}
if (options.color === "red" || options.color === "yellow") {
if (["red", "yellow"].includes(options.color)) {
modifiers.push(
createMultiplicativeModifier(() => ({
multiplier: boosts.orange1,
@ -119,7 +137,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
}))
);
}
if (options.color == "yellow" || options.color == "blue") {
if (["yellow", "blue"].includes(options.color)) {
modifiers.push(
createMultiplicativeModifier(() => ({
multiplier: boosts.green1,
@ -127,7 +145,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
}))
);
}
if (options.color == "red" || options.color == "blue") {
if (["red", "blue"].includes(options.color)) {
modifiers.push(
createMultiplicativeModifier(() => ({
multiplier: boosts.purple1,
@ -135,7 +153,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
}))
);
}
if (options.color == "red" || options.color == "yellow" || options.color == "blue") {
if (["red", "yellow", "blue"].includes(options.color)) {
modifiers.push(
createMultiplicativeModifier(() => ({
multiplier: 2,
@ -153,21 +171,17 @@ const layer = createLayer(id, function (this: BaseLayer) {
modifiers.push(
createMultiplicativeModifier(() => ({
multiplier: 2,
description: "Wrapping Paper Milestone 1",
enabled: wrappingPaper.milestones.primaryBoost.earned
description: "Carol Level 1",
enabled: management.elfTraining.dyeElfTraining.milestones[0].earned
}))
);
}
if (
options.color == "orange" ||
options.color == "green" ||
options.color == "purple"
) {
if (["orange", "green", "purple"].includes(options.color)) {
modifiers.push(
createMultiplicativeModifier(() => ({
multiplier: 2,
description: "Wrapping Paper Milestone 2",
enabled: wrappingPaper.milestones.secondaryBoost.earned
description: "Carol Level 2",
enabled: management.elfTraining.dyeElfTraining.milestones[1].earned
}))
);
}
@ -189,6 +203,25 @@ const layer = createLayer(id, function (this: BaseLayer) {
}) as WithRequired<Modifier, "description" | "revert">;
const computedToGenerate = computed(() => toGenerate.apply(0));
let dyeBook: ElfBuyable & {
resource: Resource;
freeLevels: ComputedRef<DecimalSource>;
totalAmount: ComputedRef<DecimalSource>;
};
switch (options.color) {
case "red":
case "yellow":
case "blue":
case "black":
dyeBook = paper.books.primaryDyeBook;
break;
case "orange":
case "green":
case "purple":
dyeBook = paper.books.secondaryDyeBook;
break;
}
const buyable: ElfBuyable = createBuyable(() => {
const costs = convertComputable(options.costs);
return {
@ -200,7 +233,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
display: jsx(() => {
return (
<span>
<h3>{options.name} Chambers</h3>
<h3>
{options.name} Chambers <HotkeyVue hotkey={hotkey} />
</h3>
<br />
Create {format(computedToGenerate.value)} {options.name}
{options.dyesToReset.length > 0
@ -222,7 +257,21 @@ const layer = createLayer(id, function (this: BaseLayer) {
{unref(costs).map(c =>
render(
jsx(() => (
<div>
<div
class={
Decimal.lt(
c.res.value,
unref(
Decimal.pow(
unref(buyable.cost) ?? Decimal.dInf,
unref(c.root ?? 1)
).times(unref(c.base))
)
)
? "unaffordable"
: ""
}
>
{format(
unref(
Decimal.pow(
@ -245,12 +294,14 @@ const layer = createLayer(id, function (this: BaseLayer) {
let v = buyable.amount.value;
if (Decimal.gte(v, 25)) v = Decimal.pow(v, 2).div(20); // intentional price jump #2
if (Decimal.gte(v, 10)) v = Decimal.pow(v, 2).div(5); // intentional price jump
v = Decimal.mul(v, Decimal.pow(0.95, paper.books.dyeBook.totalAmount.value));
if (Decimal.gte(v, 3125)) v = Decimal.pow(v, 2).div(3125);
v = Decimal.mul(v, Decimal.pow(0.95, dyeBook.totalAmount.value));
return Decimal.div(v, 10).plus(1);
},
inverseCostPre(x: DecimalSource) {
let v = Decimal.sub(x, 1).mul(10);
v = v.div(Decimal.pow(0.95, paper.books.dyeBook.totalAmount.value));
v = v.div(Decimal.pow(0.95, dyeBook.totalAmount.value));
if (Decimal.gte(v, 3125)) v = Decimal.mul(v, 3125).root(2);
if (Decimal.gte(v, 10)) v = Decimal.mul(v, 5).root(2);
if (Decimal.gte(v, 25)) v = Decimal.mul(v, 20).root(2);
return Decimal.isNaN(v) ? Decimal.dZero : v.floor().max(0);
@ -269,7 +320,12 @@ const layer = createLayer(id, function (this: BaseLayer) {
);
},
canPurchase: computed((cost?: DecimalSource) => {
if (unref(buyable.visibility) != Visibility.Visible) return false;
if (unref(buyable.visibility) != Visibility.Visible) {
return false;
}
if (main.isMastery.value && !masteryEffectActive.value) {
return false;
}
const trueCost = cost ?? unref(buyable.cost) ?? Decimal.dInf;
return unref(costs).every(c =>
Decimal.div(c.res.value, unref(c.base))
@ -278,34 +334,57 @@ const layer = createLayer(id, function (this: BaseLayer) {
);
}),
onPurchase(cost?: DecimalSource) {
const trueCost = cost ?? unref(buyable.cost) ?? Decimal.dInf;
let buyMax = false;
switch (options.color) {
case "red":
case "yellow":
case "blue":
buyMax =
management.elfTraining.dyeElfTraining.milestones[2].earned.value;
break;
case "orange":
case "green":
case "purple":
buyMax =
management.elfTraining.dyeElfTraining.milestones[4].earned.value;
break;
}
amount.value = Decimal.add(amount.value, computedToGenerate.value);
if (buyMax) {
const buyAmount = this.inverseCost().sub(this.amount.value).plus(1);
if (buyAmount.lte(0)) return;
buyable.amount.value = Decimal.add(buyable.amount.value, buyAmount);
} else {
buyable.amount.value = Decimal.add(buyable.amount.value, 1);
if (!wrappingPaper.milestones.secondaryNoReset.earned.value) {
unref(costs).forEach(c => {
c.res.value = Decimal.sub(
c.res.value,
Decimal.pow(trueCost, unref(c.root ?? 1)).times(unref(c.base))
);
});
}
if (!management.elfTraining.dyeElfTraining.milestones[3].earned.value) {
options.dyesToReset.forEach(dye => dye.reset());
}
}
};
});
const hotkey = createHotkey(() => ({
key: options.key,
description: `${options.name} Chambers`,
onPress: () => {
if (unref(buyable.canClick)) buyable.onClick();
},
enabled: main.days[day - 1].opened
}));
return {
name: options.name,
amount,
buyable,
hotkey,
toGenerate,
computedToGenerate,
display: jsx(() => (
<MainDisplay
resource={amount}
color={options.color}
shadowColor={options.shadowColor ?? options.color}
style="margin: 0; width: 200px; width: 180px; padding: 10px;"
sticky={false}
/>
@ -317,6 +396,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
red: createDye({
name: "Red Dye",
color: "red",
key: "r",
costs: () => [
{
base: "2e18",
@ -338,6 +418,10 @@ const layer = createLayer(id, function (this: BaseLayer) {
boosts.red1.value
)} effective Oil Pumps (does not impact coal consumption)`
)
},
{
visible: masteryEffectActive,
desc: computed(() => `x${format(boosts.red2.value)} drill power`)
}
],
dyesToReset: []
@ -345,6 +429,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
yellow: createDye({
name: "Yellow Dye",
color: "yellow",
key: "y",
costs: () => [
{
base: "1e18",
@ -361,6 +446,10 @@ const layer = createLayer(id, function (this: BaseLayer) {
{
visible: true,
desc: computed(() => `x${format(boosts.yellow1.value)} Paper \& Plastic gain`)
},
{
visible: masteryEffectActive,
desc: computed(() => `x${format(boosts.yellow2.value)} cloth actions`)
}
],
dyesToReset: []
@ -368,6 +457,8 @@ const layer = createLayer(id, function (this: BaseLayer) {
blue: createDye({
name: "Blue Dye",
color: "blue",
shadowColor: "lightblue",
key: "u",
costs: () => [
{
base: "5e17",
@ -387,8 +478,36 @@ const layer = createLayer(id, function (this: BaseLayer) {
() =>
`+${formatWhole(
boosts.blue1.value
)} forest size (after all other modifiers).`
)} forest size (after all other modifiers)`
)
},
{
visible: masteryEffectActive,
desc: computed(() => `/${format(boosts.blue2.value)} plastic buyables cost`)
}
],
dyesToReset: []
}),
black: createDye({
name: "Black Dye",
color: "black",
key: "a",
costs: () => [
{
base: "1e42",
root: 5,
res: trees.logs
},
{
base: computed(() => (upgrades.yellowDyeUpg2.bought.value ? "1e15" : "2e15")),
root: 2,
res: oil.oil
}
],
listedBoosts: [
{
visible: true,
desc: computed(() => `*${format(boosts.black1.value)} letters processed.`)
}
],
dyesToReset: []
@ -396,6 +515,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
orange: createDye({
name: "Orange Dye",
color: "orange",
key: "o",
costs: () => [
{
base: 15,
@ -422,14 +542,12 @@ const layer = createLayer(id, function (this: BaseLayer) {
{
name: "Red Dye",
reset() {
dyes.red.amount.value = 0;
dyes.red.buyable.amount.value = 0;
}
},
{
name: "Yellow Dye",
reset() {
dyes.yellow.amount.value = 0;
dyes.yellow.buyable.amount.value = 0;
}
}
@ -438,6 +556,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
green: createDye({
name: "Green Dye",
color: "green",
key: "g",
costs: () => [
{
base: 15,
@ -469,14 +588,12 @@ const layer = createLayer(id, function (this: BaseLayer) {
{
name: "Yellow Dye",
reset() {
dyes.yellow.amount.value = 0;
dyes.yellow.buyable.amount.value = 0;
}
},
{
name: "Blue Dye",
reset() {
dyes.blue.amount.value = 0;
dyes.blue.buyable.amount.value = 0;
}
}
@ -485,6 +602,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
purple: createDye({
name: "Purple Dye",
color: "purple",
key: "e",
costs: () => [
{
base: 15,
@ -513,14 +631,12 @@ const layer = createLayer(id, function (this: BaseLayer) {
{
name: "Blue Dye",
reset() {
dyes.blue.amount.value = 0;
dyes.blue.buyable.amount.value = 0;
}
},
{
name: "Red Dye",
reset() {
dyes.red.amount.value = 0;
dyes.red.buyable.amount.value = 0;
}
}
@ -535,8 +651,16 @@ const layer = createLayer(id, function (this: BaseLayer) {
upgrades.blueDyeUpg2.bought.value ? 1.5 : 1
)
),
red2: computed(() =>
Decimal.pow(
Decimal.add(dyes.red.amount.value, 1).log2().plus(1),
upgrades.blueDyeUpg2.bought.value ? 1.5 : 1
)
),
yellow1: computed(() => Decimal.add(dyes.yellow.amount.value, 1).log2().plus(1)),
yellow2: computed(() => Decimal.add(dyes.yellow.amount.value, 1).log2().plus(1).times(3)),
blue1: computed(() => Decimal.add(dyes.blue.amount.value, 1).log2().sqrt().times(5e6)),
blue2: computed(() => Decimal.add(dyes.blue.amount.value, 1).log2().plus(1).pow(2)),
orange1: computed(() =>
Decimal.pow(2, Decimal.add(dyes.orange.amount.value, 1).log2().sqrt())
@ -565,7 +689,12 @@ const layer = createLayer(id, function (this: BaseLayer) {
.pow(upgrades.coalUpg.bought.value ? 1.2 : 1)
.pow(management.elfTraining.clothElfTraining.milestones[3].earned.value ? 1.1 : 1)
),
purple2: computed(() => Decimal.add(dyes.purple.amount.value, 1).log2().plus(1))
purple2: computed(() => Decimal.add(dyes.purple.amount.value, 1).log2().plus(1)),
black1: computed(() =>
Decimal.pow(2, Decimal.add(dyes.black.amount.value, 1).log2().sqrt())
.pow(upgrades.coalUpg.bought.value ? 1.2 : 1)
.pow(management.elfTraining.clothElfTraining.milestones[3].earned.value ? 1.1 : 1)
)
};
const [generalTab, generalTabCollapsed] = createCollapsibleModifierSections(() => [
@ -584,6 +713,11 @@ const layer = createLayer(id, function (this: BaseLayer) {
modifier: dyes.blue.toGenerate,
base: 0
},
{
title: "Black Dye Creation",
modifier: dyes.black.toGenerate,
base: 0
},
{
title: "Orange Dye Creation",
modifier: dyes.orange.toGenerate,
@ -618,9 +752,8 @@ const layer = createLayer(id, function (this: BaseLayer) {
))
},
cost: 1000,
resource: noPersist(dyes.blue.amount),
resource: dyes.blue.amount,
onPurchase() {
dyes.blue.amount.value = 0;
dyes.blue.buyable.amount.value = 0;
}
})),
@ -640,9 +773,8 @@ const layer = createLayer(id, function (this: BaseLayer) {
))
},
cost: 1500,
resource: noPersist(dyes.red.amount),
resource: dyes.red.amount,
onPurchase() {
dyes.red.amount.value = 0;
dyes.red.buyable.amount.value = 0;
}
})),
@ -655,20 +787,10 @@ const layer = createLayer(id, function (this: BaseLayer) {
),
display: {
title: "Wetter Dyes",
description: "Double Red, Yellow, and Blue Dye gain, but reset their amounts."
description: "Double Red, Yellow, and Blue Dye gain."
},
cost: 2000,
resource: noPersist(dyes.yellow.amount),
onPurchase() {
dyes.red.amount.value = 0;
dyes.red.buyable.amount.value = 0;
dyes.yellow.amount.value = 0;
dyes.yellow.buyable.amount.value = 0;
dyes.blue.amount.value = 0;
dyes.blue.buyable.amount.value = 0;
}
resource: dyes.yellow.amount
})),
yellowDyeUpg2: createUpgrade(() => ({
visibility: () => showIf(upgrades.yellowDyeUpg.bought.value),
@ -677,9 +799,8 @@ const layer = createLayer(id, function (this: BaseLayer) {
description: "Halve the Oil cost of Red, Yellow, and Blue Dyes."
},
cost: 5000,
resource: noPersist(dyes.yellow.amount),
resource: dyes.yellow.amount,
onPurchase() {
dyes.yellow.amount.value = 0;
dyes.yellow.buyable.amount.value = 0;
}
})),
@ -694,9 +815,8 @@ const layer = createLayer(id, function (this: BaseLayer) {
))
},
cost: 6000,
resource: noPersist(dyes.red.amount),
resource: dyes.red.amount,
onPurchase() {
dyes.red.amount.value = 0;
dyes.red.buyable.amount.value = 0;
}
})),
@ -707,9 +827,8 @@ const layer = createLayer(id, function (this: BaseLayer) {
description: "Raise Red Dye's effect ^1.5."
},
cost: 7500,
resource: noPersist(dyes.blue.amount),
resource: dyes.blue.amount,
onPurchase() {
dyes.blue.amount.value = 0;
dyes.blue.buyable.amount.value = 0;
}
})),
@ -723,20 +842,10 @@ const layer = createLayer(id, function (this: BaseLayer) {
display: {
title: "Denser Spectrum",
description:
"Orange, Green, and Purple Dyes' first effect is raised ^1.2, and Green Dye's second effect is squared. Buying this resets Red, Yellow, and Blue Dyes."
"Orange, Green, and Purple Dyes' first effect is raised ^1.2, and Green Dye's second effect is squared."
},
cost: "5e30",
resource: coal.coal,
onPurchase() {
dyes.red.amount.value = 0;
dyes.red.buyable.amount.value = 0;
dyes.yellow.amount.value = 0;
dyes.yellow.buyable.amount.value = 0;
dyes.blue.amount.value = 0;
dyes.blue.buyable.amount.value = 0;
}
resource: coal.coal
}))
};
@ -770,7 +879,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
goal: 6e4,
name,
day,
color,
background: color,
textColor: "var(--feature-foreground)",
modal: {
show: showModifiersModal,
@ -779,8 +888,42 @@ const layer = createLayer(id, function (this: BaseLayer) {
ignoreTotal: true
});
const mastery = {
dyes: {
red: {
buyable: { amount: persistent<DecimalSource>(0) }
},
green: {
buyable: { amount: persistent<DecimalSource>(0) }
},
blue: {
buyable: { amount: persistent<DecimalSource>(0) }
},
yellow: {
buyable: { amount: persistent<DecimalSource>(0) }
},
purple: {
buyable: { amount: persistent<DecimalSource>(0) }
},
orange: {
buyable: { amount: persistent<DecimalSource>(0) }
}
},
upgrades: {
blueDyeUpg: { bought: persistent<boolean>(false) },
redDyeUpg: { bought: persistent<boolean>(false) },
yellowDyeUpg: { bought: persistent<boolean>(false) },
yellowDyeUpg2: { bought: persistent<boolean>(false) },
redDyeUpg2: { bought: persistent<boolean>(false) },
blueDyeUpg2: { bought: persistent<boolean>(false) },
coalUpg: { bought: persistent<boolean>(false) }
}
};
const mastered = persistent<boolean>(false);
return {
name,
day,
color,
dyes,
dyeSum,
@ -794,7 +937,20 @@ const layer = createLayer(id, function (this: BaseLayer) {
<>
{render(trackerDisplay)}
<Spacer />
{masteryEffectActive.value ? (
<>
<div class="decoration-effect ribbon">
Decoration effect:
<br />
Each primary dye gains a second effect
</div>
<Spacer />
</>
) : null}
<div style="width: 620px">
{renderRow(dyes.black.display)}
{renderRow(dyes.black.buyable)}
<Spacer />
{renderRow(dyes.red.display, dyes.yellow.display, dyes.blue.display)}
{renderRow(dyes.red.buyable, dyes.yellow.buyable, dyes.blue.buyable)}
<Spacer />
@ -809,7 +965,10 @@ const layer = createLayer(id, function (this: BaseLayer) {
</div>
{render(upgrades.coalUpg)}
</>
))
)),
mastery,
mastered,
masteryEffectActive
};
});

View file

@ -36,7 +36,9 @@ import plastic from "./plastic";
import trees from "./trees";
import workshop from "./workshop";
import wrappingPaper from "./wrapping-paper";
import dyes from "./dyes";
import dyes, { enumColor } from "./dyes";
import ribbon from "./ribbon";
import letters from "./letters";
export interface ElfBuyable extends GenericBuyable {
/** The inverse function of the cost formula, used to calculate the maximum amount that can be bought by elves. */
@ -418,12 +420,39 @@ const layer = createLayer(id, function (this: BaseLayer) {
enabled: elvesMilestone2.earned
}))
]);
const dyeCooldown = createSequentialModifier(() => [
createMultiplicativeModifier(() => ({
multiplier: Infinity,
description: "Dye",
enabled: () => true
multiplier: 2,
description: "6 Elves Trained",
enabled: elvesMilestone.earned
})),
createMultiplicativeModifier(() => ({
multiplier: () =>
Decimal.times(paper.books.primaryDyeBook.totalAmount.value, 0.1).add(1),
description: "Arts and Crafts",
enabled: () => Decimal.gt(paper.books.primaryDyeBook.totalAmount.value, 0)
})),
createMultiplicativeModifier(() => ({
multiplier: 2,
description: "10 Elves Trained",
enabled: elvesMilestone2.earned
}))
]);
const plasticCooldown = createSequentialModifier(() => [
createMultiplicativeModifier(() => ({
multiplier: 2,
description: "6 Elves Trained",
enabled: elvesMilestone.earned
})),
createMultiplicativeModifier(() => ({
multiplier: () => Decimal.times(paper.books.plasticBook.totalAmount.value, 0.1).add(1),
description: "One Plastic Bag",
enabled: () => Decimal.gt(paper.books.plasticBook.totalAmount.value, 0)
})),
createMultiplicativeModifier(() => ({
multiplier: 2,
description: "10 Elves Trained",
enabled: elvesMilestone2.earned
}))
]);
@ -517,28 +546,50 @@ const layer = createLayer(id, function (this: BaseLayer) {
modifier: coalDrillCooldown,
base: 10,
unit: "/s",
visible: management.elfTraining.expandersElfTraining.milestones[3].earned
visible: () =>
management.elfTraining.expandersElfTraining.milestones[3].earned.value ||
letters.masteryEffectActive.value
},
{
title: "Frosty Auto-Buy Frequency",
modifier: heavyDrillCooldown,
base: 10,
unit: "/s",
visible: management.elfTraining.cutterElfTraining.milestones[4].earned.value
visible: () =>
management.elfTraining.cutterElfTraining.milestones[4].earned.value ||
letters.masteryEffectActive.value
},
{
title: "Cocoa Auto-Buy Frequency",
modifier: oilCooldown,
base: 10,
unit: "/s",
visible: management.elfTraining.heatedCutterElfTraining.milestones[4].earned.value
visible: () =>
management.elfTraining.heatedCutterElfTraining.milestones[4].earned.value ||
letters.masteryEffectActive.value
},
{
title: "Twinkle Auto-Buy Frequency",
modifier: metalCooldown,
base: 10,
unit: "/s",
visible: management.elfTraining.fertilizerElfTraining.milestones[4].earned
visible: () =>
management.elfTraining.fertilizerElfTraining.milestones[4].earned.value ||
letters.masteryEffectActive.value
},
{
title: "Carol Auto-Buy Frequency",
modifier: dyeCooldown,
base: 10,
unit: "/s",
visible: wrappingPaper.unlockDyeElfMilestone.earned.value && !main.isMastery.value
},
{
title: "Tinsel Auto-Buy Frequency",
modifier: plasticCooldown,
base: 10,
unit: "/s",
visible: plastic.masteryEffectActive
}
]);
const showModifiersModal = ref(false);
@ -554,10 +605,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
));
const trainingCost = computed(() => {
let cost = Decimal.pow(
Decimal.sub(4, wrappingPaper.boosts.jazzy1.value),
totalElves.value
).times(1e6);
let cost = Decimal.pow(4, totalElves.value).times(1e6);
if (Decimal.gte(totalElves.value, 9)) {
cost = Decimal.times(cost, 1e15);
}
@ -588,7 +636,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
} & Partial<ClickableOptions>
) {
const buyProgress = persistent<DecimalSource>(0);
const amountOfTimesDone = persistent(0);
const amountOfTimesDone = persistent<number>(0);
const toggle = options.hasToggle ? persistent<boolean>(false) : ref(true);
const computedAutoBuyCooldown = computed(() => options.cooldownModifier.apply(10));
@ -647,6 +695,11 @@ const layer = createLayer(id, function (this: BaseLayer) {
computedAutoBuyCooldown,
amountOfTimesDone,
name: options.name,
canAfford() {
return (
Decimal.gte(coal.coal.value, unref(trainingCost)) && !main.isMastery.value
);
},
display: () => ({
title: options.name,
description: jsx(() => (
@ -678,13 +731,17 @@ const layer = createLayer(id, function (this: BaseLayer) {
style: "width: 190px",
onPurchase() {
options.onPurchase?.();
if (!["Peppermint", "Twinkle", "Cocoa", "Frosty"].includes(options.name)) {
if (
!["Peppermint", "Twinkle", "Cocoa", "Frosty", "Carol"].includes(
options.name
)
) {
elfReset.reset();
}
}
};
}) as GenericUpgrade & {
buyProgress: Ref<number>;
buyProgress: Ref<DecimalSource>;
update: (diff: number) => void;
toggle: Ref<boolean>;
name: string;
@ -740,7 +797,8 @@ const layer = createLayer(id, function (this: BaseLayer) {
description:
"Noel will automatically purchase fertilized soil you can afford, without actually spending any ash.",
buyable: coal.moreFertilizer,
cooldownModifier: fertilizerCooldown
cooldownModifier: fertilizerCooldown,
buyMax: () => management.elfTraining.heatedPlanterElfTraining.milestones[2].earned.value
});
const coalElves = [heatedCuttersElf, heatedPlantersElf, fertilizerElf];
const smallFireElf = createElf({
@ -838,7 +896,10 @@ const layer = createLayer(id, function (this: BaseLayer) {
buyable: coal.buildDrill,
cooldownModifier: coalDrillCooldown,
visibility: () =>
showIf(management.elfTraining.expandersElfTraining.milestones[3].earned.value),
showIf(
management.elfTraining.expandersElfTraining.milestones[3].earned.value ||
letters.masteryEffectActive.value
),
hasToggle: true,
toggleDesc: "Activate auto-purchased coal drills",
onAutoPurchase(_, amount) {
@ -854,7 +915,10 @@ const layer = createLayer(id, function (this: BaseLayer) {
buyable: [oil.buildHeavy, oil.buildHeavy2, oil.buildExtractor],
cooldownModifier: heavyDrillCooldown,
visibility: () =>
showIf(management.elfTraining.cutterElfTraining.milestones[4].earned.value),
showIf(
management.elfTraining.cutterElfTraining.milestones[4].earned.value ||
letters.masteryEffectActive.value
),
hasToggle: true,
toggleDesc: "Activate auto-purchased oil drills",
onAutoPurchase(buyable, amount) {
@ -876,7 +940,10 @@ const layer = createLayer(id, function (this: BaseLayer) {
buyable: [oil.buildPump, oil.buildBurner, oil.buildSmelter],
cooldownModifier: oilCooldown,
visibility: () =>
showIf(management.elfTraining.heatedCutterElfTraining.milestones[4].earned.value),
showIf(
management.elfTraining.heatedCutterElfTraining.milestones[4].earned.value ||
letters.masteryEffectActive.value
),
hasToggle: true,
toggleDesc: "Activate auto-purchased oil-using machines",
onAutoPurchase(buyable, amount) {
@ -899,18 +966,48 @@ const layer = createLayer(id, function (this: BaseLayer) {
buyable: [metal.oreDrill, metal.industrialCrucible, metal.hotterForge],
cooldownModifier: metalCooldown,
visibility: () =>
showIf(management.elfTraining.fertilizerElfTraining.milestones[4].earned.value)
showIf(
management.elfTraining.fertilizerElfTraining.milestones[4].earned.value ||
letters.masteryEffectActive.value
)
});
const managementElves2 = [metalElf];
const dyeColors = Object.fromEntries(
(["blue", "red", "yellow", "orange", "green", "purple"] as enumColor[]).map(color => [
dyes.dyes[color].buyable.id,
color
])
) as Record<string, enumColor>;
const dyeElf = createElf({
name: "Carol",
description:
"Carol will automatically purchase all dyes you can afford, without actually spending any resources.",
"Carol will automatically purchase all primary dyes you can afford, without actually spending any resources.",
buyable: Object.values(dyes.dyes).map(dye => dye.buyable),
cooldownModifier: dyeCooldown, // Note: Buy max will be unlocked at this point
visibility: () => showIf(wrappingPaper.milestones.unlockDyeElf.earned.value)
visibility: () =>
showIf(wrappingPaper.unlockDyeElfMilestone.earned.value && !main.isMastery.value),
buyMax: () => management.elfTraining.dyeElfTraining.milestones[2].earned.value,
onAutoPurchase(buyable, amount) {
buyable.amount.value = Decimal.sub(buyable.amount.value, amount);
if (["orange", "green", "purple"].includes(dyeColors[buyable.id])) {
if (!ribbon.milestones.secondaryDyeElf.earned.value) {
return;
}
}
buyable.amount.value = Decimal.add(buyable.amount.value, amount);
}
});
const wrappingPaperElves = [dyeElf];
const plasticElf = createElf({
name: "Tinsel",
description:
"Tinsel will automatically purchase all plastic buyables you can afford, without actually spending any resources.",
buyable: Object.values(plastic.buyables),
cooldownModifier: plasticCooldown,
visibility: () => showIf(plastic.masteryEffectActive.value),
buyMax: () => management.elfTraining.plasticElfTraining.milestones[4].earned.value
});
const wrappingPaperElves = [dyeElf, plasticElf];
const elves = {
cuttersElf,
plantersElf,
@ -928,7 +1025,8 @@ const layer = createLayer(id, function (this: BaseLayer) {
heavyDrillElf,
oilElf,
metalElf,
dyeElf
dyeElf,
plasticElf
};
const totalElves = computed(() => Object.values(elves).filter(elf => elf.bought.value).length);
@ -1010,7 +1108,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
effectDisplay: "Elves work twice as fast (again)"
},
shouldEarn: () => Decimal.gte(totalElves.value, 10),
visibility: () => showIf(main.day.value >= 10)
visibility: () => showIf(main.day.value >= 10 && treeUpgradesMilestone.earned.value)
}));
const coalUpgradesMilestone = createMilestone(() => ({
display: {
@ -1078,8 +1176,124 @@ const layer = createLayer(id, function (this: BaseLayer) {
}
});
const mastery = {
elves: {
cuttersElf: {
buyProgress: persistent<DecimalSource>(0),
amountOfTimesDone: persistent<number>(0),
bought: persistent<boolean>(false)
},
plantersElf: {
buyProgress: persistent<DecimalSource>(0),
amountOfTimesDone: persistent<number>(0),
bought: persistent<boolean>(false)
},
expandersElf: {
buyProgress: persistent<DecimalSource>(0),
amountOfTimesDone: persistent<number>(0),
bought: persistent<boolean>(false)
},
heatedCuttersElf: {
buyProgress: persistent<DecimalSource>(0),
amountOfTimesDone: persistent<number>(0),
bought: persistent<boolean>(false)
},
heatedPlantersElf: {
buyProgress: persistent<DecimalSource>(0),
amountOfTimesDone: persistent<number>(0),
bought: persistent<boolean>(false)
},
fertilizerElf: {
buyProgress: persistent<DecimalSource>(0),
amountOfTimesDone: persistent<number>(0),
bought: persistent<boolean>(false)
},
smallFireElf: {
buyProgress: persistent<DecimalSource>(0),
amountOfTimesDone: persistent<number>(0),
toggle: persistent<boolean>(false),
bought: persistent<boolean>(false)
},
bonfireElf: {
buyProgress: persistent<DecimalSource>(0),
amountOfTimesDone: persistent<number>(0),
toggle: persistent<boolean>(false),
bought: persistent<boolean>(false)
},
kilnElf: {
buyProgress: persistent<DecimalSource>(0),
amountOfTimesDone: persistent<number>(0),
toggle: persistent<boolean>(false),
bought: persistent<boolean>(false)
},
paperElf: {
buyProgress: persistent<DecimalSource>(0),
amountOfTimesDone: persistent<number>(0),
bought: persistent<boolean>(false)
},
boxElf: {
buyProgress: persistent<DecimalSource>(0),
amountOfTimesDone: persistent<number>(0),
bought: persistent<boolean>(false)
},
clothElf: {
buyProgress: persistent<DecimalSource>(0),
amountOfTimesDone: persistent<number>(0),
bought: persistent<boolean>(false)
},
coalDrillElf: {
buyProgress: persistent<DecimalSource>(0),
amountOfTimesDone: persistent<number>(0),
toggle: persistent<boolean>(false),
bought: persistent<boolean>(false)
},
heavyDrillElf: {
buyProgress: persistent<DecimalSource>(0),
amountOfTimesDone: persistent<number>(0),
toggle: persistent<boolean>(false),
bought: persistent<boolean>(false)
},
oilElf: {
buyProgress: persistent<DecimalSource>(0),
amountOfTimesDone: persistent<number>(0),
toggle: persistent<boolean>(false),
bought: persistent<boolean>(false)
},
metalElf: {
buyProgress: persistent<DecimalSource>(0),
amountOfTimesDone: persistent<number>(0),
bought: persistent<boolean>(false)
},
dyeElf: {
buyProgress: persistent<DecimalSource>(0),
amountOfTimesDone: persistent<number>(0),
bought: persistent<boolean>(false)
},
plasticElf: {
buyProgress: persistent<DecimalSource>(0),
amountOfTimesDone: persistent<number>(0),
bought: persistent<boolean>(false)
}
},
milestones: [
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) }
]
};
return {
name,
day,
color: colorBright,
elves,
totalElves,
@ -1119,7 +1333,8 @@ const layer = createLayer(id, function (this: BaseLayer) {
</div>
{milestonesDisplay()}
</>
))
)),
mastery
};
});

View file

@ -24,9 +24,12 @@ import { createBuyable, GenericBuyable } from "features/buyable";
import metal from "./metal";
import plastic from "./plastic";
import paper from "./paper";
import dyes from "./dyes";
import SqrtVue from "components/math/Sqrt.vue";
import { globalBus } from "game/events";
import { main } from "data/projEntry";
import { createHotkey } from "features/hotkey";
import HotkeyVue from "components/Hotkey.vue";
const id = "letters";
const day = 14;
@ -43,13 +46,17 @@ const layer = createLayer(id, function (this: BaseLayer) {
height: 10,
style: "margin-top: 8px",
borderStyle: "border-color: black",
baseStyle: "margin-top: 0",
fillStyle: "margin-top: 0; transition-duration: 0s; background: black",
baseStyle: "margin-top: -1px",
fillStyle: "margin-top: -1px; transition-duration: 0s; background: black",
progress: () => Decimal.div(processingProgress.value, computedProcessingCooldown.value)
}));
const process = createClickable(() => ({
display: {
title: "Process Letters",
title: jsx(() => (
<h3>
Process letters <HotkeyVue hotkey={processHK} />
</h3>
)),
description: jsx(() => (
<>
Process {format(computedLettersGain.value, 1)} letters
@ -61,7 +68,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
style: {
minHeight: "80px"
},
canClick: () => Decimal.gte(processingProgress.value, computedProcessingCooldown.value),
canClick: () =>
Decimal.gte(processingProgress.value, computedProcessingCooldown.value) &&
(!main.isMastery.value || masteryEffectActive.value),
onClick() {
if (Decimal.lt(processingProgress.value, computedProcessingCooldown.value)) {
return;
@ -75,6 +84,15 @@ const layer = createLayer(id, function (this: BaseLayer) {
}
}));
const processHK = createHotkey(() => ({
key: "l",
description: "Process letters",
onPress: () => {
if (process.canClick.value) process.onClick();
},
enabled: main.days[day - 1].opened
}));
const metalBuyable = createBuyable(() => ({
display: {
title: "Sorting Machine",
@ -87,7 +105,8 @@ const layer = createLayer(id, function (this: BaseLayer) {
resource: metal.metal,
cost() {
return Decimal.pow(10, metalBuyable.amount.value).times(1e21);
}
},
visibility: () => showIf(!main.isMastery.value || masteryEffectActive.value)
})) as GenericBuyable;
const plasticBuyable = createBuyable(() => ({
display: {
@ -101,7 +120,8 @@ const layer = createLayer(id, function (this: BaseLayer) {
resource: plastic.plastic,
cost() {
return Decimal.pow(1.5, plasticBuyable.amount.value).times(1e9);
}
},
visibility: () => showIf(!main.isMastery.value || masteryEffectActive.value)
})) as GenericBuyable;
const paperBuyable = createBuyable(() => ({
display: {
@ -114,7 +134,8 @@ const layer = createLayer(id, function (this: BaseLayer) {
resource: paper.paper,
cost() {
return Decimal.pow(3, paperBuyable.amount.value).times(1e38);
}
},
visibility: () => showIf(!main.isMastery.value || masteryEffectActive.value)
})) as GenericBuyable;
const buyables = { metalBuyable, plasticBuyable, paperBuyable };
@ -169,13 +190,17 @@ const layer = createLayer(id, function (this: BaseLayer) {
createCollapsibleMilestones(milestones);
const synergy = computed(() => {
const amount = Decimal.add(totalLetters.value, 1);
let amount = Decimal.add(totalLetters.value, 1);
if (synergyMilestone.earned.value) {
const preSoftcap = Decimal.log2(10001).add(1);
return preSoftcap.add(amount.sub(9999).sqrt());
amount = preSoftcap.add(amount.sub(9999).sqrt());
} else {
return Decimal.log2(amount).add(1);
amount = Decimal.log2(amount).add(1);
}
if (masteryEffectActive.value) {
amount = Decimal.pow(amount, 2);
}
return amount;
});
const lettersGain = createSequentialModifier(() => [
@ -190,13 +215,23 @@ const layer = createLayer(id, function (this: BaseLayer) {
createMultiplicativeModifier(() => ({
multiplier: () => Decimal.div(paperBuyable.amount.value, 2).add(1),
description: "Printed Labels"
})),
createMultiplicativeModifier(() => ({
multiplier: () => dyes.boosts.black1.value,
description: "Black Dye Boost"
}))
]);
const computedLettersGain = computed(() => lettersGain.apply(1));
const processingCooldown = createSequentialModifier(() => [
createMultiplicativeModifier(() => ({
multiplier: () => Decimal.div(metalBuyable.amount.value, 2).add(1).recip(),
description: "Sorting Machine"
})),
createMultiplicativeModifier(() => ({
multiplier: () => Decimal.sqrt(synergy.value).recip(),
description: "Letters Decoration",
enabled: masteryEffectActive
}))
]);
const computedProcessingCooldown = computed(() => processingCooldown.apply(5));
@ -245,7 +280,10 @@ const layer = createLayer(id, function (this: BaseLayer) {
goal: 1e6,
name,
day,
color,
background: {
gradient: "letters-bar",
duration: "15s"
},
textColor: "var(--feature-foreground)",
modal: {
show: showModifiersModal,
@ -253,6 +291,26 @@ const layer = createLayer(id, function (this: BaseLayer) {
}
});
const mastery = {
letters: persistent<DecimalSource>(0),
totalLetters: persistent<DecimalSource>(0),
buyables: {
metalBuyable: { amount: persistent<DecimalSource>(0) },
plasticBuyable: { amount: persistent<DecimalSource>(0) },
paperBuyable: { amount: persistent<DecimalSource>(0) }
},
milestones: {
autoSmeltingMilestone: { earned: persistent<boolean>(false) },
miningMilestone: { earned: persistent<boolean>(false) },
synergyMilestone: { earned: persistent<boolean>(false) },
industrialCrucibleMilestone: { earned: persistent<boolean>(false) }
}
};
const mastered = persistent<boolean>(false);
const masteryEffectActive = computed(
() => mastered.value || main.currentlyMastering.value?.name === name
);
return {
name,
day,
@ -260,6 +318,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
letters,
totalLetters,
processingProgress,
processHK,
buyables,
milestones,
minWidth: 700,
@ -269,6 +328,17 @@ const layer = createLayer(id, function (this: BaseLayer) {
<>
{render(trackerDisplay)}
<Spacer />
{masteryEffectActive.value ? (
<>
<div class="decoration-effect ribbon">
Decoration effect:
<br />
Letter processing experience is stronger and affects processing cooldown
at reduced rate
</div>
<Spacer />
</>
) : null}
<MainDisplay resource={letters} color={color} />
{render(process)}
<div>
@ -284,9 +354,14 @@ const layer = createLayer(id, function (this: BaseLayer) {
minimizedDisplay: jsx(() => (
<div>
{name}{" "}
<span class="desc">{format(letters.value)} {letters.displayName}</span>
<span class="desc">
{format(letters.value)} {letters.displayName}
</span>
</div>
)),
mastery,
mastered,
masteryEffectActive
};
});

View file

@ -34,6 +34,8 @@ import plastic from "./plastic";
import trees from "./trees";
import "./styles/management.css";
import { Resource } from "features/resources/resource";
import { isArray } from "@vue/shared";
const id = "management";
const day = 12;
@ -99,7 +101,8 @@ const layer = createLayer(id, () => {
"The Elves probably need to be taught if they're to do better. Maybe you'll build a school so you can teach them?"
},
resource: trees.logs,
cost: 1e21
cost: 1e21,
visibility: () => showIf(!main.isMastery.value)
}));
const classroomUpgrade = createUpgrade(() => ({
@ -120,7 +123,11 @@ const layer = createLayer(id, () => {
"Time for some advanced training! Now that all the elves know the basics, you have a foundation you can truly build off of. Everyone seems to be learning twice as quickly!"
},
visibility: () =>
showIf(main.day.value >= advancedDay && main.days[advancedDay - 1].opened.value),
showIf(
!main.isMastery.value &&
main.day.value >= advancedDay &&
main.days[advancedDay - 1].opened.value
),
resource: boxes.boxes,
style: "width: 150px",
cost: 1e25
@ -180,7 +187,8 @@ const layer = createLayer(id, () => {
"Frosty",
"Cocoa",
"Twinkle",
"Carol"
"Carol",
"Tinsel"
].indexOf(elf.name) + 1;
if (elf.name == "Star" || elf.name == "Bell") {
costMulti /= 3;
@ -344,7 +352,10 @@ const layer = createLayer(id, () => {
effectDisplay: "Unlock an elf that autobuys oil drills and extractors."
},
visibility: () => showIf(cutterElfMilestones[3].earned.value && main.day.value >= 13),
shouldEarn: () => cutterElfTraining.level.value >= 5
shouldEarn: () => cutterElfTraining.level.value >= 5,
onComplete() {
main.days[3].recentlyUpdated.value = true;
}
}))
] as Array<GenericMilestone>;
const planterElfMilestones = [
@ -509,7 +520,7 @@ const layer = createLayer(id, () => {
createMilestone(() => ({
display: {
requirement: "Mary Level 3",
effectDisplay: "Mary and Faith now buy max."
effectDisplay: "Mary, Noel, and Faith now buy max."
},
visibility: () => showIf(heatedPlanterElfMilestones[1].earned.value),
shouldEarn: () => heatedPlanterElfTraining.level.value >= 3
@ -831,33 +842,18 @@ const layer = createLayer(id, () => {
createMilestone(() => ({
display: {
requirement: "Gingersnap Level 3",
effectDisplay: "Double all dye colors and cloth actions, but reset all dyes."
effectDisplay: "Double all dye colors and cloth actions"
},
visibility: () => showIf(clothElfMilestones[1].earned.value),
shouldEarn: () => clothElfTraining.level.value >= 3,
onComplete() {
(["red", "yellow", "blue", "orange", "green", "purple"] as const).forEach(
dyeColor => {
dyes.dyes[dyeColor].amount.value = 0;
dyes.dyes[dyeColor].buyable.amount.value = 0;
}
);
}
shouldEarn: () => clothElfTraining.level.value >= 3
})),
createMilestone(() => ({
display: {
requirement: "Gingersnap Level 4",
effectDisplay:
"Raise secondary dyes' first effects to the 1.1 but reset primary dyes"
effectDisplay: "Raise secondary dyes' first effects to the 1.1"
},
visibility: () => showIf(clothElfMilestones[2].earned.value && main.day.value >= 13),
shouldEarn: () => clothElfTraining.level.value >= 4,
onComplete() {
(["red", "yellow", "blue"] as const).forEach(dyeColor => {
dyes.dyes[dyeColor].amount.value = 0;
dyes.dyes[dyeColor].buyable.amount.value = 0;
});
}
shouldEarn: () => clothElfTraining.level.value >= 4
})),
createMilestone(() => ({
display: {
@ -1056,6 +1052,93 @@ const layer = createLayer(id, () => {
}
}))
] as Array<GenericMilestone>;
const dyeElfMilestones = [
createMilestone(() => ({
display: {
requirement: "Carol Level 1",
effectDisplay: "Double primary dye gain"
},
shouldEarn: () => dyeElfTraining.level.value >= 1
})),
createMilestone(() => ({
display: {
requirement: "Carol Level 2",
effectDisplay: "Double secondary dye gain"
},
shouldEarn: () => dyeElfTraining.level.value >= 2,
visibility: () => showIf(dyeElfMilestones[0].earned.value)
})),
createMilestone(() => ({
display: {
requirement: "Carol Level 3",
effectDisplay: "Buy maximum primary dyes"
},
shouldEarn: () => dyeElfTraining.level.value >= 3,
visibility: () => showIf(dyeElfMilestones[1].earned.value)
})),
createMilestone(() => ({
display: {
requirement: "Carol Level 4",
effectDisplay: "Secondary dyes don't spend primary dyes"
},
shouldEarn: () => dyeElfTraining.level.value >= 4,
visibility: () => showIf(dyeElfMilestones[2].earned.value && main.day.value >= 16)
})),
createMilestone(() => ({
display: {
requirement: "Carol Level 5",
effectDisplay: "Buy maximum secondary dyes"
},
shouldEarn: () => dyeElfTraining.level.value >= 5,
visibility: () => showIf(dyeElfMilestones[3].earned.value && main.day.value >= 16)
}))
] as Array<GenericMilestone>;
const plasticElfMilestones = [
createMilestone(() => ({
display: {
requirement: "Tinsel Level 1",
effectDisplay: "Double plastic gain"
},
shouldEarn: () => plasticElfTraining.level.value >= 1
})),
createMilestone(() => ({
display: {
requirement: "Tinsel Level 2",
effectDisplay: jsx(() => (
<>
Every plastic buyable adds <Sqrt>level</Sqrt> levels to the other plastic
buyables.
</>
))
},
shouldEarn: () => plasticElfTraining.level.value >= 2,
visibility: () => showIf(plasticElfMilestones[0].earned.value)
})),
createMilestone(() => ({
display: {
requirement: "Tinsel Level 3",
effectDisplay: "Refineries don't spend oil"
},
shouldEarn: () => plasticElfTraining.level.value >= 3,
visibility: () => showIf(plasticElfMilestones[1].earned.value)
})),
createMilestone(() => ({
display: {
requirement: "Tinsel Level 4",
effectDisplay: "Increase plastic gain by +1% for each refinery"
},
shouldEarn: () => plasticElfTraining.level.value >= 4,
visibility: () => showIf(plasticElfMilestones[2].earned.value && main.day.value >= 16)
})),
createMilestone(() => ({
display: {
requirement: "Tinsel Level 5",
effectDisplay: "Buy maximum plastic buyables"
},
shouldEarn: () => plasticElfTraining.level.value >= 5,
visibility: () => showIf(plasticElfMilestones[3].earned.value && main.day.value >= 16)
}))
] as Array<GenericMilestone>;
// ------------------------------------------------------------------------------- Milestone display
const currentShown = persistent<string>("Holly");
@ -1123,7 +1206,7 @@ const layer = createLayer(id, () => {
}))
);
const clothElfTraining = createElfTraining(elves.elves.clothElf, clothElfMilestones);
const plasticElfTraining = [paperElfTraining, boxElfTraining, clothElfTraining];
const plasticElvesTraining = [paperElfTraining, boxElfTraining, clothElfTraining];
const coalDrillElfTraining = createElfTraining(
elves.elves.coalDrillElf,
coalDrillElfMilestones
@ -1134,8 +1217,10 @@ const layer = createLayer(id, () => {
elves.elves.heavyDrillElf,
heavyDrillElfMilestones
);
const dyeElfTraining = createElfTraining(elves.elves.dyeElf, dyeElfMilestones);
const plasticElfTraining = createElfTraining(elves.elves.plasticElf, plasticElfMilestones);
const row5Elves = [coalDrillElfTraining, heavyDrillElfTraining, oilElfTraining];
const row6Elves = [metalElfTraining];
const row6Elves = [metalElfTraining, dyeElfTraining, plasticElfTraining];
const elfTraining = {
cutterElfTraining,
planterElfTraining,
@ -1152,7 +1237,9 @@ const layer = createLayer(id, () => {
coalDrillElfTraining,
metalElfTraining,
oilElfTraining,
heavyDrillElfTraining
heavyDrillElfTraining,
dyeElfTraining,
plasticElfTraining
};
const day12Elves = [
cutterElfTraining,
@ -1409,6 +1496,19 @@ const layer = createLayer(id, () => {
};
});
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 schools = createBuyable(() => ({
display: jsx(() => (
<>
@ -1424,13 +1524,19 @@ const layer = createLayer(id, () => {
</div>
{Decimal.lt(schools.amount.value, unref(schools.purchaseLimit)) ? (
<div>
Costs {format(schoolCost.value.wood)} logs, {format(schoolCost.value.coal)}{" "}
coal, {format(schoolCost.value.paper)} paper,{" "}
{format(schoolCost.value.boxes)} boxes,{" "}
{format(schoolCost.value.metalIngots)} metal ingots,{" "}
{format(schoolCost.value.cloth)} cloth, {format(schoolCost.value.plastic)}{" "}
plastic, and requires {format(schoolCost.value.dye)} of red, yellow, and
blue dye
Costs {displayCost(trees.logs, schoolCost.value.wood, "logs")},{" "}
{displayCost(coal.coal, schoolCost.value.coal, "coal")},{" "}
{displayCost(paper.paper, schoolCost.value.paper, "paper")},{" "}
{displayCost(boxes.boxes, schoolCost.value.boxes, "boxes")},{" "}
{displayCost(metal.metal, schoolCost.value.metalIngots, "metal ingots")},{" "}
{displayCost(cloth.cloth, schoolCost.value.cloth, "cloth")},{" "}
{displayCost(plastic.plastic, schoolCost.value.plastic, "plastic")}, and
requires{" "}
{displayCost(
[dyes.dyes.red.amount, dyes.dyes.yellow.amount, dyes.dyes.blue.amount],
schoolCost.value.dye,
"red, yellow, and blue dye"
)}
</div>
) : null}
</>
@ -1498,9 +1604,10 @@ const layer = createLayer(id, () => {
multiplying elves' XP gain by {format(classroomEffect.value)}
</div>
<div>
Costs {format(classroomCost.value.wood)} logs,
{format(classroomCost.value.paper)} paper, {format(classroomCost.value.boxes)}{" "}
boxes, {format(classroomCost.value.metalIngots)} metal ingots
Costs {displayCost(trees.logs, classroomCost.value.wood, "logs")},
{displayCost(paper.paper, classroomCost.value.paper, "paper")},{" "}
{displayCost(boxes.boxes, classroomCost.value.boxes, "boxes")},{" "}
{displayCost(metal.metal, classroomCost.value.metalIngots, "metal ingots")}
</div>
</>
)),
@ -1618,6 +1725,42 @@ const layer = createLayer(id, () => {
modifier: clothElfTraining.elfXPGain,
base: 0.1,
unit: " XP"
},
{
title: "Peppermint XP Gain per Action",
modifier: coalDrillElfTraining.elfXPGain,
base: 0.1,
unit: " XP"
},
{
title: "Frosty XP Gain per Action",
modifier: heavyDrillElfTraining.elfXPGain,
base: 0.1,
unit: " XP"
},
{
title: "Cocoa XP Gain per Action",
modifier: oilElfTraining.elfXPGain,
base: 0.1,
unit: " XP"
},
{
title: "Twinkle XP Gain per Action",
modifier: metalElfTraining.elfXPGain,
base: 0.1,
unit: " XP"
},
{
title: "Carol XP Gain per Action",
modifier: dyeElfTraining.elfXPGain,
base: 0.1,
unit: " XP"
},
{
title: "Tinsel XP Gain per Action",
modifier: plasticElfTraining.elfXPGain,
base: 0.1,
unit: " XP"
}
]);
const showModifiersModal = ref(false);
@ -1637,12 +1780,217 @@ const layer = createLayer(id, () => {
main.completeDay();
} else if (
main.day.value === advancedDay &&
day12Elves.every(elf => elf.level.value >= 5) &&
day13Elves.every(elf => elf.level.value >= 5)
) {
main.completeDay();
}
});
const mastery = {
elfTraining: {
bonfireElfTraining: {
exp: persistent<DecimalSource>(0),
milestones: [
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) }
]
},
boxElfTraining: {
exp: persistent<DecimalSource>(0),
milestones: [
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) }
]
},
clothElfTraining: {
exp: persistent<DecimalSource>(0),
milestones: [
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) }
]
},
coalDrillElfTraining: {
exp: persistent<DecimalSource>(0),
milestones: [
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) }
]
},
cutterElfTraining: {
exp: persistent<DecimalSource>(0),
milestones: [
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) }
]
},
expandersElfTraining: {
exp: persistent<DecimalSource>(0),
milestones: [
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) }
]
},
fertilizerElfTraining: {
exp: persistent<DecimalSource>(0),
milestones: [
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) }
]
},
heatedCutterElfTraining: {
exp: persistent<DecimalSource>(0),
milestones: [
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) }
]
},
heatedPlanterElfTraining: {
exp: persistent<DecimalSource>(0),
milestones: [
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) }
]
},
heavyDrillElfTraining: {
exp: persistent<DecimalSource>(0),
milestones: [
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) }
]
},
kilnElfTraining: {
exp: persistent<DecimalSource>(0),
milestones: [
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) }
]
},
metalElfTraining: {
exp: persistent<DecimalSource>(0),
milestones: [
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) }
]
},
oilElfTraining: {
exp: persistent<DecimalSource>(0),
milestones: [
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) }
]
},
paperElfTraining: {
exp: persistent<DecimalSource>(0),
milestones: [
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) }
]
},
planterElfTraining: {
exp: persistent<DecimalSource>(0),
milestones: [
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) }
]
},
smallfireElfTraining: {
exp: persistent<DecimalSource>(0),
milestones: [
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) }
]
},
dyeElfTraining: {
exp: persistent<DecimalSource>(0),
milestones: [
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) }
]
},
plasticElfTraining: {
exp: persistent<DecimalSource>(0),
milestones: [
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) }
]
}
},
teaching: { bought: persistent<boolean>(false) },
schools: { amount: persistent<DecimalSource>(0) },
classrooms: { amount: persistent<DecimalSource>(0) },
classroomUpgrade: { bought: persistent<boolean>(false) },
advancedUpgrade: { bought: persistent<boolean>(false) },
upgrades: [
{ bought: persistent<boolean>(false) },
{ bought: persistent<boolean>(false) },
{ bought: persistent<boolean>(false) }
],
upgrades2: [
{ bought: persistent<boolean>(false) },
{ bought: persistent<boolean>(false) },
{ bought: persistent<boolean>(false) }
],
focusMulti: persistent<DecimalSource>(1),
focusTargets: persistent<Record<string, boolean>>({}),
focusCooldown: persistent<number>(0),
focusTime: persistent<number>(0)
};
// ------------------------------------------------------------------------------- Return
return {
@ -1664,7 +2012,7 @@ const layer = createLayer(id, () => {
classroomUpgrade,
advancedUpgrade,
focusMultiplier: focusMulti,
focusMulti,
upgrades,
upgrades2,
focusTargets,
@ -1694,19 +2042,19 @@ const layer = createLayer(id, () => {
<Spacer />
{Decimal.gt(schools.amount.value, 0) ? (
<>
<br />
<Spacer />
Click on an elf to see their milestones.
<br />
<br />
<Spacer />
<Spacer />
{render(focusButton)}
{renderGrid(upgrades, upgrades2)}
<br />
<Spacer />
{renderGrid(
[focusMeter],
treeElfTraining,
coalElfTraining,
fireElfTraining,
plasticElfTraining,
plasticElvesTraining,
row5Elves,
row6Elves
)}
@ -1717,7 +2065,9 @@ const layer = createLayer(id, () => {
""
)}
</>
))
)),
mastery
};
});

View file

@ -35,6 +35,8 @@ import oil from "./oil";
import paper from "./paper";
import plastic from "./plastic";
import workshop from "./workshop";
import wrappingPaper from "./wrapping-paper";
import toys from "./toys";
const id = "metal";
const day = 7;
@ -48,6 +50,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
const ore = createResource<DecimalSource>(0, "ore");
const bestOre = trackBest(ore);
const lastOreGained = ref<DecimalSource>(0);
const lastOreSmelted = ref<DecimalSource>(0);
const orePurity = createSequentialModifier(() => [
createMultiplicativeModifier(() => ({
multiplier: 5,
@ -169,6 +174,16 @@ const layer = createLayer(id, function (this: BaseLayer) {
.log10(),
description: "The Ultimate Metal Dye",
enabled: oil.row3Upgrades[4].bought
})),
createMultiplicativeModifier(() => ({
multiplier: wrappingPaper.boosts.jazzy1,
description: "Jazzy Wrapping Paper",
enabled: computed(() => Decimal.gt(wrappingPaper.boosts.jazzy1.value, 1))
})),
createAdditiveModifier(() => ({
addend: () => Decimal.sub(lastOreGained.value, lastOreSmelted.value).max(0),
description: "Metal Decoration",
enabled: masteryEffectActive
}))
]);
const computedAutoSmeltSpeed = computed(() => autoSmeltSpeed.apply(0));
@ -193,6 +208,11 @@ const layer = createLayer(id, function (this: BaseLayer) {
multiplier: () => Decimal.add(industrialCrucible.amount.value, 1).sqrt(),
description: "100,000 Letters Processed",
enabled: letters.milestones.industrialCrucibleMilestone.earned
})),
createMultiplicativeModifier(() => ({
multiplier: () => Decimal.add(toys.clothes.value, 1),
description: "Give elves clothes to wear",
enabled: toys.row1Upgrades[1].bought
}))
]);
const computedAutoSmeltMulti = computed(() => autoSmeltMulti.apply(1));
@ -268,6 +288,11 @@ const layer = createLayer(id, function (this: BaseLayer) {
multiplier: () => Decimal.add(dyes.dyes.blue.amount.value, 1).sqrt(),
description: "1000 Letters Processed",
enabled: letters.milestones.miningMilestone.earned
})),
createMultiplicativeModifier(() => ({
multiplier: () => Decimal.add(toys.clothes.value, 1),
description: "Give elves clothes to wear",
enabled: toys.row1Upgrades[1].bought
}))
]);
const computedOreAmount = computed(() => oreAmount.apply(1));
@ -305,6 +330,14 @@ const layer = createLayer(id, function (this: BaseLayer) {
),
description: "100 Letters Processed",
enabled: letters.milestones.autoSmeltingMilestone.earned
})),
createAdditiveModifier(() => ({
addend: () =>
Decimal.sub(lastOreSmelted.value, lastOreGained.value)
.max(0)
.div(computedOreAmount.value),
description: "Metal Decoration",
enabled: masteryEffectActive
}))
]);
const computedOreSpeed = computed(() => oreSpeed.apply(Decimal.recip(maxOreProgress)));
@ -314,7 +347,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
width: 400,
height: 25,
direction: Direction.Right,
fillStyle: { backgroundColor: color },
fillStyle: { backgroundColor: color, transitionDuration: "0s" },
progress: () => oreProgress.value
}));
@ -552,6 +585,26 @@ const layer = createLayer(id, function (this: BaseLayer) {
const hotterForgeEffect = computed(() => Decimal.times(hotterForge.amount.value, 0.25));
globalBus.on("update", diff => {
if (
Decimal.lt(main.day.value, day) ||
(main.isMastery.value &&
!mastered.value &&
main.currentlyMastering.value?.name !== name)
) {
return;
}
const oreGained = Decimal.sub(
Decimal.times(computedOreSpeed.value, computedOreAmount.value),
Decimal.sub(lastOreSmelted.value, lastOreGained.value).max(0)
);
const oreSmelted = Decimal.sub(
computedAutoSmeltSpeed.value,
Decimal.sub(lastOreGained.value, lastOreSmelted.value).max(0)
);
lastOreGained.value = Decimal.isNaN(oreGained) ? 0 : oreGained;
lastOreSmelted.value = Decimal.isNaN(oreSmelted) ? 0 : oreSmelted;
oreProgress.value = Decimal.times(diff, computedOreSpeed.value).plus(oreProgress.value);
const oreGain = oreProgress.value.trunc();
oreProgress.value = oreProgress.value.minus(oreGain);
@ -572,7 +625,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
base: 0,
unit: "/s",
visible() {
return Decimal.gt(industrialCrucible.amount.value, 0);
return Decimal.gt(industrialCrucible.amount.value, 0) || masteryEffectActive.value;
}
},
{
@ -617,13 +670,35 @@ const layer = createLayer(id, function (this: BaseLayer) {
goal: 25000,
name,
day,
color,
background: color,
modal: {
show: showModifiersModal,
display: modifiersModal
}
});
const mastery = {
ore: persistent<DecimalSource>(0),
bestOre: persistent<DecimalSource>(0),
oreProgress: persistent<DecimalSource>(0),
metal: persistent<DecimalSource>(0),
bestMetal: persistent<DecimalSource>(0),
totalMetal: persistent<DecimalSource>(0),
simplePickaxe: { bought: persistent<boolean>(false) },
doublePickaxe: { bought: persistent<boolean>(false) },
crucible: { bought: persistent<boolean>(false) },
coalDrill: { bought: persistent<boolean>(false) },
industrialFurnace: { bought: persistent<boolean>(false) },
efficientDrill: { bought: persistent<boolean>(false) },
oreDrill: { amount: persistent<DecimalSource>(0) },
industrialCrucible: { amount: persistent<DecimalSource>(0) },
hotterForge: { amount: persistent<DecimalSource>(0) }
};
const mastered = persistent<boolean>(false);
const masteryEffectActive = computed(
() => mastered.value || main.currentlyMastering.value?.name === name
);
return {
name,
day,
@ -650,6 +725,17 @@ const layer = createLayer(id, function (this: BaseLayer) {
<>
{render(trackerDisplay)}
<Spacer />
{masteryEffectActive.value ? (
<>
<div class="decoration-effect">
Decoration effect:
<br />
The lesser of ore mining amount x speed and auto smelting speed is
increased to match the greater
</div>
<Spacer />
</>
) : null}
<MainDisplay
resource={metal}
color={color}
@ -658,7 +744,8 @@ const layer = createLayer(id, function (this: BaseLayer) {
productionDisplay={jsx(() => (
<>
{autoSmeltEnabled.value &&
Decimal.gte(industrialCrucible.amount.value, 1)
(Decimal.gte(industrialCrucible.amount.value, 1) ||
masteryEffectActive.value)
? `+${formatLimit(
[
[computedAutoSmeltSpeed.value, "smelting speed"],
@ -680,7 +767,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
/>
<Spacer />
{render(smeltOreButton)}
{Decimal.gte(industrialCrucible.amount.value, 1) ? (
{Decimal.gte(industrialCrucible.amount.value, 1) || masteryEffectActive.value ? (
<div style={{ width: "150px" }}>
<Toggle
title="Auto Smelt"
@ -722,9 +809,13 @@ const layer = createLayer(id, function (this: BaseLayer) {
minimizedDisplay: jsx(() => (
<div>
{name}{" "}
<span class="desc">{format(metal.value)} {metal.displayName}</span>
<span class="desc">
{format(metal.value)} {metal.displayName}
</span>
</div>
)),
mastery,
mastered
};
});

View file

@ -87,9 +87,13 @@ const layer = createLayer(id, function (this: BaseLayer) {
const activeHeavy = persistent<DecimalSource>(0);
const heavyCoal = computed(() =>
Decimal.times(
masteryEffectActive.value
? 0
: Decimal.times(
Decimal.pow(activeHeavy.value, heavy2Power.value).pow(
management.elfTraining.coalDrillElfTraining.milestones[0].earned.value ? 2.5 : 2
management.elfTraining.coalDrillElfTraining.milestones[0].earned.value
? 2.5
: 2
),
1e14
)
@ -141,7 +145,8 @@ const layer = createLayer(id, function (this: BaseLayer) {
color: colorText,
width: "160px",
flexGrow: 1
}
},
visibility: () => showIf(!main.isMastery.value || masteryEffectActive.value)
})) as ElfBuyable & { resource: Resource };
const {
min: minHeavy,
@ -222,7 +227,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
});
const activeExtractor = persistent<DecimalSource>(0);
const extractorPower = computed(() => Decimal.pow(1 / 3, activeExtractor.value));
const extractorPower = computed(() =>
masteryEffectActive.value ? 1 : Decimal.pow(1 / 3, activeExtractor.value)
);
const extractorCoal = computed(() => Decimal.pow(2, activeExtractor.value));
const extractorOre = computed(() => Decimal.pow(1.2, activeExtractor.value));
const buildExtractor = createBuyable(() => ({
@ -279,7 +286,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
const activePump = persistent<DecimalSource>(0);
const pumpCoal = computed(() =>
Decimal.pow(row2Upgrades[3].bought.value ? 4 : 5, activePump.value)
masteryEffectActive.value
? 1
: Decimal.pow(row2Upgrades[3].bought.value ? 4 : 5, activePump.value)
);
const pumpOil = computed(() =>
Decimal.add(activePump.value, computedExtraOilPumps.value)
@ -367,7 +376,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
}
return burners;
});
const burnerOil = computed(() => Decimal.pow(effectiveBurners.value, 2));
const burnerOil = computed(() =>
masteryEffectActive.value ? 0 : Decimal.pow(effectiveBurners.value, 2)
);
const burnerCoal = computed(() => Decimal.pow(effectiveBurners.value, 3).mul(1e19));
const burnerMetal = computed(() => Decimal.add(effectiveBurners.value, 1));
const buildBurner = createBuyable(() => ({
@ -429,7 +440,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
});
const activeSmelter = persistent<DecimalSource>(0);
const smelterOil = computed(() => Decimal.pow(activeSmelter.value, 2).mul(100));
const smelterOil = computed(() =>
masteryEffectActive.value ? 0 : Decimal.pow(activeSmelter.value, 2).mul(100)
);
const smelterMetal = computed(() => Decimal.add(activeSmelter.value, 1));
const buildSmelter = createBuyable(() => ({
resource: metal.metal,
@ -861,10 +874,20 @@ const layer = createLayer(id, function (this: BaseLayer) {
description: "Cocoa Level 3",
enabled: management.elfTraining.oilElfTraining.milestones[2].earned
})),
createMultiplicativeModifier(() => ({
multiplier: 4,
description: "Workshop 1200%",
enabled: workshop.milestones.extraExpansionMilestone6.earned
})),
createMultiplicativeModifier(() => ({
multiplier: () => coalEffectiveness.value,
description: "Effectiveness",
enabled: () => Decimal.lt(coalEffectiveness.value, 1)
})),
createMultiplicativeModifier(() => ({
multiplier: dyes.boosts.red2,
description: "Red Dye",
enabled: dyes.masteryEffectActive
}))
]);
const computedDrillPower = computed(() => drillPower.apply(0));
@ -1079,13 +1102,72 @@ const layer = createLayer(id, function (this: BaseLayer) {
goal: 250000,
name,
day,
color,
background: color,
modal: {
show: showModifiersModal,
display: modifiersModal
}
});
const mastery = {
oil: persistent<DecimalSource>(0),
totalOil: persistent<DecimalSource>(0),
depth: persistent<DecimalSource>(0),
drillProgress: persistent<DecimalSource>(0),
activeHeavy: persistent<DecimalSource>(0),
buildHeavy: { amount: persistent<DecimalSource>(0) },
activeHeavy2: persistent<DecimalSource>(0),
buildHeavy2: { amount: persistent<DecimalSource>(0) },
activeExtractor: persistent<DecimalSource>(0),
buildExtractor: { amount: persistent<DecimalSource>(0) },
activePump: persistent<DecimalSource>(0),
buildPump: { amount: persistent<DecimalSource>(0) },
activeBurner: persistent<DecimalSource>(0),
buildBurner: { amount: persistent<DecimalSource>(0) },
activeSmelter: persistent<DecimalSource>(0),
buildSmelter: { amount: persistent<DecimalSource>(0) },
depthMilestones: [
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) }
],
oilMilestones: [
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) },
{ earned: persistent<boolean>(false) }
],
row1Upgrades: [
{ bought: persistent<boolean>(false) },
{ bought: persistent<boolean>(false) },
{ bought: persistent<boolean>(false) },
{ bought: persistent<boolean>(false) },
{ bought: persistent<boolean>(false) }
],
row2Upgrades: [
{ bought: persistent<boolean>(false) },
{ bought: persistent<boolean>(false) },
{ bought: persistent<boolean>(false) },
{ bought: persistent<boolean>(false) },
{ bought: persistent<boolean>(false) }
],
row3Upgrades: [
{ bought: persistent<boolean>(false) },
{ bought: persistent<boolean>(false) },
{ bought: persistent<boolean>(false) },
{ bought: persistent<boolean>(false) },
{ bought: persistent<boolean>(false) }
]
};
const mastered = persistent<boolean>(false);
const masteryEffectActive = computed(
() => mastered.value || main.currentlyMastering.value?.name === name
);
return {
name,
day,
@ -1104,6 +1186,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
buildExtractor,
activePump,
buildPump,
burnerCoal,
activeBurner,
effectiveBurners,
buildBurner,
@ -1145,6 +1228,17 @@ const layer = createLayer(id, function (this: BaseLayer) {
<>
{render(trackerDisplay)}
<Spacer />
{masteryEffectActive.value ? (
<>
<div class="decoration-effect ribbon">
Decoration effect:
<br />
Remove all negative effects of mining drills and oil machines, and
oil burner produces coal
</div>
<Spacer />
</>
) : null}
{Decimal.lt(coalEffectiveness.value, 1) ? (
<div>
Coal efficiency: {format(Decimal.mul(coalEffectiveness.value, 100))}%
@ -1284,10 +1378,14 @@ const layer = createLayer(id, function (this: BaseLayer) {
minimizedDisplay: jsx(() => (
<div>
{name}{" "}
<span class="desc">{format(oil.value)} {oil.displayName}</span>
<span class="desc">
{format(oil.value)} {oil.displayName}
</span>
</div>
)),
mastery,
mastered,
masteryEffectActive
};
});

View file

@ -16,7 +16,7 @@ import { createUpgrade, GenericUpgrade } from "features/upgrades/upgrade";
import { globalBus } from "game/events";
import { BaseLayer, createLayer } from "game/layers";
import { createMultiplicativeModifier, createSequentialModifier, Modifier } from "game/modifiers";
import { noPersist } from "game/persistence";
import { noPersist, persistent } from "game/persistence";
import Decimal, { DecimalSource, format, formatSmall, formatWhole } from "util/bignum";
import { WithRequired } from "util/common";
import { render, renderCol, renderGrid } from "util/vue";
@ -27,6 +27,7 @@ import dyes from "./dyes";
import elves, { ElfBuyable } from "./elves";
import management from "./management";
import plastic from "./plastic";
import ribbon from "./ribbon";
import trees from "./trees";
import workshop from "./workshop";
import wrappingPaper from "./wrapping-paper";
@ -91,7 +92,8 @@ const layer = createLayer(id, function (this: BaseLayer) {
}
paperConversion.convert();
},
style: "width: 600px; min-height: unset"
style: "width: 600px; min-height: unset",
visibility: () => showIf(!main.isMastery.value || masteryEffectActive.value)
}));
function createBook(
@ -283,12 +285,25 @@ const layer = createLayer(id, function (this: BaseLayer) {
buyableName: "Metal Machines",
visibility: () => showIf(elves.elves.metalElf.bought.value)
});
const dyeBook = createBook({
const primaryDyeBook = createBook({
name: "Arts and Crafts",
elfName: "Carol",
buyableName: "Dyes",
buyableName: "Primary Dyes",
visibility: () => showIf(elves.elves.dyeElf.bought.value)
});
const secondaryDyeBook = createBook({
name: "Natural Dyeing",
elfName: "Carol",
buyableName: "Secondary Dyes",
visibility: () =>
showIf(elves.elves.dyeElf.bought.value && ribbon.milestones.dyeBook.earned.value)
});
const plasticBook = createBook({
name: "One Plastic Bag",
elfName: "Tinsel",
buyableName: "Plastic Buyables",
visibility: () => showIf(plastic.masteryEffectActive.value)
});
const books = {
cuttersBook,
plantersBook,
@ -306,7 +321,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
heavyDrillBook,
oilBook,
metalBook,
dyeBook
primaryDyeBook,
secondaryDyeBook,
plasticBook
};
const sumBooks = computed(() =>
Object.values(books).reduce((acc, curr) => acc.add(curr.amount.value), new Decimal(0))
@ -413,6 +430,11 @@ const layer = createLayer(id, function (this: BaseLayer) {
multiplier: 0.1,
description: "Star Level 2",
enabled: management.elfTraining.paperElfTraining.milestones[1].earned
})),
createMultiplicativeModifier(() => ({
multiplier: 0,
description: "Coal Decoration",
enabled: masteryEffectActive
}))
]) as WithRequired<Modifier, "description" | "revert">;
const computedAshCost = computed(() => ashCost.apply(1e6));
@ -456,9 +478,10 @@ const layer = createLayer(id, function (this: BaseLayer) {
const { total: totalPaper, trackerDisplay } = setUpDailyProgressTracker({
resource: paper,
goal: 5e3,
masteryGoal: 5e7,
name,
day,
color,
background: color,
textColor: "var(--feature-foreground)",
modal: {
show: showModifiersModal,
@ -466,6 +489,46 @@ const layer = createLayer(id, function (this: BaseLayer) {
}
});
const mastery = {
paper: persistent<DecimalSource>(0),
totalPaper: persistent<DecimalSource>(0),
books: {
cuttersBook: { amount: persistent<DecimalSource>(0) },
plantersBook: { amount: persistent<DecimalSource>(0) },
expandersBook: { amount: persistent<DecimalSource>(0) },
heatedCuttersBook: { amount: persistent<DecimalSource>(0) },
heatedPlantersBook: { amount: persistent<DecimalSource>(0) },
fertilizerBook: { amount: persistent<DecimalSource>(0) },
smallFireBook: { amount: persistent<DecimalSource>(0) },
bonfireBook: { amount: persistent<DecimalSource>(0) },
kilnBook: { amount: persistent<DecimalSource>(0) },
paperBook: { amount: persistent<DecimalSource>(0) },
boxBook: { amount: persistent<DecimalSource>(0) },
clothBook: { amount: persistent<DecimalSource>(0) },
coalDrillBook: { amount: persistent<DecimalSource>(0) },
heavyDrillBook: { amount: persistent<DecimalSource>(0) },
oilBook: { amount: persistent<DecimalSource>(0) },
metalBook: { amount: persistent<DecimalSource>(0) },
primaryDyeBook: { amount: persistent<DecimalSource>(0) },
secondaryDyeBook: { amount: persistent<DecimalSource>(0) },
plasticBook: { amount: persistent<DecimalSource>(0) }
},
upgrades: {
clothUpgrade: { bought: persistent<boolean>(false) },
drillingUpgrade: { bought: persistent<boolean>(false) },
oilUpgrade: { bought: persistent<boolean>(false) }
},
upgrades2: {
ashUpgrade: { bought: persistent<boolean>(false) },
bookUpgrade: { bought: persistent<boolean>(false) },
treeUpgrade: { bought: persistent<boolean>(false) }
}
};
const mastered = persistent<boolean>(false);
const masteryEffectActive = computed(
() => mastered.value || main.currentlyMastering.value?.name === name
);
return {
name,
day,
@ -482,21 +545,39 @@ const layer = createLayer(id, function (this: BaseLayer) {
<>
{render(trackerDisplay)}
<Spacer />
{masteryEffectActive.value ? (
<>
<div class="decoration-effect">
Decoration effect:
<br />
Pulp no longer requires ash
</div>
<Spacer />
</>
) : null}
<MainDisplay resource={paper} color={color} style="margin-bottom: 0" />
<Spacer />
{!main.isMastery.value || masteryEffectActive.value ? (
<>
{render(makePaper)}
<Spacer />
{renderGrid(Object.values(upgrades), Object.values(upgrades2))}
<Spacer />
{renderCol(...Object.values(books))}
</>
) : null}
</>
)),
minimizedDisplay: jsx(() => (
<div>
{name}{" "}
<span class="desc">{format(paper.value)} {paper.displayName}</span>
<span class="desc">
{format(paper.value)} {paper.displayName}
</span>
</div>
)),
mastery,
mastered
};
});

View file

@ -28,11 +28,13 @@ import { noPersist, persistent } from "game/persistence";
import Decimal, { DecimalSource, format, formatWhole } from "util/bignum";
import { render, renderCol, renderRow } from "util/vue";
import { computed, ComputedRef, ref, unref } from "vue";
import boxes from "./boxes";
import boxes, { BoxesBuyable } from "./boxes";
import dyes from "./dyes";
import elves from "./elves";
import management from "./management";
import metal from "./metal";
import oil from "./oil";
import dyes from "./dyes";
import management from "./management";
import paper from "./paper";
import workshop from "./workshop";
const id = "plastic";
@ -64,7 +66,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
const activeRefinery = persistent<DecimalSource>(0);
const oilCost = computed(() =>
Decimal.times(activeRefinery.value, 100).times(
management.elfTraining.plasticElfTraining.milestones[2].earned.value
? 0
: Decimal.times(activeRefinery.value, 100).times(
management.elfTraining.oilElfTraining.milestones[3].earned.value ? 5 : 1
)
) as ComputedRef<DecimalSource>;
@ -101,7 +105,8 @@ const layer = createLayer(id, function (this: BaseLayer) {
},
style: {
width: "300px"
}
},
visibility: () => showIf(!main.isMastery.value || masteryEffectActive.value)
})) as GenericBuyable & { resource: Resource };
const {
min: minRefinery,
@ -116,7 +121,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
const upgradeCost = computed(() =>
Decimal.pow(
5,
masteryEffectActive.value ? 4 : 5,
Decimal.add(
[...Object.values(upgrades), ...Object.values(elfUpgrades)].filter(
upg => upg.bought.value
@ -162,7 +167,12 @@ const layer = createLayer(id, function (this: BaseLayer) {
title: "Paper Elf Recruitment",
description: "Double plastic gain and unlock a new elf for training",
showCost: !paperElf.bought.value
})
}),
onPurchase() {
if (masteryEffectActive.value) {
elves.elves.paperElf.bought.value = true;
}
}
})) as GenericUpgrade;
const boxElf = createUpgrade(() => ({
resource: noPersist(plastic),
@ -172,7 +182,12 @@ const layer = createLayer(id, function (this: BaseLayer) {
title: "Box Elf Recruitment",
description: "Double plastic gain and unlock a new elf for training",
showCost: !boxElf.bought.value
})
}),
onPurchase() {
if (masteryEffectActive.value) {
elves.elves.boxElf.bought.value = true;
}
}
})) as GenericUpgrade;
const clothElf = createUpgrade(() => ({
resource: noPersist(plastic),
@ -182,54 +197,116 @@ const layer = createLayer(id, function (this: BaseLayer) {
title: "Cloth Elf Recruitment",
description: "Double plastic gain and unlock a new elf for training",
showCost: !clothElf.bought.value
})
}),
onPurchase() {
if (masteryEffectActive.value) {
elves.elves.clothElf.bought.value = true;
}
}
})) as GenericUpgrade;
const elfUpgrades = { paperElf, boxElf, clothElf };
const passivePaper = createBuyable(() => ({
resource: noPersist(plastic),
cost() {
const amount = this.amount.value;
return Decimal.pow(1.3, amount).times(100);
let v = passivePaper.amount.value;
v = Decimal.pow(0.95, paper.books.plasticBook.totalAmount.value).times(v);
return Decimal.pow(1.3, v).times(100).div(dyes.boosts.blue2.value);
},
inverseCost(x: DecimalSource) {
let v = Decimal.times(x, dyes.boosts.blue2.value).div(100).log(1.3);
v = v.div(Decimal.pow(0.95, paper.books.plasticBook.totalAmount.value));
return Decimal.isNaN(v) ? Decimal.dZero : v.floor().max(0);
},
visibility: () => showIf(paperElf.bought.value),
display: {
title: "Plastic Printing Press",
description: "Gain +1% of your paper gain per second",
effectDisplay: jsx(() => <>{formatWhole(passivePaper.amount.value)}%</>),
effectDisplay: jsx(() => <>{formatWhole(passivePaper.totalAmount.value)}%</>),
showAmount: false
},
freeLevels: computed(() => {
let levels: DecimalSource = 0;
if (management.elfTraining.plasticElfTraining.milestones[1].earned.value) {
levels = Decimal.max(passiveBoxes.amount.value, 1)
.sqrt()
.floor()
.add(Decimal.max(clothGains.amount.value, 1).sqrt().floor());
}
})) as GenericBuyable;
return levels;
}),
totalAmount: computed(() =>
Decimal.add(passivePaper.amount.value, passivePaper.freeLevels.value)
)
})) as BoxesBuyable;
const passiveBoxes = createBuyable(() => ({
resource: noPersist(plastic),
cost() {
const amount = this.amount.value;
return Decimal.pow(1.3, amount).times(100);
let v = passiveBoxes.amount.value;
v = Decimal.pow(0.95, paper.books.plasticBook.totalAmount.value).times(v);
return Decimal.pow(1.3, v).times(100).div(dyes.boosts.blue2.value);
},
inverseCost(x: DecimalSource) {
let v = Decimal.times(x, dyes.boosts.blue2.value).div(100).log(1.3);
v = v.div(Decimal.pow(0.95, paper.books.plasticBook.totalAmount.value));
return Decimal.isNaN(v) ? Decimal.dZero : v.floor().max(0);
},
visibility: () => showIf(boxElf.bought.value),
display: {
title: "Plastic Box Folder",
description: "Gain +1% of your box gain per second",
effectDisplay: jsx(() => <>{formatWhole(passiveBoxes.amount.value)}%</>),
effectDisplay: jsx(() => <>{formatWhole(passiveBoxes.totalAmount.value)}%</>),
showAmount: false
},
freeLevels: computed(() => {
let levels: DecimalSource = 0;
if (management.elfTraining.plasticElfTraining.milestones[1].earned.value) {
levels = Decimal.max(passivePaper.amount.value, 1)
.sqrt()
.floor()
.add(Decimal.max(clothGains.amount.value, 1).sqrt().floor());
}
})) as GenericBuyable;
return levels;
}),
totalAmount: computed(() =>
Decimal.add(passiveBoxes.amount.value, passiveBoxes.freeLevels.value)
)
})) as BoxesBuyable;
const clothGains = createBuyable(() => ({
resource: noPersist(plastic),
cost() {
const amount = this.amount.value;
return Decimal.pow(1.3, amount).times(100);
let v = clothGains.amount.value;
v = Decimal.pow(0.95, paper.books.plasticBook.totalAmount.value).times(v);
return Decimal.pow(1.3, v).times(100).div(dyes.boosts.blue2.value);
},
inverseCost(x: DecimalSource) {
let v = Decimal.times(x, dyes.boosts.blue2.value).div(100).log(1.3);
v = v.div(Decimal.pow(0.95, paper.books.plasticBook.totalAmount.value));
return Decimal.isNaN(v) ? Decimal.dZero : v.floor().max(0);
},
visibility: () => showIf(clothElf.bought.value),
display: {
title: "Plastic Shepherd",
description: "All cloth actions are +10% more efficient",
effectDisplay: jsx(() => (
<>{formatWhole(Decimal.times(clothGains.amount.value, 10))}%</>
<>{formatWhole(Decimal.times(clothGains.totalAmount.value, 10))}%</>
)),
showAmount: false
},
freeLevels: computed(() => {
let levels: DecimalSource = 0;
if (management.elfTraining.plasticElfTraining.milestones[1].earned.value) {
levels = Decimal.max(passivePaper.amount.value, 1)
.sqrt()
.floor()
.add(Decimal.max(passiveBoxes.amount.value, 1).sqrt().floor());
}
})) as GenericBuyable;
return levels;
}),
totalAmount: computed(() =>
Decimal.add(clothGains.amount.value, clothGains.freeLevels.value)
)
})) as BoxesBuyable;
const buyables = { passivePaper, passiveBoxes, clothGains };
const plasticGain = createSequentialModifier(() => [
@ -286,6 +363,16 @@ const layer = createLayer(id, function (this: BaseLayer) {
multiplier: () => Decimal.add(dyes.secondaryDyeSum.value, 1).cbrt(),
description: "Colorful Plastic",
enabled: oil.row3Upgrades[2].bought
})),
createMultiplicativeModifier(() => ({
multiplier: 2,
description: "Tinsel Level 1",
enabled: management.elfTraining.plasticElfTraining.milestones[0].earned
})),
createMultiplicativeModifier(() => ({
multiplier: () => Decimal.div(buildRefinery.amount.value, 100).add(1),
description: "Tinsel Level 4",
enabled: management.elfTraining.plasticElfTraining.milestones[3].earned
}))
]);
const computedPlasticGain = computed(() => plasticGain.apply(0));
@ -303,7 +390,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
goal: 2.5e5,
name,
day,
color,
background: color,
textColor: "var(--feature-foreground)",
modal: {
show: showModifiersModal,
@ -311,8 +398,35 @@ const layer = createLayer(id, function (this: BaseLayer) {
}
});
const mastery = {
plastic: persistent<DecimalSource>(0),
totalPlastic: persistent<DecimalSource>(0),
activeRefinery: persistent<DecimalSource>(0),
buildRefinery: { amount: persistent<DecimalSource>(0) },
upgrades: {
paperTools: { bought: persistent<boolean>(false) },
boxTools: { bought: persistent<boolean>(false) },
clothTools: { bought: persistent<boolean>(false) }
},
elfUpgrades: {
paperElf: { bought: persistent<boolean>(false) },
boxElf: { bought: persistent<boolean>(false) },
clothElf: { bought: persistent<boolean>(false) }
},
buyables: {
passivePaper: { amount: persistent<DecimalSource>(0) },
passiveBoxes: { amount: persistent<DecimalSource>(0) },
clothGains: { amount: persistent<DecimalSource>(0) }
}
};
const mastered = persistent<boolean>(false);
const masteryEffectActive = computed(
() => mastered.value || main.currentlyMastering.value?.name === name
);
return {
name,
day,
color,
plastic,
totalPlastic,
@ -328,6 +442,16 @@ const layer = createLayer(id, function (this: BaseLayer) {
<>
{render(trackerDisplay)}
<Spacer />
{masteryEffectActive.value ? (
<>
<div class="decoration-effect ribbon">
Decoration effect:
<br />
Unlock a new elf for training, and upgrades go up in cost slower
</div>
<Spacer />
</>
) : null}
<MainDisplay
resource={plastic}
color={color}
@ -357,9 +481,14 @@ const layer = createLayer(id, function (this: BaseLayer) {
minimizedDisplay: jsx(() => (
<div>
{name}{" "}
<span class="desc">{format(plastic.value)} {plastic.displayName}</span>
<span class="desc">
{format(plastic.value)} {plastic.displayName}
</span>
</div>
)),
mastery,
mastered,
masteryEffectActive
};
});

276
src/data/layers/ribbon.tsx Normal file
View file

@ -0,0 +1,276 @@
import Spacer from "components/layout/Spacer.vue";
import Modal from "components/Modal.vue";
import { createCollapsibleMilestones, createCollapsibleModifierSections } from "data/common";
import { createBar, GenericBar } from "features/bars/bar";
import { createClickable } from "features/clickables/clickable";
import { softcap } from "features/conversion";
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 { createLayer, layers } from "game/layers";
import { createSequentialModifier } from "game/modifiers";
import { persistent } from "game/persistence";
import player from "game/player";
import { DecimalSource } from "lib/break_eternity";
import Decimal, { format, formatWhole } from "util/bignum";
import { Direction } from "util/common";
import { render } from "util/vue";
import { computed, ref, unref, watchEffect } from "vue";
import { main } from "../projEntry";
import cloth from "./cloth";
import dyes from "./dyes";
import elves from "./elves";
const id = "ribbon";
const day = 16;
const layer = createLayer(id, () => {
const name = "Ribbon";
const color = "darkred";
const ribbon = createResource<DecimalSource>(0, "Ribbon");
const currentDyeCost = computed(() =>
Decimal.times(
softcap(ribbon.value, 10, 2),
[dyes.dyes.orange, dyes.dyes.green, dyes.dyes.purple].includes(currentDyeType.value)
? 2e6
: 1e13
)
);
const currentDyeType = computed(
() => Object.values(dyes.dyes)[new Decimal(ribbon.value).toNumber() % 6]
);
const ribbonProgress = persistent<DecimalSource>(0);
const ribbonProgressBar = 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(ribbonProgress.value, computedRibbonCooldown.value)
}));
const makeRibbon = createClickable(() => ({
display: {
title: "Make Ribbon",
description: jsx(() => (
<>
Create another ribbon with {format(currentDyeCost.value)}{" "}
{currentDyeType.value.name} and {format(1e9)} {cloth.cloth.displayName}
<br />
{render(ribbonProgressBar)}
</>
))
},
style: {
minHeight: "80px"
},
canClick: () =>
Decimal.gte(ribbonProgress.value, computedRibbonCooldown.value) &&
Decimal.gte(currentDyeType.value.amount.value, currentDyeCost.value) &&
Decimal.gte(cloth.cloth.value, 1e9),
onClick() {
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);
ribbonProgress.value = 0;
}
}));
const ribbonCooldown = createSequentialModifier(() => []);
const computedRibbonCooldown = computed(() => ribbonCooldown.apply(10));
const [generalTab, generalTabCollapsed] = createCollapsibleModifierSections(() => [
{
title: "Ribbon Cooldown",
modifier: ribbonCooldown,
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
}}
/>
));
const secondaryDyeElf = createMilestone(() => ({
display: {
requirement: "5 Ribbons",
effectDisplay: "Carol will now mix secondary dyes for you"
},
shouldEarn: () => Decimal.gte(ribbon.value, 5)
}));
const dyeBook = createMilestone(() => ({
display: {
requirement: "10 Ribbons",
effectDisplay: "Unlock a new book"
},
shouldEarn: () => Decimal.gte(ribbon.value, 10),
visibility: () => showIf(secondaryDyeElf.earned.value)
}));
const milestones = { secondaryDyeElf, dyeBook };
const { collapseMilestones, display: milestonesDisplay } =
createCollapsibleMilestones(milestones);
const masteryReq = computed(() =>
Decimal.sub(main.masteredDays.value, 5).times(
Decimal.sub(main.masteredDays.value, 4).div(2)
)
);
const enterMasteryButton = createClickable(() => ({
display: () => ({
title: `${main.isMastery.value ? "Stop Decorating" : "Begin Decorating"} ${
Object.values(layers).find(
layer =>
unref((layer as any).mastered) === false &&
!["Elves", "Management"].includes(unref(layer?.name ?? ""))
)?.name
}`,
description: jsx(() => {
return (
<>
<br />
Decorating brings you to a separate version of each day that only allows
layers that are decorated or being decorated to work. These days will have a
new decoration effect that applies outside of decorating as well.
<br />
You can safely start and stop decorating without losing progress
{main.isMastery.value ? null : (
<>
<br />
<br />
Requires {formatWhole(masteryReq.value)} total ribbons
</>
)}
</>
);
})
}),
visibility: () => showIf(main.day.value === day),
canClick() {
return main.isMastery.value || Decimal.gte(ribbon.value, masteryReq.value);
},
onClick() {
if (!unref(enterMasteryButton.canClick)) {
return;
}
main.toggleMastery();
const layer = main.currentlyMastering.value?.id ?? "trees";
if (!player.tabs.includes(layer)) {
main.openDay(layer);
}
if (layer === "cloth") {
elves.elves.plasticElf.bought.value = true;
} else if (layer === "letters") {
elves.elves.coalDrillElf.bought.value = true;
elves.elves.heavyDrillElf.bought.value = true;
elves.elves.oilElf.bought.value = true;
elves.elves.metalElf.bought.value = true;
}
},
style: {
width: "300px",
minHeight: "160px"
}
}));
const dayProgress = createBar(() => ({
direction: Direction.Right,
width: 600,
height: 25,
fillStyle: "animation: 15s ribbon-bar linear infinite",
progress: () => (main.day.value === day ? Decimal.div(main.masteredDays.value - 6, 5) : 1),
display: jsx(() =>
main.day.value === day ? (
<>
{main.masteredDays.value - 6}
/5 days decorated
</>
) : (
""
)
)
})) as GenericBar;
watchEffect(() => {
if (
main.day.value === day &&
Decimal.gte(main.masteredDays.value, 11) &&
main.showLoreModal.value === false
) {
main.completeDay();
}
});
globalBus.on("update", diff => {
if (Decimal.lt(main.day.value, day)) {
return;
}
if (Decimal.gte(ribbonProgress.value, computedRibbonCooldown.value)) {
ribbonProgress.value = computedRibbonCooldown.value;
} else {
ribbonProgress.value = Decimal.add(ribbonProgress.value, diff);
if (makeRibbon.isHolding.value) {
makeRibbon.onClick();
}
}
});
return {
name,
day,
color,
ribbon,
ribbonProgress,
milestones,
collapseMilestones,
generalTabCollapsed,
display: jsx(() => {
return (
<div style="width: 620px">
<div>
{main.day.value === day
? `Decorate 5 previous days 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={ribbon} color={color} />
{render(makeRibbon)}
<Spacer />
{render(enterMasteryButton)}
<Spacer />
{render(milestonesDisplay)}
</div>
);
}),
minWidth: 700
};
});
export default layer;

View file

@ -0,0 +1,65 @@
@keyframes letters-bar {
from {
background: 0 0 / auto 70% no-repeat linear-gradient(white, white), 0 0 / 113px 113px repeat
repeating-linear-gradient(-45deg,
red 0 20px, white 20px 40px,
blue 40px 60px, white 60px 80px
);
}
to {
background: 0 0 / auto 70% no-repeat linear-gradient(white, white), 113px 0px / 113px 113px repeat
repeating-linear-gradient(-45deg,
red 0 20px, white 20px 40px,
blue 40px 60px, white 60px 80px
);
}
}
@keyframes wrapping-paper-bar {
from {
background: 0 0 / 113px 113px repeat repeating-linear-gradient(-45deg,
rgb(255, 76, 76) 0 10px,
rgb(255, 255, 255) 10px 20px,
rgb(65, 255, 95) 20px 30px,
rgb(255, 255, 255) 30px 40px
);
}
to {
background: 113px 0 / 113px 113px repeat repeating-linear-gradient(-45deg,
rgb(255, 76, 76) 0 10px,
rgb(255, 255, 255) 10px 20px,
rgb(65, 255, 95) 20px 30px,
rgb(255, 255, 255) 30px 40px
);
}
}
@keyframes ribbon-bar {
from {
background: 0 0 / 114px 114px repeat repeating-linear-gradient(-45deg,
darkred 0 10px,
#af0000 10px 20px
);
}
to {
background: 114px 0px / 114px 114px repeat repeating-linear-gradient(-45deg,
darkred 0 10px,
#af0000 10px 20px
);
}
}
@keyframes toys-bar {
from {
background: 0 0 / 114px 114px repeat repeating-linear-gradient(-45deg,
#4bdc13 0 10px,
green 10px 20px
);
}
to {
background: 114px 0px / 114px 114px repeat repeating-linear-gradient(-45deg,
#4bdc13 0 10px,
green 10px 20px
);
}
}

View file

@ -1,21 +1,21 @@
@keyframes focused-focus-bar {
from {
background: 0 0 / 28px 28px repeat
repeating-linear-gradient(-45deg, red, red 10px, green 10px, green 20px);
repeating-linear-gradient(-45deg, red 0 10px, green 10px 20px);
}
to {
background: 28px 0px / 28px 28px repeat
repeating-linear-gradient(-45deg, red, red 10px, green 10px, green 20px);
repeating-linear-gradient(-45deg, red 0 10px, green 10px 20px);
}
}
@keyframes focused-xp-bar {
from {
background: 0 0 / 28px 28px repeat
repeating-linear-gradient(-45deg, yellow, yellow 10px, lime 10px, lime 20px);
repeating-linear-gradient(-45deg, yellow 0 10px, lime 10px 20px);
}
to {
background: 28px 0px / 28px 28px repeat
repeating-linear-gradient(-45deg, yellow, yellow 10px, lime 10px, lime 20px);
repeating-linear-gradient(-45deg, yellow 0 10px, lime 10px 20px);
}
}

325
src/data/layers/toys.tsx Normal file
View file

@ -0,0 +1,325 @@
/**
* @module
* @hidden
*/
import Spacer from "components/layout/Spacer.vue";
import Modal from "components/Modal.vue";
import { main } from "data/projEntry";
import { createBar } from "features/bars/bar";
import {
createCollapsibleMilestones,
createCollapsibleModifierSections,
setUpDailyProgressTracker
} from "data/common";
import { createBuyable, GenericBuyable } from "features/buyable";
import { createClickable } from "features/clickables/clickable";
import { jsx, showIf } from "features/feature";
import { createHotkey } from "features/hotkey";
import MainDisplay from "features/resources/MainDisplay.vue";
import { createMilestone } from "features/milestones/milestone";
import { createResource, Resource } from "features/resources/resource";
import { createUpgrade } from "features/upgrades/upgrade";
import { globalBus } from "game/events";
import { BaseLayer, createLayer } from "game/layers";
import {
createAdditiveModifier,
createExponentialModifier,
createMultiplicativeModifier,
createSequentialModifier,
Modifier
} from "game/modifiers";
import { noPersist, persistent } from "game/persistence";
import Decimal, { DecimalSource, format, formatGain, formatLimit, formatWhole } from "util/bignum";
import { Direction, WithRequired } from "util/common";
import { render, renderGrid, renderRow } from "util/vue";
import { computed, ref } from "vue";
import metal from "./metal";
import plastic from "./plastic";
import cloth from "./cloth";
import trees from "./trees";
import dyes from "./dyes";
import paper from "./paper";
import workshop from "./workshop";
const id = "toys";
const day = 17;
const layer = createLayer(id, function (this: BaseLayer) {
const name = "Toys";
const colorBright = "#4BDC13";
const colorDark = "green";
const clothes = createResource<DecimalSource>(0, "clothes");
const woodenBlocks = createResource<DecimalSource>(0, " wooden blocks");
const trucks = createResource<DecimalSource>(0, "trucks");
const toyGain = createSequentialModifier(() => []);
const toySum = createResource(
computed(() => Decimal.add(clothes.value, woodenBlocks.value).add(trucks.value)),
"toys"
);
const clothesCost = computed(() => {
let clothFactor = Decimal.add(1, clothesBuyable.amount.value);
if (milestones.milestone1.earned) {
clothFactor = clothFactor.div(
Decimal.div(workshop.foundationProgress.value, 100).floor()
);
}
return {
cloth: clothFactor.mul(1e8),
dye: clothFactor.mul(1e6)
};
});
const clothesBuyable = createBuyable(() => ({
display: jsx(() => (
<>
<h3>Make Clothes</h3>
<div>Click this buyable to make some clothes!</div>
<div>You have {formatWhole(clothes.value)} clothes.</div>
<div>
Costs {format(clothesCost.value.cloth)} cloth and requires{" "}
{format(clothesCost.value.dye)} of red, yellow, and blue dye
</div>
</>
)),
canPurchase(): boolean {
return (
clothesCost.value.cloth.lte(cloth.cloth.value) &&
clothesCost.value.dye.lte(dyes.dyes.blue.amount.value) &&
clothesCost.value.dye.lte(dyes.dyes.red.amount.value) &&
clothesCost.value.dye.lte(dyes.dyes.yellow.amount.value)
);
},
onPurchase() {
cloth.cloth.value = Decimal.sub(cloth.cloth.value, clothesCost.value.cloth);
this.amount.value = Decimal.add(this.amount.value, 1);
clothes.value = this.amount.value;
}
})) as GenericBuyable;
const woodenBlocksCost = computed(() => {
let woodFactor = Decimal.add(1, woodenBlocksBuyable.amount.value).pow(5);
if (milestones.milestone1.earned) {
woodFactor = woodFactor.div(
Decimal.div(workshop.foundationProgress.value, 100).floor()
);
}
return {
wood: woodFactor.mul(1e40)
};
});
const woodenBlocksBuyable = createBuyable(() => ({
display: jsx(() => (
<>
<h3>Make Wooden Blocks</h3>
<div>Click this buyable to make some wooden blocks!</div>
<div>You have {formatWhole(woodenBlocks.value)} wooden blocks.</div>
<div>Costs {format(woodenBlocksCost.value.wood)} logs</div>
</>
)),
canPurchase(): boolean {
return woodenBlocksCost.value.wood.lte(trees.logs.value);
},
onPurchase() {
trees.logs.value = Decimal.sub(trees.logs.value, woodenBlocksCost.value.wood);
this.amount.value = Decimal.add(this.amount.value, 1);
woodenBlocks.value = this.amount.value;
}
})) as GenericBuyable;
const trucksCost = computed(() => {
let factor = Decimal.add(1, trucksBuyable.amount.value).pow(3);
let plasticFactor = Decimal.add(1, trucksBuyable.amount.value);
if (milestones.milestone1.earned) {
factor = factor.div(Decimal.div(workshop.foundationProgress.value, 100).floor());
plasticFactor = plasticFactor.div(
Decimal.div(workshop.foundationProgress.value, 100).floor()
);
}
return {
metal: factor.mul(1e25),
plastic: plasticFactor.mul(1e10)
};
});
const trucksBuyable = createBuyable(() => ({
display: jsx(() => (
<>
<h3>Make Trucks</h3>
<div>Click this buyable to make some trucks!</div>
<div>You have {formatWhole(trucks.value)} trucks.</div>
<div>
Costs {format(trucksCost.value.metal)} metal and{" "}
{format(trucksCost.value.plastic)} plastic
</div>
</>
)),
canPurchase(): boolean {
return (
trucksCost.value.metal.lte(metal.metal.value) &&
trucksCost.value.plastic.lte(plastic.plastic.value)
);
},
onPurchase() {
metal.metal.value = Decimal.sub(metal.metal.value, trucksCost.value.metal);
plastic.plastic.value = Decimal.sub(plastic.plastic.value, trucksCost.value.plastic);
this.amount.value = Decimal.add(this.amount.value, 1);
trucks.value = this.amount.value;
}
})) as GenericBuyable;
const buyables = [clothesBuyable, woodenBlocksBuyable, trucksBuyable];
const trucksUpgrade1 = createUpgrade(() => ({
resource: noPersist(trucks),
cost: 10,
display: {
title: "Load logs onto trucks",
description: "Log gain is doubled."
}
}));
const clothesUpgrade1 = createUpgrade(() => ({
resource: noPersist(clothes),
cost: 30,
display: {
title: "Give elves clothes to wear",
description:
"Multiply ore per mining operation and auto-smelt purity by the number of clothes you have."
}
}));
const woodenBlocksUpgrade1 = createUpgrade(() => ({
resource: noPersist(woodenBlocks),
cost: 15,
display: {
title: "Build wooden towers",
description: "You can now build 2 extra tall workshops!"
}
}));
const row1Upgrades = [trucksUpgrade1, clothesUpgrade1, woodenBlocksUpgrade1];
const milestone1 = createMilestone(() => ({
display: {
requirement: "10 toys",
effectDisplay:
"The cost of making toys is divided by the number of complete workshops you have."
},
shouldEarn: () => Decimal.gte(toySum.value, 10)
}));
const milestone2 = createMilestone(() => ({
display: {
requirement: "100 toys",
effectDisplay: "Unlock black dyes."
},
shouldEarn: () => Decimal.gte(toySum.value, 100)
}));
const milestones = { milestone1, milestone2 };
const { collapseMilestones, display: milestonesDisplay } =
createCollapsibleMilestones(milestones);
const [generalTab, generalTabCollapsed] = createCollapsibleModifierSections(() => [
{
title: `Toy Gain`,
modifier: toyGain,
base: 1,
visible: true
}
]);
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.lt(clothes.value, clothesBuyable.amount.value)) {
clothesBuyable.amount.value = clothes.value;
}
if (Decimal.lt(woodenBlocks.value, woodenBlocksBuyable.amount.value)) {
woodenBlocksBuyable.amount.value = woodenBlocks.value;
}
if (Decimal.lt(trucks.value, trucksBuyable.amount.value)) {
trucksBuyable.amount.value = trucks.value;
}
});
const { total: totalToys, trackerDisplay } = setUpDailyProgressTracker({
resource: toySum,
goal: 200,
name,
day,
background: {
gradient: "toys-bar",
duration: "15s"
},
modal: {
show: showModifiersModal,
display: modifiersModal
}
});
return {
name,
day,
color: colorBright,
clothes,
woodenBlocks,
trucks,
toySum,
totalToys,
buyables,
row1Upgrades,
milestones,
generalTabCollapsed,
collapseMilestones,
minWidth: 700,
display: jsx(() => (
<>
{render(trackerDisplay)}
<Spacer />
<MainDisplay
resource={clothes}
color={colorBright}
style="margin-bottom: 0"
productionDisplay={undefined}
/>
<MainDisplay
resource={woodenBlocks}
color={colorDark}
style="margin-bottom: 0"
productionDisplay={undefined}
/>
<MainDisplay
resource={trucks}
color={colorDark}
style="margin-bottom: 0"
productionDisplay={undefined}
/>
<Spacer />
{renderRow(...buyables)}
<Spacer />
{renderGrid(row1Upgrades)}
<Spacer />
{milestonesDisplay()}
</>
)),
minimizedDisplay: jsx(() => (
<div>
{name} - {format(toySum.value)} {"total toys"}
</div>
))
};
});
export default layer;

View file

@ -2,17 +2,18 @@
* @module
* @hidden
*/
import HotkeyVue from "components/Hotkey.vue";
import Spacer from "components/layout/Spacer.vue";
import Modal from "components/Modal.vue";
import { createCollapsibleModifierSections, setUpDailyProgressTracker } from "data/common";
import { main } from "data/projEntry";
import { createBar } from "features/bars/bar";
import { createBuyable, GenericBuyable } from "features/buyable";
import { createBuyable } from "features/buyable";
import { createClickable } from "features/clickables/clickable";
import { jsx, showIf } from "features/feature";
import { createHotkey } from "features/hotkey";
import MainDisplay from "features/resources/MainDisplay.vue";
import { createResource, Resource } from "features/resources/resource";
import { createResource, Resource, trackTotal } from "features/resources/resource";
import { createUpgrade } from "features/upgrades/upgrade";
import { globalBus } from "game/events";
import { BaseLayer, createLayer } from "game/layers";
@ -37,6 +38,7 @@ import management from "./management";
import paper from "./paper";
import workshop from "./workshop";
import wrappingPaper from "./wrapping-paper";
import toys from "./toys";
const id = "trees";
const day = 1;
@ -51,8 +53,9 @@ const layer = createLayer(id, function (this: BaseLayer) {
const logs = createResource<DecimalSource>(0, "logs");
// Think of saplings as spent trees
const saplings = createResource<DecimalSource>(0, "saplings");
const createdSaplings = persistent<DecimalSource>(0);
const ema = ref<DecimalSource>(0);
const averageLogGain = ref<DecimalSource>(0);
const lastAutoCuttingAmount = ref<DecimalSource>(0);
const lastAutoPlantedAmount = ref<DecimalSource>(0);
@ -92,6 +95,11 @@ const layer = createLayer(id, function (this: BaseLayer) {
addend: () => Decimal.pow(computedManualCuttingAmount.value, 0.99),
description: "Hope Level 1",
enabled: management.elfTraining.expandersElfTraining.milestones[0].earned
})),
createAdditiveModifier(() => ({
addend: createdSaplings,
description: "Trees Decoration",
enabled: masteryEffectActive
}))
]) as WithRequired<Modifier, "description" | "revert">;
const trees = createResource(
@ -396,7 +404,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
createAdditiveModifier(() => ({
addend: 1,
description: "Automated Spade",
enabled: autoPlantUpgrade1.bought
enabled: autoPlantUpgrade1.bought.value
})),
createAdditiveModifier(() => ({
addend: () => Decimal.div(autoPlantingBuyable1.amount.value, 2),
@ -528,6 +536,16 @@ const layer = createLayer(id, function (this: BaseLayer) {
description: "Christmas Wrapping Paper",
enabled: computed(() => Decimal.gt(wrappingPaper.boosts.christmas1.value, 1))
})),
createMultiplicativeModifier(() => ({
multiplier: () => Decimal.add(computedTotalTrees.value, 1).log10(),
description: "Trees Decoration",
enabled: masteryEffectActive
})),
createMultiplicativeModifier(() => ({
multiplier: 2,
description: "Load logs onto trucks",
enabled: toys.row1Upgrades[0].bought
})),
createExponentialModifier(() => ({
exponent: 1.2,
description: "100% Foundation Completed",
@ -553,7 +571,11 @@ const layer = createLayer(id, function (this: BaseLayer) {
const cutTree = createClickable(() => ({
display: {
title: "Cut trees",
title: jsx(() => (
<h3>
Cut trees <HotkeyVue hotkey={cutTreeHK} />
</h3>
)),
description: jsx(() => (
<>
Cut down up to {formatWhole(Decimal.floor(computedManualCuttingAmount.value))}{" "}
@ -585,9 +607,14 @@ const layer = createLayer(id, function (this: BaseLayer) {
).floor()
)
)
);
).max(0);
if (masteryEffectActive.value) {
createdSaplings.value = Decimal.add(createdSaplings.value, amount).max(0);
}
logs.value = Decimal.add(logs.value, Decimal.times(logGain.apply(1), amount));
saplings.value = Decimal.add(saplings.value, amount);
saplings.value = Decimal.mul(amount, masteryEffectActive.value ? 2 : 1).add(
saplings.value
);
manualCutProgress.value = 0;
}
}));
@ -604,7 +631,11 @@ const layer = createLayer(id, function (this: BaseLayer) {
}));
const plantTree = createClickable(() => ({
display: {
title: "Plant trees",
title: jsx(() => (
<h3>
Plant trees <HotkeyVue hotkey={plantTreeHK} />
</h3>
)),
description: jsx(() => (
<>
Plant up to {formatWhole(Decimal.floor(computedManualPlantingAmount.value))}{" "}
@ -636,7 +667,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
).floor()
)
)
);
).max(0);
saplings.value = Decimal.sub(saplings.value, amount);
manualPlantProgress.value = 0;
}
@ -688,14 +719,14 @@ const layer = createLayer(id, function (this: BaseLayer) {
title: `Auto Planting Amount`,
modifier: autoPlantingAmount,
base: 0,
visible: autoPlantUpgrade1.bought,
visible: autoCutUpgrade1.bought,
unit: "/s"
},
{
title: `Forest Size`,
modifier: totalTrees,
base: 10,
visible: researchUpgrade2.bought
visible: () => researchUpgrade2.bought.value || masteryEffectActive.value
}
]);
const showModifiersModal = ref(false);
@ -746,27 +777,32 @@ const layer = createLayer(id, function (this: BaseLayer) {
const amountCut = Decimal.min(
trees.value,
Decimal.times(computedAutoCuttingAmount.value, diff)
);
).max(0);
const logsGained = Decimal.mul(logGain.apply(1), amountCut);
const effectiveLogsGained = Decimal.div(logsGained, diff);
ema.value = Decimal.mul(effectiveLogsGained, SMOOTHING_FACTOR).add(
Decimal.mul(ema.value, Decimal.dOne.sub(SMOOTHING_FACTOR))
averageLogGain.value = Decimal.mul(effectiveLogsGained, SMOOTHING_FACTOR).add(
Decimal.mul(averageLogGain.value, Decimal.dOne.sub(SMOOTHING_FACTOR))
);
logs.value = Decimal.add(logs.value, logsGained);
saplings.value = Decimal.add(saplings.value, amountCut);
saplings.value = Decimal.mul(amountCut, masteryEffectActive.value ? 2 : 1).add(
saplings.value
);
if (masteryEffectActive.value) {
createdSaplings.value = Decimal.add(createdSaplings.value, amountCut);
}
const amountPlanted = Decimal.min(
saplings.value,
Decimal.times(computedAutoPlantingAmount.value, diff)
);
).max(0);
saplings.value = Decimal.sub(saplings.value, amountPlanted);
if(Decimal.gte(saplings.value, computedTotalTrees.value)) saplings.value = computedTotalTrees.value;
});
const netSaplingGain = computed(() =>
Decimal.sub(computedAutoCuttingAmount.value, computedAutoPlantingAmount.value)
Decimal.sub(
Decimal.mul(computedAutoCuttingAmount.value, mastered.value ? 2 : 1),
computedAutoPlantingAmount.value
)
);
const netTreeGain = computed(() =>
Decimal.sub(computedAutoPlantingAmount.value, computedAutoCuttingAmount.value)
@ -774,17 +810,19 @@ const layer = createLayer(id, function (this: BaseLayer) {
const cutTreeHK = createHotkey(() => ({
key: "c",
description: 'Press the "Cut trees" button.',
description: "Cut trees",
onPress: () => {
if (cutTree.canClick.value) cutTree.onClick();
}
},
enabled: main.days[day - 1].opened
}));
const plantTreeHK = createHotkey(() => ({
key: "p",
description: 'Press the "Plant trees" button.',
description: "Plant trees",
onPress: () => {
if (plantTree.canClick.value) plantTree.onClick();
}
},
enabled: main.days[day - 1].opened
}));
const { total: totalLogs, trackerDisplay } = setUpDailyProgressTracker({
@ -792,20 +830,52 @@ const layer = createLayer(id, function (this: BaseLayer) {
goal: 1e4,
name,
day,
color: colorDark,
background: colorDark,
modal: {
show: showModifiersModal,
display: modifiersModal
}
});
const mastery = {
logs: persistent<DecimalSource>(0),
totalLogs: persistent<DecimalSource>(0),
saplings: persistent<DecimalSource>(0),
createdSaplings: persistent<DecimalSource>(0),
row1Upgrades: [
{ bought: persistent<boolean>(false) },
{ bought: persistent<boolean>(false) },
{ bought: persistent<boolean>(false) },
{ bought: persistent<boolean>(false) },
{ bought: persistent<boolean>(false) }
],
row2Upgrades: [
{ bought: persistent<boolean>(false) },
{ bought: persistent<boolean>(false) },
{ bought: persistent<boolean>(false) },
{ bought: persistent<boolean>(false) },
{ bought: persistent<boolean>(false) }
],
row1Buyables: [
{ amount: persistent<DecimalSource>(0) },
{ amount: persistent<DecimalSource>(0) },
{ amount: persistent<DecimalSource>(0) }
]
};
const mastered = persistent<boolean>(false);
const masteryEffectActive = computed(
() => mastered.value || main.currentlyMastering.value?.name === name
);
return {
name,
day,
color: colorBright,
logs,
totalLogs,
trees,
saplings,
createdSaplings,
cutTree,
plantTree,
cutTreeHK,
@ -822,13 +892,25 @@ const layer = createLayer(id, function (this: BaseLayer) {
<>
{render(trackerDisplay)}
<Spacer />
{masteryEffectActive.value ? (
<>
<div class="decoration-effect">
Decoration effect:
<br />
Trees drop 2 saplings, and forest size increases log gain
</div>
<Spacer />
</>
) : null}
<MainDisplay
resource={logs}
color={colorBright}
style="margin-bottom: 0"
productionDisplay={
Decimal.gt(computedAutoCuttingAmount.value, 0)
? `+${format(ema.value)}/s average<br/>equilibrium: +${formatLimit(
? `+${format(
averageLogGain.value
)}/s average<br/>equilibrium: +${formatLimit(
[
[computedAutoCuttingAmount.value, "cutting speed"],
[computedAutoPlantingAmount.value, "planting speed"],
@ -864,9 +946,13 @@ const layer = createLayer(id, function (this: BaseLayer) {
minimizedDisplay: jsx(() => (
<div>
{name}{" "}
<span class="desc">{format(logs.value)} {logs.displayName}</span>
<span class="desc">
{format(logs.value)} {logs.displayName}
</span>
</div>
)),
mastery,
mastered
};
});

View file

@ -2,6 +2,7 @@
* @module
* @hidden
*/
import HotkeyVue from "components/Hotkey.vue";
import Spacer from "components/layout/Spacer.vue";
import { createCollapsibleMilestones } from "data/common";
import { main } from "data/projEntry";
@ -23,8 +24,8 @@ import {
createMultiplicativeModifier,
createSequentialModifier
} from "game/modifiers";
import { noPersist } from "game/persistence";
import Decimal, { DecimalSource, format, formatWhole } from "util/bignum";
import { noPersist, persistent } from "game/persistence";
import Decimal, { DecimalSource, formatWhole } from "util/bignum";
import { Direction } from "util/common";
import { render } from "util/vue";
import { computed, unref, watchEffect } from "vue";
@ -32,6 +33,7 @@ import elves from "./elves";
import management from "./management";
import trees from "./trees";
import wrappingPaper from "./wrapping-paper";
import toys from "./toys";
const id = "workshop";
const day = 2;
@ -47,7 +49,11 @@ const layer = createLayer(id, function (this: BaseLayer) {
scaling: addHardcap(
addSoftcap(addSoftcap(createPolynomialScaling(250, 1.5), 5423, 1 / 1e10), 1e20, 3e8),
computed(() =>
management.elfTraining.expandersElfTraining.milestones[2].earned.value ? 1000 : 100
toys.row1Upgrades[2].bought
? 1200
: management.elfTraining.expandersElfTraining.milestones[2].earned.value
? 1000
: 100
)
),
baseResource: trees.logs,
@ -55,18 +61,19 @@ const layer = createLayer(id, function (this: BaseLayer) {
roundUpCost: true,
// buyMax: management.elfTraining.expandersElfTraining.milestones[2].earned,
spend(gain, spent) {
if (masteryEffectActive.value) return;
trees.logs.value = Decimal.sub(trees.logs.value, spent);
},
costModifier: createSequentialModifier(() => [
createMultiplicativeModifier(() => ({
multiplier: computed(() => wrappingPaper.boosts.beach1.value),
multiplier: wrappingPaper.boosts.beach1,
description: "Beach Wrapping Paper",
enabled: computed(() => Decimal.gt(wrappingPaper.boosts.beach1.value, 1))
})),
createExponentialModifier(() => ({
exponent: 1 / 0.99,
description: "Hope Level 5",
enabled: management.elfTraining.expandersElfTraining.milestones[4].earned
description: "Holly Level 5",
enabled: management.elfTraining.cutterElfTraining.milestones[4].earned
}))
])
}));
@ -75,12 +82,13 @@ const layer = createLayer(id, function (this: BaseLayer) {
display: jsx(() => (
<>
<b style="font-size: x-large">
Build {formatWhole(foundationConversion.actualGain.value)}% of the foundation
Build {formatWhole(foundationConversion.actualGain.value)}% of the foundation{" "}
<HotkeyVue hotkey={buildFoundationHK} />
</b>
<br />
<br />
<span style="font-size: large">
Cost:{" "}
{masteryEffectActive.value ? "Requirement" : "Cost"}:{" "}
{displayResource(
trees.logs,
Decimal.gte(foundationConversion.actualGain.value, 1)
@ -95,17 +103,32 @@ const layer = createLayer(id, function (this: BaseLayer) {
showIf(
Decimal.lt(
foundationProgress.value,
management.elfTraining.expandersElfTraining.milestones[2].earned.value
toys.row1Upgrades[2].bought.value
? 1200
: management.elfTraining.expandersElfTraining.milestones[2].earned.value
? 1000
: 100
)
),
canClick: () =>
Decimal.gte(trees.logs.value, foundationConversion.nextAt.value) &&
Decimal.lt(
foundationProgress.value,
management.elfTraining.expandersElfTraining.milestones[2].earned.value ? 1000 : 100
),
canClick: () => {
if (Decimal.lt(trees.logs.value, foundationConversion.nextAt.value)) {
return false;
}
if (main.isMastery.value && main.currentlyMastering.value?.name === "Trees") {
return false;
}
let cap = 100;
if (management.elfTraining.expandersElfTraining.milestones[2].earned.value) {
cap = 1000;
}
if (toys.row1Upgrades[2].bought.value) {
cap = 1200;
}
if (Decimal.gte(foundationProgress.value, cap)) {
return false;
}
return true;
},
onClick() {
if (!unref(this.canClick)) {
return;
@ -117,10 +140,11 @@ const layer = createLayer(id, function (this: BaseLayer) {
const buildFoundationHK = createHotkey(() => ({
key: "w",
description: "Build part of the foundation.",
description: "Build foundation",
onPress: () => {
if (buildFoundation.canClick.value) buildFoundation.onClick();
}
},
enabled: main.days[day - 1].opened
}));
const shouldShowPopups = computed(() => !elves.milestones[6].earned.value);
@ -261,6 +285,16 @@ const layer = createLayer(id, function (this: BaseLayer) {
),
showPopups: shouldShowPopups
}));
const extraExpansionMilestone6 = createMilestone(() => ({
display: {
requirement: "1200% Foundation Completed",
effectDisplay: "Quadruple oil gain"
},
shouldEarn: () => Decimal.gte(foundationProgress.value, 1200),
visibility: () =>
showIf(extraExpansionMilestone5.earned.value && toys.row1Upgrades[2].bought.value),
showPopups: shouldShowPopups
}));
const milestones = {
logGainMilestone1,
autoCutMilestone1,
@ -274,7 +308,8 @@ const layer = createLayer(id, function (this: BaseLayer) {
extraExpansionMilestone2,
extraExpansionMilestone3,
extraExpansionMilestone4,
extraExpansionMilestone5
extraExpansionMilestone5,
extraExpansionMilestone6
};
const { collapseMilestones, display: milestonesDisplay } =
createCollapsibleMilestones(milestones);
@ -284,18 +319,53 @@ const layer = createLayer(id, function (this: BaseLayer) {
width: 600,
height: 25,
fillStyle: `backgroundColor: ${colorDark}`,
progress: () => (main.day.value === day ? Decimal.div(foundationProgress.value, 100) : 1),
progress: () =>
main.day.value === day || main.currentlyMastering.value?.name === name
? Decimal.div(foundationProgress.value, 100)
: 1,
display: jsx(() =>
main.day.value === day ? <>{formatWhole(foundationProgress.value)}%</> : ""
main.day.value === day || main.currentlyMastering.value?.name === name ? (
<>{formatWhole(foundationProgress.value)}%</>
) : (
""
)
)
}));
watchEffect(() => {
if (main.day.value === day && Decimal.gte(foundationProgress.value, 100)) {
main.completeDay();
} else if (
main.currentlyMastering.value?.name === name &&
Decimal.gte(foundationProgress.value, 100)
) {
main.completeMastery();
}
});
const mastery = {
foundationProgress: persistent<DecimalSource>(0),
milestones: {
logGainMilestone1: { earned: persistent<boolean>(false) },
autoCutMilestone1: { earned: persistent<boolean>(false) },
autoPlantMilestone1: { earned: persistent<boolean>(false) },
autoCutMilestone2: { earned: persistent<boolean>(false) },
autoPlantMilestone2: { earned: persistent<boolean>(false) },
logGainMilestone2: { earned: persistent<boolean>(false) },
morePlantsMilestone1: { earned: persistent<boolean>(false) },
logGainMilestone3: { earned: persistent<boolean>(false) },
extraExpansionMilestone1: { earned: persistent<boolean>(false) },
extraExpansionMilestone2: { earned: persistent<boolean>(false) },
extraExpansionMilestone3: { earned: persistent<boolean>(false) },
extraExpansionMilestone4: { earned: persistent<boolean>(false) },
extraExpansionMilestone5: { earned: persistent<boolean>(false) }
}
};
const mastered = persistent<boolean>(false);
const masteryEffectActive = computed(
() => mastered.value || main.currentlyMastering.value?.name === name
);
return {
name,
day,
@ -311,10 +381,22 @@ const layer = createLayer(id, function (this: BaseLayer) {
<div>
{main.day.value === day
? `Complete the foundation to complete the day`
: main.currentlyMastering.value?.name === name
? `Complete the foundation to decorate the day`
: `${name} Complete!`}
</div>
{render(dayProgress)}
<Spacer />
{masteryEffectActive.value ? (
<>
<div class="decoration-effect">
Decoration effect:
<br />
Logs are just a requirement instead of a cost
</div>
<Spacer />
</>
) : null}
<div>
<span>The foundation is </span>
<h2 style={`color: ${color}; text-shadow: 0 0 10px ${color}`}>
@ -334,9 +416,13 @@ const layer = createLayer(id, function (this: BaseLayer) {
minimizedDisplay: jsx(() => (
<div>
{name}{" "}
<span class="desc">{formatWhole(foundationProgress.value)}% {foundationProgress.displayName}</span>
<span class="desc">
{formatWhole(foundationProgress.value)}% {foundationProgress.displayName}
</span>
</div>
)),
mastery,
mastered
};
});

View file

@ -1,19 +1,21 @@
import { BuyableOptions, createBuyable, GenericBuyable } from "features/buyable";
import { jsx, JSXFunction, showIf } from "features/feature";
import { createResource, Resource } from "features/resources/resource";
import { createLayer } from "game/layers";
import { noPersist, persistent } from "game/persistence";
import Decimal, { DecimalSource } from "util/bignum";
import MainDisplay from "features/resources/MainDisplay.vue";
import { render, renderRow } from "util/vue";
import { default as dyes, type enumColor } from "./dyes"
import { BaseTransition, computed, Ref, ref, unref } from "vue"
import Spacer from "components/layout/Spacer.vue";
import { Computable } from "util/computed";
import { format } from "util/bignum";
import { createCollapsibleModifierSections, setUpDailyProgressTracker, createCollapsibleMilestones } from "data/common";
import Modal from "components/Modal.vue";
import { createBar, GenericBar } from "features/bars/bar";
import { BuyableOptions, createBuyable, GenericBuyable } from "features/buyable";
import { createClickable } from "features/clickables/clickable";
import { jsx, JSXFunction, showIf } from "features/feature";
import { createMilestone } from "features/milestones/milestone";
import MainDisplay from "features/resources/MainDisplay.vue";
import { createResource, Resource } from "features/resources/resource";
import { createLayer, layers } from "game/layers";
import player from "game/player";
import Decimal, { DecimalSource, format, formatWhole } from "util/bignum";
import { Direction } from "util/common";
import { Computable } from "util/computed";
import { render, renderRow } from "util/vue";
import { computed, Ref, unref, watchEffect } from "vue";
import { main } from "../projEntry";
import { default as dyes, type enumColor } from "./dyes";
import elves from "./elves";
const id = "wrappingPaper";
const day = 15;
@ -37,39 +39,42 @@ interface Scaling {
interface WrappingPaperOptions {
ratio: {
[key in enumColor]?: Scaling;
},
};
name: string;
id: string;
background: string;
listedBoosts: {
desc: Ref<string>;
}[]
}[];
}
const layer = createLayer (id, () => {
const layer = createLayer(id, () => {
const name = "Wrapping Paper";
const color = "white"; // todo: change
const color = "gold";
const createWrappingPaper = (options: WrappingPaperOptions & Partial<BuyableOptions>) => {
const getCost: Computable<{
const getCost: Computable<
{
resource: Resource;
cost: DecimalSource;
}[]> = computed(() => {
}[]
> = computed(() => {
const dyeCosts = [];
for (const [color, ratio] of Object.entries(options.ratio)) {
dyeCosts.push({
resource: dyes.dyes[color as enumColor].amount,
cost: Decimal.mul(ratio.base, Decimal.pow(ratio.exponent, buyable.amount.value)),
cost: Decimal.mul(ratio.base, Decimal.pow(ratio.exponent, buyable.amount.value))
});
}
return dyeCosts;
})
});
const buyable: GenericBuyable = createBuyable(() => {
return {
style: () => ({
background: unref(buyable.canPurchase) ? options.background : "#545454",
minWidth: "200px",
boxShadow: "0 3px 0 #00000022 inset, 3px 0 0 #00000022 inset, 0 0 3px #00000022 inset, 0 0 0 3px #00000022 inset",
boxShadow:
"0 3px 0 #00000022 inset, 3px 0 0 #00000022 inset, 0 0 3px #00000022 inset, 0 0 0 3px #00000022 inset",
border: "none"
}),
display: jsx(() => {
@ -79,42 +84,43 @@ const layer = createLayer (id, () => {
<br />
Create {options.name}.
<br />
Requirement:{" "}{
getCost.value.map(({ resource, cost }) => {
return render(jsx(() => (
<div>
Requirement:{" "}
{getCost.value.map(({ resource, cost }) => {
return render(
jsx(() => (
<div
class={
Decimal.lt(resource.value, cost)
? "unaffordable"
: ""
}
>
{format(cost)} {resource.displayName} <br />
</div>
)))
})
}
))
);
})}
<br />
Currently:{" "}
{
options.listedBoosts.map(({desc}) => {
return render(jsx(() => (
<div>
{unref(desc)}
</div>
)))
})
}
{options.listedBoosts.map(({ desc }) => {
return render(jsx(() => <div>{unref(desc)}</div>));
})}
</span>
)
);
}),
canPurchase () {
for (let {resource, cost} of getCost.value) {
canPurchase() {
for (const { resource, cost } of getCost.value) {
if (Decimal.lt(resource.value, cost)) return false;
}
return true;
},
onPurchase () {
onPurchase() {
buyable.amount.value = Decimal.add(buyable.amount.value, 1);
// todo: stuff
}
}
})
const resource = createResource(buyable.amount, options.name)
};
});
const resource = createResource(buyable.amount, options.name);
return {
resource,
buyable,
@ -126,25 +132,27 @@ const layer = createLayer (id, () => {
style="margin: 0; width: 200px; width: 180px; padding: 10px;"
sticky={false}
/>
)
);
})
}
}
};
};
const wrappingPaper: Record<string, WrappingPaper> = {
christmas: createWrappingPaper({
name: "Christmas Wrapping Paper",
id: "christmas",
ratio: {
red: {base: basePrimaryCost * 3, exponent: basePrimaryRatio},
green: {base: baseSecondaryCost * 3, exponent: baseSecondaryRatio},
red: { base: basePrimaryCost * 3, exponent: basePrimaryRatio },
green: { base: baseSecondaryCost * 3, exponent: baseSecondaryRatio }
},
background: "linear-gradient(225deg, rgba(255,76,76,1) 10.8%, rgba(255,255,255,1) 11.1%, rgba(255,255,255,1) 21.9%, rgba(65,255,95,1) 22.2%, rgba(65,255,95,1) 33.0%, rgba(255,255,255,1) 33.3%, rgba(255,255,255,1) 44.1%, rgba(255,76,76,1) 44.4%, rgba(255,76,76,1) 55.2%, rgba(255,255,255,1) 55.5%, rgba(255,255,255,1) 66.3%, rgba(65,255,95,1) 66.6%, rgba(65,255,95,1) 77.4%, rgba(255,255,255,1) 77.7%, rgba(255,255,255,1) 88.5%, rgba(255,76,76,1) 88.8%)",
background:
"linear-gradient(225deg, rgba(255,76,76,1) 10.8%, rgba(255,255,255,1) 11.1%, rgba(255,255,255,1) 21.9%, rgba(65,255,95,1) 22.2%, rgba(65,255,95,1) 33.0%, rgba(255,255,255,1) 33.3%, rgba(255,255,255,1) 44.1%, rgba(255,76,76,1) 44.4%, rgba(255,76,76,1) 55.2%, rgba(255,255,255,1) 55.5%, rgba(255,255,255,1) 66.3%, rgba(65,255,95,1) 66.6%, rgba(65,255,95,1) 77.4%, rgba(255,255,255,1) 77.7%, rgba(255,255,255,1) 88.5%, rgba(255,76,76,1) 88.8%)",
listedBoosts: [
{
desc: computed(() => `
desc: computed(
() => `
x${format(unref(boosts.christmas1))} to wood production
`)
`
)
}
]
}),
@ -152,19 +160,22 @@ const layer = createLayer (id, () => {
name: "Rainbow Wrapping Paper",
id: "rainbow",
ratio: {
red: {base: basePrimaryCost, exponent: basePrimaryRatio + 0.2},
green: {base: baseSecondaryCost, exponent: baseSecondaryRatio + 0.1},
blue: {base: basePrimaryCost, exponent: basePrimaryRatio + 0.2},
yellow: {base: basePrimaryCost, exponent: basePrimaryRatio + 0.2},
purple: {base: baseSecondaryCost, exponent: baseSecondaryRatio + 0.1},
orange: {base: baseSecondaryCost, exponent: baseSecondaryRatio + 0.1},
red: { base: basePrimaryCost, exponent: basePrimaryRatio + 0.2 },
green: { base: baseSecondaryCost, exponent: baseSecondaryRatio + 0.1 },
blue: { base: basePrimaryCost, exponent: basePrimaryRatio + 0.2 },
yellow: { base: basePrimaryCost, exponent: basePrimaryRatio + 0.2 },
purple: { base: baseSecondaryCost, exponent: baseSecondaryRatio + 0.1 },
orange: { base: baseSecondaryCost, exponent: baseSecondaryRatio + 0.1 }
},
background: "linear-gradient(135deg, rgba(255,0,0,1) 0%, rgba(255,0,0,1) 2%, rgba(255,155,0,1) 14%, rgba(255,155,0,1) 18%, rgba(255,254,0,1) 31%, rgba(255,254,0,1) 35%, rgba(100,244,61,1) 48%, rgba(100,244,61,1) 52%, rgba(70,218,234,1) 64%, rgba(70,218,234,1) 68%, rgba(205,0,210,1) 81%, rgba(205,0,210,1) 85%, rgba(255,0,0,1) 98%, rgba(255,0,0,1) 100%)",
background:
"linear-gradient(135deg, rgba(255,0,0,1) 0%, rgba(255,0,0,1) 2%, rgba(255,155,0,1) 14%, rgba(255,155,0,1) 18%, rgba(255,254,0,1) 31%, rgba(255,254,0,1) 35%, rgba(100,244,61,1) 48%, rgba(100,244,61,1) 52%, rgba(70,218,234,1) 64%, rgba(70,218,234,1) 68%, rgba(205,0,210,1) 81%, rgba(205,0,210,1) 85%, rgba(255,0,0,1) 98%, rgba(255,0,0,1) 100%)",
listedBoosts: [
{
desc: computed(() => `
desc: computed(
() => `
/${format(unref(boosts.rainbow1))} to coal buyable cost
`)
`
)
}
]
}),
@ -172,15 +183,18 @@ const layer = createLayer (id, () => {
name: "Jazzy Wrapping Paper",
id: "jazzy",
ratio: {
purple: {base: baseSecondaryCost * 3, exponent: baseSecondaryRatio},
orange: {base: baseSecondaryCost * 3, exponent: baseSecondaryRatio},
purple: { base: baseSecondaryCost * 3, exponent: baseSecondaryRatio },
orange: { base: baseSecondaryCost * 3, exponent: baseSecondaryRatio }
},
background: "linear-gradient(90deg, rgba(255,177,0,1) 10.8%, rgba(189,69,255,1) 11.1%, rgba(189,69,255,1) 21.9%, rgba(255,177,0,1) 22.2%, rgba(255,177,0,1) 33.0%, rgba(189,69,255,1) 33.3%, rgba(189,69,255,1) 44.1%, rgba(255,177,0,1) 44.4%, rgba(255,177,0,1) 55.2%, rgba(189,69,255,1) 55.5%, rgba(189,69,255,1) 66.3%, rgba(255,177,0,1) 66.6%, rgba(255,177,0,1) 77.4%, rgba(189,69,255,1) 77.7%, rgba(189,69,255,1) 88.5%, rgba(255,177,0,1) 88.8%)",
background:
"linear-gradient(90deg, rgba(255,177,0,1) 10.8%, rgba(189,69,255,1) 11.1%, rgba(189,69,255,1) 21.9%, rgba(255,177,0,1) 22.2%, rgba(255,177,0,1) 33.0%, rgba(189,69,255,1) 33.3%, rgba(189,69,255,1) 44.1%, rgba(255,177,0,1) 44.4%, rgba(255,177,0,1) 55.2%, rgba(189,69,255,1) 55.5%, rgba(189,69,255,1) 66.3%, rgba(255,177,0,1) 66.6%, rgba(255,177,0,1) 77.4%, rgba(189,69,255,1) 77.7%, rgba(189,69,255,1) 88.5%, rgba(255,177,0,1) 88.8%)",
listedBoosts: [
{
desc: computed(() => `
-${format(unref(boosts.jazzy1))} to elf cost scaling
`)
desc: computed(
() => `
x${format(unref(boosts.jazzy1))} to auto-smelting speed
`
)
}
]
}),
@ -188,16 +202,19 @@ const layer = createLayer (id, () => {
name: "Sunshine Wrapping Paper",
id: "sunshine",
ratio: {
red: {base: basePrimaryCost * 2, exponent: basePrimaryRatio + .1},
yellow: {base: basePrimaryCost * 2, exponent: basePrimaryRatio + .1},
orange: {base: baseSecondaryCost * 2, exponent: baseSecondaryRatio + .05},
red: { base: basePrimaryCost * 2, exponent: basePrimaryRatio + 0.1 },
yellow: { base: basePrimaryCost * 2, exponent: basePrimaryRatio + 0.1 },
orange: { base: baseSecondaryCost * 2, exponent: baseSecondaryRatio + 0.05 }
},
background: "radial-gradient(circle, rgba(238,250,0,1) 16%, rgba(250,157,0,1) 50%, rgba(255,76,76,1) 83%)",
background:
"radial-gradient(circle, rgba(238,250,0,1) 16%, rgba(250,157,0,1) 50%, rgba(255,76,76,1) 83%)",
listedBoosts: [
{
desc: computed(() => `
desc: computed(
() => `
x${format(unref(boosts.sunshine1))} to paper production
`)
`
)
}
]
}),
@ -205,16 +222,19 @@ const layer = createLayer (id, () => {
name: "Ocean Wrapping Paper",
id: "ocean",
ratio: {
blue: {base: basePrimaryCost * 2, exponent: basePrimaryRatio + .1},
green: {base: baseSecondaryCost * 2, exponent: baseSecondaryRatio + .05},
purple: {base: baseSecondaryCost * 2, exponent: baseSecondaryRatio + .05},
blue: { base: basePrimaryCost * 2, exponent: basePrimaryRatio + 0.1 },
green: { base: baseSecondaryCost * 2, exponent: baseSecondaryRatio + 0.05 },
purple: { base: baseSecondaryCost * 2, exponent: baseSecondaryRatio + 0.05 }
},
background: "linear-gradient(20deg, rgba(0,183,250,0.6) 8%, rgba(0,223,62,0.6) 12%, rgba(0,183,250,0.6) 17%, rgba(0,183,250,0.6) 27%, rgba(124,109,230,0.6) 38%, rgba(0,183,250,0.6) 46%, rgba(0,183,250,0.6) 50%, rgba(0,223,62,0.6) 53%, rgba(0,183,250,0.6) 60%, rgba(124,109,230,0.6) 67%, rgba(0,183,250,0.6) 73%, rgba(0,183,250,0.6) 84%, rgba(0,223,62,0.6) 88%, rgba(0,183,250,0.6) 91%), linear-gradient(340deg, rgba(0,183,250,0.6) 8%, rgba(0,223,62,0.6) 12%, rgba(0,183,250,0.6) 17%, rgba(0,183,250,0.6) 27%, rgba(124,109,230,0.6) 38%, rgba(0,183,250,0.6) 46%, rgba(0,183,250,0.6) 50%, rgba(0,223,62,0.6) 53%, rgba(0,183,250,0.6) 60%, rgba(124,109,230,0.6) 67%, rgba(0,183,250,0.6) 73%, rgba(0,183,250,0.6) 84%, rgba(0,223,62,0.6) 88%, rgba(0,183,250,0.6) 91%)",
background:
"linear-gradient(20deg, rgba(0,183,250,0.6) 8%, rgba(0,223,62,0.6) 12%, rgba(0,183,250,0.6) 17%, rgba(0,183,250,0.6) 27%, rgba(124,109,230,0.6) 38%, rgba(0,183,250,0.6) 46%, rgba(0,183,250,0.6) 50%, rgba(0,223,62,0.6) 53%, rgba(0,183,250,0.6) 60%, rgba(124,109,230,0.6) 67%, rgba(0,183,250,0.6) 73%, rgba(0,183,250,0.6) 84%, rgba(0,223,62,0.6) 88%, rgba(0,183,250,0.6) 91%), linear-gradient(340deg, rgba(0,183,250,0.6) 8%, rgba(0,223,62,0.6) 12%, rgba(0,183,250,0.6) 17%, rgba(0,183,250,0.6) 27%, rgba(124,109,230,0.6) 38%, rgba(0,183,250,0.6) 46%, rgba(0,183,250,0.6) 50%, rgba(0,223,62,0.6) 53%, rgba(0,183,250,0.6) 60%, rgba(124,109,230,0.6) 67%, rgba(0,183,250,0.6) 73%, rgba(0,183,250,0.6) 84%, rgba(0,223,62,0.6) 88%, rgba(0,183,250,0.6) 91%)",
listedBoosts: [
{
desc: computed(() => `
desc: computed(
() => `
/${format(unref(boosts.ocean1))} to box buyable cost
`)
`
)
}
]
}),
@ -222,158 +242,203 @@ const layer = createLayer (id, () => {
name: "Beach Wrapping Paper",
id: "beach",
ratio: {
yellow: {base: basePrimaryCost * 3, exponent: basePrimaryRatio},
blue: {base: basePrimaryCost * 3, exponent: basePrimaryRatio},
yellow: { base: basePrimaryCost * 3, exponent: basePrimaryRatio },
blue: { base: basePrimaryCost * 3, exponent: basePrimaryRatio }
},
background: "radial-gradient(circle at 80% 10%, rgba(255,255,76,1) 8%, rgba(0,0,0,0) 21%), linear-gradient(180deg, rgba(0,255,246,1) 60%, rgba(0,255,246,0) 61%), linear-gradient(215deg, rgba(0,93,255,0) 0%, rgba(0,93,255,0) 66%, rgba(255,255,76,1) 68%), linear-gradient(180deg, rgba(0,0,0,0) 68%, rgba(0,93,255,1) 70%), linear-gradient(205deg, rgba(0,255,246,1) 0%, rgba(0,255,246,1) 100%)",
background:
"radial-gradient(circle at 80% 10%, rgba(255,255,76,1) 8%, rgba(0,0,0,0) 21%), linear-gradient(180deg, rgba(0,255,246,1) 60%, rgba(0,255,246,0) 61%), linear-gradient(215deg, rgba(0,93,255,0) 0%, rgba(0,93,255,0) 66%, rgba(255,255,76,1) 68%), linear-gradient(180deg, rgba(0,0,0,0) 68%, rgba(0,93,255,1) 70%), linear-gradient(205deg, rgba(0,255,246,1) 0%, rgba(0,255,246,1) 100%)",
listedBoosts: [
{
desc: computed(() => `
desc: computed(
() => `
/${format(unref(boosts.beach1))} to workshop cost
`)
`
)
}
]
})
}
};
const boosts = {
christmas1: computed(() => Decimal.add(wrappingPaper.christmas.buyable.amount.value, 1)), // Probably not the best way to do this, but it works
christmas1: computed(() =>
main.isMastery.value ? 1 : Decimal.add(wrappingPaper.christmas.buyable.amount.value, 1)
), // Probably not the best way to do this, but it works
rainbow1: computed(() =>
Decimal.pow(2, wrappingPaper.rainbow.buyable.amount.value)
main.isMastery.value ? 1 : Decimal.pow(2, wrappingPaper.rainbow.buyable.amount.value)
),
jazzy1: computed(() =>
Decimal.ln(
Decimal.add(
Decimal.ln(Decimal.add(wrappingPaper.jazzy.buyable.amount.value, 1)),
1
)
)
main.isMastery.value ? 1 : Decimal.add(wrappingPaper.jazzy.buyable.amount.value, 1)
),
sunshine1: computed(() =>
main.isMastery.value ? 1 : Decimal.add(wrappingPaper.sunshine.buyable.amount.value, 1)
),
sunshine1: computed(() => Decimal.add(wrappingPaper.sunshine.buyable.amount.value, 1)),
ocean1: computed(() =>
Decimal.pow(1.5, wrappingPaper.ocean.buyable.amount.value)
main.isMastery.value ? 1 : Decimal.pow(1.5, wrappingPaper.ocean.buyable.amount.value)
),
beach1: computed(() => Decimal.add(wrappingPaper.beach.buyable.amount.value, 1)),
}
const wrappingPaperSum = createResource(computed(() => Object.values(wrappingPaper).map(paper => paper.buyable.amount.value).reduce(Decimal.add, 0)), "Total Wrapping Paper")
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
}}
/>
));
beach1: computed(() =>
main.isMastery.value
? 1
: Decimal.add(wrappingPaper.beach.buyable.amount.value, 1).log10().add(1)
)
};
const wrappingPaperSum = createResource(
computed(() =>
Object.values(wrappingPaper)
.map(paper => paper.buyable.amount.value)
.reduce(Decimal.add, 0)
),
"Total Wrapping Paper"
);
const [generalTab, generalTabCollapsed] = createCollapsibleModifierSections(() => [
]);
const { total: totalWrappingPaper, trackerDisplay } = setUpDailyProgressTracker({
resource: wrappingPaperSum,
goal: 1e20,
name,
day,
color,
textColor: "var(--feature-foreground)",
modal: {
show: showModifiersModal,
display: modifiersModal
},
ignoreTotal: true
});
const milestoneCosts = [6, 12, 18, 24, 30, 36] // change
const primaryBoostMilestone = createMilestone(() => ({
display: {
requirement: milestoneCosts[0] + " Total Wrapping Paper",
effectDisplay: "Double primary colour dye gain"
},
shouldEarn: () => Decimal.gte(totalWrappingPaper.value, milestoneCosts[0]),
visibility: () => showIf(true)
}));
const secondaryBoostMilestone = createMilestone(() => ({
display: {
requirement: milestoneCosts[1] + " Total Wrapping Paper",
effectDisplay: "Double secondary colour dye gain"
},
shouldEarn: () => Decimal.gte(totalWrappingPaper.value, milestoneCosts[1]),
visibility: () => showIf(primaryBoostMilestone.earned.value)
}));
const buyMaxPrimaryMilestone = createMilestone(() => ({
display: {
requirement: milestoneCosts[2] + " Total Wrapping Paper",
effectDisplay: "Buy maximum primary colour dyes"
},
shouldEarn: () => Decimal.gte(totalWrappingPaper.value, milestoneCosts[2]),
visibility: () => showIf(secondaryBoostMilestone.earned.value)
}));
const secondaryNoResetMilestone = createMilestone(() => ({
display: {
requirement: milestoneCosts[3] + " Total Wrapping Paper",
effectDisplay: "Secondary colour dyes don't spend primary colour dyes"
},
shouldEarn: () => Decimal.gte(totalWrappingPaper.value, milestoneCosts[3]),
visibility: () => showIf(buyMaxPrimaryMilestone.earned.value)
}));
const buyMaxSecondaryMilestone = createMilestone(() => ({
display: {
requirement: milestoneCosts[4] + " Total Wrapping Paper",
effectDisplay: "Buy maximum secondary colour dyes"
},
shouldEarn: () => Decimal.gte(totalWrappingPaper.value, milestoneCosts[4]),
visibility: () => showIf(secondaryNoResetMilestone.earned.value)
}));
const unlockDyeElfMilestone = createMilestone(() => ({
display: {
requirement: milestoneCosts[5] + " Total Wrapping Paper",
requirement: "80 Total Wrapping Paper",
effectDisplay: "Unlock a new elf to help with dyes"
},
shouldEarn: () => Decimal.gte(totalWrappingPaper.value, milestoneCosts[5]),
visibility: () => showIf(buyMaxSecondaryMilestone.earned.value)
shouldEarn: () => Decimal.gte(wrappingPaperSum.value, 80),
onComplete() {
main.days[3].recentlyUpdated.value = true;
}
}));
const milestones = {
primaryBoost: primaryBoostMilestone,
secondaryBoost: secondaryBoostMilestone,
buyMaxPrimary: buyMaxPrimaryMilestone,
secondaryNoReset: secondaryNoResetMilestone,
buyMaxSecondary: buyMaxSecondaryMilestone,
unlockDyeElf: unlockDyeElfMilestone
const masteryReq = computed(() =>
Decimal.add(main.masteredDays.value, 1).times(20).add(140).ceil()
);
const enterMasteryButton = createClickable(() => ({
display: () => ({
title: `${main.isMastery.value ? "Stop Decorating" : "Begin Decorating"} ${
Object.values(layers).find(
layer =>
unref((layer as any).mastered) === false &&
!["Elves", "Management"].includes(unref(layer?.name ?? ""))
)?.name
}`,
description: jsx(() => {
return (
<>
<br />
Decorating brings you to a separate version of each day that only allows
layers that are decorated or being decorated to work. These days will have a
new decoration effect that applies outside of decorating as well.
<br />
You can safely start and stop decorating without losing progress
{main.isMastery.value ? null : (
<>
<br />
<br />
Requires {formatWhole(masteryReq.value)} total wrapping paper
</>
)}
</>
);
})
}),
visibility: () => showIf(main.day.value === day),
canClick() {
return main.isMastery.value || Decimal.gte(wrappingPaperSum.value, masteryReq.value);
},
onClick() {
if (!unref(enterMasteryButton.canClick)) {
return;
}
main.toggleMastery();
const layer = main.currentlyMastering.value?.id ?? "trees";
if (!player.tabs.includes(layer)) {
main.openDay(layer);
}
if (layer === "paper") {
// Purchase first 6 elves
elves.elves.cuttersElf.bought.value = true;
elves.elves.plantersElf.bought.value = true;
elves.elves.expandersElf.bought.value = true;
elves.elves.heatedCuttersElf.bought.value = true;
elves.elves.heatedPlantersElf.bought.value = true;
elves.elves.fertilizerElf.bought.value = true;
}
},
style: {
width: "300px",
minHeight: "160px"
}
}));
const { collapseMilestones, display: milestonesDisplay } =
createCollapsibleMilestones(milestones);
const dayProgress = createBar(() => ({
direction: Direction.Right,
width: 600,
height: 25,
fillStyle: `animation: 15s wrapping-paper-bar linear infinite`,
textStyle: `color: var(--feature-foreground)`,
progress: () => (main.day.value === day ? Decimal.div(main.masteredDays.value, 6) : 1),
display: jsx(() =>
main.day.value === day ? (
<>
{main.masteredDays.value}
/6 days decorated
</>
) : (
""
)
)
})) as GenericBar;
watchEffect(() => {
if (
main.day.value === day &&
Decimal.gte(main.masteredDays.value, 6) &&
main.showLoreModal.value === false
) {
main.completeDay();
}
});
return {
name,
day,
color,
display: jsx(() => {
return (
<div style="width: 620px">
{render(trackerDisplay)}
<MainDisplay resource={wrappingPaperSum} />
{renderRow(wrappingPaper.christmas.display, wrappingPaper.rainbow.display, wrappingPaper.jazzy.display)}
{renderRow(wrappingPaper.christmas.buyable, wrappingPaper.rainbow.buyable, wrappingPaper.jazzy.buyable)}
<Spacer />
{renderRow(wrappingPaper.sunshine.display, wrappingPaper.ocean.display, wrappingPaper.beach.display)}
{renderRow(wrappingPaper.sunshine.buyable, wrappingPaper.ocean.buyable, wrappingPaper.beach.buyable)}
<Spacer />
button goes here
<Spacer />
{milestonesDisplay()}
<div>
{main.day.value === day
? `Decorate 6 previous days to complete the day`
: `${name} Complete!`}
</div>
)
{render(dayProgress)}
<Spacer />
<MainDisplay resource={wrappingPaperSum} />
{renderRow(
wrappingPaper.christmas.display,
wrappingPaper.rainbow.display,
wrappingPaper.jazzy.display
)}
{renderRow(
wrappingPaper.christmas.buyable,
wrappingPaper.rainbow.buyable,
wrappingPaper.jazzy.buyable
)}
<Spacer />
{renderRow(
wrappingPaper.sunshine.display,
wrappingPaper.ocean.display,
wrappingPaper.beach.display
)}
{renderRow(
wrappingPaper.sunshine.buyable,
wrappingPaper.ocean.buyable,
wrappingPaper.beach.buyable
)}
<Spacer />
{render(enterMasteryButton)}
<Spacer />
{render(unlockDyeElfMilestone)}
</div>
);
}),
wrappingPaper,
totalWrappingPaper,
generalTabCollapsed,
boosts,
milestones,
collapseMilestones
}
})
unlockDyeElfMilestone,
minWidth: 700
};
});
export default layer;

View file

@ -7,7 +7,7 @@ import {
jsx
} from "features/feature";
import { BaseLayer, createLayer, GenericLayer, layers } from "game/layers";
import { persistent } from "game/persistence";
import { isPersistent, persistent } from "game/persistence";
import type { LayerData, PlayerData } from "game/player";
import player from "game/player";
import Decimal, { format, formatTime } from "util/bignum";
@ -30,6 +30,8 @@ import metal from "./layers/metal";
import oil from "./layers/oil";
import paper from "./layers/paper";
import plastic from "./layers/plastic";
import ribbon from "./layers/ribbon";
import toys from "./layers/toys";
import trees from "./layers/trees";
import workshop from "./layers/workshop";
import factory from "./layers/factory";
@ -45,6 +47,7 @@ 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 ribbonsSymbol from "./symbols/ribbons.png";
import workshopSymbol from "./symbols/sws.png";
import treeSymbol from "./symbols/tree.png";
import advManagementSymbol from "./symbols/workshopMansion.png";
@ -56,6 +59,7 @@ export interface Day extends VueFeature {
symbol: string;
story: string;
completedStory: string;
masteredStory: string;
opened: Ref<boolean>;
recentlyUpdated: Ref<boolean>; // Has the tab recieved an update since the player last opened it?
shouldNotify: ProcessedComputable<boolean>;
@ -72,6 +76,79 @@ export const main = createLayer("main", function (this: BaseLayer) {
const loreTitle = ref<string>("");
const loreBody = ref<CoercableComponent | undefined>();
const currentlyMastering = computed(() =>
isMastery.value
? Object.values(layers).find(
layer =>
unref((layer as any).mastered) === false &&
!["Elves", "Management"].includes(unref(layer?.name ?? ""))
)
: undefined
);
const swappingMastery = ref(false);
const isMastery = persistent<boolean>(false);
const toggleMastery = () => {
swappingMastery.value = true;
isMastery.value = !isMastery.value;
for (const layer of [
trees,
workshop,
coal,
elves,
paper,
boxes,
metal,
cloth,
oil,
plastic,
dyes,
management,
letters
]) {
swapMastery(layer.mastery, layer);
}
swappingMastery.value = false;
};
function swapMastery(mastery: Record<string, any>, layer: Record<string, any>) {
for (const key of Object.keys(mastery)) {
if (isPersistent(mastery[key])) {
[mastery[key].value, layer[key].value] = [layer[key].value, mastery[key].value];
} else {
swapMastery(mastery[key], layer[key]);
}
}
}
const masteredDays = computed(() => {
let index = Object.values(layers)
.filter(l => l && "mastered" in l)
.findIndex(l => (l as any).mastered.value === false);
if (index === -1) {
index = Object.values(layers).filter(l => l && "mastered" in l).length;
}
return index;
});
function openDay(layer: string) {
// 1468 is because two tabs with minWidth of 700px plus the minimized calendar of 60px plus 2 dividers of 4px each
if (window.matchMedia("(min-width: 1468px)").matches) {
// Desktop, allow multiple tabs to be open
if (player.tabs.includes(layer)) {
const index = player.tabs.lastIndexOf(layer);
player.tabs.splice(index, 1);
} else {
player.tabs.push(layer);
main.minimized.value = true;
}
} else {
// Mobile, use single tab mode
player.tabs.splice(1, Infinity, layer);
}
layers[layer]!.minimized.value = false;
}
function createDay(
optionsFunc: () => {
day: number;
@ -80,6 +157,7 @@ export const main = createLayer("main", function (this: BaseLayer) {
symbol: string;
story: string;
completedStory: string;
masteredStory: string;
}
): Day {
const opened = persistent<boolean>(false);
@ -108,9 +186,12 @@ export const main = createLayer("main", function (this: BaseLayer) {
shouldNotify,
story,
completedStory,
masteredStory,
recentlyUpdated
} = this;
const mastered: Ref<boolean> =
(layers[layer ?? ""] as any)?.mastered ?? ref(false);
return {
day,
symbol,
@ -118,13 +199,22 @@ export const main = createLayer("main", function (this: BaseLayer) {
opened,
recentlyUpdated,
shouldNotify,
mastered,
onOpenLore() {
const completed = main.day.value > day;
loreScene.value = completed ? day - 1 : -1;
const title = unref(layers[layer ?? "trees"]?.name ?? "");
loreTitle.value = completed ? `${title} - Completed!` : title;
loreTitle.value = mastered.value
? `${title} - Decorated!`
: completed
? `${title} - Completed!`
: title;
loreBody.value = completed
? unref(mastered)
? `${story}<hr style="
margin: 10px 0;"/>${completedStory}<hr style="
margin: 10px 0;"/>${masteredStory}`
: `${story}<hr style="
margin: 10px 0;"/>${completedStory}`
: story;
showLoreModal.value = true;
@ -150,7 +240,7 @@ export const main = createLayer("main", function (this: BaseLayer) {
}
layers[layer ?? "trees"]!.minimized.value = false;
} else {
player.tabs.splice(0, Infinity, layer)
player.tabs.splice(0, Infinity, layer);
}
},
onUnlockLayer() {
@ -179,7 +269,9 @@ export const main = createLayer("main", function (this: BaseLayer) {
symbol: treeSymbol,
story: "Oh no! Santa forgot about Christmas and it's only 25 days away! He's asked for your help due to your history getting large quantities of things in short amounts of time. Unfortunately you're really starting from scratch here - let's start with getting wood, which you'll need for everything from building workshops to wrapping paper to many of the toys themselves!",
completedStory:
"Santa looks at all the wood you've gathered and tells you you've done well! He says you should take the rest of the day off so you're refreshed for tomorrow's work. Good Job!"
"Santa looks at all the wood you've gathered and tells you you've done well! He says you should take the rest of the day off so you're refreshed for tomorrow's work. Good Job!",
masteredStory:
"As you repeat the basic actions again, you feel like you've learned something that you didn't know the first time around. Santa is impressed at your new knowledge and inspires you to attempt this with more jobs. Great Job!"
})),
createDay(() => ({
day: 2,
@ -188,7 +280,9 @@ export const main = createLayer("main", function (this: BaseLayer) {
symbol: workshopSymbol,
story: "Santa looked over your tree farm and was impressed with how much you could accomplish in just one day. Today's goal is to get a workshop built up for the elves to work in - and apparently, they need quite a lot of space to work!",
completedStory:
"The workshop complete, Santa once again dismisses you for the day. With a strong foundation, this workshop should suffice for supporting future work toward this impossible mission. Good Job!"
"The workshop complete, Santa once again dismisses you for the day. With a strong foundation, this workshop should suffice for supporting future work toward this impossible mission. Good Job!",
masteredStory:
"As you attempt to build the workshop again with your newfound experiences and resources, you realize you could have built the workshop a little bit better. As you keep building and building, you realize that you could've built it without wasting any resources. Great Job!"
})),
createDay(() => ({
day: 3,
@ -197,7 +291,9 @@ export const main = createLayer("main", function (this: BaseLayer) {
symbol: coalSymbol,
story: "Santa tells you that unfortunately there are quite a few naughty children out there this year, and he's going to need you to gather as much coal as you can for him to give out.",
completedStory:
"Santa looks at all the coal you've gathered and tells you you've done well! He says you should take the rest of the day off so you're refreshed for tomorrow's work. Good Job!"
"Santa looks at all the coal you've gathered and tells you you've done well! He says you should take the rest of the day off so you're refreshed for tomorrow's work. Good Job!",
masteredStory:
"It's another typical day, attempting to redo your work again, but this time for coal. While doing this tedious task, an elf comes up to you. It gives you a improved blueprint on how to make small fires. You try it, and you realize that it's a lot more efficent than your old buildings designs. You thank the elf, and resume your work. Great Job!"
})),
createDay(() => ({
day: 4,
@ -206,7 +302,9 @@ export const main = createLayer("main", function (this: BaseLayer) {
symbol: elfSymbol,
story: "Alright, it seems you finally have enough things set up to start bringing in the elves! Unfortunately, it seems they'll need to be retrained on how to help, since they've stopped practicing for 11 months!",
completedStory:
"The workshop now hums with the bustling elves working on everything. They can take it from here - you deserve a break after such a long day! Good Job!"
"The workshop now hums with the bustling elves working on everything. They can take it from here - you deserve a break after such a long day! Good Job!",
masteredStory:
"This place feels a lot more better, with less naughty elves who are more excited than ever before to do something! As you collapse into a chair thinking of all of your hard work, Santa comes by yet again to congratulate you on your hard work. You feel a pang of jealousy as Santa is taking all the credit for your work, but you decide that saving Christmas is worth it. Great Job!"
})),
createDay(() => ({
day: 5,
@ -215,7 +313,9 @@ export const main = createLayer("main", function (this: BaseLayer) {
symbol: paperSymbol,
story: "With the elves trained, we're almost ready to start working on these presents! Just a couple more pre-reqs first, starting with turning all this wood into wood pulp and finally into paper, which will be required for wrapping paper later on but in the meantime can be used to help write guides which will help these elves continue their education!",
completedStory:
"You look upon your rivers of book pulp as you hand out stacks of papers to elves to read through. You've continued getting closer and closer to preparing for Christmas, and can go to bed satisfied with your progress. Good Job!"
"You look upon your rivers of book pulp as you hand out stacks of papers to elves to read through. You've continued getting closer and closer to preparing for Christmas, and can go to bed satisfied with your progress. Good Job!",
masteredStory:
"Paper. Who knew it could be so versatile? As you slowly but surely improve your skills on making paper, you find more efficent ways to make it, and as a bonus, it's also environmentally friendly (which kinda makes up for you chopping a bit too many trees)! As you pass this information along to Santa's elves, they become more excited. Great Job!"
})),
createDay(() => ({
day: 6,
@ -224,7 +324,9 @@ export const main = createLayer("main", function (this: BaseLayer) {
symbol: boxesSymbol,
story: "You watch all these elves carrying incredibly large loads just in their open elf-sized hands, and realize there's probably a better way. You need to put the toys in boxes anyways, so why don't we get started working on those so the workers can take advantage as well?",
completedStory:
"Wow, those boxes are really convenient! The workshop feels more and more proper with every day. You tick another requirement on your list and start looking towards tomorrow. Good Job!"
"Wow, those boxes are really convenient! The workshop feels more and more proper with every day. You tick another requirement on your list and start looking towards tomorrow. Good Job!",
masteredStory:
"You look at your massive amounts of boxes, but something doesn't feel right. Oh wait, the elves are only filling the boxes to half the amount that it can actually store! As realisation hits you on how you can make boxes more efficent by using simple methods, you realize that you ought to teach the art of dumping-more-stuff-in-boxes-also-known-as-hoarding to the elves. Whew, that was a lot of work. Great Job!"
})),
createDay(() => ({
day: 7,
@ -233,7 +335,9 @@ export const main = createLayer("main", function (this: BaseLayer) {
symbol: metalSymbol,
story: "You woke up ready to make some toys, before realizing most toys these days are made out of more than just wood! You're sure you're close to really getting to work, but there's a few more materials you're going to need - like metal! Lots of things need metal!",
completedStory:
"The sounds of drills and metal clanging join the already loud din as yet another piece of the puzzle fits into place. You're making solid progress, Good Job!"
"The sounds of drills and metal clanging join the already loud din as yet another piece of the puzzle fits into place. You're making solid progress, Good Job!",
masteredStory:
"Cling clang clang clang. The sounds of even more drills hit your ears. As you fondly look back at the terrific work you've done, you become more motivated to work harder. Just then, Santa appears in front of you and you scream. He says, \"I see you're working hard. I suggest that you take a break.\" You thank Santa for the break, sit in a chair made by the elves as a gift, and relax. Great Job!"
})),
createDay(() => ({
day: 8,
@ -242,7 +346,9 @@ export const main = createLayer("main", function (this: BaseLayer) {
symbol: clothSymbol,
story: "Another resource you're going to need for gifts is cloth! Fortunately you think this should be pretty easy to prepare using a sheep farm - and as you've already proven with the tree farm, that's something you can handle!",
completedStory:
"You fall into a pile of wool, sighing contentedly as you look at all the progress you've made today. Good Job!"
"You fall into a pile of wool, sighing contentedly as you look at all the progress you've made today. Good Job!",
masteredStory:
"You're able to bundle yourself in layer after layer of clothing. You watch as everything happens together, harmoniously. Great Job!"
})),
createDay(() => ({
day: 9,
@ -251,7 +357,9 @@ export const main = createLayer("main", function (this: BaseLayer) {
symbol: oilSymbol,
story: "Looks like you just need one more thing before the toy factory can start running: plastic! Every toy nowadays is made with plastic! But wait, how are you going to get plastic? What can make plastic? Wait that's right, oil! You figured out you might as well repurpose your coal and ore drills into something that can get you oil, but unfortunately you'll need to mine much deeper that you're currently doing, so let's get to work!",
completedStory:
"It took a while, but you finally got enough oil for the next step! You deserve a good rest after all this digging work - tomorrow will be a busy day! Good Job!"
"It took a while, but you finally got enough oil for the next step! You deserve a good rest after all this digging work - tomorrow will be a busy day! Good Job!",
masteredStory:
"Oil shoots into the air like never before. Physics itself seems to be broken, as there's no other explanation for how you can make everything perfectly efficient without any kind of loss whatsoever. But to be fair, there's probably already a bit of physics shenanigans going on in a typical Christmas anyways. Great Job!"
})),
createDay(() => ({
day: 10,
@ -260,7 +368,9 @@ export const main = createLayer("main", function (this: BaseLayer) {
symbol: plasticSymbol,
story: "Now that plenty of oil has been prepared, it's time to start refining it into plastic! This should be incredibly useful not only for toys, but making tools and other items!",
completedStory:
"You've started refining massive amounts of oil into slightly less massive amounts of plastic. You have a slight pang of regret thinking of the environmental impact, but ultimately decide Christmas is worth it. Good Job!"
"You've started refining massive amounts of oil into slightly less massive amounts of plastic. You have a slight pang of regret thinking of the environmental impact, but ultimately decide Christmas is worth it. Good Job!",
masteredStory:
"You're now making more plastic than you know what to do with. You'll be able to make so many toys with all of this! Great Job!"
})),
createDay(() => ({
day: 11,
@ -269,7 +379,9 @@ export const main = createLayer("main", function (this: BaseLayer) {
symbol: dyesSymbol,
story: "To make toys, we're going to need some color to make them look nice and enticing! We can't just give kids clear toys after all! To add some color to our toys, we'll need some dyes!",
completedStory:
"After all that effort, you finally have a rainbow of dyes to choose from! Now the children won't be able to resist the toys you have to offer, once you get them made of course... Good Job!"
"After all that effort, you finally have a rainbow of dyes to choose from! Now the children won't be able to resist the toys you have to offer, once you get them made of course... Good Job!",
masteredStory:
"You remember back to when making various dyes was such a painful process, and contrast it to now where everything is trivialized and you even have more uses for all the dyes! Great Job!"
})),
createDay(() => ({
day: 12,
@ -278,7 +390,8 @@ export const main = createLayer("main", function (this: BaseLayer) {
symbol: managementSymbol,
story: "You watch as the elves work, and you realize that they could probably be trained to help out better. Just then, Santa comes over to check on your progress. You reply that you're doing fine, except that the elves may need a bit of behavior management. Santa offers to help, saying that he doesn't want to leave you to do everything. Unfortunately for you, the behavior problems won't fix themselves, so let's get to work!",
completedStory:
"Woo! You are exhausted - this layer felt really long to you. It's great seeing the elves so productive, although you worry a bit about your own job security now! Good Job!"
"Woo! You are exhausted - this layer felt really long to you. It's great seeing the elves so productive, although you worry a bit about your own job security now! Good Job!",
masteredStory: ""
})),
createDay(() => ({
day: 13,
@ -287,7 +400,8 @@ export const main = createLayer("main", function (this: BaseLayer) {
symbol: advManagementSymbol,
story: "So after a good night's rest you decide that maybe making these elves able to do all the work for you isn't something to be scared of, but rather encouraged. Let's spend another day continuing to train them up and really get this place spinning. They are Santa's elves after all, they're supposed to be able to run everything without you!",
completedStory:
"The elves are doing an incredible job, and Santa does not seem keen on firing you - Score! Now you can get to work on guiding this properly trained highly functional group of hard workers to make Christmas as great as possible. Good Job!"
"The elves are doing an incredible job, and Santa does not seem keen on firing you - Score! Now you can get to work on guiding this properly trained highly functional group of hard workers to make Christmas as great as possible. Good Job!",
masteredStory: ""
})),
createDay(() => ({
day: 14,
@ -296,48 +410,56 @@ export const main = createLayer("main", function (this: BaseLayer) {
symbol: lettersSymbol,
story: "Fully prepared to start working on presents, you realize you don't actually know what to make! You ask Santa and he points at a massive pile of letters hiding just off-camera. Those are all the letters to Santa that need to be processed, sorted, and categorized appropriately so every kid gets what they need!",
completedStory:
"The letters are sorted! You have a slight feeling you may have rushed a little, and suddenly understand why sometimes you don't get everything you asked Santa for every year, or even the occasional bad gift. You sympathetically pat Santa on the back as you head to bed for the day. Good Job!"
"The letters are sorted! You have a slight feeling you may have rushed a little, and suddenly understand why sometimes you don't get everything you asked Santa for every year, or even the occasional bad gift. You sympathetically pat Santa on the back as you head to bed for the day. Good Job!",
masteredStory:
"Finally, you've become the letter processing machine you always knew you could be. There's nothing anyone can do to stop you from processing every gosh darn letter to Santa there is. Great Job!"
})),
createDay(() => ({
day: 15,
shouldNotify: false,
layer: null, // "wrapping paper"
layer: "wrappingPaper",
symbol: wrappingPaperSymbol,
story: "You'll need to produce wrapping paper so the presents can be wrapped. The elves are getting a bit bored of their boring old workstations, so you decide to let them decorate with some wrapping paper.",
completedStory:
"You've produced enough wrapping paper, and the elves are happy with their new workstations. However, some will need more than just wrapping paper to decorate."
"You've produced enough wrapping paper, and the elves are happy with their new workstations. However, some will need more than just wrapping paper to decorate. For now, Good Job!",
masteredStory: ""
})),
createDay(() => ({
day: 16,
shouldNotify: false,
layer: null, // "ribbons"
symbol: "",
story: "",
completedStory: ""
layer: "ribbon",
symbol: ribbonsSymbol,
story: "In addition to wrapping paper, you think some ribbons are in order! These should work pretty similarly, allowing you to decorate even more workstations!",
completedStory:
"Ribbon surrounds the north pole now - everything looks fantastic, and you're pretty sure now you have every single material you could possibly need to start making toys and preparing them for Christmas! With just under 10 days left until Christmas, you go to sleep giddy with anticipation. Good Job!",
masteredStory: ""
})),
createDay(() => ({
day: 17,
shouldNotify: false,
layer: null, // "toys 1"
layer: "toys", // "toys1"
symbol: "",
story: "",
completedStory: ""
completedStory: "",
masteredStory: ""
})),
createDay(() => ({
day: 18,
shouldNotify: false,
layer: null, // "toys 2"
layer: null, // "toys2"
symbol: "",
story: "",
completedStory: ""
completedStory: "",
masteredStory: ""
})),
createDay(() => ({
day: 19,
shouldNotify: false,
layer: null, // "toys 3"
layer: null, // "toys3"
symbol: "",
story: "",
completedStory: ""
completedStory: "",
masteredStory: ""
})),
createDay(() => ({
day: 20,
@ -345,7 +467,8 @@ export const main = createLayer("main", function (this: BaseLayer) {
layer: "factory", // "presents"
symbol: wrappingPaperSymbol,
story: "",
completedStory: ""
completedStory: "",
masteredStory: ""
})),
createDay(() => ({
day: 21,
@ -353,7 +476,8 @@ export const main = createLayer("main", function (this: BaseLayer) {
layer: null, // "reindeer"
symbol: "",
story: "",
completedStory: ""
completedStory: "",
masteredStory: ""
})),
createDay(() => ({
day: 22,
@ -361,7 +485,8 @@ export const main = createLayer("main", function (this: BaseLayer) {
layer: null, // "sleigh"
symbol: "",
story: "",
completedStory: ""
completedStory: "",
masteredStory: ""
})),
createDay(() => ({
day: 23,
@ -369,7 +494,8 @@ export const main = createLayer("main", function (this: BaseLayer) {
layer: null, // "distribution route planning"
symbol: "",
story: "",
completedStory: ""
completedStory: "",
masteredStory: ""
})),
createDay(() => ({
day: 24,
@ -377,7 +503,8 @@ export const main = createLayer("main", function (this: BaseLayer) {
layer: null, // "packing the presents"
symbol: "",
story: "",
completedStory: ""
completedStory: "",
masteredStory: ""
}))
];
@ -392,17 +519,42 @@ export const main = createLayer("main", function (this: BaseLayer) {
save();
}
function completeMastery() {
const completedLayer = currentlyMastering.value;
if (completedLayer == null) {
return;
}
loreScene.value = (completedLayer as any).day - 1;
loreTitle.value = "Day Decorated!";
loreBody.value = days[loreScene.value].masteredStory;
showLoreModal.value = true;
if ((completedLayer as any).mastered != null) {
(completedLayer as any).mastered.value = true;
}
toggleMastery();
if (completedLayer.id === "cloth") {
elves.elves.plasticElf.bought.value = true;
}
}
return {
name: "Calendar",
days,
day,
openDay,
timeUntilNewDay,
loreScene,
loreTitle,
loreBody,
showLoreModal,
completeDay,
completeMastery,
minWidth: 700,
isMastery,
toggleMastery,
swappingMastery,
currentlyMastering,
masteredDays,
display: jsx(() => (
<>
{player.devSpeed === 0 ? <div>Game Paused</div> : null}
@ -413,7 +565,13 @@ export const main = createLayer("main", function (this: BaseLayer) {
<div>Offline Time: {formatTime(player.offlineTime)}</div>
) : null}
<Spacer />
<div class="advent">
{isMastery.value ? (
<>
<div>Now decorating {currentlyMastering.value?.name}</div>
<Spacer />
</>
) : null}
<div class={{ advent: true, decorating: isMastery.value }}>
{days
.reduce(
(acc, curr) => {
@ -452,10 +610,12 @@ export const getInitialLayers = (
oil,
plastic,
dyes,
wrappingPaper,
management,
factory,
letters
letters,
wrappingPaper,
ribbon,
toys,
factory
];
/**

View file

@ -28,7 +28,7 @@
<div
class="overlayTextContainer border"
:style="[
{ width: unref(width) + 'px', height: (unref(height) - 1) + 'px' },
{ width: unref(width) - 1 + 'px', height: unref(height) - 1 + 'px' },
unref(borderStyle) ?? {}
]"
>

View file

@ -13,6 +13,7 @@ import type {
import { processComputable } from "util/computed";
import { createLazyProxy } from "util/proxies";
import { shallowReactive, unref } from "vue";
import HotkeyVue from "components/Hotkey.vue";
export const hotkeys: Record<string, GenericHotkey | undefined> = shallowReactive({});
export const HotkeyType = Symbol("Hotkey");
@ -101,12 +102,14 @@ registerInfoComponent(
<div>
<br />
<h4>Hotkeys</h4>
<div style="column-count: 2">
{keys.map(hotkey => (
<div>
{hotkey?.key}: {hotkey?.description}
<HotkeyVue hotkey={hotkey as GenericHotkey} /> {hotkey?.description}
</div>
))}
</div>
</div>
);
})
);

View file

@ -1,5 +1,5 @@
import Select from "components/fields/Select.vue";
import type { CoercableComponent, OptionsFunc, Replace, StyleValue } from "features/feature";
import type { CoercableComponent, GenericComponent, OptionsFunc, Replace, StyleValue } from "features/feature";
import { Component, GatherProps, getUniqueID, jsx, setDefault, Visibility } from "features/feature";
import MilestoneComponent from "features/milestones/Milestone.vue";
import { globalBus } from "game/events";
@ -55,7 +55,7 @@ export interface BaseMilestone {
earned: Persistent<boolean>;
complete: VoidFunction;
type: typeof MilestoneType;
[Component]: typeof MilestoneComponent;
[Component]: GenericComponent;
[GatherProps]: () => Record<string, unknown>;
}
@ -85,7 +85,7 @@ export function createMilestone<T extends MilestoneOptions>(
const milestone = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>);
milestone.id = getUniqueID("milestone-");
milestone.type = MilestoneType;
milestone[Component] = MilestoneComponent;
milestone[Component] = MilestoneComponent as GenericComponent;
milestone.earned = earned;
milestone.complete = function () {

View file

@ -7,7 +7,12 @@
>
<div class="main-display">
<span v-if="showPrefix">You have </span>
<ResourceVue :resource="resource" :color="color || 'white'" :style="resourceStyle" />
<ResourceVue
:resource="resource"
:color="color || 'white'"
:shadow-color="shadowColor"
:style="resourceStyle"
/>
{{ resource.displayName
}}<!-- remove whitespace -->
<span v-if="effectComponent"
@ -20,14 +25,20 @@
</div>
</div>
</Sticky>
<div v-else
<div
v-else
class="main-display-container"
:class="classes ?? {}"
:style="[{ 'min-height': '50px' }, style ?? {}]"
>
<div class="main-display">
<span v-if="showPrefix">You have </span>
<ResourceVue :resource="resource" :color="color || 'white'" :style="resourceStyle" />
<ResourceVue
:resource="resource"
:color="color || 'white'"
:shadow-color="shadowColor"
:style="resourceStyle"
/>
{{ resource.displayName
}}<!-- remove whitespace -->
<span v-if="effectComponent"
@ -51,16 +62,20 @@ import { computeOptionalComponent } from "util/vue";
import { ComponentPublicInstance, ref, Ref, StyleValue } from "vue";
import { computed, toRefs } from "vue";
const _props = withDefaults(defineProps<{
const _props = withDefaults(
defineProps<{
resource: Resource;
color?: string;
shadowColor?: string;
classes?: Record<string, boolean>;
style?: StyleValue;
resourceStyle?: StyleValue;
effectDisplay?: CoercableComponent;
productionDisplay?: CoercableComponent;
sticky?: boolean
}>(), {sticky: true});
sticky?: boolean;
}>(),
{ sticky: true }
);
const props = toRefs(_props);
const effectRef = ref<ComponentPublicInstance | null>(null);

View file

@ -1,5 +1,5 @@
<template>
<h2 :style="[{ color, 'text-shadow': '0px 0px 10px ' + color }, style ?? {}]">
<h2 :style="[{ color, 'text-shadow': '0px 0px 10px ' + (shadowColor ?? color) }, style ?? {}]">
{{ amount }}
</h2>
</template>
@ -12,6 +12,7 @@ import { computed, StyleValue } from "vue";
const props = defineProps<{
resource: Resource;
color: string;
shadowColor?: string;
style?: StyleValue;
}>();

View file

@ -1,3 +1,4 @@
import { main } from "data/projEntry";
import { globalBus } from "game/events";
import { NonPersistent, Persistent, State } from "game/persistence";
import { persistent } from "game/persistence";
@ -67,7 +68,7 @@ export function trackBest(resource: Resource): Ref<DecimalSource> {
export function trackTotal(resource: Resource): Ref<DecimalSource> {
const total = persistent(resource.value);
watch(resource, (amount, prevAmount) => {
if (loadingSave.value) {
if (loadingSave.value || main.swappingMastery.value) {
return;
}
if (Decimal.gt(amount, prevAmount)) {

View file

@ -1,4 +1,10 @@
import type { CoercableComponent, GenericComponent, OptionsFunc, Replace, StyleValue } from "features/feature";
import type {
CoercableComponent,
GenericComponent,
OptionsFunc,
Replace,
StyleValue
} from "features/feature";
import {
Component,
findFeatures,
@ -23,7 +29,7 @@ import type {
} from "util/computed";
import { processComputable } from "util/computed";
import { createLazyProxy } from "util/proxies";
import type { Ref } from "vue";
import { isReadonly, Ref } from "vue";
import { computed, unref } from "vue";
export const UpgradeType = Symbol("Upgrade");
@ -120,7 +126,11 @@ export function createUpgrade<T extends UpgradeOptions>(
if (!unref(genericUpgrade.canPurchase)) {
return;
}
if (genericUpgrade.resource != null && genericUpgrade.cost != null) {
if (
genericUpgrade.resource != null &&
!isReadonly(genericUpgrade.resource) &&
genericUpgrade.cost != null
) {
genericUpgrade.resource.value = Decimal.sub(
genericUpgrade.resource.value,
unref(genericUpgrade.cost)

View file

@ -137,6 +137,33 @@ ul {
background: #070710;
}
.unaffordable {
color: var(--danger);
}
.decoration-effect {
border: solid 8px darkred;
padding: 4px;
width: 576px;
position: relative;
border-radius: 10px;
}
.decoration-effect:not(.ribbon) {
border-image: repeating-linear-gradient(-45deg, rgb(255, 76, 76) 0 10px, rgb(255, 255, 255) 10px 20px, rgb(65, 255, 95) 20px 30px, rgb(255, 255, 255) 30px 40px) 12/10px;
}
.decoration-effect.ribbon::before {
content: "🎀";
color: red;
position: absolute;
top: -20px;
left: -20px;
font-size: xx-large;
transform: rotateZ(-45deg);
z-index: 1;
}
.layer-container {
min-width: 100%;
min-height: 100%;

View file

@ -229,7 +229,7 @@ export function computeOptionalComponent(
const comp = shallowRef<Component | "" | null>(null);
watchEffect(() => {
const currComponent = unwrapRef(component);
comp.value = currComponent == null ? null : coerceComponent(currComponent, defaultWrapper);
comp.value = !currComponent ? null : coerceComponent(currComponent, defaultWrapper);
});
return comp;
}