forked from profectus/Profectus
Fixed runtime issues with vue
Entire demo tree has been tested and is fully functional, including all the options and save manager functionality
This commit is contained in:
parent
ebf26c58e7
commit
e2126472b2
77 changed files with 2941 additions and 1862 deletions
|
@ -5,7 +5,10 @@
|
|||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&display=swap" rel="stylesheet">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Kalam&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
<div class="app" @mousemove="updateMouse" :style="theme" :class="{ useHeader }">
|
||||
<Nav v-if="useHeader" />
|
||||
<Game />
|
||||
<TPS v-if="showTPS" />
|
||||
<TPS v-if="unref(showTPS)" />
|
||||
<GameOverScreen />
|
||||
<NaNScreen />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, toRef } from "vue";
|
||||
import { computed, toRef, unref } from "vue";
|
||||
import Game from "./components/system/Game.vue";
|
||||
import GameOverScreen from "./components/system/GameOverScreen.vue";
|
||||
import NaNScreen from "./components/system/NaNScreen.vue";
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
.notify.floating,
|
||||
*:not(.subtabs) > .notify,
|
||||
*:not(.subtabs) > .notify button {
|
||||
transform: scale(1.05, 1.05);
|
||||
border-color: rgba(0, 0, 0, 0.125);
|
||||
box-shadow: -4px -4px 4px rgba(0, 0, 0, 0.25) inset, 0 0 20px #ff0000;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.subtabs > .notify:not(.floating) {
|
||||
box-shadow: 0px 20px 15px -15px #ff0000;
|
||||
}
|
||||
|
||||
*:not(.subtabs) > .resetNotify:not(.notify),
|
||||
*:not(.subtabs) > .resetNotify:not(.notify) button {
|
||||
box-shadow: -4px -4px 4px rgba(0, 0, 0, 0.25) inset, 0 0 8px #ffffff;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.subtabs > .resetNotify:not(.notify):not(.floating) {
|
||||
box-shadow: 0px 10px 8px -10px #ffffff;
|
||||
}
|
|
@ -33,3 +33,59 @@
|
|||
height: 100%;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.row.mergeAdjacent > .feature,
|
||||
.row.mergeAdjacent > .tooltip-container > .feature {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.row.mergeAdjacent > .feature:first-child,
|
||||
.row.mergeAdjacent > .tooltip-container:first-child > .feature {
|
||||
border-radius: var(--border-radius) 0 0 var(--border-radius);
|
||||
}
|
||||
|
||||
.row.mergeAdjacent > .feature:last-child,
|
||||
.row.mergeAdjacent > .tooltip-container:last-child > .feature {
|
||||
border-radius: 0 var(--border-radius) var(--border-radius) 0;
|
||||
}
|
||||
|
||||
.row.mergeAdjacent > .feature:first-child:last-child,
|
||||
.row.mergeAdjacent > .tooltip-container:first-child:last-child > .feature {
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
/*
|
||||
TODO how to implement mergeAdjacent for grids?
|
||||
.row.mergeAdjacent + .row.mergeAdjacent > .feature {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
*/
|
||||
|
||||
.col.mergeAdjacent .feature {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.col.mergeAdjacent .feature:first-child {
|
||||
border-radius: var(--border-radius) var(--border-radius) 0 0;
|
||||
}
|
||||
|
||||
.col.mergeAdjacent .feature:last-child {
|
||||
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
||||
}
|
||||
|
||||
.col.mergeAdjacent .feature:first-child:last-child {
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
/*
|
||||
TODO how to implement mergeAdjacent for grids?
|
||||
.col.mergeAdjacent + .col.mergeAdjacent > .feature {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
*/
|
||||
|
|
|
@ -1,63 +1,67 @@
|
|||
<template>
|
||||
<Tooltip
|
||||
v-if="visibility !== Visibility.None"
|
||||
v-show="visibility === Visibility.Visible"
|
||||
:display="tooltip"
|
||||
<div
|
||||
v-if="unref(visibility) !== Visibility.None"
|
||||
:style="[
|
||||
{
|
||||
visibility: unref(visibility) === Visibility.Hidden ? 'hidden' : undefined,
|
||||
backgroundImage: (earned && image && `url(${image})`) || ''
|
||||
},
|
||||
unref(style) ?? []
|
||||
]"
|
||||
:class="{
|
||||
feature: true,
|
||||
achievement: true,
|
||||
locked: !unref(earned),
|
||||
bought: unref(earned),
|
||||
...unref(classes)
|
||||
}"
|
||||
>
|
||||
<div
|
||||
:style="[{ backgroundImage: (earned && image && `url(${image})`) || '' }, style ?? []]"
|
||||
:class="{
|
||||
feature: true,
|
||||
achievement: true,
|
||||
locked: !earned,
|
||||
bought: earned,
|
||||
...classes
|
||||
}"
|
||||
>
|
||||
<component v-if="component" :is="component" />
|
||||
<MarkNode :mark="mark" />
|
||||
<LinkNode :id="id" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
<component v-if="component" :is="component" />
|
||||
<MarkNode :mark="unref(mark)" />
|
||||
<LinkNode :id="id" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { CoercableComponent, Visibility } from "@/features/feature";
|
||||
import { coerceComponent } from "@/util/vue";
|
||||
import { computed, defineComponent, PropType, StyleValue, toRefs } from "vue";
|
||||
import { computeOptionalComponent, processedPropType } from "@/util/vue";
|
||||
import { defineComponent, StyleValue, toRefs, unref } from "vue";
|
||||
import Tooltip from "@/components/system/Tooltip.vue";
|
||||
import LinkNode from "../system/LinkNode.vue";
|
||||
import MarkNode from "./MarkNode.vue";
|
||||
import "@/components/common/features.css";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
visibility: {
|
||||
type: Object as PropType<Visibility>,
|
||||
type: processedPropType<Visibility>(Number),
|
||||
required: true
|
||||
},
|
||||
display: [Object, String] as PropType<CoercableComponent>,
|
||||
tooltip: [Object, String] as PropType<CoercableComponent>,
|
||||
display: processedPropType<CoercableComponent>(Object, String, Function),
|
||||
earned: {
|
||||
type: Boolean,
|
||||
type: processedPropType<boolean>(Boolean),
|
||||
required: true
|
||||
},
|
||||
image: String,
|
||||
style: Object as PropType<StyleValue>,
|
||||
classes: Object as PropType<Record<string, boolean>>,
|
||||
mark: [Boolean, String],
|
||||
image: processedPropType<string>(String),
|
||||
style: processedPropType<StyleValue>(String, Object, Array),
|
||||
classes: processedPropType<Record<string, boolean>>(Object),
|
||||
mark: processedPropType<boolean | string>(Boolean, String),
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
components: {
|
||||
LinkNode,
|
||||
MarkNode,
|
||||
Tooltip
|
||||
},
|
||||
setup(props) {
|
||||
const { display } = toRefs(props);
|
||||
|
||||
return {
|
||||
component: computed(() => {
|
||||
return display.value && coerceComponent(display.value);
|
||||
}),
|
||||
LinkNode,
|
||||
MarkNode,
|
||||
component: computeOptionalComponent(display),
|
||||
unref,
|
||||
Visibility
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,31 +1,45 @@
|
|||
<template>
|
||||
<div
|
||||
v-if="visibility !== Visibility.None"
|
||||
v-show="visibility === Visibility.Visible"
|
||||
:style="[{ width: width + 'px', height: height + 'px' }, style ?? {}]"
|
||||
v-if="unref(visibility) !== Visibility.None"
|
||||
:style="[
|
||||
{
|
||||
width: unref(width) + 'px',
|
||||
height: unref(height) + 'px',
|
||||
visibility: unref(visibility) === Visibility.Hidden ? 'hidden' : undefined
|
||||
},
|
||||
unref(style) ?? {}
|
||||
]"
|
||||
:class="{
|
||||
bar: true,
|
||||
...classes
|
||||
...unref(classes)
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="overlayTextContainer border"
|
||||
:style="[{ width: width + 'px', height: height + 'px' }, borderStyle ?? {}]"
|
||||
:style="[
|
||||
{ width: unref(width) + 'px', height: unref(height) + 'px' },
|
||||
unref(borderStyle) ?? {}
|
||||
]"
|
||||
>
|
||||
<component v-if="component" class="overlayText" :style="textStyle" :is="component" />
|
||||
<component
|
||||
v-if="component"
|
||||
class="overlayText"
|
||||
:style="unref(textStyle)"
|
||||
:is="component"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="border"
|
||||
:style="[
|
||||
{ width: width + 'px', height: height + 'px' },
|
||||
style ?? {},
|
||||
baseStyle ?? {},
|
||||
borderStyle ?? {}
|
||||
{ width: unref(width) + 'px', height: unref(height) + 'px' },
|
||||
unref(style) ?? {},
|
||||
unref(baseStyle) ?? {},
|
||||
unref(borderStyle) ?? {}
|
||||
]"
|
||||
>
|
||||
<div class="fill" :style="[barStyle, style ?? {}, fillStyle ?? {}]" />
|
||||
<div class="fill" :style="[barStyle, unref(style) ?? {}, unref(fillStyle) ?? {}]" />
|
||||
</div>
|
||||
<MarkNode :mark="mark" />
|
||||
<MarkNode :mark="unref(mark)" />
|
||||
<LinkNode :id="id" />
|
||||
</div>
|
||||
</template>
|
||||
|
@ -34,46 +48,50 @@
|
|||
import { Direction } from "@/features/bar";
|
||||
import { CoercableComponent, Visibility } from "@/features/feature";
|
||||
import Decimal, { DecimalSource } from "@/util/bignum";
|
||||
import { coerceComponent } from "@/util/vue";
|
||||
import { computed, CSSProperties, defineComponent, PropType, StyleValue, toRefs, unref } from "vue";
|
||||
import { computeOptionalComponent, processedPropType, unwrapRef } from "@/util/vue";
|
||||
import { computed, CSSProperties, defineComponent, StyleValue, toRefs, unref } from "vue";
|
||||
import LinkNode from "../system/LinkNode.vue";
|
||||
import MarkNode from "./MarkNode.vue";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
progress: {
|
||||
type: Object as PropType<DecimalSource>,
|
||||
type: processedPropType<DecimalSource>(String, Object, Number),
|
||||
required: true
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
type: processedPropType<number>(Number),
|
||||
required: true
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
type: processedPropType<number>(Number),
|
||||
required: true
|
||||
},
|
||||
direction: {
|
||||
type: Object as PropType<Direction>,
|
||||
type: processedPropType<Direction>(String),
|
||||
required: true
|
||||
},
|
||||
display: [Object, String] as PropType<CoercableComponent>,
|
||||
display: processedPropType<CoercableComponent>(Object, String, Function),
|
||||
visibility: {
|
||||
type: Object as PropType<Visibility>,
|
||||
type: processedPropType<Visibility>(Number),
|
||||
required: true
|
||||
},
|
||||
style: Object as PropType<StyleValue>,
|
||||
classes: Object as PropType<Record<string, boolean>>,
|
||||
borderStyle: Object as PropType<StyleValue>,
|
||||
textStyle: Object as PropType<StyleValue>,
|
||||
baseStyle: Object as PropType<StyleValue>,
|
||||
fillStyle: Object as PropType<StyleValue>,
|
||||
mark: [Boolean, String],
|
||||
style: processedPropType<StyleValue>(Object, String, Array),
|
||||
classes: processedPropType<Record<string, boolean>>(Object),
|
||||
borderStyle: processedPropType<StyleValue>(Object, String, Array),
|
||||
textStyle: processedPropType<StyleValue>(Object, String, Array),
|
||||
baseStyle: processedPropType<StyleValue>(Object, String, Array),
|
||||
fillStyle: processedPropType<StyleValue>(Object, String, Array),
|
||||
mark: processedPropType<boolean | string>(Boolean, String),
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
components: {
|
||||
MarkNode,
|
||||
LinkNode
|
||||
},
|
||||
setup(props) {
|
||||
const { progress, width, height, direction, display } = toRefs(props);
|
||||
|
||||
|
@ -87,17 +105,17 @@ export default defineComponent({
|
|||
|
||||
const barStyle = computed(() => {
|
||||
const barStyle: Partial<CSSProperties> = {
|
||||
width: unref(width) + 0.5 + "px",
|
||||
height: unref(height) + 0.5 + "px"
|
||||
width: unwrapRef(width) + 0.5 + "px",
|
||||
height: unwrapRef(height) + 0.5 + "px"
|
||||
};
|
||||
switch (unref(direction)) {
|
||||
case Direction.Up:
|
||||
barStyle.clipPath = `inset(${normalizedProgress.value}% 0% 0% 0%)`;
|
||||
barStyle.width = unref(width) + 1 + "px";
|
||||
barStyle.width = unwrapRef(width) + 1 + "px";
|
||||
break;
|
||||
case Direction.Down:
|
||||
barStyle.clipPath = `inset(0% 0% ${normalizedProgress.value}% 0%)`;
|
||||
barStyle.width = unref(width) + 1 + "px";
|
||||
barStyle.width = unwrapRef(width) + 1 + "px";
|
||||
break;
|
||||
case Direction.Right:
|
||||
barStyle.clipPath = `inset(0% ${normalizedProgress.value}% 0% 0%)`;
|
||||
|
@ -112,17 +130,13 @@ export default defineComponent({
|
|||
return barStyle;
|
||||
});
|
||||
|
||||
const component = computed(() => {
|
||||
const currDisplay = unref(display);
|
||||
return currDisplay && coerceComponent(unref(currDisplay));
|
||||
});
|
||||
const component = computeOptionalComponent(display);
|
||||
|
||||
return {
|
||||
normalizedProgress,
|
||||
barStyle,
|
||||
component,
|
||||
MarkNode,
|
||||
LinkNode,
|
||||
unref,
|
||||
Visibility
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,66 +1,85 @@
|
|||
<template>
|
||||
<div
|
||||
v-if="visibility !== Visibility.None"
|
||||
v-show="visibility === Visibility.Visible"
|
||||
:style="style"
|
||||
v-if="unref(visibility) !== Visibility.None"
|
||||
:style="[
|
||||
{
|
||||
visibility: unref(visibility) === Visibility.Hidden ? 'hidden' : undefined
|
||||
},
|
||||
notifyStyle,
|
||||
unref(style) ?? {}
|
||||
]"
|
||||
:class="{
|
||||
feature: true,
|
||||
challenge: true,
|
||||
resetNotify: active,
|
||||
notify: active && canComplete,
|
||||
done: completed,
|
||||
canStart,
|
||||
maxed,
|
||||
...classes
|
||||
done: unref(completed),
|
||||
canStart: unref(canStart),
|
||||
maxed: unref(maxed),
|
||||
...unref(classes)
|
||||
}"
|
||||
>
|
||||
<button class="toggleChallenge" @click="toggle">
|
||||
{{ buttonText }}
|
||||
</button>
|
||||
<component v-if="component" :is="component" />
|
||||
<MarkNode :mark="mark" />
|
||||
<component v-if="unref(comp)" :is="unref(comp)" />
|
||||
<MarkNode :mark="unref(mark)" />
|
||||
<LinkNode :id="id" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="tsx">
|
||||
import "@/components/common/features.css";
|
||||
import { GenericChallenge } from "@/features/challenge";
|
||||
import { StyleValue, Visibility } from "@/features/feature";
|
||||
import { coerceComponent, isCoercableComponent } from "@/util/vue";
|
||||
import { computed, defineComponent, PropType, toRefs, UnwrapRef } from "vue";
|
||||
import { jsx, StyleValue, Visibility } from "@/features/feature";
|
||||
import { getHighNotifyStyle, getNotifyStyle } from "@/game/notifications";
|
||||
import { coerceComponent, isCoercableComponent, processedPropType, unwrapRef } from "@/util/vue";
|
||||
import {
|
||||
Component,
|
||||
computed,
|
||||
defineComponent,
|
||||
PropType,
|
||||
shallowRef,
|
||||
toRefs,
|
||||
unref,
|
||||
UnwrapRef,
|
||||
watchEffect
|
||||
} from "vue";
|
||||
import LinkNode from "../system/LinkNode.vue";
|
||||
import MarkNode from "./MarkNode.vue";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
active: {
|
||||
type: Boolean,
|
||||
type: processedPropType<boolean>(Boolean),
|
||||
required: true
|
||||
},
|
||||
maxed: {
|
||||
type: Boolean,
|
||||
type: processedPropType<boolean>(Boolean),
|
||||
required: true
|
||||
},
|
||||
canComplete: {
|
||||
type: Boolean,
|
||||
type: processedPropType<boolean>(Boolean),
|
||||
required: true
|
||||
},
|
||||
display: Object as PropType<UnwrapRef<GenericChallenge["display"]>>,
|
||||
display: processedPropType<UnwrapRef<GenericChallenge["display"]>>(
|
||||
String,
|
||||
Object,
|
||||
Function
|
||||
),
|
||||
visibility: {
|
||||
type: Object as PropType<Visibility>,
|
||||
type: processedPropType<Visibility>(Number),
|
||||
required: true
|
||||
},
|
||||
style: Object as PropType<StyleValue>,
|
||||
classes: Object as PropType<Record<string, boolean>>,
|
||||
style: processedPropType<StyleValue>(String, Object, Array),
|
||||
classes: processedPropType<Record<string, boolean>>(Object),
|
||||
completed: {
|
||||
type: Boolean,
|
||||
type: processedPropType<boolean>(Boolean),
|
||||
required: true
|
||||
},
|
||||
canStart: {
|
||||
type: Boolean,
|
||||
type: processedPropType<boolean>(Boolean),
|
||||
required: true
|
||||
},
|
||||
mark: [Boolean, String],
|
||||
mark: processedPropType<boolean | string>(Boolean, String),
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
|
@ -70,6 +89,10 @@ export default defineComponent({
|
|||
required: true
|
||||
}
|
||||
},
|
||||
components: {
|
||||
MarkNode,
|
||||
LinkNode
|
||||
},
|
||||
setup(props) {
|
||||
const { active, maxed, canComplete, display } = toRefs(props);
|
||||
|
||||
|
@ -83,46 +106,72 @@ export default defineComponent({
|
|||
return "Start";
|
||||
});
|
||||
|
||||
const component = computed(() => {
|
||||
const currDisplay = display.value;
|
||||
const comp = shallowRef<Component | string>("");
|
||||
|
||||
const notifyStyle = computed(() => {
|
||||
const currActive = unwrapRef(active);
|
||||
const currCanComplete = unwrapRef(canComplete);
|
||||
if (currActive) {
|
||||
if (currCanComplete) {
|
||||
return getHighNotifyStyle();
|
||||
}
|
||||
return getNotifyStyle();
|
||||
}
|
||||
return {};
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
const currDisplay = unwrapRef(display);
|
||||
if (currDisplay == null) {
|
||||
return null;
|
||||
comp.value = "";
|
||||
return;
|
||||
}
|
||||
if (isCoercableComponent(currDisplay)) {
|
||||
return coerceComponent(currDisplay);
|
||||
comp.value = coerceComponent(currDisplay);
|
||||
return;
|
||||
}
|
||||
return (
|
||||
<span>
|
||||
<template v-if={currDisplay.title}>
|
||||
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
||||
<component v-is={coerceComponent(currDisplay.title!, "h3")} />
|
||||
</template>
|
||||
<component v-is={coerceComponent(currDisplay.description, "div")} />
|
||||
<div v-if={currDisplay.goal}>
|
||||
<br />
|
||||
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
||||
Goal: <component v-is={coerceComponent(currDisplay.goal!)} />
|
||||
</div>
|
||||
<div v-if={currDisplay.reward}>
|
||||
<br />
|
||||
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
||||
Reward: <component v-is={coerceComponent(currDisplay.reward!)} />
|
||||
</div>
|
||||
<div v-if={currDisplay.effectDisplay}>
|
||||
Currently:{" "}
|
||||
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
||||
<component v-is={coerceComponent(currDisplay.effectDisplay!)} />
|
||||
</div>
|
||||
</span>
|
||||
const Title = coerceComponent(currDisplay.title || "", "h3");
|
||||
const Description = coerceComponent(currDisplay.description, "div");
|
||||
const Goal = coerceComponent(currDisplay.goal || "");
|
||||
const Reward = coerceComponent(currDisplay.reward || "");
|
||||
const EffectDisplay = coerceComponent(currDisplay.effectDisplay || "");
|
||||
comp.value = coerceComponent(
|
||||
jsx(() => (
|
||||
<span>
|
||||
{currDisplay.title ? (
|
||||
<div>
|
||||
<Title />
|
||||
</div>
|
||||
) : null}
|
||||
<Description />
|
||||
{currDisplay.goal ? (
|
||||
<div>
|
||||
<br />
|
||||
Goal: <Goal />
|
||||
</div>
|
||||
) : null}
|
||||
{currDisplay.reward ? (
|
||||
<div>
|
||||
<br />
|
||||
Reward: <Reward />
|
||||
</div>
|
||||
) : null}
|
||||
{currDisplay.effectDisplay ? (
|
||||
<div>
|
||||
Currently: <EffectDisplay />
|
||||
</div>
|
||||
) : null}
|
||||
</span>
|
||||
))
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
buttonText,
|
||||
component,
|
||||
MarkNode,
|
||||
LinkNode,
|
||||
Visibility
|
||||
notifyStyle,
|
||||
comp,
|
||||
Visibility,
|
||||
unref
|
||||
};
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
<template>
|
||||
<div v-if="visibility !== Visibility.None" v-show="visibility === Visibility.Visible">
|
||||
<div
|
||||
v-if="unref(visibility) !== Visibility.None"
|
||||
:style="{ visibility: unref(visibility) === Visibility.Hidden ? 'hidden' : undefined }"
|
||||
>
|
||||
<button
|
||||
:style="style"
|
||||
:style="unref(style)"
|
||||
@click="onClick"
|
||||
@mousedown="start"
|
||||
@mouseleave="stop"
|
||||
|
@ -9,18 +12,18 @@
|
|||
@touchstart="start"
|
||||
@touchend="stop"
|
||||
@touchcancel="stop"
|
||||
:disabled="!canClick"
|
||||
:disabled="!unref(canClick)"
|
||||
:class="{
|
||||
feature: true,
|
||||
clickable: true,
|
||||
can: canClick,
|
||||
locked: !canClick,
|
||||
can: unref(canClick),
|
||||
locked: !unref(canClick),
|
||||
small,
|
||||
...classes
|
||||
...unref(classes)
|
||||
}"
|
||||
>
|
||||
<component v-if="component" :is="component" />
|
||||
<MarkNode :mark="mark" />
|
||||
<component v-if="unref(comp)" :is="unref(comp)" />
|
||||
<MarkNode :mark="unref(mark)" />
|
||||
<LinkNode :id="id" />
|
||||
</button>
|
||||
</div>
|
||||
|
@ -28,56 +31,89 @@
|
|||
|
||||
<script lang="tsx">
|
||||
import { GenericClickable } from "@/features/clickable";
|
||||
import { StyleValue, Visibility } from "@/features/feature";
|
||||
import { coerceComponent, isCoercableComponent, setupHoldToClick } from "@/util/vue";
|
||||
import { computed, defineComponent, PropType, toRefs, unref, UnwrapRef } from "vue";
|
||||
import { jsx, StyleValue, Visibility } from "@/features/feature";
|
||||
import {
|
||||
coerceComponent,
|
||||
isCoercableComponent,
|
||||
processedPropType,
|
||||
setupHoldToClick,
|
||||
unwrapRef
|
||||
} from "@/util/vue";
|
||||
import {
|
||||
Component,
|
||||
defineComponent,
|
||||
PropType,
|
||||
shallowRef,
|
||||
toRefs,
|
||||
unref,
|
||||
UnwrapRef,
|
||||
watchEffect
|
||||
} from "vue";
|
||||
import LinkNode from "../system/LinkNode.vue";
|
||||
import MarkNode from "./MarkNode.vue";
|
||||
import "@/components/common/features.css";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
display: {
|
||||
type: Object as PropType<UnwrapRef<GenericClickable["display"]>>,
|
||||
type: processedPropType<UnwrapRef<GenericClickable["display"]>>(
|
||||
Object,
|
||||
String,
|
||||
Function
|
||||
),
|
||||
required: true
|
||||
},
|
||||
visibility: {
|
||||
type: Object as PropType<Visibility>,
|
||||
type: processedPropType<Visibility>(Number),
|
||||
required: true
|
||||
},
|
||||
style: Object as PropType<StyleValue>,
|
||||
classes: Object as PropType<Record<string, boolean>>,
|
||||
style: processedPropType<StyleValue>(Object, String, Array),
|
||||
classes: processedPropType<Record<string, boolean>>(Object),
|
||||
onClick: Function as PropType<VoidFunction>,
|
||||
onHold: Function as PropType<VoidFunction>,
|
||||
canClick: {
|
||||
type: Boolean,
|
||||
type: processedPropType<boolean>(Boolean),
|
||||
required: true
|
||||
},
|
||||
small: Boolean,
|
||||
mark: [Boolean, String],
|
||||
mark: processedPropType<boolean | string>(Boolean, String),
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
components: {
|
||||
LinkNode,
|
||||
MarkNode
|
||||
},
|
||||
setup(props) {
|
||||
const { display, onClick, onHold } = toRefs(props);
|
||||
|
||||
const component = computed(() => {
|
||||
const currDisplay = unref(display);
|
||||
const comp = shallowRef<Component | string>("");
|
||||
|
||||
watchEffect(() => {
|
||||
const currDisplay = unwrapRef(display);
|
||||
if (currDisplay == null) {
|
||||
return null;
|
||||
comp.value = "";
|
||||
return;
|
||||
}
|
||||
if (isCoercableComponent(currDisplay)) {
|
||||
return coerceComponent(currDisplay);
|
||||
comp.value = coerceComponent(currDisplay);
|
||||
return;
|
||||
}
|
||||
return (
|
||||
<span>
|
||||
<div v-if={currDisplay.title}>
|
||||
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
||||
<component v-is={coerceComponent(currDisplay.title!, "h2")} />
|
||||
</div>
|
||||
<component v-is={coerceComponent(currDisplay.description, "div")} />
|
||||
</span>
|
||||
const Title = coerceComponent(currDisplay.title || "", "h3");
|
||||
const Description = coerceComponent(currDisplay.description, "div");
|
||||
comp.value = coerceComponent(
|
||||
jsx(() => (
|
||||
<span>
|
||||
{currDisplay.title ? (
|
||||
<div>
|
||||
<Title />
|
||||
</div>
|
||||
) : null}
|
||||
<Description />
|
||||
</span>
|
||||
))
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -86,10 +122,9 @@ export default defineComponent({
|
|||
return {
|
||||
start,
|
||||
stop,
|
||||
component,
|
||||
LinkNode,
|
||||
MarkNode,
|
||||
Visibility
|
||||
comp,
|
||||
Visibility,
|
||||
unref
|
||||
};
|
||||
}
|
||||
});
|
||||
|
@ -101,4 +136,8 @@ export default defineComponent({
|
|||
width: 120px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.clickable.small {
|
||||
min-height: unset;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
<template>
|
||||
<div
|
||||
v-if="visibility !== Visibility.None"
|
||||
v-show="visibility === Visibility.Visible"
|
||||
v-if="unref(visibility) !== Visibility.None"
|
||||
:style="{
|
||||
visibility: unref(visibility) === Visibility.Hidden ? 'hidden' : undefined
|
||||
}"
|
||||
class="table"
|
||||
>
|
||||
<div v-for="row in rows" class="row" :key="row">
|
||||
<div v-for="col in cols" :key="col">
|
||||
<GridCell v-bind="cells[row * 100 + col]" />
|
||||
</div>
|
||||
<div v-for="row in unref(rows)" class="row" :class="{ mergeAdjacent }" :key="row">
|
||||
<GridCell
|
||||
v-for="col in unref(cols)"
|
||||
:key="col"
|
||||
v-bind="gatherCellProps(unref(cells)[row * 100 + col])"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -15,30 +19,42 @@
|
|||
<script lang="ts">
|
||||
import { Visibility } from "@/features/feature";
|
||||
import { GridCell } from "@/features/grid";
|
||||
import { defineComponent, PropType } from "vue";
|
||||
import { processedPropType } from "@/util/vue";
|
||||
import { computed, defineComponent, unref } from "vue";
|
||||
import GridCellVue from "./GridCell.vue";
|
||||
import "@/components/common/table.css";
|
||||
import settings from "@/game/settings";
|
||||
import themes from "@/data/themes";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
visibility: {
|
||||
type: Object as PropType<Visibility>,
|
||||
type: processedPropType<Visibility>(Number),
|
||||
required: true
|
||||
},
|
||||
rows: {
|
||||
type: Number,
|
||||
type: processedPropType<number>(Number),
|
||||
required: true
|
||||
},
|
||||
cols: {
|
||||
type: Number,
|
||||
type: processedPropType<number>(Number),
|
||||
required: true
|
||||
},
|
||||
cells: {
|
||||
type: Object as PropType<Record<string, GridCell>>,
|
||||
type: processedPropType<Record<string, GridCell>>(Object),
|
||||
required: true
|
||||
}
|
||||
},
|
||||
components: { GridCell: GridCellVue },
|
||||
setup() {
|
||||
return { GridCell: GridCellVue, Visibility };
|
||||
const mergeAdjacent = computed(() => themes[settings.theme].mergeAdjacent);
|
||||
|
||||
function gatherCellProps(cell: GridCell) {
|
||||
const { visibility, onClick, onHold, display, title, style, canClick, id } = cell;
|
||||
return { visibility, onClick, onHold, display, title, style, canClick, id };
|
||||
}
|
||||
|
||||
return { unref, gatherCellProps, Visibility, mergeAdjacent };
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
<template>
|
||||
<button
|
||||
v-if="visibility !== Visibility.None"
|
||||
v-show="visibility === Visibility.Visible"
|
||||
:class="{ feature: true, tile: true, can: canClick, locked: !canClick }"
|
||||
:style="style"
|
||||
v-if="unref(visibility) !== Visibility.None"
|
||||
:class="{ feature: true, tile: true, can: unref(canClick), locked: !unref(canClick) }"
|
||||
:style="[
|
||||
{
|
||||
visibility: unref(visibility) === Visibility.Hidden ? 'hidden' : undefined
|
||||
},
|
||||
unref(style) ?? {}
|
||||
]"
|
||||
@click="onClick"
|
||||
@mousedown="start"
|
||||
@mouseleave="stop"
|
||||
|
@ -11,7 +15,7 @@
|
|||
@touchstart="start"
|
||||
@touchend="stop"
|
||||
@touchcancel="stop"
|
||||
:disabled="!canClick"
|
||||
:disabled="!unref(canClick)"
|
||||
>
|
||||
<div v-if="title"><component :is="titleComponent" /></div>
|
||||
<component :is="component" style="white-space: pre-line" />
|
||||
|
@ -21,26 +25,32 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { CoercableComponent, StyleValue, Visibility } from "@/features/feature";
|
||||
import { coerceComponent, setupHoldToClick } from "@/util/vue";
|
||||
import { computed, defineComponent, PropType, toRefs, unref } from "vue";
|
||||
import {
|
||||
computeComponent,
|
||||
computeOptionalComponent,
|
||||
processedPropType,
|
||||
setupHoldToClick
|
||||
} from "@/util/vue";
|
||||
import { defineComponent, PropType, toRefs, unref } from "vue";
|
||||
import LinkNode from "../system/LinkNode.vue";
|
||||
import "@/components/common/features.css";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
visibility: {
|
||||
type: Object as PropType<Visibility>,
|
||||
type: processedPropType<Visibility>(Number),
|
||||
required: true
|
||||
},
|
||||
onClick: Function as PropType<VoidFunction>,
|
||||
onHold: Function as PropType<VoidFunction>,
|
||||
display: {
|
||||
type: [Object, String] as PropType<CoercableComponent>,
|
||||
type: processedPropType<CoercableComponent>(Object, String, Function),
|
||||
required: true
|
||||
},
|
||||
title: [Object, String] as PropType<CoercableComponent>,
|
||||
style: Object as PropType<StyleValue>,
|
||||
title: processedPropType<CoercableComponent>(Object, String, Function),
|
||||
style: processedPropType<StyleValue>(String, Object, Array),
|
||||
canClick: {
|
||||
type: Boolean,
|
||||
type: processedPropType<boolean>(Boolean),
|
||||
required: true
|
||||
},
|
||||
id: {
|
||||
|
@ -48,16 +58,16 @@ export default defineComponent({
|
|||
required: true
|
||||
}
|
||||
},
|
||||
components: {
|
||||
LinkNode
|
||||
},
|
||||
setup(props) {
|
||||
const { onClick, onHold, title, display } = toRefs(props);
|
||||
|
||||
const { start, stop } = setupHoldToClick(onClick, onHold);
|
||||
|
||||
const titleComponent = computed(() => {
|
||||
const currTitle = unref(title);
|
||||
return currTitle && coerceComponent(currTitle);
|
||||
});
|
||||
const component = computed(() => coerceComponent(unref(display)));
|
||||
const titleComponent = computeOptionalComponent(title);
|
||||
const component = computeComponent(display);
|
||||
|
||||
return {
|
||||
start,
|
||||
|
@ -65,7 +75,7 @@ export default defineComponent({
|
|||
titleComponent,
|
||||
component,
|
||||
Visibility,
|
||||
LinkNode
|
||||
unref
|
||||
};
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,22 +1,27 @@
|
|||
<template>
|
||||
<div
|
||||
class="infobox"
|
||||
v-if="visibility !== Visibility.None"
|
||||
v-show="visibility === Visibility.Visible"
|
||||
:style="[{ borderColor: color }, style || []]"
|
||||
:class="{ collapsed, stacked, ...classes }"
|
||||
v-if="unref(visibility) !== Visibility.None"
|
||||
:style="[
|
||||
{
|
||||
borderColor: unref(color),
|
||||
visibility: unref(visibility) === Visibility.Hidden ? 'hidden' : undefined
|
||||
},
|
||||
unref(style) ?? {}
|
||||
]"
|
||||
:class="{ collapsed: unref(collapsed), stacked, ...unref(classes) }"
|
||||
>
|
||||
<button
|
||||
class="title"
|
||||
:style="[{ backgroundColor: color }, titleStyle || []]"
|
||||
@click="collapsed = !collapsed"
|
||||
:style="[{ backgroundColor: unref(color) }, unref(titleStyle) || []]"
|
||||
@click="collapsed.value = !unref(collapsed)"
|
||||
>
|
||||
<span class="toggle">▼</span>
|
||||
<component :is="titleComponent" />
|
||||
</button>
|
||||
<CollapseTransition>
|
||||
<div v-if="!collapsed" class="body" :style="{ backgroundColor: color }">
|
||||
<component :is="bodyComponent" :style="bodyStyle" />
|
||||
<div v-if="!unref(collapsed)" class="body" :style="{ backgroundColor: unref(color) }">
|
||||
<component :is="bodyComponent" :style="unref(bodyStyle)" />
|
||||
</div>
|
||||
</CollapseTransition>
|
||||
<LinkNode :id="id" />
|
||||
|
@ -27,49 +32,55 @@
|
|||
import themes from "@/data/themes";
|
||||
import { CoercableComponent, Visibility } from "@/features/feature";
|
||||
import settings from "@/game/settings";
|
||||
import { coerceComponent } from "@/util/vue";
|
||||
import { computeComponent, processedPropType } from "@/util/vue";
|
||||
import CollapseTransition from "@ivanv/vue-collapse-transition/src/CollapseTransition.vue";
|
||||
import { computed, defineComponent, PropType, StyleValue, toRefs } from "vue";
|
||||
import { computed, defineComponent, PropType, Ref, StyleValue, toRefs, unref } from "vue";
|
||||
import LinkNode from "../system/LinkNode.vue";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
visibility: {
|
||||
type: Object as PropType<Visibility>,
|
||||
type: processedPropType<Visibility>(Number),
|
||||
required: true
|
||||
},
|
||||
display: {
|
||||
type: [Object, String] as PropType<CoercableComponent>,
|
||||
type: processedPropType<CoercableComponent>(Object, String, Function),
|
||||
required: true
|
||||
},
|
||||
title: [Object, String] as PropType<CoercableComponent>,
|
||||
color: String,
|
||||
title: {
|
||||
type: processedPropType<CoercableComponent>(Object, String, Function),
|
||||
required: true
|
||||
},
|
||||
color: processedPropType<string>(String),
|
||||
collapsed: {
|
||||
type: Boolean,
|
||||
type: Object as PropType<Ref<boolean>>,
|
||||
required: true
|
||||
},
|
||||
style: Object as PropType<StyleValue>,
|
||||
titleStyle: Object as PropType<StyleValue>,
|
||||
bodyStyle: Object as PropType<StyleValue>,
|
||||
classes: Object as PropType<Record<string, boolean>>,
|
||||
style: processedPropType<StyleValue>(Object, String, Array),
|
||||
titleStyle: processedPropType<StyleValue>(Object, String, Array),
|
||||
bodyStyle: processedPropType<StyleValue>(Object, String, Array),
|
||||
classes: processedPropType<Record<string, boolean>>(Object),
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
components: {
|
||||
LinkNode,
|
||||
CollapseTransition
|
||||
},
|
||||
setup(props) {
|
||||
const { title, display } = toRefs(props);
|
||||
|
||||
const titleComponent = computed(() => title.value && coerceComponent(title.value));
|
||||
const bodyComponent = computed(() => coerceComponent(display.value));
|
||||
const titleComponent = computeComponent(title);
|
||||
const bodyComponent = computeComponent(display);
|
||||
const stacked = computed(() => themes[settings.theme].stackedInfoboxes);
|
||||
|
||||
return {
|
||||
titleComponent,
|
||||
bodyComponent,
|
||||
stacked,
|
||||
LinkNode,
|
||||
CollapseTransition,
|
||||
unref,
|
||||
Visibility
|
||||
};
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div>
|
||||
<span v-if="showPrefix">You have </span>
|
||||
<ResourceVue :resource="resource" :color="color || 'white'" />
|
||||
{{ resource
|
||||
{{ resource.displayName
|
||||
}}<!-- remove whitespace -->
|
||||
<span v-if="effectComponent">, <component :is="effectComponent" /></span>
|
||||
<br /><br />
|
||||
|
@ -13,8 +13,8 @@
|
|||
import { CoercableComponent } from "@/features/feature";
|
||||
import { Resource } from "@/features/resource";
|
||||
import Decimal from "@/util/bignum";
|
||||
import { coerceComponent } from "@/util/vue";
|
||||
import { computed, StyleValue, toRefs } from "vue";
|
||||
import { computeOptionalComponent } from "@/util/vue";
|
||||
import { computed, Ref, StyleValue, toRefs } from "vue";
|
||||
import ResourceVue from "../system/Resource.vue";
|
||||
|
||||
const _props = defineProps<{
|
||||
|
@ -26,10 +26,9 @@ const _props = defineProps<{
|
|||
}>();
|
||||
const props = toRefs(_props);
|
||||
|
||||
const effectComponent = computed(() => {
|
||||
const effectDisplay = props.effectDisplay?.value;
|
||||
return effectDisplay && coerceComponent(effectDisplay);
|
||||
});
|
||||
const effectComponent = computeOptionalComponent(
|
||||
props.effectDisplay as Ref<CoercableComponent | undefined>
|
||||
);
|
||||
|
||||
const showPrefix = computed(() => {
|
||||
return Decimal.lt(props.resource.value, "1e1000");
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{ mark: boolean | string | undefined }>();
|
||||
defineProps<{ mark?: boolean | string }>();
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -1,36 +1,45 @@
|
|||
<template>
|
||||
<div
|
||||
v-if="visibility !== Visibility.None"
|
||||
v-show="visibility === Visibility.Visible"
|
||||
:style="style"
|
||||
:class="{ feature: true, milestone: true, done: earned, ...classes }"
|
||||
v-if="unref(visibility) !== Visibility.None"
|
||||
:style="[
|
||||
{
|
||||
visibility: unref(visibility) === Visibility.Hidden ? 'hidden' : undefined
|
||||
},
|
||||
unref(style) ?? {}
|
||||
]"
|
||||
:class="{ feature: true, milestone: true, done: unref(earned), ...unref(classes) }"
|
||||
>
|
||||
<component v-if="component" :is="component" />
|
||||
<component :is="unref(comp)" />
|
||||
<LinkNode :id="id" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="tsx">
|
||||
import { StyleValue, Visibility } from "@/features/feature";
|
||||
import { jsx, StyleValue, Visibility } from "@/features/feature";
|
||||
import { GenericMilestone } from "@/features/milestone";
|
||||
import { coerceComponent, isCoercableComponent } from "@/util/vue";
|
||||
import { computed, defineComponent, PropType, toRefs, UnwrapRef } from "vue";
|
||||
import { coerceComponent, isCoercableComponent, processedPropType, unwrapRef } from "@/util/vue";
|
||||
import { Component, defineComponent, shallowRef, toRefs, unref, UnwrapRef, watchEffect } from "vue";
|
||||
import LinkNode from "../system/LinkNode.vue";
|
||||
import "@/components/common/features.css";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
visibility: {
|
||||
type: Object as PropType<Visibility>,
|
||||
type: processedPropType<Visibility>(Number),
|
||||
required: true
|
||||
},
|
||||
display: {
|
||||
type: Object as PropType<UnwrapRef<GenericMilestone["display"]>>,
|
||||
type: processedPropType<UnwrapRef<GenericMilestone["display"]>>(
|
||||
String,
|
||||
Object,
|
||||
Function
|
||||
),
|
||||
required: true
|
||||
},
|
||||
style: Object as PropType<StyleValue>,
|
||||
classes: Object as PropType<Record<string, boolean>>,
|
||||
style: processedPropType<StyleValue>(String, Object, Array),
|
||||
classes: processedPropType<Record<string, boolean>>(Object),
|
||||
earned: {
|
||||
type: Boolean,
|
||||
type: processedPropType<boolean>(Boolean),
|
||||
required: true
|
||||
},
|
||||
id: {
|
||||
|
@ -38,35 +47,49 @@ export default defineComponent({
|
|||
required: true
|
||||
}
|
||||
},
|
||||
components: {
|
||||
LinkNode
|
||||
},
|
||||
setup(props) {
|
||||
const { display } = toRefs(props);
|
||||
|
||||
const component = computed(() => {
|
||||
const currDisplay = display.value;
|
||||
const comp = shallowRef<Component | string>("");
|
||||
|
||||
watchEffect(() => {
|
||||
const currDisplay = unwrapRef(display);
|
||||
if (currDisplay == null) {
|
||||
return null;
|
||||
comp.value = "";
|
||||
return;
|
||||
}
|
||||
if (isCoercableComponent(currDisplay)) {
|
||||
return coerceComponent(currDisplay);
|
||||
comp.value = coerceComponent(currDisplay);
|
||||
return;
|
||||
}
|
||||
return (
|
||||
<span>
|
||||
<component v-is={coerceComponent(currDisplay.requirement, "h3")} />
|
||||
<div v-if={currDisplay.effectDisplay}>
|
||||
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
||||
<component v-is={coerceComponent(currDisplay.effectDisplay!, "b")} />
|
||||
</div>
|
||||
<div v-if={currDisplay.optionsDisplay}>
|
||||
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
||||
<component v-is={coerceComponent(currDisplay.optionsDisplay!, "span")} />
|
||||
</div>
|
||||
</span>
|
||||
const Requirement = coerceComponent(currDisplay.requirement, "h3");
|
||||
const EffectDisplay = coerceComponent(currDisplay.effectDisplay || "", "b");
|
||||
const OptionsDisplay = coerceComponent(currDisplay.optionsDisplay || "", "span");
|
||||
comp.value = coerceComponent(
|
||||
jsx(() => (
|
||||
<span>
|
||||
<Requirement />
|
||||
{currDisplay.effectDisplay ? (
|
||||
<div>
|
||||
<EffectDisplay />
|
||||
</div>
|
||||
) : null}
|
||||
{currDisplay.optionsDisplay ? (
|
||||
<div class="equal-spaced">
|
||||
<OptionsDisplay />
|
||||
</div>
|
||||
) : null}
|
||||
</span>
|
||||
))
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
component,
|
||||
LinkNode,
|
||||
comp,
|
||||
unref,
|
||||
Visibility
|
||||
};
|
||||
}
|
||||
|
@ -84,11 +107,21 @@ export default defineComponent({
|
|||
border-width: 4px;
|
||||
border-radius: 5px;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
margin: 0;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.milestone.done {
|
||||
background-color: var(--bought);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.milestone >>> .equal-spaced {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.milestone >>> .equal-spaced > * {
|
||||
margin: auto;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { CoercableComponent } from "@/features/feature";
|
||||
import { coerceComponent } from "@/util/vue";
|
||||
import { computed, toRefs } from "vue";
|
||||
import { computeComponent } from "@/util/vue";
|
||||
import { toRefs } from "vue";
|
||||
|
||||
const _props = defineProps<{ display: CoercableComponent }>();
|
||||
const { display } = toRefs(_props);
|
||||
const component = computed(() => coerceComponent(display));
|
||||
const component = computeComponent(display);
|
||||
</script>
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
<template>
|
||||
<button
|
||||
v-if="unref(visibility) !== Visibility.None"
|
||||
@click="selectTab"
|
||||
class="tabButton"
|
||||
:style="unref(style)"
|
||||
:style="[
|
||||
{
|
||||
visibility: unref(visibility) === Visibility.Hidden ? 'hidden' : undefined
|
||||
},
|
||||
glowColorStyle,
|
||||
unref(style) ?? {}
|
||||
]"
|
||||
:class="{
|
||||
active,
|
||||
...unref(classes)
|
||||
|
@ -13,27 +20,44 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { CoercableComponent, StyleValue } from "@/features/feature";
|
||||
import { ProcessedComputable } from "@/util/computed";
|
||||
import { computeComponent } from "@/util/vue";
|
||||
import { defineComponent, PropType, toRefs, unref } from "vue";
|
||||
import { CoercableComponent, StyleValue, Visibility } from "@/features/feature";
|
||||
import { getNotifyStyle } from "@/game/notifications";
|
||||
import { computeComponent, processedPropType, unwrapRef } from "@/util/vue";
|
||||
import { computed, defineComponent, toRefs, unref } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
display: {
|
||||
type: [Object, String] as PropType<ProcessedComputable<CoercableComponent>>,
|
||||
visibility: {
|
||||
type: processedPropType<Visibility>(Number),
|
||||
required: true
|
||||
},
|
||||
style: Object as PropType<ProcessedComputable<StyleValue>>,
|
||||
classes: Object as PropType<ProcessedComputable<Record<string, boolean>>>,
|
||||
active: [Object, Boolean] as PropType<ProcessedComputable<boolean>>
|
||||
display: {
|
||||
type: processedPropType<CoercableComponent>(Object, String, Function),
|
||||
required: true
|
||||
},
|
||||
style: processedPropType<StyleValue>(String, Object, Array),
|
||||
classes: processedPropType<Record<string, boolean>>(Object),
|
||||
glowColor: processedPropType<string>(String),
|
||||
active: Boolean,
|
||||
floating: Boolean
|
||||
},
|
||||
emits: ["selectTab"],
|
||||
setup(props, { emit }) {
|
||||
const { display } = toRefs(props);
|
||||
const { display, glowColor, floating } = toRefs(props);
|
||||
|
||||
const component = computeComponent(display);
|
||||
|
||||
const glowColorStyle = computed(() => {
|
||||
const color = unwrapRef(glowColor);
|
||||
if (!color) {
|
||||
return {};
|
||||
}
|
||||
if (unref(floating)) {
|
||||
return getNotifyStyle(color);
|
||||
}
|
||||
return { boxShadow: `0px 9px 5px -6px ${color}` };
|
||||
});
|
||||
|
||||
function selectTab() {
|
||||
emit("selectTab");
|
||||
}
|
||||
|
@ -41,7 +65,9 @@ export default defineComponent({
|
|||
return {
|
||||
selectTab,
|
||||
component,
|
||||
unref
|
||||
glowColorStyle,
|
||||
unref,
|
||||
Visibility
|
||||
};
|
||||
}
|
||||
});
|
||||
|
@ -58,6 +84,7 @@ export default defineComponent({
|
|||
border-radius: 5px;
|
||||
border: 2px solid;
|
||||
flex-shrink: 0;
|
||||
border-color: var(--layer-color);
|
||||
}
|
||||
|
||||
.tabButton:hover {
|
||||
|
|
|
@ -1,47 +1,79 @@
|
|||
<template>
|
||||
<div class="tab-family-container" :class="classes" :style="style">
|
||||
<div
|
||||
v-if="unref(visibility) !== Visibility.None"
|
||||
class="tab-family-container"
|
||||
:class="{ ...unref(classes), ...tabClasses }"
|
||||
:style="[
|
||||
{
|
||||
visibility: unref(visibility) === Visibility.Hidden ? 'hidden' : undefined
|
||||
},
|
||||
unref(style) ?? [],
|
||||
tabStyle ?? []
|
||||
]"
|
||||
>
|
||||
<Sticky class="tab-buttons-container">
|
||||
<div class="tab-buttons" :class="{ floating }">
|
||||
<TabButton
|
||||
v-for="(button, id) in tabs"
|
||||
@selectTab="selected = id"
|
||||
v-for="(button, id) in unref(tabs)"
|
||||
@selectTab="selected.value = id"
|
||||
:floating="floating"
|
||||
:key="id"
|
||||
:active="button.tab === activeTab"
|
||||
v-bind="button"
|
||||
:active="unref(button.tab) === unref(activeTab)"
|
||||
v-bind="gatherButtonProps(button)"
|
||||
/>
|
||||
</div>
|
||||
</Sticky>
|
||||
<template v-if="activeTab">
|
||||
<component :is="display!" />
|
||||
<template v-if="unref(activeTab)">
|
||||
<component :is="unref(component)" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import themes from "@/data/themes";
|
||||
import { CoercableComponent } from "@/features/feature";
|
||||
import { CoercableComponent, StyleValue, Visibility } from "@/features/feature";
|
||||
import { GenericTab } from "@/features/tab";
|
||||
import { GenericTabButton } from "@/features/tabFamily";
|
||||
import settings from "@/game/settings";
|
||||
import { coerceComponent, isCoercableComponent } from "@/util/vue";
|
||||
import { computed, defineComponent, PropType, toRefs, unref } from "vue";
|
||||
import { coerceComponent, isCoercableComponent, processedPropType, unwrapRef } from "@/util/vue";
|
||||
import {
|
||||
Component,
|
||||
computed,
|
||||
defineComponent,
|
||||
PropType,
|
||||
Ref,
|
||||
shallowRef,
|
||||
toRefs,
|
||||
unref,
|
||||
watchEffect
|
||||
} from "vue";
|
||||
import Sticky from "../system/Sticky.vue";
|
||||
import TabButton from "./TabButton.vue";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
visibility: {
|
||||
type: processedPropType<Visibility>(Number),
|
||||
required: true
|
||||
},
|
||||
activeTab: {
|
||||
type: Object as PropType<GenericTab | CoercableComponent | null>,
|
||||
type: processedPropType<GenericTab | CoercableComponent | null>(Object),
|
||||
required: true
|
||||
},
|
||||
selected: {
|
||||
type: String,
|
||||
type: Object as PropType<Ref<string>>,
|
||||
required: true
|
||||
},
|
||||
tabs: {
|
||||
type: Object as PropType<Record<string, GenericTabButton>>,
|
||||
type: processedPropType<Record<string, GenericTabButton>>(Object),
|
||||
required: true
|
||||
}
|
||||
},
|
||||
style: processedPropType<StyleValue>(String, Object, Array),
|
||||
classes: processedPropType<Record<string, boolean>>(Object)
|
||||
},
|
||||
components: {
|
||||
Sticky,
|
||||
TabButton
|
||||
},
|
||||
setup(props) {
|
||||
const { activeTab } = toRefs(props);
|
||||
|
@ -50,19 +82,23 @@ export default defineComponent({
|
|||
return themes[settings.theme].floatingTabs;
|
||||
});
|
||||
|
||||
const display = computed(() => {
|
||||
const currActiveTab = activeTab.value;
|
||||
return currActiveTab
|
||||
? coerceComponent(
|
||||
isCoercableComponent(currActiveTab)
|
||||
? currActiveTab
|
||||
: unref(currActiveTab.display)
|
||||
)
|
||||
: null;
|
||||
const component = shallowRef<Component | string>("");
|
||||
|
||||
watchEffect(() => {
|
||||
const currActiveTab = unwrapRef(activeTab);
|
||||
if (currActiveTab == null) {
|
||||
component.value = "";
|
||||
return;
|
||||
}
|
||||
if (isCoercableComponent(currActiveTab)) {
|
||||
component.value = coerceComponent(currActiveTab);
|
||||
return;
|
||||
}
|
||||
component.value = coerceComponent(unref(currActiveTab.display));
|
||||
});
|
||||
|
||||
const classes = computed(() => {
|
||||
const currActiveTab = activeTab.value;
|
||||
const tabClasses = computed(() => {
|
||||
const currActiveTab = unwrapRef(activeTab);
|
||||
const tabClasses =
|
||||
isCoercableComponent(currActiveTab) || !currActiveTab
|
||||
? undefined
|
||||
|
@ -70,20 +106,26 @@ export default defineComponent({
|
|||
return tabClasses;
|
||||
});
|
||||
|
||||
const style = computed(() => {
|
||||
const currActiveTab = activeTab.value;
|
||||
const tabStyle = computed(() => {
|
||||
const currActiveTab = unwrapRef(activeTab);
|
||||
return isCoercableComponent(currActiveTab) || !currActiveTab
|
||||
? undefined
|
||||
: unref(currActiveTab.style);
|
||||
});
|
||||
|
||||
function gatherButtonProps(button: GenericTabButton) {
|
||||
const { display, style, classes, glowColor, visibility } = button;
|
||||
return { display, style, classes, glowColor, visibility };
|
||||
}
|
||||
|
||||
return {
|
||||
floating,
|
||||
display,
|
||||
classes,
|
||||
style,
|
||||
Sticky,
|
||||
TabButton
|
||||
tabClasses,
|
||||
tabStyle,
|
||||
Visibility,
|
||||
component,
|
||||
gatherButtonProps,
|
||||
unref
|
||||
};
|
||||
}
|
||||
});
|
||||
|
@ -91,35 +133,44 @@ export default defineComponent({
|
|||
|
||||
<style scoped>
|
||||
.tab-family-container {
|
||||
margin: var(--feature-margin) -11px;
|
||||
margin: calc(50px + var(--feature-margin)) 20px var(--feature-margin) 20px;
|
||||
position: relative;
|
||||
border: solid 4px var(--outline);
|
||||
border: solid 4px;
|
||||
border-color: var(--outline);
|
||||
}
|
||||
|
||||
.tab-buttons:not(.floating) {
|
||||
text-align: left;
|
||||
border-bottom: inherit;
|
||||
border-width: 4px;
|
||||
box-sizing: border-box;
|
||||
height: 50px;
|
||||
.layer-tab > .tab-family-container:first-child {
|
||||
margin: -4px -11px var(--feature-margin) -11px;
|
||||
}
|
||||
|
||||
.layer-tab > .tab-family-container:first-child:nth-last-child(3) {
|
||||
border-bottom-style: none;
|
||||
border-left-style: none;
|
||||
border-right-style: none;
|
||||
height: calc(100% + 50px);
|
||||
}
|
||||
|
||||
.tab-family-container > :nth-child(2) {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.tab-family-container[data-v-f18896fc] > :last-child {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.tab-family-container .sticky {
|
||||
margin-left: unset !important;
|
||||
margin-right: unset !important;
|
||||
margin-left: -3px !important;
|
||||
margin-right: -3px !important;
|
||||
}
|
||||
|
||||
.tab-buttons {
|
||||
margin-bottom: 24px;
|
||||
display: flex;
|
||||
flex-flow: wrap;
|
||||
padding-right: 60px;
|
||||
z-index: 4;
|
||||
.tab-buttons-container {
|
||||
width: calc(100% - 14px);
|
||||
}
|
||||
|
||||
.tab-buttons-container:not(.floating) {
|
||||
border-top: solid 4px var(--outline);
|
||||
border-bottom: solid 4px var(--outline);
|
||||
border-top: solid 4px;
|
||||
border-bottom: solid 4px;
|
||||
border-color: inherit;
|
||||
}
|
||||
|
||||
.tab-buttons-container:not(.floating) .tab-buttons {
|
||||
|
@ -137,6 +188,28 @@ export default defineComponent({
|
|||
margin-top: -25px;
|
||||
}
|
||||
|
||||
.tab-buttons {
|
||||
margin-bottom: 24px;
|
||||
display: flex;
|
||||
flex-flow: wrap;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.layer-tab
|
||||
> .tab-family-container:first-child:nth-last-child(3)
|
||||
> .tab-buttons-container
|
||||
> .tab-buttons {
|
||||
padding-right: 60px;
|
||||
}
|
||||
|
||||
.tab-buttons:not(.floating) {
|
||||
text-align: left;
|
||||
border-bottom: inherit;
|
||||
border-width: 4px;
|
||||
box-sizing: border-box;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.modal-body .tab-buttons {
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
|
@ -144,9 +217,18 @@ export default defineComponent({
|
|||
padding-left: 0;
|
||||
}
|
||||
|
||||
.showGoBack > .tab-buttons-container:not(.floating) .subtabs {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
.showGoBack
|
||||
> .tab-family-container
|
||||
> .tab-buttons-container:not(.floating):first-child
|
||||
.tab-buttons {
|
||||
padding-left: 70px;
|
||||
}
|
||||
|
||||
:not(.showGoBack)
|
||||
> .tab-family-container
|
||||
> .tab-buttons-container:not(.floating):first-child
|
||||
.tab-buttons {
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
.tab-buttons-container:not(.floating):first-child {
|
||||
|
@ -161,10 +243,6 @@ export default defineComponent({
|
|||
margin-top: -50px;
|
||||
}
|
||||
|
||||
:not(.showGoBack) > .tab-buttons-container:not(.floating) .tab-buttons {
|
||||
padding-left: 70px;
|
||||
}
|
||||
|
||||
.tab-buttons-container + * {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
|
|
@ -1,64 +1,72 @@
|
|||
<template>
|
||||
<button
|
||||
v-if="visibility !== Visibility.None"
|
||||
v-show="visibility === Visibility.Visible"
|
||||
:style="style"
|
||||
v-if="unref(visibility) !== Visibility.None"
|
||||
:style="[
|
||||
{
|
||||
visibility: unref(visibility) === Visibility.Hidden ? 'hidden' : undefined
|
||||
},
|
||||
unref(style) ?? {}
|
||||
]"
|
||||
@click="purchase"
|
||||
:class="{
|
||||
feature: true,
|
||||
upgrade: true,
|
||||
can: canPurchase && !bought,
|
||||
locked: !canPurchase && !bought,
|
||||
bought,
|
||||
...classes
|
||||
can: unref(canPurchase),
|
||||
locked: !unref(canPurchase),
|
||||
bought: unref(bought),
|
||||
...unref(classes)
|
||||
}"
|
||||
:disabled="!canPurchase && !bought"
|
||||
:disabled="!unref(canPurchase)"
|
||||
>
|
||||
<component v-if="component" :is="component" />
|
||||
<MarkNode :mark="mark" />
|
||||
<component v-if="unref(component)" :is="unref(component)" />
|
||||
<MarkNode :mark="unref(mark)" />
|
||||
<LinkNode :id="id" />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script lang="tsx">
|
||||
import { StyleValue, Visibility } from "@/features/feature";
|
||||
import { jsx, StyleValue, Visibility } from "@/features/feature";
|
||||
import { displayResource, Resource } from "@/features/resource";
|
||||
import { GenericUpgrade } from "@/features/upgrade";
|
||||
import { DecimalSource } from "@/lib/break_eternity";
|
||||
import { coerceComponent, isCoercableComponent } from "@/util/vue";
|
||||
import { computed, defineComponent, PropType, Ref, toRef, toRefs, unref, UnwrapRef } from "vue";
|
||||
import { coerceComponent, isCoercableComponent, processedPropType, unwrapRef } from "@/util/vue";
|
||||
import {
|
||||
Component,
|
||||
defineComponent,
|
||||
PropType,
|
||||
shallowRef,
|
||||
toRefs,
|
||||
unref,
|
||||
UnwrapRef,
|
||||
watchEffect
|
||||
} from "vue";
|
||||
import LinkNode from "../system/LinkNode.vue";
|
||||
import MarkNode from "./MarkNode.vue";
|
||||
import "@/components/common/features.css";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
display: {
|
||||
type: Object as PropType<UnwrapRef<GenericUpgrade["display"]>>,
|
||||
type: processedPropType<UnwrapRef<GenericUpgrade["display"]>>(String, Object, Function),
|
||||
required: true
|
||||
},
|
||||
visibility: {
|
||||
type: Object as PropType<Visibility>,
|
||||
required: true
|
||||
},
|
||||
style: Object as PropType<StyleValue>,
|
||||
classes: Object as PropType<Record<string, boolean>>,
|
||||
resource: {
|
||||
type: Object as PropType<Resource>,
|
||||
required: true
|
||||
},
|
||||
cost: {
|
||||
type: Object as PropType<DecimalSource>,
|
||||
type: processedPropType<Visibility>(Number),
|
||||
required: true
|
||||
},
|
||||
style: processedPropType<StyleValue>(String, Object, Array),
|
||||
classes: processedPropType<Record<string, boolean>>(Object),
|
||||
resource: Object as PropType<Resource>,
|
||||
cost: processedPropType<DecimalSource>(String, Object, Number),
|
||||
canPurchase: {
|
||||
type: Boolean,
|
||||
type: processedPropType<boolean>(Boolean),
|
||||
required: true
|
||||
},
|
||||
bought: {
|
||||
type: Boolean,
|
||||
type: processedPropType<boolean>(Boolean),
|
||||
required: true
|
||||
},
|
||||
mark: [Boolean, String],
|
||||
mark: processedPropType<boolean | string>(Boolean, String),
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
|
@ -68,45 +76,59 @@ export default defineComponent({
|
|||
required: true
|
||||
}
|
||||
},
|
||||
components: {
|
||||
LinkNode,
|
||||
MarkNode
|
||||
},
|
||||
setup(props) {
|
||||
const { display, cost } = toRefs(props);
|
||||
const resource = toRef(props, "resource") as unknown as Ref<Resource>;
|
||||
|
||||
const component = computed(() => {
|
||||
const currDisplay = display.value;
|
||||
const component = shallowRef<Component | string>("");
|
||||
|
||||
watchEffect(() => {
|
||||
const currDisplay = unwrapRef(display);
|
||||
if (currDisplay == null) {
|
||||
return null;
|
||||
component.value = "";
|
||||
return;
|
||||
}
|
||||
if (isCoercableComponent(currDisplay)) {
|
||||
return coerceComponent(currDisplay);
|
||||
component.value = coerceComponent(currDisplay);
|
||||
return;
|
||||
}
|
||||
return (
|
||||
<span>
|
||||
<div v-if={currDisplay.title}>
|
||||
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
||||
<component v-is={coerceComponent(currDisplay.title!, "h2")} />
|
||||
</div>
|
||||
<component v-is={coerceComponent(currDisplay.description, "div")} />
|
||||
<div v-if={currDisplay.effectDisplay}>
|
||||
<br />
|
||||
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
||||
Currently: <component v-is={coerceComponent(currDisplay.effectDisplay!)} />
|
||||
</div>
|
||||
<template v-if={resource.value != null && cost.value != null}>
|
||||
<br />
|
||||
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
||||
Cost: {displayResource(resource.value, cost.value)}{" "}
|
||||
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
||||
{resource.value.displayName}
|
||||
</template>
|
||||
</span>
|
||||
const currCost = unwrapRef(cost);
|
||||
const Title = coerceComponent(currDisplay.title || "", "h3");
|
||||
const Description = coerceComponent(currDisplay.description, "div");
|
||||
const EffectDisplay = coerceComponent(currDisplay.effectDisplay || "");
|
||||
component.value = coerceComponent(
|
||||
jsx(() => (
|
||||
<span>
|
||||
{currDisplay.title ? (
|
||||
<div>
|
||||
<Title />
|
||||
</div>
|
||||
) : null}
|
||||
<Description />
|
||||
{currDisplay.effectDisplay ? (
|
||||
<div>
|
||||
Currently: <EffectDisplay />
|
||||
</div>
|
||||
) : null}
|
||||
{props.resource != null ? (
|
||||
<>
|
||||
<br />
|
||||
Cost: {props.resource &&
|
||||
displayResource(props.resource, currCost)}{" "}
|
||||
{props.resource?.displayName}
|
||||
</>
|
||||
) : null}
|
||||
</span>
|
||||
))
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
component,
|
||||
LinkNode,
|
||||
MarkNode,
|
||||
unref,
|
||||
Visibility
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
<template>
|
||||
<span class="row" v-for="(row, index) in nodes" :key="index">
|
||||
<span class="row" v-for="(row, index) in unref(nodes)" :key="index" v-bind="$attrs">
|
||||
<TreeNode
|
||||
v-for="(node, nodeIndex) in row"
|
||||
:key="nodeIndex"
|
||||
v-bind="node"
|
||||
v-bind="gatherNodeProps(node)"
|
||||
:force-tooltip="node.forceTooltip"
|
||||
/>
|
||||
</span>
|
||||
<span class="left-side-nodes" v-if="leftSideNodes">
|
||||
<span class="left-side-nodes" v-if="unref(leftSideNodes)">
|
||||
<TreeNode
|
||||
v-for="(node, nodeIndex) in leftSideNodes"
|
||||
v-for="(node, nodeIndex) in unref(leftSideNodes)"
|
||||
:key="nodeIndex"
|
||||
v-bind="node"
|
||||
v-bind="gatherNodeProps(node)"
|
||||
:force-tooltip="node.forceTooltip"
|
||||
small
|
||||
/>
|
||||
</span>
|
||||
<span class="side-nodes" v-if="rightSideNodes">
|
||||
<span class="side-nodes" v-if="unref(rightSideNodes)">
|
||||
<TreeNode
|
||||
v-for="(node, nodeIndex) in rightSideNodes"
|
||||
v-for="(node, nodeIndex) in unref(rightSideNodes)"
|
||||
:key="nodeIndex"
|
||||
v-bind="node"
|
||||
v-bind="gatherNodeProps(node)"
|
||||
:force-tooltip="node.forceTooltip"
|
||||
small
|
||||
/>
|
||||
|
@ -28,21 +28,60 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import "@/components/common/table.css";
|
||||
import { GenericTreeNode } from "@/features/tree";
|
||||
import { defineComponent, PropType } from "vue";
|
||||
import { processedPropType } from "@/util/vue";
|
||||
import { defineComponent, unref } from "vue";
|
||||
import TreeNode from "./TreeNode.vue";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
nodes: {
|
||||
type: Array as PropType<GenericTreeNode[][]>,
|
||||
type: processedPropType<GenericTreeNode[][]>(Array),
|
||||
required: true
|
||||
},
|
||||
leftSideNodes: Array as PropType<GenericTreeNode[]>,
|
||||
rightSideNodes: Array as PropType<GenericTreeNode[]>
|
||||
leftSideNodes: processedPropType<GenericTreeNode[]>(Array),
|
||||
rightSideNodes: processedPropType<GenericTreeNode[]>(Array)
|
||||
},
|
||||
components: { TreeNode },
|
||||
setup() {
|
||||
return { TreeNode };
|
||||
function gatherNodeProps(node: GenericTreeNode) {
|
||||
const {
|
||||
display,
|
||||
visibility,
|
||||
style,
|
||||
classes,
|
||||
tooltip,
|
||||
onClick,
|
||||
onHold,
|
||||
color,
|
||||
glowColor,
|
||||
forceTooltip,
|
||||
canClick,
|
||||
mark,
|
||||
id
|
||||
} = node;
|
||||
return {
|
||||
display,
|
||||
visibility,
|
||||
style,
|
||||
classes,
|
||||
tooltip,
|
||||
onClick,
|
||||
onHold,
|
||||
color,
|
||||
glowColor,
|
||||
forceTooltip,
|
||||
canClick,
|
||||
mark,
|
||||
id
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
gatherNodeProps,
|
||||
unref
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<Tooltip
|
||||
v-if="unref(visibility) !== Visibility.None"
|
||||
v-show="unref(visibility) === Visibility.Visible"
|
||||
v-bind="tooltipToBind"
|
||||
v-bind="tooltipToBind && gatherTooltipProps(tooltipToBind)"
|
||||
:display="tooltipDisplay"
|
||||
:force="forceTooltip"
|
||||
:style="{ visibility: unref(visibility) === Visibility.Hidden ? 'hidden' : undefined }"
|
||||
:class="{
|
||||
treeNode: true,
|
||||
can: unref(canClick),
|
||||
|
@ -31,56 +31,71 @@
|
|||
]"
|
||||
:disabled="!unref(canClick)"
|
||||
>
|
||||
<component :is="component" />
|
||||
<component :is="unref(comp)" />
|
||||
</button>
|
||||
<MarkNode :mark="unref(mark)" />
|
||||
<LinkNode :id="unref(id)" />
|
||||
<LinkNode :id="id" />
|
||||
</Tooltip>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import TooltipVue from "@/components/system/Tooltip.vue";
|
||||
import { CoercableComponent, StyleValue, Visibility } from "@/features/feature";
|
||||
import { Tooltip } from "@/features/tooltip";
|
||||
import { gatherTooltipProps, Tooltip } from "@/features/tooltip";
|
||||
import { ProcessedComputable } from "@/util/computed";
|
||||
import {
|
||||
computeOptionalComponent,
|
||||
isCoercableComponent,
|
||||
processedPropType,
|
||||
setupHoldToClick,
|
||||
unwrapRef
|
||||
} from "@/util/vue";
|
||||
import { computed, defineComponent, PropType, Ref, toRefs, unref } from "vue";
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
PropType,
|
||||
Ref,
|
||||
shallowRef,
|
||||
toRefs,
|
||||
unref,
|
||||
watchEffect
|
||||
} from "vue";
|
||||
import LinkNode from "../../system/LinkNode.vue";
|
||||
import MarkNode from "../MarkNode.vue";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
display: [Object, String] as PropType<ProcessedComputable<CoercableComponent>>,
|
||||
display: processedPropType<CoercableComponent>(Object, String, Function),
|
||||
visibility: {
|
||||
type: Object as PropType<ProcessedComputable<Visibility>>,
|
||||
type: processedPropType<Visibility>(Number),
|
||||
required: true
|
||||
},
|
||||
style: Object as PropType<ProcessedComputable<StyleValue>>,
|
||||
classes: Object as PropType<ProcessedComputable<Record<string, boolean>>>,
|
||||
tooltip: Object as PropType<ProcessedComputable<CoercableComponent | Tooltip>>,
|
||||
style: processedPropType<StyleValue>(String, Object, Array),
|
||||
classes: processedPropType<Record<string, boolean>>(Object),
|
||||
tooltip: processedPropType<CoercableComponent | Tooltip>(Object, String, Function),
|
||||
onClick: Function as PropType<VoidFunction>,
|
||||
onHold: Function as PropType<VoidFunction>,
|
||||
color: [Object, String] as PropType<ProcessedComputable<string>>,
|
||||
glowColor: [Object, String] as PropType<ProcessedComputable<string>>,
|
||||
color: processedPropType<string>(String),
|
||||
glowColor: processedPropType<string>(String),
|
||||
forceTooltip: {
|
||||
type: Object as PropType<Ref<boolean>>,
|
||||
required: true
|
||||
},
|
||||
canClick: {
|
||||
type: [Object, Boolean] as PropType<ProcessedComputable<boolean>>,
|
||||
type: processedPropType<boolean>(Boolean),
|
||||
required: true
|
||||
},
|
||||
mark: [Object, Boolean, String] as PropType<ProcessedComputable<boolean | string>>,
|
||||
mark: processedPropType<boolean | string>(Boolean, String),
|
||||
id: {
|
||||
type: [Object, String] as PropType<ProcessedComputable<string>>,
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
small: [Object, Boolean] as PropType<ProcessedComputable<boolean>>
|
||||
small: processedPropType<boolean>(Boolean)
|
||||
},
|
||||
components: {
|
||||
Tooltip: TooltipVue,
|
||||
MarkNode,
|
||||
LinkNode
|
||||
},
|
||||
setup(props) {
|
||||
const { tooltip, forceTooltip, onClick, onHold, display } = toRefs(props);
|
||||
|
@ -93,14 +108,18 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
|
||||
const component = computeOptionalComponent(display);
|
||||
const tooltipDisplay = computed(() => {
|
||||
const comp = computeOptionalComponent(display);
|
||||
const tooltipDisplay = shallowRef<ProcessedComputable<CoercableComponent> | undefined>(
|
||||
undefined
|
||||
);
|
||||
watchEffect(() => {
|
||||
const currTooltip = unwrapRef(tooltip);
|
||||
|
||||
if (typeof currTooltip === "object" && !isCoercableComponent(currTooltip)) {
|
||||
return currTooltip.display;
|
||||
tooltipDisplay.value = currTooltip.display;
|
||||
return;
|
||||
}
|
||||
return currTooltip || "";
|
||||
tooltipDisplay.value = currTooltip;
|
||||
});
|
||||
const tooltipToBind = computed(() => {
|
||||
const currTooltip = unwrapRef(tooltip);
|
||||
|
@ -117,14 +136,12 @@ export default defineComponent({
|
|||
click,
|
||||
start,
|
||||
stop,
|
||||
component,
|
||||
comp,
|
||||
tooltipDisplay,
|
||||
tooltipToBind,
|
||||
Tooltip: TooltipVue,
|
||||
MarkNode,
|
||||
LinkNode,
|
||||
unref,
|
||||
Visibility,
|
||||
gatherTooltipProps,
|
||||
isCoercableComponent
|
||||
};
|
||||
}
|
||||
|
|
|
@ -4,8 +4,9 @@
|
|||
<VueNextSelect
|
||||
:options="options"
|
||||
v-model="value"
|
||||
@update:model-value="onUpdate"
|
||||
:min="1"
|
||||
label-by="label"
|
||||
:reduce="(option: SelectOption) => option.value"
|
||||
:placeholder="placeholder"
|
||||
:close-on-select="closeOnSelect"
|
||||
/>
|
||||
|
@ -13,38 +14,40 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import "@/components/common/fields.css";
|
||||
import { CoercableComponent } from "@/features/feature";
|
||||
import { coerceComponent } from "@/util/vue";
|
||||
import { computed, toRefs, unref } from "vue";
|
||||
import { computeOptionalComponent } from "@/util/vue";
|
||||
import { ref, toRef, watch } from "vue";
|
||||
import VueNextSelect from "vue-next-select";
|
||||
import "vue-next-select/dist/index.css";
|
||||
|
||||
export type SelectOption = { label: string; value: unknown };
|
||||
|
||||
const _props = defineProps<{
|
||||
const props = defineProps<{
|
||||
title?: CoercableComponent;
|
||||
modelValue?: unknown;
|
||||
options: SelectOption[];
|
||||
placeholder?: string;
|
||||
closeOnSelect?: boolean;
|
||||
}>();
|
||||
const props = toRefs(_props);
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", value: unknown): void;
|
||||
}>();
|
||||
|
||||
const titleComponent = computed(
|
||||
() => props.title?.value && coerceComponent(props.title.value, "span")
|
||||
);
|
||||
const titleComponent = computeOptionalComponent(toRef(props, "title"), "span");
|
||||
|
||||
const value = computed({
|
||||
get() {
|
||||
return unref(props.modelValue);
|
||||
},
|
||||
set(value: unknown) {
|
||||
emit("update:modelValue", value);
|
||||
const value = ref<SelectOption | undefined>(
|
||||
props.options.find(option => option.value === props.modelValue)
|
||||
);
|
||||
watch(toRef(props, "modelValue"), modelValue => {
|
||||
if (value.value?.value !== modelValue) {
|
||||
value.value = props.options.find(option => option.value === modelValue);
|
||||
}
|
||||
});
|
||||
|
||||
function onUpdate(value: SelectOption) {
|
||||
emit("update:modelValue", value.value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, toRefs, unref } from "vue";
|
||||
import Tooltip from "../system/Tooltip.vue";
|
||||
import "@/components/common/fields.css";
|
||||
|
||||
const _props = defineProps<{
|
||||
title?: string;
|
||||
|
@ -24,10 +25,10 @@ const emit = defineEmits<{
|
|||
|
||||
const value = computed({
|
||||
get() {
|
||||
return unref(props.modelValue) || 0;
|
||||
return String(unref(props.modelValue) || 0);
|
||||
},
|
||||
set(value: number) {
|
||||
emit("update:modelValue", value);
|
||||
set(value: string) {
|
||||
emit("update:modelValue", Number(value));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -30,6 +30,7 @@ import { CoercableComponent } from "@/features/feature";
|
|||
import { coerceComponent } from "@/util/vue";
|
||||
import { computed, onMounted, ref, toRefs, unref } from "vue";
|
||||
import VueTextareaAutosize from "vue-textarea-autosize";
|
||||
import "@/components/common/fields.css";
|
||||
|
||||
const _props = defineProps<{
|
||||
title?: CoercableComponent;
|
||||
|
|
|
@ -8,22 +8,22 @@
|
|||
<script setup lang="ts">
|
||||
import { CoercableComponent } from "@/features/feature";
|
||||
import { coerceComponent } from "@/util/vue";
|
||||
import { computed, toRefs, unref } from "vue";
|
||||
import { computed, unref } from "vue";
|
||||
import "@/components/common/fields.css";
|
||||
|
||||
const _props = defineProps<{
|
||||
const props = defineProps<{
|
||||
title?: CoercableComponent;
|
||||
modelValue?: boolean;
|
||||
}>();
|
||||
const props = toRefs(_props);
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", value: boolean): void;
|
||||
}>();
|
||||
|
||||
const component = computed(() => coerceComponent(unref(props.title) || "", "span"));
|
||||
const component = computed(() => coerceComponent(unref(props.title) || "<span></span>", "span"));
|
||||
|
||||
const value = computed({
|
||||
get() {
|
||||
return !!unref(props.modelValue);
|
||||
return !!props.modelValue;
|
||||
},
|
||||
set(value: boolean) {
|
||||
emit("update:modelValue", value);
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
<template>
|
||||
<div class="table">
|
||||
<div class="col">
|
||||
<div class="col" :class="{ mergeAdjacent }">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
<script setup lang="ts">
|
||||
import "@/components/common/table.css";
|
||||
import themes from "@/data/themes";
|
||||
import settings from "@/game/settings";
|
||||
import { computed } from "vue";
|
||||
|
||||
const mergeAdjacent = computed(() => themes[settings.theme].mergeAdjacent);
|
||||
</script>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<div class="inner-tab">
|
||||
<Layer
|
||||
v-if="layerKeys.includes(tab)"
|
||||
v-bind="layers[tab]!"
|
||||
v-bind="gatherLayerProps(layers[tab]!)"
|
||||
:index="index"
|
||||
:tab="() => (($refs[`tab-${index}`] as HTMLElement[] | undefined)?.[0])"
|
||||
/>
|
||||
|
@ -18,7 +18,7 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import modInfo from "@/data/modInfo.json";
|
||||
import { layers } from "@/game/layers";
|
||||
import { GenericLayer, layers } from "@/game/layers";
|
||||
import player from "@/game/player";
|
||||
import { computed, toRef } from "vue";
|
||||
import Layer from "./Layer.vue";
|
||||
|
@ -27,6 +27,11 @@ import Nav from "./Nav.vue";
|
|||
const tabs = toRef(player, "tabs");
|
||||
const layerKeys = computed(() => Object.keys(layers));
|
||||
const useHeader = modInfo.useHeader;
|
||||
|
||||
function gatherLayerProps(layer: GenericLayer) {
|
||||
const { display, minimized, minWidth, name, color, style, classes, links, minimizable } = layer;
|
||||
return { display, minimized, minWidth, name, color, style, classes, links, minimizable };
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
@ -58,10 +63,10 @@ const useHeader = modInfo.useHeader;
|
|||
|
||||
.separator {
|
||||
position: absolute;
|
||||
right: -3px;
|
||||
right: -4px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 6px;
|
||||
width: 8px;
|
||||
background: var(--outline);
|
||||
z-index: 1;
|
||||
}
|
||||
|
@ -72,7 +77,7 @@ const useHeader = modInfo.useHeader;
|
|||
height: 4px;
|
||||
border: none;
|
||||
background: var(--outline);
|
||||
margin: 7px -10px;
|
||||
margin: var(--feature-margin) -10px;
|
||||
}
|
||||
|
||||
.tab .modal-body hr {
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
<template>
|
||||
<div class="layer-container">
|
||||
<div class="layer-container" :style="{ '--layer-color': unref(color) }">
|
||||
<button v-if="showGoBack" class="goBack" @click="goBack">←</button>
|
||||
<button class="layer-tab minimized" v-if="minimized.value" @click="minimized.value = false">
|
||||
<div>{{ unref(name) }}</div>
|
||||
</button>
|
||||
<div class="layer-tab" :style="unref(style)" :class="unref(classes)" v-else>
|
||||
<Links v-if="links" :links="unref(links)">
|
||||
<div
|
||||
class="layer-tab"
|
||||
:style="unref(style)"
|
||||
:class="[{ showGoBack }, unref(classes)]"
|
||||
v-else
|
||||
>
|
||||
<Links :links="unref(links)">
|
||||
<component :is="component" />
|
||||
</Links>
|
||||
<component v-else :is="component" />
|
||||
</div>
|
||||
<button v-if="unref(minimizable)" class="minimize" @click="minimized.value = true">
|
||||
▼
|
||||
|
@ -22,8 +26,7 @@ import modInfo from "@/data/modInfo.json";
|
|||
import { CoercableComponent, PersistentRef, StyleValue } from "@/features/feature";
|
||||
import { Link } from "@/features/links";
|
||||
import player from "@/game/player";
|
||||
import { ProcessedComputable } from "@/util/computed";
|
||||
import { computeComponent, wrapRef } from "@/util/vue";
|
||||
import { computeComponent, processedPropType, wrapRef } from "@/util/vue";
|
||||
import { computed, defineComponent, nextTick, PropType, toRefs, unref, watch } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
|
@ -38,7 +41,7 @@ export default defineComponent({
|
|||
required: true
|
||||
},
|
||||
display: {
|
||||
type: [Object, String] as PropType<ProcessedComputable<CoercableComponent>>,
|
||||
type: processedPropType<CoercableComponent>(Object, String, Function),
|
||||
required: true
|
||||
},
|
||||
minimized: {
|
||||
|
@ -46,28 +49,29 @@ export default defineComponent({
|
|||
required: true
|
||||
},
|
||||
minWidth: {
|
||||
type: [Object, Number] as PropType<ProcessedComputable<number>>,
|
||||
type: processedPropType<number>(Number),
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: [Object, String] as PropType<ProcessedComputable<string>>,
|
||||
type: processedPropType<string>(String),
|
||||
required: true
|
||||
},
|
||||
style: Object as PropType<ProcessedComputable<StyleValue>>,
|
||||
classes: Object as PropType<ProcessedComputable<Record<string, boolean>>>,
|
||||
links: [Object, Array] as PropType<ProcessedComputable<Link[]>>,
|
||||
minimizable: [Object, Boolean] as PropType<ProcessedComputable<boolean>>
|
||||
color: processedPropType<string>(String),
|
||||
style: processedPropType<StyleValue>(String, Object, Array),
|
||||
classes: processedPropType<Record<string, boolean>>(Object),
|
||||
links: processedPropType<Link[]>(Array),
|
||||
minimizable: processedPropType<boolean>(Boolean)
|
||||
},
|
||||
setup(props) {
|
||||
const { display, index, minimized, minWidth, tab } = toRefs(props);
|
||||
|
||||
const component = computeComponent(display);
|
||||
const showGoBack = computed(
|
||||
() => modInfo.allowGoBack && unref(index) > 0 && !minimized.value
|
||||
() => modInfo.allowGoBack && index.value > 0 && !minimized.value
|
||||
);
|
||||
|
||||
function goBack() {
|
||||
player.tabs = player.tabs.slice(0, unref(props.index));
|
||||
player.tabs.splice(unref(props.index), Infinity);
|
||||
}
|
||||
|
||||
nextTick(() => updateTab(minimized.value, unref(minWidth.value)));
|
||||
|
@ -187,6 +191,7 @@ export default defineComponent({
|
|||
transform: rotate(-90deg);
|
||||
top: 10px;
|
||||
right: 18px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.goBack {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<template>
|
||||
<line
|
||||
stroke-width="15px"
|
||||
stroke="white"
|
||||
v-bind="link"
|
||||
:x1="startPosition.x"
|
||||
:y1="startPosition.y"
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { RegisterLinkNodeInjectionKey, UnregisterLinkNodeInjectionKey } from "@/features/links";
|
||||
import { computed, inject, ref, toRefs, unref, watch } from "vue";
|
||||
import { computed, inject, onUnmounted, ref, toRefs, unref, watch } from "vue";
|
||||
|
||||
const _props = defineProps<{ id: string }>();
|
||||
const props = toRefs(_props);
|
||||
|
@ -24,6 +24,8 @@ if (register && unregister) {
|
|||
register(newID, newNode);
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => unregister(unref(props.id)));
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -19,11 +19,11 @@ import {
|
|||
RegisterLinkNodeInjectionKey,
|
||||
UnregisterLinkNodeInjectionKey
|
||||
} from "@/features/links";
|
||||
import { computed, nextTick, onMounted, provide, ref, toRefs } from "vue";
|
||||
import { computed, nextTick, onMounted, provide, ref, toRef } from "vue";
|
||||
import LinkVue from "./Link.vue";
|
||||
|
||||
const _props = defineProps<{ links: Link[] }>();
|
||||
const { links } = toRefs(_props);
|
||||
const _props = defineProps<{ links?: Link[] }>();
|
||||
const links = toRef(_props, "links");
|
||||
|
||||
const observer = new MutationObserver(updateNodes);
|
||||
const resizeObserver = new ResizeObserver(updateBounds);
|
||||
|
@ -42,16 +42,17 @@ onMounted(() => {
|
|||
updateNodes();
|
||||
});
|
||||
|
||||
const validLinks = computed(() =>
|
||||
links.value.filter(link => {
|
||||
const n = nodes.value;
|
||||
return (
|
||||
n[link.startNode.id]?.x != undefined &&
|
||||
n[link.startNode.id]?.y != undefined &&
|
||||
n[link.endNode.id]?.x != undefined &&
|
||||
n[link.endNode.id]?.y != undefined
|
||||
);
|
||||
})
|
||||
const validLinks = computed(
|
||||
() =>
|
||||
links.value?.filter(link => {
|
||||
const n = nodes.value;
|
||||
return (
|
||||
n[link.startNode.id]?.x != undefined &&
|
||||
n[link.startNode.id]?.y != undefined &&
|
||||
n[link.endNode.id]?.x != undefined &&
|
||||
n[link.endNode.id]?.y != undefined
|
||||
);
|
||||
}) ?? []
|
||||
);
|
||||
|
||||
const observerOptions = {
|
||||
|
|
|
@ -63,7 +63,7 @@ const isAnimating = ref(false);
|
|||
defineExpose({ isOpen });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style>
|
||||
.modal-mask {
|
||||
position: fixed;
|
||||
z-index: 9998;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<img v-if="banner" :src="banner" height="100%" :alt="title" />
|
||||
<div v-else class="title">{{ title }}</div>
|
||||
<div @click="changelog?.open()" class="version-container">
|
||||
<Tooltip display="<span>Changelog</span>" bottom class="version"
|
||||
<Tooltip display="Changelog" bottom class="version"
|
||||
><span>v{{ versionNumber }}</span></Tooltip
|
||||
>
|
||||
</div>
|
||||
|
|
|
@ -25,10 +25,11 @@ import { MilestoneDisplay } from "@/features/milestone";
|
|||
import player from "@/game/player";
|
||||
import settings from "@/game/settings";
|
||||
import { camelToTitle } from "@/util/common";
|
||||
import { computed, ref, toRef, toRefs } from "vue";
|
||||
import { computed, ref, toRefs } from "vue";
|
||||
import Toggle from "../fields/Toggle.vue";
|
||||
import Select from "../fields/Select.vue";
|
||||
import Tooltip from "./Tooltip.vue";
|
||||
import { jsx } from "@/features/feature";
|
||||
|
||||
const isOpen = ref(false);
|
||||
|
||||
|
@ -51,31 +52,30 @@ const msDisplayOptions = Object.values(MilestoneDisplay).map(option => ({
|
|||
|
||||
const { showTPS, hideChallenges, theme, msDisplay, unthrottled } = toRefs(settings);
|
||||
const { autosave, offlineProd } = toRefs(player);
|
||||
const devSpeed = toRef(player, "devSpeed");
|
||||
const isPaused = computed({
|
||||
get() {
|
||||
return devSpeed.value === 0;
|
||||
return player.devSpeed === 0;
|
||||
},
|
||||
set(value: boolean) {
|
||||
devSpeed.value = value ? null : 0;
|
||||
player.devSpeed = value ? 0 : null;
|
||||
}
|
||||
});
|
||||
|
||||
const offlineProdTitle = (
|
||||
<template>
|
||||
const offlineProdTitle = jsx(() => (
|
||||
<span>
|
||||
Offline Production<Tooltip display="Save-specific">*</Tooltip>
|
||||
</template>
|
||||
);
|
||||
const autosaveTitle = (
|
||||
<template>
|
||||
</span>
|
||||
));
|
||||
const autosaveTitle = jsx(() => (
|
||||
<span>
|
||||
Autosave<Tooltip display="Save-specific">*</Tooltip>
|
||||
</template>
|
||||
);
|
||||
const isPausedTitle = (
|
||||
<template>
|
||||
</span>
|
||||
));
|
||||
const isPausedTitle = jsx(() => (
|
||||
<span>
|
||||
Pause game<Tooltip display="Save-specific">*</Tooltip>
|
||||
</template>
|
||||
);
|
||||
</span>
|
||||
));
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
<template>
|
||||
<h2 v-bind:style="{ color, 'text-shadow': '0px 0px 10px ' + color }">
|
||||
<h2 :style="{ color, 'text-shadow': '0px 0px 10px ' + color }">
|
||||
{{ amount }}
|
||||
</h2>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { displayResource, Resource } from "@/features/resource";
|
||||
import { computed, toRefs } from "vue";
|
||||
import { computed } from "vue";
|
||||
|
||||
const _props = defineProps<{
|
||||
const props = defineProps<{
|
||||
resource: Resource;
|
||||
color: string;
|
||||
}>();
|
||||
const props = toRefs(_props);
|
||||
|
||||
const amount = computed(() => displayResource(props.resource));
|
||||
</script>
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
<template>
|
||||
<div class="table">
|
||||
<div class="row">
|
||||
<div class="row" :class="{ mergeAdjacent }">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts"></script>
|
||||
<script setup lang="ts">
|
||||
import "@/components/common/table.css";
|
||||
import themes from "@/data/themes";
|
||||
import settings from "@/game/settings";
|
||||
import { computed } from "vue";
|
||||
|
||||
const mergeAdjacent = computed(() => themes[settings.theme].mergeAdjacent);
|
||||
</script>
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
<div class="details" v-else-if="save.error == undefined && isEditing">
|
||||
<Text v-model="newName" class="editname" @submit="changeName" />
|
||||
</div>
|
||||
<div v-else class="details error">Error: Failed to load save with id {{ save.id }}</div>
|
||||
<div v-else class="details error">Error: Failed to load save with id {{ save.id }}<br/>{{ save.error }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -88,7 +88,7 @@ const isEditing = ref(false);
|
|||
const isConfirming = ref(false);
|
||||
const newName = ref("");
|
||||
|
||||
watch(isEditing, () => (newName.value = ""));
|
||||
watch(isEditing, () => (newName.value = save.value.name || ""));
|
||||
|
||||
const isActive = computed(() => save.value && save.value.id === player.id);
|
||||
const currentTime = computed(() =>
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
<Select
|
||||
v-if="Object.keys(bank).length > 0"
|
||||
:options="bank"
|
||||
:modelValue="[]"
|
||||
:modelValue="undefined"
|
||||
@update:modelValue="preset => newFromPreset(preset as string)"
|
||||
closeOnSelect
|
||||
placeholder="Select preset"
|
||||
|
@ -61,7 +61,15 @@ import Modal from "@/components/system/Modal.vue";
|
|||
import player, { PlayerData } from "@/game/player";
|
||||
import settings from "@/game/settings";
|
||||
import { getUniqueID, loadSave, save, newSave } from "@/util/save";
|
||||
import { ComponentPublicInstance, computed, nextTick, reactive, ref, unref, watch } from "vue";
|
||||
import {
|
||||
ComponentPublicInstance,
|
||||
computed,
|
||||
nextTick,
|
||||
ref,
|
||||
shallowReactive,
|
||||
unref,
|
||||
watch
|
||||
} from "vue";
|
||||
import Select from "../fields/Select.vue";
|
||||
import Text from "../fields/Text.vue";
|
||||
import Save from "./Save.vue";
|
||||
|
@ -121,22 +129,27 @@ let bank = ref(
|
|||
}, [])
|
||||
);
|
||||
|
||||
const cachedSaves = reactive<Record<string, LoadablePlayerData>>({});
|
||||
const cachedSaves = shallowReactive<Record<string, LoadablePlayerData | undefined>>({});
|
||||
function getCachedSave(id: string) {
|
||||
if (!(id in cachedSaves)) {
|
||||
if (cachedSaves[id] == null) {
|
||||
const save = localStorage.getItem(id);
|
||||
if (save == null) {
|
||||
cachedSaves[id] = { error: `Save with id "${id}" doesn't exist`, id };
|
||||
cachedSaves[id] = { error: `Save doesn't exist in localStorage`, id };
|
||||
} else if (save === "dW5kZWZpbmVk") {
|
||||
cachedSaves[id] = { error: `Save is undefined`, id };
|
||||
} else {
|
||||
try {
|
||||
cachedSaves[id] = JSON.parse(decodeURIComponent(escape(atob(save))));
|
||||
cachedSaves[id].id = id;
|
||||
cachedSaves[id] = { ...JSON.parse(decodeURIComponent(escape(atob(save)))), id };
|
||||
} catch (error) {
|
||||
cachedSaves[id] = { error, id };
|
||||
console.warn(
|
||||
`SavesManager: Failed to load info about save with id ${id}:\n${error}\n${save}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return cachedSaves[id];
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return cachedSaves[id]!;
|
||||
}
|
||||
// Wipe cache whenever the modal is opened
|
||||
watch(isOpen, isOpen => {
|
||||
|
@ -187,6 +200,7 @@ function duplicateSave(id: string) {
|
|||
function deleteSave(id: string) {
|
||||
settings.saves = settings.saves.filter((save: string) => save !== id);
|
||||
localStorage.removeItem(id);
|
||||
cachedSaves[id] = undefined;
|
||||
}
|
||||
|
||||
function openSave(id: string) {
|
||||
|
@ -195,6 +209,8 @@ function openSave(id: string) {
|
|||
save();
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
loadSave(saves.value[id]!);
|
||||
// Delete cached version in case of opening it again
|
||||
cachedSaves[id] = undefined;
|
||||
}
|
||||
|
||||
function newFromPreset(preset: string) {
|
||||
|
@ -217,6 +233,7 @@ function editSave(id: string, newName: string) {
|
|||
save();
|
||||
} else {
|
||||
localStorage.setItem(id, btoa(unescape(encodeURIComponent(JSON.stringify(currSave)))));
|
||||
cachedSaves[id] = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ onMounted(() => {
|
|||
margin-right: -10px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
width: 100%;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
<template>
|
||||
<div class="tpsDisplay" v-if="!tps.isNan">TPS: {{ tps }}</div>
|
||||
<div class="tpsDisplay" v-if="!tps.isNan()">
|
||||
TPS: {{ formatWhole(tps) }}
|
||||
<transition name="fade"
|
||||
><span v-if="showLow" class="low">{{ formatWhole(low) }}</span></transition
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import state from "@/game/state";
|
||||
import Decimal from "@/util/bignum";
|
||||
import { computed } from "vue";
|
||||
import Decimal, { DecimalSource, formatWhole } from "@/util/bignum";
|
||||
import { computed, ref, watchEffect } from "vue";
|
||||
|
||||
const tps = computed(() =>
|
||||
Decimal.div(
|
||||
|
@ -13,6 +18,20 @@ const tps = computed(() =>
|
|||
state.lastTenTicks.reduce((acc, curr) => acc + curr, 0)
|
||||
)
|
||||
);
|
||||
|
||||
const lastTenFPS = ref<number[]>([]);
|
||||
watchEffect(() => {
|
||||
lastTenFPS.value.push(Math.round(tps.value.toNumber()));
|
||||
if (lastTenFPS.value.length > 10) {
|
||||
lastTenFPS.value = lastTenFPS.value.slice(1);
|
||||
}
|
||||
});
|
||||
|
||||
const low = computed(() =>
|
||||
lastTenFPS.value.reduce<DecimalSource>((acc, curr) => Decimal.max(acc, curr), 0)
|
||||
);
|
||||
|
||||
const showLow = computed(() => Decimal.sub(tps.value, low.value).gt(1));
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
@ -22,4 +41,12 @@ const tps = computed(() =>
|
|||
bottom: 10px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.low {
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
'--yoffset': unref(yoffset) || '0px'
|
||||
}"
|
||||
>
|
||||
<component v-if="component" :is="component" />
|
||||
<component v-if="comp" :is="comp" />
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
|
@ -29,35 +29,31 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { CoercableComponent } from "@/features/feature";
|
||||
import { ProcessedComputable } from "@/util/computed";
|
||||
import { computeOptionalComponent, unwrapRef } from "@/util/vue";
|
||||
import { computed, defineComponent, PropType, ref, toRefs, unref } from "vue";
|
||||
import { computeOptionalComponent, processedPropType, unwrapRef } from "@/util/vue";
|
||||
import { computed, defineComponent, ref, toRefs, unref } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
display: {
|
||||
type: [Object, String] as PropType<ProcessedComputable<CoercableComponent>>,
|
||||
required: true
|
||||
},
|
||||
top: Boolean as PropType<ProcessedComputable<boolean>>,
|
||||
left: Boolean as PropType<ProcessedComputable<boolean>>,
|
||||
right: Boolean as PropType<ProcessedComputable<boolean>>,
|
||||
bottom: Boolean as PropType<ProcessedComputable<boolean>>,
|
||||
xoffset: String as PropType<ProcessedComputable<string>>,
|
||||
yoffset: String as PropType<ProcessedComputable<string>>,
|
||||
force: Boolean as PropType<ProcessedComputable<boolean>>
|
||||
display: processedPropType<CoercableComponent>(Object, String, Function),
|
||||
top: processedPropType<boolean>(Boolean),
|
||||
left: processedPropType<boolean>(Boolean),
|
||||
right: processedPropType<boolean>(Boolean),
|
||||
bottom: processedPropType<boolean>(Boolean),
|
||||
xoffset: processedPropType<string>(String),
|
||||
yoffset: processedPropType<string>(String),
|
||||
force: processedPropType<boolean>(Boolean)
|
||||
},
|
||||
setup(props) {
|
||||
const { display, force } = toRefs(props);
|
||||
|
||||
const isHovered = ref(false);
|
||||
const isShown = computed(() => unwrapRef(force) || isHovered.value);
|
||||
const component = computeOptionalComponent(display);
|
||||
const isShown = computed(() => (unwrapRef(force) || isHovered.value) && comp.value);
|
||||
const comp = computeOptionalComponent(display);
|
||||
|
||||
return {
|
||||
isHovered,
|
||||
isShown,
|
||||
component,
|
||||
comp,
|
||||
unref
|
||||
};
|
||||
}
|
||||
|
|
|
@ -13,6 +13,6 @@ defineProps<{
|
|||
width: 4px;
|
||||
background: var(--outline);
|
||||
height: 100%;
|
||||
margin: auto 7px;
|
||||
margin: auto var(--feature-margin);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
GenericClickable
|
||||
} from "@/features/clickable";
|
||||
import { GenericConversion } from "@/features/conversion";
|
||||
import { CoercableComponent, Replace, setDefault } from "@/features/feature";
|
||||
import { CoercableComponent, jsx, Replace, setDefault } from "@/features/feature";
|
||||
import { displayResource } from "@/features/resource";
|
||||
import {
|
||||
createTreeNode,
|
||||
|
@ -56,55 +56,61 @@ export type GenericResetButton = Replace<
|
|||
>;
|
||||
|
||||
export function createResetButton<T extends ClickableOptions & ResetButtonOptions>(
|
||||
options: T
|
||||
optionsFunc: () => T
|
||||
): ResetButton<T> {
|
||||
setDefault(options, "showNextAt", true);
|
||||
if (options.resetDescription == null) {
|
||||
options.resetDescription = computed(() =>
|
||||
Decimal.lt(proxy.conversion.gainResource.value, 1e3) ? "Reset for " : ""
|
||||
);
|
||||
}
|
||||
if (options.display == null) {
|
||||
options.display = computed(() => {
|
||||
const nextAt = unref(proxy.showNextAt) && (
|
||||
<template>
|
||||
<br />
|
||||
<br />
|
||||
Next:{" "}
|
||||
{displayResource(
|
||||
proxy.conversion.baseResource,
|
||||
unref(proxy.conversion.nextAt)
|
||||
)}{" "}
|
||||
{proxy.conversion.baseResource.displayName}
|
||||
</template>
|
||||
return createClickable(() => {
|
||||
const resetButton = optionsFunc();
|
||||
|
||||
processComputable(resetButton as T, "showNextAt");
|
||||
setDefault(resetButton, "showNextAt", true);
|
||||
|
||||
if (resetButton.resetDescription == null) {
|
||||
resetButton.resetDescription = computed(() =>
|
||||
Decimal.lt(resetButton.conversion.gainResource.value, 1e3) ? "Reset for " : ""
|
||||
);
|
||||
return (
|
||||
} else {
|
||||
processComputable(resetButton as T, "resetDescription");
|
||||
}
|
||||
|
||||
if (resetButton.display == null) {
|
||||
resetButton.display = jsx(() => (
|
||||
<span>
|
||||
{proxy.resetDescription}
|
||||
{unref(resetButton.resetDescription as ProcessedComputable<string>)}
|
||||
<b>
|
||||
{displayResource(
|
||||
proxy.conversion.gainResource,
|
||||
unref(proxy.conversion.currentGain)
|
||||
resetButton.conversion.gainResource,
|
||||
unref(resetButton.conversion.currentGain)
|
||||
)}
|
||||
</b>
|
||||
{proxy.conversion.gainResource.displayName}
|
||||
{nextAt}
|
||||
</b>{" "}
|
||||
{resetButton.conversion.gainResource.displayName}
|
||||
<div v-show={unref(resetButton.showNextAt)}>
|
||||
<br />
|
||||
Next:{" "}
|
||||
{displayResource(
|
||||
resetButton.conversion.baseResource,
|
||||
unref(resetButton.conversion.nextAt)
|
||||
)}{" "}
|
||||
{resetButton.conversion.baseResource.displayName}
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
});
|
||||
}
|
||||
if (options.canClick == null) {
|
||||
options.canClick = computed(() => Decimal.gt(unref(proxy.conversion.currentGain), 0));
|
||||
}
|
||||
const onClick = options.onClick;
|
||||
options.onClick = function () {
|
||||
proxy.conversion.convert();
|
||||
proxy.tree.reset(proxy.treeNode);
|
||||
onClick?.();
|
||||
};
|
||||
));
|
||||
}
|
||||
|
||||
const proxy = createClickable(options) as unknown as ResetButton<T>;
|
||||
return proxy;
|
||||
if (resetButton.canClick == null) {
|
||||
resetButton.canClick = computed(() =>
|
||||
Decimal.gt(unref(resetButton.conversion.currentGain), 0)
|
||||
);
|
||||
}
|
||||
|
||||
const onClick = resetButton.onClick;
|
||||
resetButton.onClick = function () {
|
||||
resetButton.conversion.convert();
|
||||
resetButton.tree.reset(resetButton.treeNode);
|
||||
onClick?.();
|
||||
};
|
||||
|
||||
return resetButton;
|
||||
}) as unknown as ResetButton<T>;
|
||||
}
|
||||
|
||||
export interface LayerTreeNodeOptions extends TreeNodeOptions {
|
||||
|
@ -120,27 +126,28 @@ export type LayerTreeNode<T extends LayerTreeNodeOptions> = Replace<
|
|||
>;
|
||||
export type GenericLayerTreeNode = LayerTreeNode<LayerTreeNodeOptions>;
|
||||
|
||||
export function createLayerTreeNode<T extends LayerTreeNodeOptions>(options: T): LayerTreeNode<T> {
|
||||
processComputable(options as T, "append");
|
||||
|
||||
return createTreeNode({
|
||||
...options,
|
||||
display: options.layerID,
|
||||
onClick:
|
||||
options.append != null && options.append
|
||||
? function () {
|
||||
if (player.tabs.includes(options.layerID)) {
|
||||
const index = player.tabs.lastIndexOf(options.layerID);
|
||||
player.tabs = [
|
||||
...player.tabs.slice(0, index),
|
||||
...player.tabs.slice(index + 1)
|
||||
];
|
||||
} else {
|
||||
player.tabs = [...player.tabs, options.layerID];
|
||||
export function createLayerTreeNode<T extends LayerTreeNodeOptions>(
|
||||
optionsFunc: () => T
|
||||
): LayerTreeNode<T> {
|
||||
return createTreeNode(() => {
|
||||
const options = optionsFunc();
|
||||
processComputable(options as T, "append");
|
||||
return {
|
||||
...options,
|
||||
display: options.layerID,
|
||||
onClick:
|
||||
options.append != null && options.append
|
||||
? function () {
|
||||
if (player.tabs.includes(options.layerID)) {
|
||||
const index = player.tabs.lastIndexOf(options.layerID);
|
||||
player.tabs.splice(index, 1);
|
||||
} else {
|
||||
player.tabs.push(options.layerID);
|
||||
}
|
||||
}
|
||||
}
|
||||
: function () {
|
||||
player.tabs.splice(1, 1, options.layerID);
|
||||
}
|
||||
: function () {
|
||||
player.tabs.splice(1, 1, options.layerID);
|
||||
}
|
||||
};
|
||||
}) as unknown as LayerTreeNode<T>;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import Row from "@/components/system/Row.vue";
|
||||
import Tooltip from "@/components/system/Tooltip.vue";
|
||||
import { main } from "@/data/mod";
|
||||
import { createAchievement } from "@/features/achievement";
|
||||
import { jsx } from "@/features/feature";
|
||||
import { createGrid } from "@/features/grid";
|
||||
import { createResource } from "@/features/resource";
|
||||
import { createTreeNode } from "@/features/tree";
|
||||
|
@ -8,6 +10,7 @@ import { createLayer } from "@/game/layers";
|
|||
import { DecimalSource } from "@/lib/break_eternity";
|
||||
import Decimal from "@/util/bignum";
|
||||
import { render, renderRow } from "@/util/vue";
|
||||
import { computed } from "vue";
|
||||
import f from "./f";
|
||||
|
||||
const layer = createLayer(() => {
|
||||
|
@ -16,59 +19,64 @@ const layer = createLayer(() => {
|
|||
const name = "Achievements";
|
||||
const points = createResource<DecimalSource>(0, "achievement power");
|
||||
|
||||
const treeNode = createTreeNode({
|
||||
tooltip: "Achievements",
|
||||
const treeNode = createTreeNode(() => ({
|
||||
display: "A",
|
||||
color,
|
||||
tooltip: {
|
||||
display: "Achievements",
|
||||
right: true
|
||||
},
|
||||
onClick() {
|
||||
// TODO open this layer as a modal
|
||||
main.showAchievements.value = true;
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
const ach1 = createAchievement({
|
||||
const ach1 = createAchievement(() => ({
|
||||
image: "https://unsoftcapped2.github.io/The-Modding-Tree-2/discord.png",
|
||||
display: "Get me!",
|
||||
tooltip() {
|
||||
if (this.earned.value) {
|
||||
tooltip: computed(() => {
|
||||
if (ach1.earned.value) {
|
||||
return "You did it!";
|
||||
}
|
||||
return "How did this happen?";
|
||||
},
|
||||
}),
|
||||
shouldEarn: true
|
||||
});
|
||||
const ach2 = createAchievement({
|
||||
}));
|
||||
const ach2 = createAchievement(() => ({
|
||||
display: "Impossible!",
|
||||
tooltip() {
|
||||
if (this.earned.value) {
|
||||
tooltip: computed(() => {
|
||||
if (ach2.earned.value) {
|
||||
return "HOW????";
|
||||
}
|
||||
return "Mwahahaha!";
|
||||
},
|
||||
}),
|
||||
style: { color: "#04e050" }
|
||||
});
|
||||
const ach3 = createAchievement({
|
||||
}));
|
||||
const ach3 = createAchievement(() => ({
|
||||
display: "EIEIO",
|
||||
tooltip:
|
||||
"Get a farm point.\n\nReward: The dinosaur is now your friend (you can max Farm Points).",
|
||||
shouldEarn: function () {
|
||||
return Decimal.gte(f.value.points.value, 1);
|
||||
return Decimal.gte(f.points.value, 1);
|
||||
},
|
||||
onComplete() {
|
||||
console.log("Bork bork bork!");
|
||||
}
|
||||
});
|
||||
}));
|
||||
const achievements = [ach1, ach2, ach3];
|
||||
|
||||
const grid = createGrid({
|
||||
const grid = createGrid(() => ({
|
||||
rows: 2,
|
||||
cols: 2,
|
||||
getStartState(id) {
|
||||
return id;
|
||||
},
|
||||
getStyle(id) {
|
||||
return { backgroundColor: `#${(Number(id) * 1234) % 999999}` };
|
||||
getStyle(id, state) {
|
||||
return { backgroundColor: `#${(Number(state) * 1234) % 999999}` };
|
||||
},
|
||||
// TODO display should return an object
|
||||
getTitle(id) {
|
||||
let direction;
|
||||
let direction = "";
|
||||
if (id === "101") {
|
||||
direction = "top";
|
||||
} else if (id === "102") {
|
||||
|
@ -78,29 +86,39 @@ const layer = createLayer(() => {
|
|||
} else if (id === "202") {
|
||||
direction = "right";
|
||||
}
|
||||
return (
|
||||
<Tooltip display={JSON.stringify(this.cells[id].style)} {...{ direction }}>
|
||||
return jsx(() => (
|
||||
<Tooltip display={JSON.stringify(this.cells[id].style)} {...{ [direction]: true }}>
|
||||
<h3>Gridable #{id}</h3>
|
||||
</Tooltip>
|
||||
);
|
||||
));
|
||||
},
|
||||
getDisplay(id) {
|
||||
return String(id);
|
||||
getDisplay(id, state) {
|
||||
return String(state);
|
||||
},
|
||||
getCanClick(): boolean {
|
||||
return Decimal.eq(main.value.points.value, 10);
|
||||
getCanClick() {
|
||||
return Decimal.eq(main.points.value, 10);
|
||||
},
|
||||
onClick(id, state) {
|
||||
this.cells[id].state = Number(state) + 1;
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
const display = (
|
||||
<template>
|
||||
{renderRow(achievements)}
|
||||
{render(grid)}
|
||||
</template>
|
||||
);
|
||||
const display = jsx(() => (
|
||||
<>
|
||||
<Row>
|
||||
<Tooltip display={ach1.tooltip} bottom>
|
||||
{render(ach1)}
|
||||
</Tooltip>
|
||||
<Tooltip display={ach2.tooltip} bottom>
|
||||
{render(ach2)}
|
||||
</Tooltip>
|
||||
<Tooltip display={ach3.tooltip} bottom>
|
||||
{render(ach3)}
|
||||
</Tooltip>
|
||||
</Row>
|
||||
{renderRow(grid)}
|
||||
</>
|
||||
));
|
||||
|
||||
return {
|
||||
id,
|
||||
|
|
|
@ -3,6 +3,7 @@ import Slider from "@/components/fields/Slider.vue";
|
|||
import Text from "@/components/fields/Text.vue";
|
||||
import Toggle from "@/components/fields/Toggle.vue";
|
||||
import Column from "@/components/system/Column.vue";
|
||||
import Modal from "@/components/system/Modal.vue";
|
||||
import Resource from "@/components/system/Resource.vue";
|
||||
import Row from "@/components/system/Row.vue";
|
||||
import Spacer from "@/components/system/Spacer.vue";
|
||||
|
@ -10,33 +11,39 @@ import Sticky from "@/components/system/Sticky.vue";
|
|||
import VerticalRule from "@/components/system/VerticalRule.vue";
|
||||
import { createLayerTreeNode, createResetButton } from "@/data/common";
|
||||
import { main } from "@/data/mod";
|
||||
import themes from "@/data/themes";
|
||||
import { createBar, Direction } from "@/features/bar";
|
||||
import { createBuyable } from "@/features/buyable";
|
||||
import { createChallenge } from "@/features/challenge";
|
||||
import { createClickable } from "@/features/clickable";
|
||||
import { createCumulativeConversion, createExponentialScaling } from "@/features/conversion";
|
||||
import { CoercableComponent, persistent, showIf } from "@/features/feature";
|
||||
import {
|
||||
addSoftcap,
|
||||
createCumulativeConversion,
|
||||
createExponentialScaling
|
||||
} from "@/features/conversion";
|
||||
import { jsx, persistent, showIf, Visibility } from "@/features/feature";
|
||||
import { createHotkey } from "@/features/hotkey";
|
||||
import { createInfobox } from "@/features/infobox";
|
||||
import { createMilestone } from "@/features/milestone";
|
||||
import { createReset } from "@/features/reset";
|
||||
import { addSoftcap, createResource, displayResource, trackBest } from "@/features/resource";
|
||||
import { createResource, displayResource, trackBest } from "@/features/resource";
|
||||
import { createTab } from "@/features/tab";
|
||||
import { createTabButton, createTabFamily } from "@/features/tabFamily";
|
||||
import { createTree, createTreeNode, GenericTreeNode, TreeBranch } from "@/features/tree";
|
||||
import { createUpgrade } from "@/features/upgrade";
|
||||
import { createLayer, getLayer } from "@/game/layers";
|
||||
import { createLayer } from "@/game/layers";
|
||||
import settings from "@/game/settings";
|
||||
import { DecimalSource } from "@/lib/break_eternity";
|
||||
import Decimal, { format, formatWhole } from "@/util/bignum";
|
||||
import { render, renderCol, renderRow } from "@/util/vue";
|
||||
import { computed, Ref } from "vue";
|
||||
import { computed, ComputedRef, ref } from "vue";
|
||||
import f from "./f";
|
||||
|
||||
const layer = createLayer(() => {
|
||||
const id = "c";
|
||||
const color = "#4BDC13";
|
||||
const name = "Candies";
|
||||
const points = addSoftcap(createResource<DecimalSource>(0, "lollipops"), 1e100, 0.5);
|
||||
const points = createResource<DecimalSource>(0, "lollipops");
|
||||
const best = trackBest(points);
|
||||
const beep = persistent<boolean>(false);
|
||||
const thingy = persistent<string>("pointy");
|
||||
|
@ -46,14 +53,15 @@ const layer = createLayer(() => {
|
|||
const waffleBoost = computed(() => Decimal.pow(points.value, 0.2));
|
||||
const icecreamCap = computed(() => Decimal.times(points.value, 10));
|
||||
|
||||
const coolInfo = createInfobox({
|
||||
const coolInfo = createInfobox(() => ({
|
||||
title: "Lore",
|
||||
titleStyle: { color: "#FE0000" },
|
||||
display: "DEEP LORE!",
|
||||
bodyStyle: { backgroundColor: "#0000EE" }
|
||||
});
|
||||
bodyStyle: { backgroundColor: "#0000EE" },
|
||||
color: "rgb(75, 220, 19)"
|
||||
}));
|
||||
|
||||
const lollipopMilestone3 = createMilestone({
|
||||
const lollipopMilestone3 = createMilestone(() => ({
|
||||
shouldEarn() {
|
||||
return Decimal.gte(best.value, 3);
|
||||
},
|
||||
|
@ -61,8 +69,8 @@ const layer = createLayer(() => {
|
|||
requirement: "3 Lollipops",
|
||||
effectDisplay: "Unlock the next milestone"
|
||||
}
|
||||
});
|
||||
const lollipopMilestone4 = createMilestone({
|
||||
}));
|
||||
const lollipopMilestone4 = createMilestone(() => ({
|
||||
visibility() {
|
||||
return showIf(lollipopMilestone3.earned.value);
|
||||
},
|
||||
|
@ -72,14 +80,20 @@ const layer = createLayer(() => {
|
|||
display: {
|
||||
requirement: "4 Lollipops",
|
||||
effectDisplay: "You can toggle beep and boop (which do nothing)",
|
||||
optionsDisplay() {
|
||||
return (
|
||||
<div style="display: flex; justify-content: center">
|
||||
<Toggle title="beep" v-model={beep} />
|
||||
<Toggle title="boop" v-model={f.value.boop as Ref<boolean>} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
optionsDisplay: jsx(() => (
|
||||
<>
|
||||
<Toggle
|
||||
title="beep"
|
||||
onUpdate:modelValue={value => (beep.value = value)}
|
||||
modelValue={beep.value}
|
||||
/>
|
||||
<Toggle
|
||||
title="boop"
|
||||
onUpdate:modelValue={value => (f.boop.value = value)}
|
||||
modelValue={f.boop.value}
|
||||
/>
|
||||
</>
|
||||
))
|
||||
},
|
||||
style() {
|
||||
if (this.earned) {
|
||||
|
@ -87,27 +101,28 @@ const layer = createLayer(() => {
|
|||
}
|
||||
return {};
|
||||
}
|
||||
});
|
||||
}));
|
||||
const lollipopMilestones = [lollipopMilestone3, lollipopMilestone4];
|
||||
|
||||
const funChallenge = createChallenge({
|
||||
const funChallenge = createChallenge(() => ({
|
||||
title: "Fun",
|
||||
completionLimit: 3,
|
||||
display: {
|
||||
description() {
|
||||
return `Makes the game 0% harder<br>${this.completions}/${this.completionLimit} completions`;
|
||||
},
|
||||
goal: "Have 20 points I guess",
|
||||
reward: "Says hi",
|
||||
effectDisplay() {
|
||||
return format(funEffect.value) + "x";
|
||||
}
|
||||
display() {
|
||||
return {
|
||||
description: `Makes the game 0% harder<br>${formatWhole(this.completions.value)}/${
|
||||
this.completionLimit
|
||||
} completions`,
|
||||
goal: "Have 20 points I guess",
|
||||
reward: "Says hi",
|
||||
effectDisplay: format(funEffect.value) + "x"
|
||||
};
|
||||
},
|
||||
visibility() {
|
||||
return showIf(Decimal.gt(best.value, 0));
|
||||
},
|
||||
goal: 20,
|
||||
resource: main.value.points,
|
||||
reset,
|
||||
resource: main.points,
|
||||
onComplete() {
|
||||
console.log("hiii");
|
||||
},
|
||||
|
@ -120,38 +135,40 @@ const layer = createLayer(() => {
|
|||
style: {
|
||||
height: "200px"
|
||||
}
|
||||
});
|
||||
}));
|
||||
const funEffect = computed(() => Decimal.add(points.value, 1).tetrate(0.02));
|
||||
|
||||
const generatorUpgrade = createUpgrade({
|
||||
title: "Generator of Genericness",
|
||||
display: "Gain 1 point every second",
|
||||
const generatorUpgrade = createUpgrade(() => ({
|
||||
display: {
|
||||
title: "Generator of Genericness",
|
||||
description: "Gain 1 point every second"
|
||||
},
|
||||
cost: 1,
|
||||
resource: points
|
||||
});
|
||||
const lollipopMultiplierUpgrade = createUpgrade({
|
||||
display: () =>
|
||||
`Point generation is faster based on your unspent Lollipops<br>Currently: ${format(
|
||||
lollipopMultiplierEffect.value
|
||||
)}x`,
|
||||
}));
|
||||
const lollipopMultiplierUpgrade = createUpgrade(() => ({
|
||||
display: () => ({
|
||||
description: "Point generation is faster based on your unspent Lollipops",
|
||||
effectDisplay: `${format(lollipopMultiplierEffect.value)}x`
|
||||
}),
|
||||
cost: 1,
|
||||
resource: points,
|
||||
visibility: () => showIf(generatorUpgrade.bought.value)
|
||||
});
|
||||
}));
|
||||
const lollipopMultiplierEffect = computed(() => {
|
||||
let ret = Decimal.add(points.value, 1).pow(0.5);
|
||||
if (ret.gte("1e20000000")) ret = ret.sqrt().times("1e10000000");
|
||||
return ret;
|
||||
});
|
||||
const unlockIlluminatiUpgrade = createUpgrade({
|
||||
const unlockIlluminatiUpgrade = createUpgrade(() => ({
|
||||
visibility() {
|
||||
return showIf(lollipopMultiplierUpgrade.bought.value);
|
||||
},
|
||||
canPurchase() {
|
||||
return Decimal.lt(main.value.points.value, 7);
|
||||
canAfford() {
|
||||
return Decimal.lt(main.points.value, 7);
|
||||
},
|
||||
onPurchase() {
|
||||
main.value.points.value = Decimal.add(main.value.points.value, 7);
|
||||
main.points.value = Decimal.add(main.points.value, 7);
|
||||
},
|
||||
display:
|
||||
"Only buyable with less than 7 points, and gives you 7 more. Unlocks a secret subtab.",
|
||||
|
@ -164,10 +181,18 @@ const layer = createLayer(() => {
|
|||
}
|
||||
return {};
|
||||
}
|
||||
});
|
||||
}));
|
||||
const quasiUpgrade = createUpgrade(() => ({
|
||||
resource: createResource(exhancers.amount, "Exhancers", 0),
|
||||
cost: 3,
|
||||
display: {
|
||||
title: "This upgrade doesn't exist",
|
||||
description: "Or does it?"
|
||||
}
|
||||
}));
|
||||
const upgrades = [generatorUpgrade, lollipopMultiplierUpgrade, unlockIlluminatiUpgrade];
|
||||
|
||||
const exhancers = createBuyable({
|
||||
const exhancers = createBuyable(() => ({
|
||||
resource: points,
|
||||
cost() {
|
||||
let x = new Decimal(this.amount.value);
|
||||
|
@ -177,49 +202,51 @@ const layer = createLayer(() => {
|
|||
const cost = Decimal.pow(2, x.pow(1.5));
|
||||
return cost.floor();
|
||||
},
|
||||
display: {
|
||||
title: "Exhancers",
|
||||
description() {
|
||||
return `Adds ${format(
|
||||
exhancersFirstEffect.value
|
||||
)} things and multiplies stuff by ${format(exhancersSecondEffect.value)}.`;
|
||||
}
|
||||
display() {
|
||||
return {
|
||||
title: "Exhancers",
|
||||
description: `Adds ${format(
|
||||
thingEffect.value
|
||||
)} things and multiplies stuff by ${format(stuffEffect.value)}.`
|
||||
};
|
||||
},
|
||||
onPurchase(cost) {
|
||||
spentOnBuyables.value = Decimal.add(spentOnBuyables.value, cost);
|
||||
},
|
||||
style: { height: "222px" },
|
||||
purchaseLimit: 4
|
||||
});
|
||||
const exhancersFirstEffect = computed(() => {
|
||||
}));
|
||||
// The following need redundant ComputedRef<Decimal> type annotations because otherwise the ts
|
||||
// interpreter thinks exhancers are cyclically referenced
|
||||
const thingEffect: ComputedRef<Decimal> = computed(() => {
|
||||
if (Decimal.gte(exhancers.amount.value, 0)) {
|
||||
return Decimal.pow(25, Decimal.pow(exhancers.amount.value, 1.1));
|
||||
}
|
||||
return Decimal.pow(1 / 25, Decimal.times(exhancers.amount.value, -1).pow(1.1));
|
||||
});
|
||||
const exhancersSecondEffect = computed(() => {
|
||||
const stuffEffect: ComputedRef<Decimal> = computed(() => {
|
||||
if (Decimal.gte(exhancers.amount.value, 0)) {
|
||||
return Decimal.pow(25, Decimal.pow(exhancers.amount.value, 1.1));
|
||||
}
|
||||
return Decimal.pow(1 / 25, Decimal.times(exhancers.amount.value, -1).pow(1.1));
|
||||
});
|
||||
const confirmRespec = persistent<boolean>(false);
|
||||
const respecBuyables = createClickable({
|
||||
const confirming = ref(false);
|
||||
const respecBuyables = createClickable(() => ({
|
||||
small: true,
|
||||
display: "Respec Thingies",
|
||||
onClick() {
|
||||
if (
|
||||
confirmRespec.value &&
|
||||
!confirm("Are you sure? Respeccing these doesn't accomplish much.")
|
||||
) {
|
||||
if (confirmRespec.value && !confirming.value) {
|
||||
confirming.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
points.value = Decimal.add(points.value, spentOnBuyables.value);
|
||||
main.value.tree.reset(treeNode);
|
||||
exhancers.amount.value = 0;
|
||||
main.tree.reset(treeNode);
|
||||
}
|
||||
});
|
||||
const sellExhancer = createClickable({
|
||||
}));
|
||||
const sellExhancer = createClickable(() => ({
|
||||
small: true,
|
||||
display: "Sell One",
|
||||
onClick() {
|
||||
|
@ -228,20 +255,53 @@ const layer = createLayer(() => {
|
|||
}
|
||||
exhancers.amount.value = Decimal.sub(exhancers.amount.value, 1);
|
||||
points.value = Decimal.add(points.value, exhancers.cost.value);
|
||||
spentOnBuyables.value = Decimal.sub(spentOnBuyables.value, exhancers.cost.value);
|
||||
}
|
||||
});
|
||||
const buyablesDisplay = (
|
||||
}));
|
||||
const buyablesDisplay = jsx(() => (
|
||||
<Column>
|
||||
<Row>
|
||||
<Toggle title="Confirm" v-model={confirmRespec} />
|
||||
{render(respecBuyables)}
|
||||
<Toggle
|
||||
title="Confirm"
|
||||
onUpdate:modelValue={value => (confirmRespec.value = value)}
|
||||
modelValue={confirmRespec.value}
|
||||
/>
|
||||
{renderRow(respecBuyables)}
|
||||
</Row>
|
||||
{render(exhancers)}
|
||||
{render(sellExhancer)}
|
||||
{renderRow(exhancers)}
|
||||
{renderRow(sellExhancer)}
|
||||
<Modal
|
||||
modelValue={confirming.value}
|
||||
onUpdate:modelValue={value => (confirming.value = value)}
|
||||
v-slots={{
|
||||
header: () => <h2>Confirm Respec</h2>,
|
||||
body: () => <>Are you sure? Respeccing these doesn't accomplish much</>,
|
||||
footer: () => (
|
||||
<div class="modal-default-footer">
|
||||
<div class="modal-default-flex-grow"></div>
|
||||
<button
|
||||
class="button modal-default-button"
|
||||
onClick={() => (confirming.value = false)}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
class="button modal-default-button danger"
|
||||
onClick={() => {
|
||||
respecBuyables.onClick();
|
||||
confirming.value = false;
|
||||
}}
|
||||
>
|
||||
Respec
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
));
|
||||
|
||||
const longBoi = createBar({
|
||||
const longBoi = createBar(() => ({
|
||||
fillStyle: { backgroundColor: "#FFFFFF" },
|
||||
baseStyle: { backgroundColor: "#696969" },
|
||||
textStyle: { color: "#04e050" },
|
||||
|
@ -249,13 +309,13 @@ const layer = createLayer(() => {
|
|||
width: 300,
|
||||
height: 30,
|
||||
progress() {
|
||||
return Decimal.add(main.value.points.value, 1).log(10).div(10).toNumber();
|
||||
return Decimal.add(main.points.value, 1).log(10).div(10).toNumber();
|
||||
},
|
||||
display() {
|
||||
return format(main.value.points.value) + " / 1e10 points";
|
||||
return format(main.points.value) + " / 1e10 points";
|
||||
}
|
||||
});
|
||||
const tallBoi = createBar({
|
||||
}));
|
||||
const tallBoi = createBar(() => ({
|
||||
fillStyle: { backgroundColor: "#4BEC13" },
|
||||
baseStyle: { backgroundColor: "#000000" },
|
||||
textStyle: { textShadow: "0px 0px 2px #000000" },
|
||||
|
@ -264,13 +324,13 @@ const layer = createLayer(() => {
|
|||
width: 50,
|
||||
height: 200,
|
||||
progress() {
|
||||
return Decimal.div(main.value.points.value, 100);
|
||||
return Decimal.div(main.points.value, 100);
|
||||
},
|
||||
display() {
|
||||
return formatWhole(Decimal.div(main.value.points.value, 1).min(100)) + "%";
|
||||
return formatWhole(Decimal.div(main.points.value, 1).min(100)) + "%";
|
||||
}
|
||||
});
|
||||
const flatBoi = createBar({
|
||||
}));
|
||||
const flatBoi = createBar(() => ({
|
||||
fillStyle: { backgroundColor: "#FE0102" },
|
||||
baseStyle: { backgroundColor: "#222222" },
|
||||
textStyle: { textShadow: "0px 0px 2px #000000" },
|
||||
|
@ -280,39 +340,39 @@ const layer = createLayer(() => {
|
|||
progress() {
|
||||
return Decimal.div(points.value, 50);
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
const conversion = createCumulativeConversion({
|
||||
scaling: createExponentialScaling(10, 5, 0.5),
|
||||
baseResource: main.value.points,
|
||||
const conversion = createCumulativeConversion(() => ({
|
||||
scaling: addSoftcap(createExponentialScaling(10, 5, 0.5), 1e100, 0.5),
|
||||
baseResource: main.points,
|
||||
gainResource: points,
|
||||
roundUpCost: true
|
||||
});
|
||||
}));
|
||||
|
||||
const reset = createReset({
|
||||
thingsToReset: () => [getLayer("c")]
|
||||
});
|
||||
const reset = createReset(() => ({
|
||||
thingsToReset: (): Record<string, unknown>[] => [layer]
|
||||
}));
|
||||
|
||||
const hotkeys = [
|
||||
createHotkey({
|
||||
createHotkey(() => ({
|
||||
key: "c",
|
||||
description: "reset for lollipops or whatever",
|
||||
onPress() {
|
||||
if (resetButton.canClick) {
|
||||
reset.reset();
|
||||
if (resetButton.canClick.value) {
|
||||
resetButton.onClick();
|
||||
}
|
||||
}
|
||||
}),
|
||||
createHotkey({
|
||||
})),
|
||||
createHotkey(() => ({
|
||||
key: "ctrl+c",
|
||||
description: "respec things",
|
||||
onPress() {
|
||||
respecBuyables.onClick();
|
||||
}
|
||||
})
|
||||
}))
|
||||
];
|
||||
|
||||
const treeNode = createLayerTreeNode({
|
||||
const treeNode = createLayerTreeNode(() => ({
|
||||
layerID: id,
|
||||
color,
|
||||
reset,
|
||||
|
@ -330,27 +390,27 @@ const layer = createLayer(() => {
|
|||
color: "#3325CC",
|
||||
textDecoration: "underline"
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
const resetButton = createResetButton({
|
||||
const resetButton = createResetButton(() => ({
|
||||
conversion,
|
||||
tree: main.value.tree,
|
||||
tree: main.tree,
|
||||
treeNode,
|
||||
style: {
|
||||
color: "#AA66AA"
|
||||
},
|
||||
resetDescription: "Melt your points into "
|
||||
});
|
||||
}));
|
||||
|
||||
const g = createTreeNode({
|
||||
const g = createTreeNode(() => ({
|
||||
display: "TH",
|
||||
color: "#6d3678",
|
||||
canClick() {
|
||||
return Decimal.gte(points.value, 10);
|
||||
return Decimal.gte(main.points.value, 10);
|
||||
},
|
||||
tooltip: "Thanos your points",
|
||||
onClick() {
|
||||
points.value = Decimal.div(points.value, 2);
|
||||
main.points.value = Decimal.div(main.points.value, 2);
|
||||
console.log("Thanos'd");
|
||||
},
|
||||
glowColor() {
|
||||
|
@ -359,35 +419,41 @@ const layer = createLayer(() => {
|
|||
}
|
||||
return "";
|
||||
}
|
||||
});
|
||||
const h = createTreeNode({
|
||||
id: "h",
|
||||
tooltip() {
|
||||
return `Restore your points to ${format(otherThingy.value)}`;
|
||||
}));
|
||||
const h = createTreeNode(() => ({
|
||||
display: "h",
|
||||
color() {
|
||||
return themes[settings.theme].variables["--locked"];
|
||||
},
|
||||
tooltip: {
|
||||
display: computed(() => `Restore your points to ${format(otherThingy.value)}`),
|
||||
right: true
|
||||
},
|
||||
canClick() {
|
||||
return Decimal.lt(main.value.points.value, otherThingy.value);
|
||||
return Decimal.lt(main.points.value, otherThingy.value);
|
||||
},
|
||||
onClick() {
|
||||
main.value.points.value = otherThingy.value;
|
||||
main.points.value = otherThingy.value;
|
||||
}
|
||||
});
|
||||
const spook = createTreeNode({});
|
||||
const tree = createTree({
|
||||
}));
|
||||
const spook = createTreeNode(() => ({
|
||||
visibility: Visibility.Hidden
|
||||
}));
|
||||
const tree = createTree(() => ({
|
||||
nodes(): GenericTreeNode[][] {
|
||||
return [
|
||||
[f.value.treeNode, treeNode],
|
||||
[f.treeNode, treeNode],
|
||||
[g, spook, h]
|
||||
];
|
||||
},
|
||||
branches(): TreeBranch[] {
|
||||
return [
|
||||
{
|
||||
startNode: f.value.treeNode,
|
||||
startNode: f.treeNode,
|
||||
endNode: treeNode,
|
||||
"stroke-width": "25px",
|
||||
stroke: "green",
|
||||
style: {
|
||||
strokeWidth: "25px",
|
||||
stroke: "blue",
|
||||
filter: "blur(5px)"
|
||||
}
|
||||
},
|
||||
|
@ -395,71 +461,71 @@ const layer = createLayer(() => {
|
|||
{ startNode: g, endNode: h }
|
||||
];
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
const illuminatiTabs = createTabFamily({
|
||||
const illuminatiTabs = createTabFamily(() => ({
|
||||
tabs: {
|
||||
first: createTabButton({
|
||||
tab: (
|
||||
<template>
|
||||
{renderRow(upgrades)}
|
||||
tab: jsx(() => (
|
||||
<>
|
||||
{renderRow(...upgrades)}
|
||||
{renderRow(quasiUpgrade)}
|
||||
<div>confirmed</div>
|
||||
</template>
|
||||
),
|
||||
</>
|
||||
)),
|
||||
display: "first"
|
||||
}),
|
||||
second: createTabButton({
|
||||
tab: f.value.display as CoercableComponent,
|
||||
tab: f.display,
|
||||
display: "second"
|
||||
})
|
||||
},
|
||||
style: {
|
||||
width: "660px",
|
||||
height: "370px",
|
||||
backgroundColor: "brown",
|
||||
"--background": "brown",
|
||||
border: "solid white",
|
||||
margin: "auto"
|
||||
marginLeft: "auto",
|
||||
marginRight: "auto"
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
const tabs = createTabFamily({
|
||||
const tabs = createTabFamily(() => ({
|
||||
tabs: {
|
||||
mainTab: createTabButton({
|
||||
tab: createTab({
|
||||
display() {
|
||||
return (
|
||||
<template>
|
||||
<MainDisplay
|
||||
resource={points}
|
||||
color={color}
|
||||
effectDisplay={`which are boosting waffles by ${format(
|
||||
waffleBoost.value
|
||||
)} and increasing the Ice Cream cap by ${format(
|
||||
icecreamCap.value
|
||||
)}`}
|
||||
/>
|
||||
<Sticky>{render(resetButton)}</Sticky>
|
||||
<Resource resource={points} color={color} />
|
||||
<Spacer height="5px" />
|
||||
<button onClick={() => console.log("yeet")}>'HI'</button>
|
||||
<div>Name your points!</div>
|
||||
<Text v-model={thingy} />
|
||||
<Sticky style="color: red; font-size: 32px; font-family: Comic Sans MS;">
|
||||
I have {displayResource(main.value.points)}!
|
||||
</Sticky>
|
||||
<hr />
|
||||
{renderCol(lollipopMilestones)}
|
||||
<Spacer />
|
||||
{renderRow(upgrades)}
|
||||
{render(funChallenge)}
|
||||
</template>
|
||||
);
|
||||
},
|
||||
style: {
|
||||
backgroundColor: "#3325CC"
|
||||
}
|
||||
}),
|
||||
tab: createTab(() => ({
|
||||
display: jsx(() => (
|
||||
<>
|
||||
<MainDisplay
|
||||
resource={points}
|
||||
color={color}
|
||||
effectDisplay={`which are boosting waffles by ${format(
|
||||
waffleBoost.value
|
||||
)} and increasing the Ice Cream cap by ${format(
|
||||
icecreamCap.value
|
||||
)}`}
|
||||
/>
|
||||
<Sticky>{render(resetButton)}</Sticky>
|
||||
<Resource resource={points} color={color} />
|
||||
<Spacer height="5px" />
|
||||
<button onClick={() => console.log("yeet")}>'HI'</button>
|
||||
<div>Name your points!</div>
|
||||
<Text
|
||||
modelValue={thingy.value}
|
||||
onUpdate:modelValue={value => (thingy.value = value)}
|
||||
/>
|
||||
<Sticky style="color: red; font-size: 32px; font-family: Comic Sans MS;">
|
||||
I have {displayResource(main.points)} {thingy.value} points!
|
||||
</Sticky>
|
||||
<hr />
|
||||
{renderCol(...lollipopMilestones)}
|
||||
<Spacer />
|
||||
{renderRow(...upgrades)}
|
||||
{renderRow(quasiUpgrade)}
|
||||
{renderRow(funChallenge)}
|
||||
</>
|
||||
))
|
||||
})),
|
||||
display: "main tab",
|
||||
glowColor() {
|
||||
if (
|
||||
|
@ -475,89 +541,92 @@ const layer = createLayer(() => {
|
|||
style: { color: "orange" }
|
||||
}),
|
||||
thingies: createTabButton({
|
||||
tab: createTab({
|
||||
glowColor: "white",
|
||||
tab: createTab(() => ({
|
||||
style() {
|
||||
return { backgroundColor: "#222222", "--background": "#222222" };
|
||||
},
|
||||
display() {
|
||||
return (
|
||||
<template>
|
||||
{buyablesDisplay}
|
||||
display: jsx(() => (
|
||||
<>
|
||||
{render(buyablesDisplay)}
|
||||
<Spacer />
|
||||
<Row style="width: 600px; height: 350px; background-color: green; border-style: solid;">
|
||||
<Toggle
|
||||
onUpdate:modelValue={value => (beep.value = value)}
|
||||
modelValue={beep.value}
|
||||
/>
|
||||
<Spacer width="30px" height="10px" />
|
||||
<div>
|
||||
<span>Beep</span>
|
||||
</div>
|
||||
<Spacer />
|
||||
<Row style="width: 600px; height: 350px; background-color: green; border-style: solid;">
|
||||
<Toggle v-model={beep} />
|
||||
<Spacer width="30px" height="10px" />
|
||||
<div>Beep</div>
|
||||
<Spacer />
|
||||
<VerticalRule height="200px" />
|
||||
</Row>
|
||||
<Spacer />
|
||||
<img src="https://unsoftcapped2.github.io/The-Modding-Tree-2/discord.png" />
|
||||
</template>
|
||||
);
|
||||
}
|
||||
}),
|
||||
<VerticalRule height="200px" />
|
||||
</Row>
|
||||
<Spacer />
|
||||
<img src="https://unsoftcapped2.github.io/The-Modding-Tree-2/discord.png" />
|
||||
</>
|
||||
))
|
||||
})),
|
||||
glowColor: "white",
|
||||
display: "thingies",
|
||||
style: { borderColor: "orange" }
|
||||
}),
|
||||
jail: createTabButton({
|
||||
tab: createTab({
|
||||
display() {
|
||||
return (
|
||||
<template>
|
||||
{render(coolInfo)}
|
||||
{render(longBoi)}
|
||||
<Spacer />
|
||||
<Row>
|
||||
<Column style="background-color: #555555; padding: 15px">
|
||||
<div style="color: teal">Sugar level:</div>
|
||||
<Spacer />
|
||||
{render(tallBoi)}
|
||||
</Column>
|
||||
tab: createTab(() => ({
|
||||
display: jsx(() => (
|
||||
<>
|
||||
{render(coolInfo)}
|
||||
{render(longBoi)}
|
||||
<Spacer />
|
||||
<Row>
|
||||
<Column style="background-color: #555555; padding: 15px">
|
||||
<div style="color: teal">Sugar level:</div>
|
||||
<Spacer />
|
||||
<Column>
|
||||
<div>idk</div>
|
||||
<Spacer width="0" height="50px" />
|
||||
{render(flatBoi)}
|
||||
</Column>
|
||||
</Row>
|
||||
{render(tallBoi)}
|
||||
</Column>
|
||||
<Spacer />
|
||||
<div>It's jail because "bars"! So funny! Ha ha!</div>
|
||||
{render(tree)}
|
||||
</template>
|
||||
);
|
||||
},
|
||||
style: {
|
||||
backgroundColor: "#3325CC"
|
||||
}
|
||||
}),
|
||||
<Column>
|
||||
<div>idk</div>
|
||||
<Spacer width="0" height="50px" />
|
||||
{render(flatBoi)}
|
||||
</Column>
|
||||
</Row>
|
||||
<Spacer />
|
||||
<div>It's jail because "bars"! So funny! Ha ha!</div>
|
||||
{render(tree)}
|
||||
</>
|
||||
))
|
||||
})),
|
||||
display: "jail"
|
||||
}),
|
||||
illuminati: createTabButton({
|
||||
tab: createTab({
|
||||
display() {
|
||||
return (
|
||||
<template>
|
||||
<h1> C O N F I R M E D </h1>
|
||||
<Spacer />
|
||||
{render(illuminatiTabs)}
|
||||
<div>Adjust how many points H gives you!</div>
|
||||
<Slider v-model={otherThingy} min={1} max={30} />
|
||||
</template>
|
||||
);
|
||||
},
|
||||
tab: createTab(() => ({
|
||||
display: jsx(() => (
|
||||
// This should really just be <> and </>, however for some reason the
|
||||
// typescript interpreter can't figure out this layer and f.tsx otherwise
|
||||
<div>
|
||||
<h1> C O N F I R M E D </h1>
|
||||
<Spacer />
|
||||
{render(illuminatiTabs)}
|
||||
<div>Adjust how many points H gives you!</div>
|
||||
<Slider
|
||||
onUpdate:modelValue={value => (otherThingy.value = value)}
|
||||
modelValue={otherThingy.value}
|
||||
min={1}
|
||||
max={30}
|
||||
/>
|
||||
</div>
|
||||
)),
|
||||
style: {
|
||||
backgroundColor: "#3325CC"
|
||||
}
|
||||
}),
|
||||
})),
|
||||
visibility() {
|
||||
return showIf(unlockIlluminatiUpgrade.bought.value);
|
||||
},
|
||||
display: "illuminati"
|
||||
})
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
return {
|
||||
id,
|
||||
|
@ -568,11 +637,14 @@ const layer = createLayer(() => {
|
|||
links.push({
|
||||
startNode: h,
|
||||
endNode: flatBoi,
|
||||
"stroke-width": "5px",
|
||||
stroke: "red",
|
||||
offsetEnd: { x: -50 + 100 * flatBoi.progress.value.toNumber(), y: 0 }
|
||||
});
|
||||
return links;
|
||||
},
|
||||
points,
|
||||
best,
|
||||
beep,
|
||||
thingy,
|
||||
otherThingy,
|
||||
|
@ -587,9 +659,8 @@ const layer = createLayer(() => {
|
|||
lollipopMultiplierUpgrade,
|
||||
lollipopMultiplierEffect,
|
||||
unlockIlluminatiUpgrade,
|
||||
quasiUpgrade,
|
||||
exhancers,
|
||||
exhancersFirstEffect,
|
||||
exhancersSecondEffect,
|
||||
respecBuyables,
|
||||
sellExhancer,
|
||||
bars: { tallBoi, longBoi, flatBoi },
|
||||
|
@ -602,8 +673,10 @@ const layer = createLayer(() => {
|
|||
hotkeys,
|
||||
treeNode,
|
||||
resetButton,
|
||||
confirmRespec,
|
||||
minWidth: 800,
|
||||
display: render(tabs)
|
||||
tabs,
|
||||
display: jsx(() => <>{render(tabs)}</>)
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -3,11 +3,11 @@ import { createLayerTreeNode, createResetButton } from "@/data/common";
|
|||
import { main } from "@/data/mod";
|
||||
import { createClickable } from "@/features/clickable";
|
||||
import { createExponentialScaling, createIndependentConversion } from "@/features/conversion";
|
||||
import { persistent } from "@/features/feature";
|
||||
import { jsx, persistent } from "@/features/feature";
|
||||
import { createInfobox } from "@/features/infobox";
|
||||
import { createReset } from "@/features/reset";
|
||||
import { createResource, displayResource } from "@/features/resource";
|
||||
import { createLayer, getLayer } from "@/game/layers";
|
||||
import { createLayer } from "@/game/layers";
|
||||
import Decimal, { DecimalSource, formatWhole } from "@/util/bignum";
|
||||
import { render } from "@/util/vue";
|
||||
import c from "./c";
|
||||
|
@ -19,20 +19,20 @@ const layer = createLayer(() => {
|
|||
const points = createResource<DecimalSource>(0, "farm points");
|
||||
const boop = persistent<boolean>(false);
|
||||
|
||||
const coolInfo = createInfobox({
|
||||
const coolInfo = createInfobox(() => ({
|
||||
title: "Lore",
|
||||
titleStyle: { color: "#FE0000" },
|
||||
display: "DEEP LORE!",
|
||||
bodyStyle: { backgroundColor: "#0000EE" }
|
||||
});
|
||||
}));
|
||||
|
||||
const clickableState = persistent<string>("Start");
|
||||
const clickable = createClickable({
|
||||
display: {
|
||||
title: "Clicky clicky!",
|
||||
description() {
|
||||
return "Current state:<br>" + clickableState.value;
|
||||
}
|
||||
const clickable = createClickable(() => ({
|
||||
display() {
|
||||
return {
|
||||
title: "Clicky clicky!",
|
||||
description: "Current state:<br>" + clickableState.value
|
||||
};
|
||||
},
|
||||
initialState: "Start",
|
||||
canClick() {
|
||||
|
@ -75,9 +75,9 @@ const layer = createLayer(() => {
|
|||
return {};
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
const resetClickable = createClickable({
|
||||
const resetClickable = createClickable(() => ({
|
||||
onClick() {
|
||||
if (clickableState.value == "Borkened...") {
|
||||
clickableState.value = "Start";
|
||||
|
@ -86,20 +86,20 @@ const layer = createLayer(() => {
|
|||
display() {
|
||||
return clickableState.value == "Borkened..." ? "Fix the clickable!" : "Does nothing";
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
const reset = createReset({
|
||||
thingsToReset: () => [getLayer("f")]
|
||||
});
|
||||
const reset = createReset(() => ({
|
||||
thingsToReset: (): Record<string, unknown>[] => [layer]
|
||||
}));
|
||||
|
||||
const conversion = createIndependentConversion({
|
||||
const conversion = createIndependentConversion(() => ({
|
||||
scaling: createExponentialScaling(10, 3, 0.5),
|
||||
baseResource: main.value.points,
|
||||
baseResource: main.points,
|
||||
gainResource: points,
|
||||
modifyGainAmount: gain => Decimal.times(gain, c.value.otherThingy.value)
|
||||
});
|
||||
modifyGainAmount: gain => Decimal.times(gain, c.otherThingy.value)
|
||||
}));
|
||||
|
||||
const treeNode = createLayerTreeNode({
|
||||
const treeNode = createLayerTreeNode(() => ({
|
||||
layerID: id,
|
||||
color,
|
||||
reset,
|
||||
|
@ -108,26 +108,26 @@ const layer = createLayer(() => {
|
|||
return `${displayResource(points)} ${points.displayName}`;
|
||||
}
|
||||
return `This weird farmer dinosaur will only see you if you have at least 10 points. You only have ${displayResource(
|
||||
main.value.points
|
||||
main.points
|
||||
)}`;
|
||||
},
|
||||
canClick() {
|
||||
return Decimal.gte(main.value.points.value, 10);
|
||||
return Decimal.gte(main.points.value, 10);
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
const resetButton = createResetButton({
|
||||
const resetButton = createResetButton(() => ({
|
||||
conversion,
|
||||
tree: main.value.tree,
|
||||
tree: main.tree,
|
||||
treeNode,
|
||||
display() {
|
||||
if (this.conversion.buyMax) {
|
||||
display: jsx(() => {
|
||||
if (resetButton.conversion.buyMax) {
|
||||
return (
|
||||
<span>
|
||||
Hi! I'm a <u>weird dinosaur</u> and I'll give you{" "}
|
||||
<b>{formatWhole(this.conversion.currentGain.value)}</b> Farm Points in
|
||||
exchange for all of your points and lollipops! (You'll get another one at{" "}
|
||||
{formatWhole(this.conversion.nextAt.value)} points)
|
||||
<b>{formatWhole(resetButton.conversion.currentGain.value)}</b> Farm Points
|
||||
in exchange for all of your points and lollipops! (You'll get another one at{" "}
|
||||
{formatWhole(resetButton.conversion.nextAt.value)} points)
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
|
@ -135,15 +135,15 @@ const layer = createLayer(() => {
|
|||
<span>
|
||||
Hi! I'm a <u>weird dinosaur</u> and I'll give you a Farm Point in exchange
|
||||
for all of your points and lollipops! (At least{" "}
|
||||
{formatWhole(this.conversion.nextAt.value)} points)
|
||||
{formatWhole(resetButton.conversion.nextAt.value)} points)
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
}));
|
||||
|
||||
const tab = (): JSX.Element => (
|
||||
<template>
|
||||
const tab = jsx(() => (
|
||||
<>
|
||||
{render(coolInfo)}
|
||||
<MainDisplay resource={points} color={color} />
|
||||
{render(resetButton)}
|
||||
|
@ -154,8 +154,8 @@ const layer = createLayer(() => {
|
|||
<div>Bork Bork!</div>
|
||||
</div>
|
||||
{render(clickable)}
|
||||
</template>
|
||||
);
|
||||
</>
|
||||
));
|
||||
|
||||
return {
|
||||
id,
|
||||
|
|
|
@ -1,53 +1,60 @@
|
|||
import Modal from "@/components/system/Modal.vue";
|
||||
import Spacer from "@/components/system/Spacer.vue";
|
||||
import { jsx } from "@/features/feature";
|
||||
import { createResource, trackBest, trackOOMPS, trackTotal } from "@/features/resource";
|
||||
import { createTree, GenericTree } from "@/features/tree";
|
||||
import { branchedResetPropagation, createTree, GenericTree } from "@/features/tree";
|
||||
import { globalBus } from "@/game/events";
|
||||
import { createLayer, GenericLayer } from "@/game/layers";
|
||||
import player, { PlayerData } from "@/game/player";
|
||||
import { DecimalSource } from "@/lib/break_eternity";
|
||||
import Decimal, { format, formatSmall, formatTime } from "@/util/bignum";
|
||||
import Decimal, { format, formatTime } from "@/util/bignum";
|
||||
import { render } from "@/util/vue";
|
||||
import { computed, ref } from "vue";
|
||||
import { computed, ref, toRaw } from "vue";
|
||||
import a from "./layers/aca/a";
|
||||
import c from "./layers/aca/c";
|
||||
import f from "./layers/aca/f";
|
||||
|
||||
export const main = createLayer(() => {
|
||||
const points = createResource<DecimalSource>(0);
|
||||
const points = createResource<DecimalSource>(10);
|
||||
const best = trackBest(points);
|
||||
const total = trackTotal(points);
|
||||
const oomps = trackOOMPS(points);
|
||||
const showModal = ref(false);
|
||||
const showAchievements = ref(false);
|
||||
|
||||
const pointGain = computed(() => {
|
||||
if (!c.value.generatorUpgrade.bought) return new Decimal(0);
|
||||
if (!c.generatorUpgrade.bought.value) return new Decimal(0);
|
||||
let gain = new Decimal(3.19);
|
||||
if (c.value.lollipopMultiplierUpgrade.bought)
|
||||
gain = gain.times(c.value.lollipopMultiplierEffect.value);
|
||||
if (c.lollipopMultiplierUpgrade.bought.value)
|
||||
gain = gain.times(c.lollipopMultiplierEffect.value);
|
||||
return gain;
|
||||
});
|
||||
globalBus.on("update", diff => {
|
||||
points.value = Decimal.add(points.value, Decimal.times(pointGain.value, diff));
|
||||
});
|
||||
const oomps = trackOOMPS(points, pointGain);
|
||||
|
||||
// Note: Casting as generic tree to avoid recursive type definitions
|
||||
const tree = createTree({
|
||||
nodes: [[c.value.treeNode], [f.value.treeNode, c.value.spook]],
|
||||
leftSideNodes: [a.value.treeNode, c.value.h],
|
||||
const tree = createTree(() => ({
|
||||
nodes: [[c.treeNode], [f.treeNode, c.spook]],
|
||||
leftSideNodes: [a.treeNode, c.h],
|
||||
branches: [
|
||||
{
|
||||
startNode: f.value.treeNode,
|
||||
endNode: c.value.treeNode,
|
||||
startNode: f.treeNode,
|
||||
endNode: c.treeNode,
|
||||
stroke: "blue",
|
||||
"stroke-width": "25px",
|
||||
style: {
|
||||
filter: "blur(5px)"
|
||||
}
|
||||
},
|
||||
{ startNode: c.value.treeNode, endNode: c.value.g }
|
||||
]
|
||||
}) as GenericTree;
|
||||
{ startNode: c.treeNode, endNode: c.g }
|
||||
],
|
||||
onReset() {
|
||||
points.value = toRaw(this.resettingNode.value) === toRaw(c.treeNode) ? 0 : 10;
|
||||
best.value = points.value;
|
||||
total.value = points.value;
|
||||
},
|
||||
resetPropagation: branchedResetPropagation
|
||||
})) as GenericTree;
|
||||
|
||||
// Note: layers don't _need_ a reference to everything,
|
||||
// but I'd recommend it over trying to remember what does and doesn't need to be included.
|
||||
|
@ -56,8 +63,8 @@ export const main = createLayer(() => {
|
|||
id: "main",
|
||||
name: "Tree",
|
||||
links: tree.links,
|
||||
display: (
|
||||
<template>
|
||||
display: jsx(() => (
|
||||
<>
|
||||
<div v-show={player.devSpeed === 0}>Game Paused</div>
|
||||
<div v-show={player.devSpeed && player.devSpeed !== 1}>
|
||||
Dev Speed: {format(player.devSpeed || 0)}x
|
||||
|
@ -70,37 +77,33 @@ export const main = createLayer(() => {
|
|||
<h2>{format(points.value)}</h2>
|
||||
<span v-show={Decimal.lt(points.value, "1e1e6")}> points</span>
|
||||
</div>
|
||||
<div v-show={Decimal.gt(pointGain.value, 0)}>
|
||||
({oomps.value === "" ? formatSmall(pointGain.value) : oomps.value}/sec)
|
||||
</div>
|
||||
<div v-show={Decimal.gt(pointGain.value, 0)}>({oomps.value})</div>
|
||||
<Spacer />
|
||||
<button onClick={() => (showAchievements.value = true)}>open achievements</button>
|
||||
<Modal
|
||||
modelValue={showModal.value}
|
||||
onUpdate:modelValue={value => (showModal.value = value)}
|
||||
>
|
||||
<svg style="height: 80vmin; width: 80vmin;">
|
||||
<path d="M 32 222 Q 128 222, 128 0 Q 128 222, 224 222 L 224 224 L 32 224" />
|
||||
|
||||
<circle cx="64" cy="128" r="64" fill="#8da8b0" />
|
||||
<circle cx="128" cy="64" r="64" fill="#71368a" />
|
||||
<circle cx="192" cy="128" r="64" fill="#fa8508" />
|
||||
</svg>
|
||||
</Modal>
|
||||
modelValue={showAchievements.value}
|
||||
onUpdate:modelValue={value => (showAchievements.value = value)}
|
||||
v-slots={{
|
||||
header: () => <h2>Achievements</h2>,
|
||||
body: a.display
|
||||
}}
|
||||
/>
|
||||
{render(tree)}
|
||||
</template>
|
||||
),
|
||||
</>
|
||||
)),
|
||||
points,
|
||||
best,
|
||||
total,
|
||||
oomps,
|
||||
tree
|
||||
tree,
|
||||
showAchievements
|
||||
};
|
||||
});
|
||||
|
||||
export const getInitialLayers = (
|
||||
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
||||
player: Partial<PlayerData>
|
||||
): Array<GenericLayer> => [main.value, f.value, c.value, a.value];
|
||||
): Array<GenericLayer> => [main, f, c, a];
|
||||
|
||||
export const hasWon = computed(() => {
|
||||
return false;
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
"versionNumber": "0.0",
|
||||
"versionTitle": "Initial Commit",
|
||||
|
||||
"allowGoBack": false,
|
||||
"allowGoBack": true,
|
||||
"allowSmall": false,
|
||||
"defaultDecimalsShown": 2,
|
||||
"useHeader": true,
|
||||
|
|
|
@ -24,6 +24,7 @@ export interface Theme {
|
|||
stackedInfoboxes: boolean;
|
||||
floatingTabs: boolean;
|
||||
showSingleTab: boolean;
|
||||
mergeAdjacent: boolean;
|
||||
}
|
||||
|
||||
declare module "@vue/runtime-dom" {
|
||||
|
@ -55,7 +56,8 @@ const defaultTheme: Theme = {
|
|||
},
|
||||
stackedInfoboxes: false,
|
||||
floatingTabs: true,
|
||||
showSingleTab: false
|
||||
showSingleTab: false,
|
||||
mergeAdjacent: true
|
||||
};
|
||||
|
||||
export enum Themes {
|
||||
|
|
|
@ -3,6 +3,7 @@ import {
|
|||
CoercableComponent,
|
||||
Component,
|
||||
findFeatures,
|
||||
GatherProps,
|
||||
getUniqueID,
|
||||
makePersistent,
|
||||
Persistent,
|
||||
|
@ -21,7 +22,7 @@ import {
|
|||
processComputable,
|
||||
ProcessedComputable
|
||||
} from "@/util/computed";
|
||||
import { createProxy } from "@/util/proxies";
|
||||
import { createLazyProxy } from "@/util/proxies";
|
||||
import { coerceComponent } from "@/util/vue";
|
||||
import { Unsubscribe } from "nanoevents";
|
||||
import { Ref, unref } from "vue";
|
||||
|
@ -37,7 +38,6 @@ export interface AchievementOptions {
|
|||
image?: Computable<string>;
|
||||
style?: Computable<StyleValue>;
|
||||
classes?: Computable<Record<string, boolean>>;
|
||||
tooltip?: Computable<CoercableComponent>;
|
||||
onComplete?: VoidFunction;
|
||||
}
|
||||
|
||||
|
@ -47,6 +47,7 @@ interface BaseAchievement extends Persistent<boolean> {
|
|||
complete: VoidFunction;
|
||||
type: typeof AchievementType;
|
||||
[Component]: typeof AchievementComponent;
|
||||
[GatherProps]: () => Record<string, unknown>;
|
||||
}
|
||||
|
||||
export type Achievement<T extends AchievementOptions> = Replace<
|
||||
|
@ -59,7 +60,6 @@ export type Achievement<T extends AchievementOptions> = Replace<
|
|||
image: GetComputableType<T["image"]>;
|
||||
style: GetComputableType<T["style"]>;
|
||||
classes: GetComputableType<T["classes"]>;
|
||||
tooltip: GetComputableTypeWithDefault<T["tooltip"], GetComputableType<T["display"]>>;
|
||||
}
|
||||
>;
|
||||
|
||||
|
@ -71,32 +71,36 @@ export type GenericAchievement = Replace<
|
|||
>;
|
||||
|
||||
export function createAchievement<T extends AchievementOptions>(
|
||||
options: T & ThisType<Achievement<T>>
|
||||
optionsFunc: () => T & ThisType<Achievement<T>>
|
||||
): Achievement<T> {
|
||||
const achievement: T & Partial<BaseAchievement> = options;
|
||||
makePersistent<boolean>(achievement, false);
|
||||
achievement.id = getUniqueID("achievement-");
|
||||
achievement.type = AchievementType;
|
||||
achievement[Component] = AchievementComponent;
|
||||
return createLazyProxy(() => {
|
||||
const achievement: T & Partial<BaseAchievement> = optionsFunc();
|
||||
makePersistent<boolean>(achievement, false);
|
||||
achievement.id = getUniqueID("achievement-");
|
||||
achievement.type = AchievementType;
|
||||
achievement[Component] = AchievementComponent;
|
||||
|
||||
achievement.earned = achievement[PersistentState];
|
||||
achievement.complete = function () {
|
||||
proxy[PersistentState].value = true;
|
||||
};
|
||||
achievement.earned = achievement[PersistentState];
|
||||
achievement.complete = function () {
|
||||
achievement[PersistentState].value = true;
|
||||
};
|
||||
|
||||
processComputable(achievement as T, "visibility");
|
||||
setDefault(achievement, "visibility", Visibility.Visible);
|
||||
processComputable(achievement as T, "shouldEarn");
|
||||
processComputable(achievement as T, "display");
|
||||
processComputable(achievement as T, "mark");
|
||||
processComputable(achievement as T, "image");
|
||||
processComputable(achievement as T, "style");
|
||||
processComputable(achievement as T, "classes");
|
||||
processComputable(achievement as T, "tooltip");
|
||||
setDefault(achievement, "tooltip", achievement.display);
|
||||
processComputable(achievement as T, "visibility");
|
||||
setDefault(achievement, "visibility", Visibility.Visible);
|
||||
processComputable(achievement as T, "shouldEarn");
|
||||
processComputable(achievement as T, "display");
|
||||
processComputable(achievement as T, "mark");
|
||||
processComputable(achievement as T, "image");
|
||||
processComputable(achievement as T, "style");
|
||||
processComputable(achievement as T, "classes");
|
||||
|
||||
const proxy = createProxy(achievement as unknown as Achievement<T>);
|
||||
return proxy;
|
||||
achievement[GatherProps] = function (this: GenericAchievement) {
|
||||
const { visibility, display, earned, image, style, classes, mark, id } = this;
|
||||
return { visibility, display, earned, image, style, classes, mark, id };
|
||||
};
|
||||
|
||||
return achievement as unknown as Achievement<T>;
|
||||
});
|
||||
}
|
||||
|
||||
const toast = useToast();
|
||||
|
|
|
@ -2,6 +2,7 @@ import BarComponent from "@/components/features/Bar.vue";
|
|||
import {
|
||||
CoercableComponent,
|
||||
Component,
|
||||
GatherProps,
|
||||
getUniqueID,
|
||||
Replace,
|
||||
setDefault,
|
||||
|
@ -16,7 +17,7 @@ import {
|
|||
processComputable,
|
||||
ProcessedComputable
|
||||
} from "@/util/computed";
|
||||
import { createProxy } from "@/util/proxies";
|
||||
import { createLazyProxy } from "@/util/proxies";
|
||||
|
||||
export const BarType = Symbol("Bar");
|
||||
|
||||
|
@ -48,6 +49,7 @@ interface BaseBar {
|
|||
id: string;
|
||||
type: typeof BarType;
|
||||
[Component]: typeof BarComponent;
|
||||
[GatherProps]: () => Record<string, unknown>;
|
||||
}
|
||||
|
||||
export type Bar<T extends BarOptions> = Replace<
|
||||
|
@ -76,27 +78,63 @@ export type GenericBar = Replace<
|
|||
}
|
||||
>;
|
||||
|
||||
export function createBar<T extends BarOptions>(options: T & ThisType<Bar<T>>): Bar<T> {
|
||||
const bar: T & Partial<BaseBar> = options;
|
||||
bar.id = getUniqueID("bar-");
|
||||
bar.type = BarType;
|
||||
bar[Component] = BarComponent;
|
||||
export function createBar<T extends BarOptions>(optionsFunc: () => T & ThisType<Bar<T>>): Bar<T> {
|
||||
return createLazyProxy(() => {
|
||||
const bar: T & Partial<BaseBar> = optionsFunc();
|
||||
bar.id = getUniqueID("bar-");
|
||||
bar.type = BarType;
|
||||
bar[Component] = BarComponent;
|
||||
|
||||
processComputable(bar as T, "visibility");
|
||||
setDefault(bar, "visibility", Visibility.Visible);
|
||||
processComputable(bar as T, "width");
|
||||
processComputable(bar as T, "height");
|
||||
processComputable(bar as T, "direction");
|
||||
processComputable(bar as T, "style");
|
||||
processComputable(bar as T, "classes");
|
||||
processComputable(bar as T, "borderStyle");
|
||||
processComputable(bar as T, "baseStyle");
|
||||
processComputable(bar as T, "textStyle");
|
||||
processComputable(bar as T, "fillStyle");
|
||||
processComputable(bar as T, "progress");
|
||||
processComputable(bar as T, "display");
|
||||
processComputable(bar as T, "mark");
|
||||
processComputable(bar as T, "visibility");
|
||||
setDefault(bar, "visibility", Visibility.Visible);
|
||||
processComputable(bar as T, "width");
|
||||
processComputable(bar as T, "height");
|
||||
processComputable(bar as T, "direction");
|
||||
processComputable(bar as T, "style");
|
||||
processComputable(bar as T, "classes");
|
||||
processComputable(bar as T, "borderStyle");
|
||||
processComputable(bar as T, "baseStyle");
|
||||
processComputable(bar as T, "textStyle");
|
||||
processComputable(bar as T, "fillStyle");
|
||||
processComputable(bar as T, "progress");
|
||||
processComputable(bar as T, "display");
|
||||
processComputable(bar as T, "mark");
|
||||
|
||||
const proxy = createProxy(bar as unknown as Bar<T>);
|
||||
return proxy;
|
||||
bar[GatherProps] = function (this: GenericBar) {
|
||||
const {
|
||||
progress,
|
||||
width,
|
||||
height,
|
||||
direction,
|
||||
display,
|
||||
visibility,
|
||||
style,
|
||||
classes,
|
||||
borderStyle,
|
||||
textStyle,
|
||||
baseStyle,
|
||||
fillStyle,
|
||||
mark,
|
||||
id
|
||||
} = this;
|
||||
return {
|
||||
progress,
|
||||
width,
|
||||
height,
|
||||
direction,
|
||||
display,
|
||||
visibility,
|
||||
style,
|
||||
classes,
|
||||
borderStyle,
|
||||
textStyle,
|
||||
baseStyle,
|
||||
fillStyle,
|
||||
mark,
|
||||
id
|
||||
};
|
||||
};
|
||||
|
||||
return bar as unknown as Bar<T>;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import BoardComponent from "@/components/features/board/Board.vue";
|
|||
import {
|
||||
Component,
|
||||
findFeatures,
|
||||
GatherProps,
|
||||
getUniqueID,
|
||||
makePersistent,
|
||||
Persistent,
|
||||
|
@ -22,7 +23,7 @@ import {
|
|||
processComputable,
|
||||
ProcessedComputable
|
||||
} from "@/util/computed";
|
||||
import { createProxy } from "@/util/proxies";
|
||||
import { createLazyProxy } from "@/util/proxies";
|
||||
import { Unsubscribe } from "nanoevents";
|
||||
import { computed, Ref, unref } from "vue";
|
||||
import { Link } from "./links";
|
||||
|
@ -177,6 +178,7 @@ interface BaseBoard extends Persistent<BoardData> {
|
|||
selectedAction: Ref<GenericBoardNodeAction | null>;
|
||||
type: typeof BoardType;
|
||||
[Component]: typeof BoardComponent;
|
||||
[GatherProps]: () => Record<string, unknown>;
|
||||
}
|
||||
|
||||
export type Board<T extends BoardOptions> = Replace<
|
||||
|
@ -198,101 +200,139 @@ export type GenericBoard = Replace<
|
|||
}
|
||||
>;
|
||||
|
||||
export function createBoard<T extends BoardOptions>(options: T & ThisType<Board<T>>): Board<T> {
|
||||
const board: T & Partial<BaseBoard> = options;
|
||||
makePersistent<BoardData>(board, {
|
||||
nodes: [],
|
||||
selectedNode: null,
|
||||
selectedAction: null
|
||||
});
|
||||
board.id = getUniqueID("board-");
|
||||
board.type = BoardType;
|
||||
board[Component] = BoardComponent;
|
||||
|
||||
board.nodes = computed(() => proxy[PersistentState].value.nodes);
|
||||
board.selectedNode = computed(
|
||||
() =>
|
||||
proxy.nodes.value.find(node => node.id === proxy[PersistentState].value.selectedNode) ||
|
||||
null
|
||||
);
|
||||
board.selectedAction = computed(() => {
|
||||
if (proxy.selectedNode.value == null) {
|
||||
return null;
|
||||
}
|
||||
const type = proxy.types[proxy.selectedNode.value.type];
|
||||
if (type.actions == null) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
type.actions.find(
|
||||
action => action.id === proxy[PersistentState].value.selectedAction
|
||||
) || null
|
||||
);
|
||||
});
|
||||
board.links = computed(() => {
|
||||
if (proxy.selectedAction.value == null) {
|
||||
return null;
|
||||
}
|
||||
if (proxy.selectedAction.value.links && proxy.selectedNode.value) {
|
||||
return getNodeProperty(proxy.selectedAction.value.links, proxy.selectedNode.value);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
processComputable(board as T, "visibility");
|
||||
setDefault(board, "visibility", Visibility.Visible);
|
||||
processComputable(board as T, "width");
|
||||
setDefault(board, "width", "100%");
|
||||
processComputable(board as T, "height");
|
||||
setDefault(board, "height", "400px");
|
||||
processComputable(board as T, "classes");
|
||||
processComputable(board as T, "style");
|
||||
|
||||
for (const type in board.types) {
|
||||
const nodeType: NodeTypeOptions & Partial<BaseNodeType> = board.types[type];
|
||||
|
||||
processComputable(nodeType, "title");
|
||||
processComputable(nodeType, "label");
|
||||
processComputable(nodeType, "size");
|
||||
setDefault(nodeType, "size", 50);
|
||||
processComputable(nodeType, "draggable");
|
||||
setDefault(nodeType, "draggable", false);
|
||||
processComputable(nodeType, "shape");
|
||||
setDefault(nodeType, "shape", Shape.Circle);
|
||||
processComputable(nodeType, "canAccept");
|
||||
setDefault(nodeType, "canAccept", false);
|
||||
processComputable(nodeType, "progress");
|
||||
processComputable(nodeType, "progressDisplay");
|
||||
setDefault(nodeType, "progressDisplay", ProgressDisplay.Fill);
|
||||
processComputable(nodeType, "progressColor");
|
||||
setDefault(nodeType, "progressColor", "none");
|
||||
processComputable(nodeType, "fillColor");
|
||||
processComputable(nodeType, "outlineColor");
|
||||
processComputable(nodeType, "titleColor");
|
||||
processComputable(nodeType, "actionDistance");
|
||||
setDefault(nodeType, "actionDistance", Math.PI / 6);
|
||||
nodeType.nodes = computed(() =>
|
||||
proxy[PersistentState].value.nodes.filter(node => node.type === type)
|
||||
);
|
||||
setDefault(nodeType, "onClick", function (node: BoardNode) {
|
||||
proxy[PersistentState].value.selectedNode = node.id;
|
||||
export function createBoard<T extends BoardOptions>(
|
||||
optionsFunc: () => T & ThisType<Board<T>>
|
||||
): Board<T> {
|
||||
return createLazyProxy(() => {
|
||||
const board: T & Partial<BaseBoard> = optionsFunc();
|
||||
makePersistent<BoardData>(board, {
|
||||
nodes: [],
|
||||
selectedNode: null,
|
||||
selectedAction: null
|
||||
});
|
||||
board.id = getUniqueID("board-");
|
||||
board.type = BoardType;
|
||||
board[Component] = BoardComponent;
|
||||
|
||||
if (nodeType.actions) {
|
||||
for (const action of nodeType.actions) {
|
||||
processComputable(action, "visibility");
|
||||
setDefault(action, "visibility", Visibility.Visible);
|
||||
processComputable(action, "icon");
|
||||
processComputable(action, "fillColor");
|
||||
processComputable(action, "tooltip");
|
||||
processComputable(action, "links");
|
||||
board.nodes = computed(() => processedBoard[PersistentState].value.nodes);
|
||||
board.selectedNode = computed(
|
||||
() =>
|
||||
processedBoard.nodes.value.find(
|
||||
node => node.id === board[PersistentState].value.selectedNode
|
||||
) || null
|
||||
);
|
||||
board.selectedAction = computed(() => {
|
||||
const selectedNode = processedBoard.selectedNode.value;
|
||||
if (selectedNode == null) {
|
||||
return null;
|
||||
}
|
||||
const type = processedBoard.types[selectedNode.type];
|
||||
if (type.actions == null) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
type.actions.find(
|
||||
action => action.id === processedBoard[PersistentState].value.selectedAction
|
||||
) || null
|
||||
);
|
||||
});
|
||||
board.links = computed(() => {
|
||||
if (processedBoard.selectedAction.value == null) {
|
||||
return null;
|
||||
}
|
||||
if (processedBoard.selectedAction.value.links && processedBoard.selectedNode.value) {
|
||||
return getNodeProperty(
|
||||
processedBoard.selectedAction.value.links,
|
||||
processedBoard.selectedNode.value
|
||||
);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
processComputable(board as T, "visibility");
|
||||
setDefault(board, "visibility", Visibility.Visible);
|
||||
processComputable(board as T, "width");
|
||||
setDefault(board, "width", "100%");
|
||||
processComputable(board as T, "height");
|
||||
setDefault(board, "height", "400px");
|
||||
processComputable(board as T, "classes");
|
||||
processComputable(board as T, "style");
|
||||
|
||||
for (const type in board.types) {
|
||||
const nodeType: NodeTypeOptions & Partial<BaseNodeType> = board.types[type];
|
||||
|
||||
processComputable(nodeType as NodeTypeOptions, "title");
|
||||
processComputable(nodeType as NodeTypeOptions, "label");
|
||||
processComputable(nodeType as NodeTypeOptions, "size");
|
||||
setDefault(nodeType, "size", 50);
|
||||
processComputable(nodeType as NodeTypeOptions, "draggable");
|
||||
setDefault(nodeType, "draggable", false);
|
||||
processComputable(nodeType as NodeTypeOptions, "shape");
|
||||
setDefault(nodeType, "shape", Shape.Circle);
|
||||
processComputable(nodeType as NodeTypeOptions, "canAccept");
|
||||
setDefault(nodeType, "canAccept", false);
|
||||
processComputable(nodeType as NodeTypeOptions, "progress");
|
||||
processComputable(nodeType as NodeTypeOptions, "progressDisplay");
|
||||
setDefault(nodeType, "progressDisplay", ProgressDisplay.Fill);
|
||||
processComputable(nodeType as NodeTypeOptions, "progressColor");
|
||||
setDefault(nodeType, "progressColor", "none");
|
||||
processComputable(nodeType as NodeTypeOptions, "fillColor");
|
||||
processComputable(nodeType as NodeTypeOptions, "outlineColor");
|
||||
processComputable(nodeType as NodeTypeOptions, "titleColor");
|
||||
processComputable(nodeType as NodeTypeOptions, "actionDistance");
|
||||
setDefault(nodeType, "actionDistance", Math.PI / 6);
|
||||
nodeType.nodes = computed(() =>
|
||||
board[PersistentState].value.nodes.filter(node => node.type === type)
|
||||
);
|
||||
setDefault(nodeType, "onClick", function (node: BoardNode) {
|
||||
board[PersistentState].value.selectedNode = node.id;
|
||||
});
|
||||
|
||||
if (nodeType.actions) {
|
||||
for (const action of nodeType.actions) {
|
||||
processComputable(action, "visibility");
|
||||
setDefault(action, "visibility", Visibility.Visible);
|
||||
processComputable(action, "icon");
|
||||
processComputable(action, "fillColor");
|
||||
processComputable(action, "tooltip");
|
||||
processComputable(action, "links");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
board.types[type] = createProxy(nodeType as unknown as GenericNodeType);
|
||||
}
|
||||
board[GatherProps] = function (this: GenericBoard) {
|
||||
const {
|
||||
nodes,
|
||||
types,
|
||||
[PersistentState]: state,
|
||||
visibility,
|
||||
width,
|
||||
height,
|
||||
style,
|
||||
classes,
|
||||
links,
|
||||
selectedAction,
|
||||
selectedNode
|
||||
} = this;
|
||||
return {
|
||||
nodes,
|
||||
types,
|
||||
[PersistentState]: state,
|
||||
visibility,
|
||||
width,
|
||||
height,
|
||||
style,
|
||||
classes,
|
||||
links,
|
||||
selectedAction,
|
||||
selectedNode
|
||||
};
|
||||
};
|
||||
|
||||
const proxy = createProxy(board as unknown as Board<T>);
|
||||
return proxy;
|
||||
// This is necessary because board.types is different from T and Board
|
||||
const processedBoard = board as unknown as Board<T>;
|
||||
|
||||
return processedBoard;
|
||||
});
|
||||
}
|
||||
|
||||
export function getNodeProperty<T>(property: NodeComputable<T>, node: BoardNode): T {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import ClickableComponent from "@/components/features/Clickable.vue";
|
||||
import { Resource } from "@/features/resource";
|
||||
import Decimal, { DecimalSource, format } from "@/util/bignum";
|
||||
import Decimal, { DecimalSource, format, formatWhole } from "@/util/bignum";
|
||||
import {
|
||||
Computable,
|
||||
GetComputableType,
|
||||
|
@ -8,13 +8,15 @@ import {
|
|||
processComputable,
|
||||
ProcessedComputable
|
||||
} from "@/util/computed";
|
||||
import { createProxy } from "@/util/proxies";
|
||||
import { isCoercableComponent } from "@/util/vue";
|
||||
import { createLazyProxy } from "@/util/proxies";
|
||||
import { coerceComponent, isCoercableComponent } from "@/util/vue";
|
||||
import { computed, Ref, unref } from "vue";
|
||||
import {
|
||||
CoercableComponent,
|
||||
Component,
|
||||
GatherProps,
|
||||
getUniqueID,
|
||||
jsx,
|
||||
makePersistent,
|
||||
Persistent,
|
||||
PersistentState,
|
||||
|
@ -51,13 +53,14 @@ export interface BuyableOptions {
|
|||
interface BaseBuyable extends Persistent<DecimalSource> {
|
||||
id: string;
|
||||
amount: Ref<DecimalSource>;
|
||||
bought: Ref<boolean>;
|
||||
maxed: Ref<boolean>;
|
||||
canAfford: Ref<boolean>;
|
||||
canClick: ProcessedComputable<boolean>;
|
||||
onClick: VoidFunction;
|
||||
purchase: VoidFunction;
|
||||
type: typeof BuyableType;
|
||||
[Component]: typeof ClickableComponent;
|
||||
[GatherProps]: () => Record<string, unknown>;
|
||||
}
|
||||
|
||||
export type Buyable<T extends BuyableOptions> = Replace<
|
||||
|
@ -86,98 +89,139 @@ export type GenericBuyable = Replace<
|
|||
>;
|
||||
|
||||
export function createBuyable<T extends BuyableOptions>(
|
||||
options: T & ThisType<Buyable<T>>
|
||||
optionsFunc: () => T & ThisType<Buyable<T>>
|
||||
): Buyable<T> {
|
||||
if (options.canPurchase == null && (options.resource == null || options.cost == null)) {
|
||||
console.warn(
|
||||
"Cannot create buyable without a canPurchase property or a resource and cost property",
|
||||
options
|
||||
);
|
||||
throw "Cannot create buyable without a canPurchase property or a resource and cost property";
|
||||
}
|
||||
return createLazyProxy(() => {
|
||||
const buyable: T & Partial<BaseBuyable> = optionsFunc();
|
||||
|
||||
const buyable: T & Partial<BaseBuyable> = options;
|
||||
makePersistent<DecimalSource>(buyable, 0);
|
||||
buyable.id = getUniqueID("buyable-");
|
||||
buyable.type = BuyableType;
|
||||
buyable[Component] = ClickableComponent;
|
||||
|
||||
buyable.amount = buyable[PersistentState];
|
||||
buyable.bought = computed(() => Decimal.gt(proxy.amount.value, 0));
|
||||
buyable.canAfford = computed(
|
||||
() =>
|
||||
proxy.resource != null &&
|
||||
proxy.cost != null &&
|
||||
Decimal.gte(unref<Resource>(proxy.resource).value, unref(proxy.cost))
|
||||
);
|
||||
if (buyable.canPurchase == null) {
|
||||
buyable.canPurchase = computed(
|
||||
() =>
|
||||
proxy.purchaseLimit != null &&
|
||||
proxy.canAfford &&
|
||||
Decimal.lt(proxy.amount.value, unref(proxy.purchaseLimit))
|
||||
);
|
||||
}
|
||||
processComputable(buyable as T, "canPurchase");
|
||||
// TODO once processComputable typing works, this can be replaced
|
||||
//buyable.canClick = buyable.canPurchase;
|
||||
buyable.canClick = computed(() => unref(proxy.canPurchase));
|
||||
buyable.onClick = buyable.purchase = function () {
|
||||
if (!unref(proxy.canPurchase) || proxy.cost == null || proxy.resource == null) {
|
||||
return;
|
||||
if (buyable.canPurchase == null && (buyable.resource == null || buyable.cost == null)) {
|
||||
console.warn(
|
||||
"Cannot create buyable without a canPurchase property or a resource and cost property",
|
||||
buyable
|
||||
);
|
||||
throw "Cannot create buyable without a canPurchase property or a resource and cost property";
|
||||
}
|
||||
const cost = unref(proxy.cost);
|
||||
unref<Resource>(proxy.resource).value = Decimal.sub(
|
||||
unref<Resource>(proxy.resource).value,
|
||||
cost
|
||||
);
|
||||
proxy.amount.value = Decimal.add(proxy.amount.value, 1);
|
||||
this.onPurchase?.(cost);
|
||||
};
|
||||
processComputable(buyable as T, "display");
|
||||
const display = buyable.display;
|
||||
buyable.display = computed(() => {
|
||||
// TODO once processComputable types correctly, remove this "as X"
|
||||
const currDisplay = unref(display) as BuyableDisplay;
|
||||
if (
|
||||
currDisplay != null &&
|
||||
!isCoercableComponent(currDisplay) &&
|
||||
proxy.cost != null &&
|
||||
proxy.resource != null
|
||||
) {
|
||||
|
||||
makePersistent<DecimalSource>(buyable, 0);
|
||||
buyable.id = getUniqueID("buyable-");
|
||||
buyable.type = BuyableType;
|
||||
buyable[Component] = ClickableComponent;
|
||||
|
||||
buyable.amount = buyable[PersistentState];
|
||||
buyable.canAfford = computed(() => {
|
||||
const genericBuyable = buyable as GenericBuyable;
|
||||
const cost = unref(genericBuyable.cost);
|
||||
return (
|
||||
<span>
|
||||
<div v-if={currDisplay.title}>
|
||||
<component v-is={currDisplay.title} />
|
||||
</div>
|
||||
<component v-is={currDisplay.description} />
|
||||
<div>
|
||||
<br />
|
||||
Amount: {format(proxy.amount.value)} / {format(unref(proxy.purchaseLimit))}
|
||||
</div>
|
||||
<div v-if={currDisplay.effectDisplay}>
|
||||
<br />
|
||||
Currently: <component v-is={currDisplay.effectDisplay} />
|
||||
</div>
|
||||
<br />
|
||||
Cost: {format(unref(proxy.cost))} {unref<Resource>(proxy.resource).displayName}
|
||||
</span>
|
||||
genericBuyable.resource != null &&
|
||||
cost != null &&
|
||||
Decimal.gte(genericBuyable.resource.value, cost)
|
||||
);
|
||||
});
|
||||
if (buyable.canPurchase == null) {
|
||||
buyable.canPurchase = computed(
|
||||
() =>
|
||||
unref((buyable as GenericBuyable).visibility) === Visibility.Visible &&
|
||||
unref((buyable as GenericBuyable).canAfford) &&
|
||||
Decimal.lt(
|
||||
(buyable as GenericBuyable).amount.value,
|
||||
unref((buyable as GenericBuyable).purchaseLimit)
|
||||
)
|
||||
);
|
||||
}
|
||||
return null;
|
||||
buyable.maxed = computed(() =>
|
||||
Decimal.gte(
|
||||
(buyable as GenericBuyable).amount.value,
|
||||
unref((buyable as GenericBuyable).purchaseLimit)
|
||||
)
|
||||
);
|
||||
processComputable(buyable as T, "classes");
|
||||
const classes = buyable.classes as ProcessedComputable<Record<string, boolean>> | undefined;
|
||||
buyable.classes = computed(() => {
|
||||
const currClasses = unref(classes) || {};
|
||||
if ((buyable as GenericBuyable).maxed.value) {
|
||||
currClasses.bought = true;
|
||||
}
|
||||
return currClasses;
|
||||
});
|
||||
processComputable(buyable as T, "canPurchase");
|
||||
buyable.canClick = buyable.canPurchase as ProcessedComputable<boolean>;
|
||||
buyable.onClick = buyable.purchase = function () {
|
||||
const genericBuyable = buyable as GenericBuyable;
|
||||
if (
|
||||
!unref(genericBuyable.canPurchase) ||
|
||||
genericBuyable.cost == null ||
|
||||
genericBuyable.resource == null
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const cost = unref(genericBuyable.cost);
|
||||
genericBuyable.resource.value = Decimal.sub(genericBuyable.resource.value, cost);
|
||||
genericBuyable.amount.value = Decimal.add(genericBuyable.amount.value, 1);
|
||||
this.onPurchase?.(cost);
|
||||
};
|
||||
processComputable(buyable as T, "display");
|
||||
const display = buyable.display;
|
||||
buyable.display = jsx(() => {
|
||||
// TODO once processComputable types correctly, remove this "as X"
|
||||
const currDisplay = unref(display) as BuyableDisplay;
|
||||
if (
|
||||
currDisplay != null &&
|
||||
!isCoercableComponent(currDisplay) &&
|
||||
buyable.cost != null &&
|
||||
buyable.resource != null
|
||||
) {
|
||||
const genericBuyable = buyable as GenericBuyable;
|
||||
const Title = coerceComponent(currDisplay.title || "", "h3");
|
||||
const Description = coerceComponent(currDisplay.description);
|
||||
const EffectDisplay = coerceComponent(currDisplay.effectDisplay || "");
|
||||
return (
|
||||
<span>
|
||||
{currDisplay.title ? (
|
||||
<div>
|
||||
<Title />
|
||||
</div>
|
||||
) : null}
|
||||
<Description />
|
||||
<div>
|
||||
<br />
|
||||
Amount: {formatWhole(genericBuyable.amount.value)} /{" "}
|
||||
{formatWhole(unref(genericBuyable.purchaseLimit))}
|
||||
</div>
|
||||
{currDisplay.effectDisplay ? (
|
||||
<div>
|
||||
<br />
|
||||
Currently: <EffectDisplay />
|
||||
</div>
|
||||
) : null}
|
||||
{genericBuyable.cost && !genericBuyable.maxed.value ? (
|
||||
<div>
|
||||
<br />
|
||||
Cost: {format(unref(genericBuyable.cost) || 0)}{" "}
|
||||
{buyable.resource.displayName}
|
||||
</div>
|
||||
) : null}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return "";
|
||||
});
|
||||
|
||||
processComputable(buyable as T, "visibility");
|
||||
setDefault(buyable, "visibility", Visibility.Visible);
|
||||
processComputable(buyable as T, "cost");
|
||||
processComputable(buyable as T, "resource");
|
||||
processComputable(buyable as T, "purchaseLimit");
|
||||
setDefault(buyable, "purchaseLimit", 1);
|
||||
processComputable(buyable as T, "style");
|
||||
processComputable(buyable as T, "mark");
|
||||
processComputable(buyable as T, "small");
|
||||
|
||||
buyable[GatherProps] = function (this: GenericBuyable) {
|
||||
const { display, visibility, style, classes, onClick, canClick, small, mark, id } =
|
||||
this;
|
||||
return { display, visibility, style, classes, onClick, canClick, small, mark, id };
|
||||
};
|
||||
|
||||
return buyable as unknown as Buyable<T>;
|
||||
});
|
||||
|
||||
processComputable(buyable as T, "visibility");
|
||||
setDefault(buyable, "visibility", Visibility.Visible);
|
||||
processComputable(buyable as T, "cost");
|
||||
processComputable(buyable as T, "resource");
|
||||
processComputable(buyable as T, "purchaseLimit");
|
||||
setDefault(buyable, "purchaseLimit", 1);
|
||||
processComputable(buyable as T, "classes");
|
||||
processComputable(buyable as T, "style");
|
||||
processComputable(buyable as T, "mark");
|
||||
processComputable(buyable as T, "small");
|
||||
|
||||
const proxy = createProxy(buyable as unknown as Buyable<T>);
|
||||
return proxy;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import ChallengeComponent from "@/components/features/Challenge.vue";
|
|||
import {
|
||||
CoercableComponent,
|
||||
Component,
|
||||
GatherProps,
|
||||
getUniqueID,
|
||||
persistent,
|
||||
PersistentRef,
|
||||
|
@ -21,7 +22,7 @@ import {
|
|||
processComputable,
|
||||
ProcessedComputable
|
||||
} from "@/util/computed";
|
||||
import { createProxy } from "@/util/proxies";
|
||||
import { createLazyProxy } from "@/util/proxies";
|
||||
import { computed, Ref, unref } from "vue";
|
||||
import { GenericReset } from "./reset";
|
||||
|
||||
|
@ -62,6 +63,7 @@ interface BaseChallenge {
|
|||
toggle: VoidFunction;
|
||||
type: typeof ChallengeType;
|
||||
[Component]: typeof ChallengeComponent;
|
||||
[GatherProps]: () => Record<string, unknown>;
|
||||
}
|
||||
|
||||
export type Challenge<T extends ChallengeOptions> = Replace<
|
||||
|
@ -97,97 +99,155 @@ export function createActiveChallenge(
|
|||
}
|
||||
|
||||
export function createChallenge<T extends ChallengeOptions>(
|
||||
options: T & ThisType<Challenge<T>>
|
||||
optionsFunc: () => T & ThisType<Challenge<T>>
|
||||
): Challenge<T> {
|
||||
if (options.canComplete == null && (options.resource == null || options.goal == null)) {
|
||||
console.warn(
|
||||
"Cannot create challenge without a canComplete property or a resource and goal property",
|
||||
options
|
||||
return createLazyProxy(() => {
|
||||
const challenge: T & Partial<BaseChallenge> = optionsFunc();
|
||||
|
||||
if (
|
||||
challenge.canComplete == null &&
|
||||
(challenge.resource == null || challenge.goal == null)
|
||||
) {
|
||||
console.warn(
|
||||
"Cannot create challenge without a canComplete property or a resource and goal property",
|
||||
challenge
|
||||
);
|
||||
throw "Cannot create challenge without a canComplete property or a resource and goal property";
|
||||
}
|
||||
|
||||
challenge.id = getUniqueID("challenge-");
|
||||
challenge.type = ChallengeType;
|
||||
challenge[Component] = ChallengeComponent;
|
||||
|
||||
challenge.completions = persistent(0);
|
||||
challenge.active = persistent(false);
|
||||
challenge.completed = computed(() =>
|
||||
Decimal.gt((challenge as GenericChallenge).completions.value, 0)
|
||||
);
|
||||
throw "Cannot create challenge without a canComplete property or a resource and goal property";
|
||||
}
|
||||
|
||||
const challenge: T & Partial<BaseChallenge> = options;
|
||||
challenge.id = getUniqueID("challenge-");
|
||||
challenge.type = ChallengeType;
|
||||
challenge[Component] = ChallengeComponent;
|
||||
|
||||
challenge.completions = persistent(0);
|
||||
challenge.active = persistent(false);
|
||||
challenge.completed = computed(() => Decimal.gt(proxy.completions.value, 0));
|
||||
challenge.maxed = computed(() =>
|
||||
Decimal.gte(proxy.completions.value, unref(proxy.completionLimit))
|
||||
);
|
||||
challenge.toggle = function () {
|
||||
if (proxy.active.value) {
|
||||
if (proxy.canComplete && unref(proxy.canComplete) && !proxy.maxed.value) {
|
||||
let completions: boolean | DecimalSource = unref(proxy.canComplete);
|
||||
if (typeof completions === "boolean") {
|
||||
completions = 1;
|
||||
challenge.maxed = computed(() =>
|
||||
Decimal.gte(
|
||||
(challenge as GenericChallenge).completions.value,
|
||||
unref((challenge as GenericChallenge).completionLimit)
|
||||
)
|
||||
);
|
||||
challenge.toggle = function () {
|
||||
const genericChallenge = challenge as GenericChallenge;
|
||||
if (genericChallenge.active.value) {
|
||||
if (
|
||||
genericChallenge.canComplete &&
|
||||
unref(genericChallenge.canComplete) &&
|
||||
!genericChallenge.maxed.value
|
||||
) {
|
||||
let completions: boolean | DecimalSource = unref(genericChallenge.canComplete);
|
||||
if (typeof completions === "boolean") {
|
||||
completions = 1;
|
||||
}
|
||||
genericChallenge.completions.value = Decimal.min(
|
||||
Decimal.add(genericChallenge.completions.value, completions),
|
||||
unref(genericChallenge.completionLimit)
|
||||
);
|
||||
genericChallenge.onComplete?.();
|
||||
}
|
||||
proxy.completions.value = Decimal.min(
|
||||
Decimal.add(proxy.completions.value, completions),
|
||||
unref(proxy.completionLimit)
|
||||
);
|
||||
proxy.onComplete?.();
|
||||
genericChallenge.active.value = false;
|
||||
genericChallenge.onExit?.();
|
||||
genericChallenge.reset?.reset();
|
||||
} else if (unref(genericChallenge.canStart)) {
|
||||
genericChallenge.reset?.reset();
|
||||
genericChallenge.active.value = true;
|
||||
genericChallenge.onEnter?.();
|
||||
}
|
||||
proxy.active.value = false;
|
||||
proxy.onExit?.();
|
||||
proxy.reset?.reset();
|
||||
} else if (unref(proxy.canStart)) {
|
||||
proxy.reset?.reset();
|
||||
proxy.active.value = true;
|
||||
proxy.onEnter?.();
|
||||
};
|
||||
processComputable(challenge as T, "visibility");
|
||||
setDefault(challenge, "visibility", Visibility.Visible);
|
||||
const visibility = challenge.visibility as ProcessedComputable<Visibility>;
|
||||
challenge.visibility = computed(() => {
|
||||
if (settings.hideChallenges === true && unref(challenge.maxed)) {
|
||||
return Visibility.None;
|
||||
}
|
||||
return unref(visibility);
|
||||
});
|
||||
if (challenge.canStart == null) {
|
||||
challenge.canStart = computed(
|
||||
() =>
|
||||
unref((challenge as GenericChallenge).visibility) === Visibility.Visible &&
|
||||
Decimal.lt(
|
||||
(challenge as GenericChallenge).completions.value,
|
||||
unref((challenge as GenericChallenge).completionLimit)
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
processComputable(challenge as T, "visibility");
|
||||
setDefault(challenge, "visibility", Visibility.Visible);
|
||||
const visibility = challenge.visibility as ProcessedComputable<Visibility>;
|
||||
challenge.visibility = computed(() => {
|
||||
if (settings.hideChallenges === true && unref(proxy.maxed)) {
|
||||
return Visibility.None;
|
||||
if (challenge.canComplete == null) {
|
||||
challenge.canComplete = computed(() => {
|
||||
const genericChallenge = challenge as GenericChallenge;
|
||||
if (
|
||||
!genericChallenge.active.value ||
|
||||
genericChallenge.resource == null ||
|
||||
genericChallenge.goal == null
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return Decimal.gte(genericChallenge.resource.value, unref(genericChallenge.goal));
|
||||
});
|
||||
}
|
||||
return unref(visibility);
|
||||
if (challenge.mark == null) {
|
||||
challenge.mark = computed(
|
||||
() =>
|
||||
Decimal.gt(unref((challenge as GenericChallenge).completionLimit), 1) &&
|
||||
!!unref(challenge.maxed)
|
||||
);
|
||||
}
|
||||
|
||||
processComputable(challenge as T, "canStart");
|
||||
processComputable(challenge as T, "canComplete");
|
||||
processComputable(challenge as T, "completionLimit");
|
||||
setDefault(challenge, "completionLimit", 1);
|
||||
processComputable(challenge as T, "mark");
|
||||
processComputable(challenge as T, "goal");
|
||||
processComputable(challenge as T, "classes");
|
||||
processComputable(challenge as T, "style");
|
||||
processComputable(challenge as T, "display");
|
||||
|
||||
if (challenge.reset != null) {
|
||||
globalBus.on("reset", currentReset => {
|
||||
if (currentReset === challenge.reset && (challenge.active as Ref<boolean>).value) {
|
||||
(challenge.toggle as VoidFunction)();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
challenge[GatherProps] = function (this: GenericChallenge) {
|
||||
const {
|
||||
active,
|
||||
maxed,
|
||||
canComplete,
|
||||
display,
|
||||
visibility,
|
||||
style,
|
||||
classes,
|
||||
completed,
|
||||
canStart,
|
||||
mark,
|
||||
id,
|
||||
toggle
|
||||
} = this;
|
||||
return {
|
||||
active,
|
||||
maxed,
|
||||
canComplete,
|
||||
display,
|
||||
visibility,
|
||||
style,
|
||||
classes,
|
||||
completed,
|
||||
canStart,
|
||||
mark,
|
||||
id,
|
||||
toggle
|
||||
};
|
||||
};
|
||||
|
||||
return challenge as unknown as Challenge<T>;
|
||||
});
|
||||
if (challenge.canStart == null) {
|
||||
challenge.canStart = computed(() =>
|
||||
Decimal.lt(proxy.completions.value, unref(proxy.completionLimit))
|
||||
);
|
||||
}
|
||||
if (challenge.canComplete == null) {
|
||||
challenge.canComplete = computed(() => {
|
||||
if (!proxy.active.value || proxy.resource == null || proxy.goal == null) {
|
||||
return false;
|
||||
}
|
||||
return Decimal.gte(proxy.resource.value, unref(proxy.goal));
|
||||
});
|
||||
}
|
||||
if (challenge.mark == null) {
|
||||
challenge.mark = computed(
|
||||
() => Decimal.gt(unref(proxy.completionLimit), 1) && unref(proxy.maxed)
|
||||
);
|
||||
}
|
||||
|
||||
processComputable(challenge as T, "canStart");
|
||||
processComputable(challenge as T, "canComplete");
|
||||
processComputable(challenge as T, "completionLimit");
|
||||
setDefault(challenge, "completionLimit", 1);
|
||||
processComputable(challenge as T, "mark");
|
||||
processComputable(challenge as T, "goal");
|
||||
processComputable(challenge as T, "classes");
|
||||
processComputable(challenge as T, "style");
|
||||
processComputable(challenge as T, "display");
|
||||
|
||||
if (challenge.reset != null) {
|
||||
globalBus.on("reset", currentReset => {
|
||||
if (currentReset === challenge.reset && (challenge.active as Ref<boolean>).value) {
|
||||
(challenge.toggle as VoidFunction)();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const proxy = createProxy(challenge as unknown as Challenge<T>);
|
||||
return proxy;
|
||||
}
|
||||
|
||||
declare module "@/game/settings" {
|
||||
|
|
|
@ -2,6 +2,7 @@ import ClickableComponent from "@/components/features/Clickable.vue";
|
|||
import {
|
||||
CoercableComponent,
|
||||
Component,
|
||||
GatherProps,
|
||||
getUniqueID,
|
||||
Replace,
|
||||
setDefault,
|
||||
|
@ -15,7 +16,7 @@ import {
|
|||
processComputable,
|
||||
ProcessedComputable
|
||||
} from "@/util/computed";
|
||||
import { createProxy } from "@/util/proxies";
|
||||
import { createLazyProxy } from "@/util/proxies";
|
||||
|
||||
export const ClickableType = Symbol("Clickable");
|
||||
|
||||
|
@ -41,6 +42,7 @@ interface BaseClickable {
|
|||
id: string;
|
||||
type: typeof ClickableType;
|
||||
[Component]: typeof ClickableComponent;
|
||||
[GatherProps]: () => Record<string, unknown>;
|
||||
}
|
||||
|
||||
export type Clickable<T extends ClickableOptions> = Replace<
|
||||
|
@ -64,21 +66,50 @@ export type GenericClickable = Replace<
|
|||
>;
|
||||
|
||||
export function createClickable<T extends ClickableOptions>(
|
||||
options: T & ThisType<Clickable<T>>
|
||||
optionsFunc: () => T & ThisType<Clickable<T>>
|
||||
): Clickable<T> {
|
||||
const clickable: T & Partial<BaseClickable> = options;
|
||||
clickable.id = getUniqueID("clickable-");
|
||||
clickable.type = ClickableType;
|
||||
clickable[Component] = ClickableComponent;
|
||||
return createLazyProxy(() => {
|
||||
const clickable: T & Partial<BaseClickable> = optionsFunc();
|
||||
clickable.id = getUniqueID("clickable-");
|
||||
clickable.type = ClickableType;
|
||||
clickable[Component] = ClickableComponent;
|
||||
|
||||
processComputable(clickable as T, "visibility");
|
||||
setDefault(clickable, "visibility", Visibility.Visible);
|
||||
processComputable(clickable as T, "canClick");
|
||||
processComputable(clickable as T, "classes");
|
||||
processComputable(clickable as T, "style");
|
||||
processComputable(clickable as T, "mark");
|
||||
processComputable(clickable as T, "display");
|
||||
processComputable(clickable as T, "visibility");
|
||||
setDefault(clickable, "visibility", Visibility.Visible);
|
||||
processComputable(clickable as T, "canClick");
|
||||
setDefault(clickable, "canClick", true);
|
||||
processComputable(clickable as T, "classes");
|
||||
processComputable(clickable as T, "style");
|
||||
processComputable(clickable as T, "mark");
|
||||
processComputable(clickable as T, "display");
|
||||
|
||||
const proxy = createProxy(clickable as unknown as Clickable<T>);
|
||||
return proxy;
|
||||
clickable[GatherProps] = function (this: GenericClickable) {
|
||||
const {
|
||||
display,
|
||||
visibility,
|
||||
style,
|
||||
classes,
|
||||
onClick,
|
||||
onHold,
|
||||
canClick,
|
||||
small,
|
||||
mark,
|
||||
id
|
||||
} = this;
|
||||
return {
|
||||
display,
|
||||
visibility,
|
||||
style,
|
||||
classes,
|
||||
onClick,
|
||||
onHold,
|
||||
canClick,
|
||||
small,
|
||||
mark,
|
||||
id
|
||||
};
|
||||
};
|
||||
|
||||
return clickable as unknown as Clickable<T>;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
processComputable,
|
||||
ProcessedComputable
|
||||
} from "@/util/computed";
|
||||
import { createProxy } from "@/util/proxies";
|
||||
import { createLazyProxy } from "@/util/proxies";
|
||||
import { computed, isRef, Ref, unref } from "vue";
|
||||
import { Replace, setDefault } from "./feature";
|
||||
import { Resource } from "./resource";
|
||||
|
@ -48,39 +48,46 @@ export type GenericConversion = Replace<
|
|||
>;
|
||||
|
||||
export function createConversion<T extends ConversionOptions>(
|
||||
options: T & ThisType<Conversion<T>>
|
||||
optionsFunc: () => T & ThisType<Conversion<T>>
|
||||
): Conversion<T> {
|
||||
const conversion: T = options;
|
||||
return createLazyProxy(() => {
|
||||
const conversion: T = optionsFunc();
|
||||
|
||||
if (conversion.convert == null) {
|
||||
conversion.convert = function () {
|
||||
unref<Resource>(proxy.gainResource).value = Decimal.add(
|
||||
unref<Resource>(proxy.gainResource).value,
|
||||
proxy.modifyGainAmount
|
||||
? proxy.modifyGainAmount(unref(proxy.currentGain))
|
||||
: unref(proxy.currentGain)
|
||||
if (conversion.currentGain == null) {
|
||||
conversion.currentGain = computed(() =>
|
||||
conversion.scaling.currentGain(conversion as GenericConversion)
|
||||
);
|
||||
// TODO just subtract cost?
|
||||
proxy.baseResource.value = 0;
|
||||
};
|
||||
}
|
||||
}
|
||||
if (conversion.nextAt == null) {
|
||||
conversion.nextAt = computed(() =>
|
||||
conversion.scaling.nextAt(conversion as GenericConversion)
|
||||
);
|
||||
}
|
||||
|
||||
if (conversion.currentGain == null) {
|
||||
conversion.currentGain = computed(() => proxy.scaling.currentGain(proxy));
|
||||
}
|
||||
if (conversion.nextAt == null) {
|
||||
conversion.nextAt = computed(() => proxy.scaling.nextAt(proxy));
|
||||
}
|
||||
if (conversion.convert == null) {
|
||||
conversion.convert = function () {
|
||||
conversion.gainResource.value = Decimal.add(
|
||||
conversion.gainResource.value,
|
||||
conversion.modifyGainAmount
|
||||
? conversion.modifyGainAmount(
|
||||
unref((conversion as GenericConversion).currentGain)
|
||||
)
|
||||
: unref((conversion as GenericConversion).currentGain)
|
||||
);
|
||||
// TODO just subtract cost?
|
||||
conversion.baseResource.value = 0;
|
||||
};
|
||||
}
|
||||
|
||||
processComputable(conversion as T, "currentGain");
|
||||
processComputable(conversion as T, "nextAt");
|
||||
processComputable(conversion as T, "buyMax");
|
||||
setDefault(conversion, "buyMax", true);
|
||||
processComputable(conversion as T, "roundUpCost");
|
||||
setDefault(conversion, "roundUpCost", true);
|
||||
processComputable(conversion as T, "currentGain");
|
||||
processComputable(conversion as T, "nextAt");
|
||||
processComputable(conversion as T, "buyMax");
|
||||
setDefault(conversion, "buyMax", true);
|
||||
processComputable(conversion as T, "roundUpCost");
|
||||
setDefault(conversion, "roundUpCost", true);
|
||||
|
||||
const proxy = createProxy(conversion as unknown as Conversion<T>);
|
||||
return proxy;
|
||||
return conversion as unknown as Conversion<T>;
|
||||
});
|
||||
}
|
||||
|
||||
export type ScalingFunction = {
|
||||
|
@ -88,14 +95,22 @@ export type ScalingFunction = {
|
|||
nextAt: (conversion: GenericConversion) => DecimalSource;
|
||||
};
|
||||
|
||||
// Gain formula is (baseResource - base) * coefficient
|
||||
// e.g. if base is 10 and coefficient is 0.5, 10 points makes 1 gain, 12 points is 2
|
||||
export function createLinearScaling(
|
||||
base: DecimalSource | Ref<DecimalSource>,
|
||||
coefficient: DecimalSource | Ref<DecimalSource>
|
||||
): ScalingFunction {
|
||||
return {
|
||||
currentGain(conversion) {
|
||||
let gain = Decimal.sub(unref<Resource>(conversion.baseResource).value, unref(base))
|
||||
.div(unref(coefficient))
|
||||
if (Decimal.lt(conversion.baseResource.value, unref(base))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let gain = Decimal.sub(conversion.baseResource.value, unref(base))
|
||||
.sub(1)
|
||||
.times(unref(coefficient))
|
||||
.add(1)
|
||||
.floor()
|
||||
.max(0);
|
||||
|
||||
|
@ -115,8 +130,8 @@ export function createLinearScaling(
|
|||
};
|
||||
}
|
||||
|
||||
// Note: Not sure this actually should be described as exponential
|
||||
// Gain formula is base * coefficient ^ (baseResource ^ exponent)
|
||||
// Gain formula is (baseResource / base) ^ exponent
|
||||
// e.g. if exponent is 0.5 and base is 10, then having 10 points makes gain 1, and 40 points is 2
|
||||
export function createExponentialScaling(
|
||||
base: DecimalSource | Ref<DecimalSource>,
|
||||
coefficient: DecimalSource | Ref<DecimalSource>,
|
||||
|
@ -124,12 +139,15 @@ export function createExponentialScaling(
|
|||
): ScalingFunction {
|
||||
return {
|
||||
currentGain(conversion) {
|
||||
let gain = Decimal.div(unref<Resource>(conversion.baseResource).value, unref(base))
|
||||
.log(unref(coefficient))
|
||||
.pow(Decimal.div(1, unref(exponent)))
|
||||
let gain = Decimal.div(conversion.baseResource.value, unref(base))
|
||||
.pow(unref(exponent))
|
||||
.floor()
|
||||
.max(0);
|
||||
|
||||
if (gain.isNan()) {
|
||||
return new Decimal(0);
|
||||
}
|
||||
|
||||
if (!conversion.buyMax) {
|
||||
gain = gain.min(1);
|
||||
}
|
||||
|
@ -147,51 +165,89 @@ export function createExponentialScaling(
|
|||
}
|
||||
|
||||
export function createCumulativeConversion<S extends ConversionOptions>(
|
||||
options: S & ThisType<Conversion<S>>
|
||||
optionsFunc: () => S & ThisType<Conversion<S>>
|
||||
): Conversion<S> {
|
||||
return createConversion(options);
|
||||
return createConversion(optionsFunc);
|
||||
}
|
||||
|
||||
export function createIndependentConversion<S extends ConversionOptions>(
|
||||
options: S & ThisType<Conversion<S>>
|
||||
optionsFunc: () => S & ThisType<Conversion<S>>
|
||||
): Conversion<S> {
|
||||
const conversion: S = options;
|
||||
return createConversion(() => {
|
||||
const conversion: S = optionsFunc();
|
||||
|
||||
setDefault(conversion, "buyMax", false);
|
||||
setDefault(conversion, "buyMax", false);
|
||||
|
||||
if (conversion.currentGain == null) {
|
||||
conversion.currentGain = computed(() =>
|
||||
Decimal.sub(proxy.scaling.currentGain(proxy), unref<Resource>(proxy.gainResource).value)
|
||||
.add(1)
|
||||
.max(1)
|
||||
);
|
||||
}
|
||||
setDefault(conversion, "convert", function () {
|
||||
unref<Resource>(proxy.gainResource).value = proxy.modifyGainAmount
|
||||
? proxy.modifyGainAmount(unref(proxy.currentGain))
|
||||
: unref(proxy.currentGain);
|
||||
// TODO just subtract cost?
|
||||
// Maybe by adding a cost function to scaling and nextAt just calls the cost function
|
||||
// with 1 + currentGain
|
||||
proxy.baseResource.value = 0;
|
||||
if (conversion.currentGain == null) {
|
||||
conversion.currentGain = computed(() =>
|
||||
Decimal.sub(
|
||||
conversion.scaling.currentGain(conversion as GenericConversion),
|
||||
conversion.gainResource.value
|
||||
)
|
||||
.add(1)
|
||||
.max(1)
|
||||
);
|
||||
}
|
||||
setDefault(conversion, "convert", function () {
|
||||
conversion.gainResource.value = conversion.modifyGainAmount
|
||||
? conversion.modifyGainAmount(unref((conversion as GenericConversion).currentGain))
|
||||
: unref((conversion as GenericConversion).currentGain);
|
||||
// TODO just subtract cost?
|
||||
// Maybe by adding a cost function to scaling and nextAt just calls the cost function
|
||||
// with 1 + currentGain
|
||||
conversion.baseResource.value = 0;
|
||||
});
|
||||
|
||||
return conversion;
|
||||
});
|
||||
|
||||
const proxy = createConversion(conversion);
|
||||
return proxy;
|
||||
}
|
||||
|
||||
export function setupPassiveGeneration(
|
||||
layer: GenericLayer,
|
||||
conversion: GenericConversion,
|
||||
rate: DecimalSource | Ref<DecimalSource> = 1
|
||||
rate: ProcessedComputable<DecimalSource> = 1
|
||||
): void {
|
||||
layer.on("preUpdate", (diff: Decimal) => {
|
||||
const currRate = isRef(rate) ? rate.value : rate;
|
||||
if (Decimal.neq(currRate, 0)) {
|
||||
conversion.gainResource.value = Decimal.add(
|
||||
unref<Resource>(conversion.gainResource).value,
|
||||
conversion.gainResource.value,
|
||||
Decimal.times(currRate, diff).times(unref(conversion.currentGain))
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function softcap(
|
||||
value: DecimalSource,
|
||||
cap: DecimalSource,
|
||||
power: DecimalSource = 0.5
|
||||
): DecimalSource {
|
||||
if (Decimal.lte(value, cap)) {
|
||||
return value;
|
||||
} else {
|
||||
return Decimal.pow(value, power).times(Decimal.pow(cap, Decimal.sub(1, power)));
|
||||
}
|
||||
}
|
||||
|
||||
export function addSoftcap(
|
||||
scaling: ScalingFunction,
|
||||
cap: ProcessedComputable<DecimalSource>,
|
||||
power: ProcessedComputable<DecimalSource> = 0.5
|
||||
): ScalingFunction {
|
||||
return {
|
||||
...scaling,
|
||||
currentGain: conversion =>
|
||||
softcap(scaling.currentGain(conversion), unref(cap), unref(power))
|
||||
};
|
||||
}
|
||||
|
||||
export function addHardcap(
|
||||
scaling: ScalingFunction,
|
||||
cap: ProcessedComputable<DecimalSource>
|
||||
): ScalingFunction {
|
||||
return {
|
||||
...scaling,
|
||||
currentGain: conversion => Decimal.min(scaling.currentGain(conversion), unref(cap))
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import { globalBus } from "@/game/events";
|
||||
import { GenericLayer } from "@/game/layers";
|
||||
import Decimal, { DecimalSource } from "@/util/bignum";
|
||||
import { ProcessedComputable } from "@/util/computed";
|
||||
import { DoNotCache, ProcessedComputable } from "@/util/computed";
|
||||
import { ProxyState } from "@/util/proxies";
|
||||
import { isArray } from "@vue/shared";
|
||||
import { ComponentOptions, CSSProperties, DefineComponent, isRef, ref, Ref } from "vue";
|
||||
import { CSSProperties, DefineComponent, isRef, ref, Ref } from "vue";
|
||||
|
||||
export const PersistentState = Symbol("PersistentState");
|
||||
export const DefaultValue = Symbol("DefaultValue");
|
||||
export const Component = Symbol("Component");
|
||||
export const GatherProps = Symbol("GatherProps");
|
||||
|
||||
// Note: This is a union of things that should be safely stringifiable without needing
|
||||
// special processes for knowing what to load them in as
|
||||
|
@ -20,7 +22,8 @@ export type State =
|
|||
| DecimalSource
|
||||
| { [key: string]: State }
|
||||
| { [key: number]: State };
|
||||
export type CoercableComponent = string | ComponentOptions | DefineComponent | JSX.Element;
|
||||
export type JSXFunction = (() => JSX.Element) & { [DoNotCache]: true };
|
||||
export type CoercableComponent = string | DefineComponent | JSXFunction;
|
||||
export type StyleValue = string | CSSProperties | Array<string | CSSProperties>;
|
||||
|
||||
export type Persistent<T extends State = State> = {
|
||||
|
@ -58,6 +61,11 @@ export enum Visibility {
|
|||
None
|
||||
}
|
||||
|
||||
export function jsx(func: () => JSX.Element | ""): JSXFunction {
|
||||
(func as Partial<JSXFunction>)[DoNotCache] = true;
|
||||
return func as JSXFunction;
|
||||
}
|
||||
|
||||
export function showIf(condition: boolean, otherwise = Visibility.None): Visibility {
|
||||
return condition ? Visibility.Visible : otherwise;
|
||||
}
|
||||
|
@ -101,7 +109,7 @@ export function findFeatures(obj: Record<string, unknown>, type: symbol): unknow
|
|||
if (value && typeof value === "object") {
|
||||
if ((value as Record<string, unknown>).type === type) {
|
||||
objects.push(value);
|
||||
} else {
|
||||
} else if (!(value instanceof Decimal) && !isRef(value)) {
|
||||
handleObject(value as Record<string, unknown>);
|
||||
}
|
||||
}
|
||||
|
@ -135,8 +143,12 @@ globalBus.on("addLayer", (layer: GenericLayer, saveData: Record<string, unknown>
|
|||
// Load previously saved value
|
||||
if (savedValue != null) {
|
||||
(persistentState[key] as Ref<unknown>).value = savedValue;
|
||||
} else {
|
||||
(persistentState[key] as Ref<unknown>).value = (value as Persistent)[
|
||||
DefaultValue
|
||||
];
|
||||
}
|
||||
} else if (!(value instanceof Decimal)) {
|
||||
} else if (!(value instanceof Decimal) && !isRef(value)) {
|
||||
// Continue traversing
|
||||
const foundPersistentInChild = handleObject(value as Record<string, unknown>, [
|
||||
...path,
|
||||
|
@ -146,10 +158,12 @@ globalBus.on("addLayer", (layer: GenericLayer, saveData: Record<string, unknown>
|
|||
// Show warning for persistent values inside arrays
|
||||
// TODO handle arrays better
|
||||
if (foundPersistentInChild) {
|
||||
if (isArray(value)) {
|
||||
if (isArray(value) && !isArray(obj)) {
|
||||
console.warn(
|
||||
"Found array that contains persistent values when adding layer. Keep in mind changing the order of elements in the array will mess with existing player saves.",
|
||||
obj,
|
||||
ProxyState in obj
|
||||
? (obj as Record<PropertyKey, unknown>)[ProxyState]
|
||||
: obj,
|
||||
key
|
||||
);
|
||||
} else {
|
||||
|
|
|
@ -2,6 +2,7 @@ import GridComponent from "@/components/features/Grid.vue";
|
|||
import {
|
||||
CoercableComponent,
|
||||
Component,
|
||||
GatherProps,
|
||||
getUniqueID,
|
||||
makePersistent,
|
||||
Persistent,
|
||||
|
@ -20,32 +21,30 @@ import {
|
|||
processComputable,
|
||||
ProcessedComputable
|
||||
} from "@/util/computed";
|
||||
import { createProxy, Proxied } from "@/util/proxies";
|
||||
import { computed, unref } from "vue";
|
||||
import { createLazyProxy } from "@/util/proxies";
|
||||
import { computed, Ref, unref } from "vue";
|
||||
|
||||
export const GridType = Symbol("Grid");
|
||||
|
||||
export type CellComputable<T> = Computable<T> | ((id: string | number, state: State) => T);
|
||||
|
||||
function createGridProxy(grid: GenericGrid): Record<string | number, GridCell> {
|
||||
return new Proxy({}, getGridHandler(grid)) as Proxied<Record<string | number, GridCell>>;
|
||||
return new Proxy({}, getGridHandler(grid)) as Record<string | number, GridCell>;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function getGridHandler(
|
||||
grid: GenericGrid
|
||||
): ProxyHandler<Record<string | number, Proxied<GridCell>>> {
|
||||
function getGridHandler(grid: GenericGrid): ProxyHandler<Record<string | number, GridCell>> {
|
||||
const keys = computed(() => {
|
||||
const keys = [];
|
||||
for (let row = 1; row <= grid.rows; row++) {
|
||||
for (let col = 1; col <= grid.cols; col++) {
|
||||
for (let row = 1; row <= unref(grid.rows); row++) {
|
||||
for (let col = 1; col <= unref(grid.cols); col++) {
|
||||
keys.push((row * 100 + col).toString());
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
});
|
||||
return {
|
||||
get(target, key) {
|
||||
get(target: Record<string | number, GridCell>, key: PropertyKey) {
|
||||
if (key === "isProxy") {
|
||||
return true;
|
||||
}
|
||||
|
@ -54,29 +53,57 @@ function getGridHandler(
|
|||
return (grid as never)[key];
|
||||
}
|
||||
|
||||
if (!keys.value.includes(key.toString())) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (target[key] == null) {
|
||||
target[key] = new Proxy(
|
||||
grid,
|
||||
getCellHandler(key.toString())
|
||||
) as unknown as Proxied<GridCell>;
|
||||
) as unknown as GridCell;
|
||||
}
|
||||
|
||||
return target[key];
|
||||
},
|
||||
set(target, key, value) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
set(target: Record<string | number, GridCell>, key: PropertyKey, value: any) {
|
||||
console.warn("Cannot set grid cells", target, key, value);
|
||||
return false;
|
||||
},
|
||||
ownKeys() {
|
||||
return keys.value;
|
||||
},
|
||||
has(target, key) {
|
||||
has(target: Record<string | number, GridCell>, key: PropertyKey) {
|
||||
return keys.value.includes(key.toString());
|
||||
},
|
||||
getOwnPropertyDescriptor(target: Record<string | number, GridCell>, key: PropertyKey) {
|
||||
if (keys.value.includes(key.toString())) {
|
||||
return {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
writable: false
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getCellHandler(id: string): ProxyHandler<GenericGrid> {
|
||||
const keys = [
|
||||
"id",
|
||||
"visibility",
|
||||
"canClick",
|
||||
"startState",
|
||||
"state",
|
||||
"style",
|
||||
"classes",
|
||||
"title",
|
||||
"display",
|
||||
"onClick",
|
||||
"onHold"
|
||||
];
|
||||
const cache: Record<string, Ref<unknown>> = {};
|
||||
return {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
get(target, key, receiver): any {
|
||||
|
@ -96,10 +123,17 @@ function getCellHandler(id: string): ProxyHandler<GenericGrid> {
|
|||
|
||||
key = key.slice(0, 1).toUpperCase() + key.slice(1);
|
||||
|
||||
if (key === "startState") {
|
||||
return prop.call(receiver, id);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
prop = (target as any)[`get${key}`];
|
||||
if (isFunction(prop)) {
|
||||
return prop.call(receiver, id, target.getState(id));
|
||||
if (!(key in cache)) {
|
||||
cache[key] = computed(() => prop.call(receiver, id, target.getState(id)));
|
||||
}
|
||||
return cache[key].value;
|
||||
} else if (prop != undefined) {
|
||||
return unref(prop);
|
||||
}
|
||||
|
@ -117,17 +151,29 @@ function getCellHandler(id: string): ProxyHandler<GenericGrid> {
|
|||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
set(target: Record<string, any>, key: string, value: any, receiver: typeof Proxy): boolean {
|
||||
if (
|
||||
`${key}Set` in target &&
|
||||
isFunction(target[`${key}Set`]) &&
|
||||
target[`${key}Set`].length < 3
|
||||
) {
|
||||
target[`${key}Set`].call(receiver, id, value);
|
||||
key = `set${key.slice(0, 1).toUpperCase() + key.slice(1)}`;
|
||||
if (key in target && isFunction(target[key]) && target[key].length < 3) {
|
||||
target[key].call(receiver, id, value);
|
||||
return true;
|
||||
} else {
|
||||
console.warn(`No setter for "${key}".`, target);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
ownKeys() {
|
||||
return keys;
|
||||
},
|
||||
has(target, key) {
|
||||
return keys.includes(key.toString());
|
||||
},
|
||||
getOwnPropertyDescriptor(target, key) {
|
||||
if (keys.includes(key.toString())) {
|
||||
return {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
writable: false
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -169,6 +215,7 @@ export interface BaseGrid extends Persistent<Record<string | number, State>> {
|
|||
cells: Record<string | number, GridCell>;
|
||||
type: typeof GridType;
|
||||
[Component]: typeof GridComponent;
|
||||
[GatherProps]: () => Record<string, unknown>;
|
||||
}
|
||||
|
||||
export type Grid<T extends GridOptions> = Replace<
|
||||
|
@ -196,40 +243,49 @@ export type GenericGrid = Replace<
|
|||
}
|
||||
>;
|
||||
|
||||
export function createGrid<T extends GridOptions>(options: T & ThisType<Grid<T>>): Grid<T> {
|
||||
const grid: T & Partial<BaseGrid> = options;
|
||||
makePersistent(grid, {});
|
||||
grid.id = getUniqueID("grid-");
|
||||
grid[Component] = GridComponent;
|
||||
export function createGrid<T extends GridOptions>(
|
||||
optionsFunc: () => T & ThisType<Grid<T>>
|
||||
): Grid<T> {
|
||||
return createLazyProxy(() => {
|
||||
const grid: T & Partial<BaseGrid> = optionsFunc();
|
||||
makePersistent(grid, {});
|
||||
grid.id = getUniqueID("grid-");
|
||||
grid[Component] = GridComponent;
|
||||
|
||||
grid.getID = function (this: GenericGrid, cell: string | number) {
|
||||
return grid.id + "-" + cell;
|
||||
};
|
||||
grid.getState = function (this: GenericGrid, cell: string | number) {
|
||||
if (this[PersistentState].value[cell] != undefined) {
|
||||
return this[PersistentState].value[cell];
|
||||
}
|
||||
return this.cells[cell].startState;
|
||||
};
|
||||
grid.setState = function (this: GenericGrid, cell: string | number, state: State) {
|
||||
this[PersistentState].value[cell] = state;
|
||||
};
|
||||
grid.getID = function (this: GenericGrid, cell: string | number) {
|
||||
return grid.id + "-" + cell;
|
||||
};
|
||||
grid.getState = function (this: GenericGrid, cell: string | number) {
|
||||
if (this[PersistentState].value[cell] != undefined) {
|
||||
return this[PersistentState].value[cell];
|
||||
}
|
||||
return this.cells[cell].startState;
|
||||
};
|
||||
grid.setState = function (this: GenericGrid, cell: string | number, state: State) {
|
||||
this[PersistentState].value[cell] = state;
|
||||
};
|
||||
|
||||
processComputable(grid as T, "visibility");
|
||||
setDefault(grid, "visibility", Visibility.Visible);
|
||||
processComputable(grid as T, "rows");
|
||||
processComputable(grid as T, "cols");
|
||||
processComputable(grid as T, "getVisibility");
|
||||
setDefault(grid, "getVisibility", Visibility.Visible);
|
||||
processComputable(grid as T, "getCanClick");
|
||||
setDefault(grid, "getCanClick", true);
|
||||
processComputable(grid as T, "getStartState");
|
||||
processComputable(grid as T, "getStyle");
|
||||
processComputable(grid as T, "getClasses");
|
||||
processComputable(grid as T, "getTitle");
|
||||
processComputable(grid as T, "getDisplay");
|
||||
grid.cells = createGridProxy(grid as GenericGrid);
|
||||
|
||||
const proxy = createProxy(grid as unknown as Grid<T>);
|
||||
(proxy as GenericGrid).cells = createGridProxy(proxy as GenericGrid);
|
||||
return proxy;
|
||||
processComputable(grid as T, "visibility");
|
||||
setDefault(grid, "visibility", Visibility.Visible);
|
||||
processComputable(grid as T, "rows");
|
||||
processComputable(grid as T, "cols");
|
||||
processComputable(grid as T, "getVisibility");
|
||||
setDefault(grid, "getVisibility", Visibility.Visible);
|
||||
processComputable(grid as T, "getCanClick");
|
||||
setDefault(grid, "getCanClick", true);
|
||||
processComputable(grid as T, "getStartState");
|
||||
processComputable(grid as T, "getStyle");
|
||||
processComputable(grid as T, "getClasses");
|
||||
processComputable(grid as T, "getTitle");
|
||||
processComputable(grid as T, "getDisplay");
|
||||
|
||||
grid[GatherProps] = function (this: GenericGrid) {
|
||||
const { visibility, rows, cols, cells, id } = this;
|
||||
return { visibility, rows, cols, cells, id };
|
||||
};
|
||||
|
||||
return grid as unknown as Grid<T>;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
ProcessedComputable,
|
||||
processComputable
|
||||
} from "@/util/computed";
|
||||
import { createProxy } from "@/util/proxies";
|
||||
import { createLazyProxy } from "@/util/proxies";
|
||||
import { unref } from "vue";
|
||||
import { findFeatures, Replace, setDefault } from "./feature";
|
||||
|
||||
|
@ -41,16 +41,19 @@ export type GenericHotkey = Replace<
|
|||
}
|
||||
>;
|
||||
|
||||
export function createHotkey<T extends HotkeyOptions>(options: T & ThisType<Hotkey<T>>): Hotkey<T> {
|
||||
const hotkey: T & Partial<BaseHotkey> = options;
|
||||
hotkey.type = HotkeyType;
|
||||
export function createHotkey<T extends HotkeyOptions>(
|
||||
optionsFunc: () => T & ThisType<Hotkey<T>>
|
||||
): Hotkey<T> {
|
||||
return createLazyProxy(() => {
|
||||
const hotkey: T & Partial<BaseHotkey> = optionsFunc();
|
||||
hotkey.type = HotkeyType;
|
||||
|
||||
processComputable(hotkey as T, "enabled");
|
||||
setDefault(hotkey, "enabled", true);
|
||||
processComputable(hotkey as T, "description");
|
||||
processComputable(hotkey as T, "enabled");
|
||||
setDefault(hotkey, "enabled", true);
|
||||
processComputable(hotkey as T, "description");
|
||||
|
||||
const proxy = createProxy(hotkey as unknown as Hotkey<T>);
|
||||
return proxy;
|
||||
return hotkey as unknown as Hotkey<T>;
|
||||
});
|
||||
}
|
||||
|
||||
globalBus.on("addLayer", layer => {
|
||||
|
|
|
@ -2,6 +2,7 @@ import InfoboxComponent from "@/components/features/Infobox.vue";
|
|||
import {
|
||||
CoercableComponent,
|
||||
Component,
|
||||
GatherProps,
|
||||
getUniqueID,
|
||||
makePersistent,
|
||||
Persistent,
|
||||
|
@ -18,7 +19,7 @@ import {
|
|||
processComputable,
|
||||
ProcessedComputable
|
||||
} from "@/util/computed";
|
||||
import { createProxy } from "@/util/proxies";
|
||||
import { createLazyProxy } from "@/util/proxies";
|
||||
import { Ref } from "vue";
|
||||
|
||||
export const InfoboxType = Symbol("Infobox");
|
||||
|
@ -39,6 +40,7 @@ interface BaseInfobox extends Persistent<boolean> {
|
|||
collapsed: Ref<boolean>;
|
||||
type: typeof InfoboxType;
|
||||
[Component]: typeof InfoboxComponent;
|
||||
[GatherProps]: () => Record<string, unknown>;
|
||||
}
|
||||
|
||||
export type Infobox<T extends InfoboxOptions> = Replace<
|
||||
|
@ -63,26 +65,54 @@ export type GenericInfobox = Replace<
|
|||
>;
|
||||
|
||||
export function createInfobox<T extends InfoboxOptions>(
|
||||
options: T & ThisType<Infobox<T>>
|
||||
optionsFunc: () => T & ThisType<Infobox<T>>
|
||||
): Infobox<T> {
|
||||
const infobox: T & Partial<BaseInfobox> = options;
|
||||
makePersistent<boolean>(infobox, false);
|
||||
infobox.id = getUniqueID("infobox-");
|
||||
infobox.type = InfoboxType;
|
||||
infobox[Component] = InfoboxComponent;
|
||||
return createLazyProxy(() => {
|
||||
const infobox: T & Partial<BaseInfobox> = optionsFunc();
|
||||
makePersistent<boolean>(infobox, false);
|
||||
infobox.id = getUniqueID("infobox-");
|
||||
infobox.type = InfoboxType;
|
||||
infobox[Component] = InfoboxComponent;
|
||||
|
||||
infobox.collapsed = infobox[PersistentState];
|
||||
infobox.collapsed = infobox[PersistentState];
|
||||
|
||||
processComputable(infobox as T, "visibility");
|
||||
setDefault(infobox, "visibility", Visibility.Visible);
|
||||
processComputable(infobox as T, "color");
|
||||
processComputable(infobox as T, "style");
|
||||
processComputable(infobox as T, "titleStyle");
|
||||
processComputable(infobox as T, "bodyStyle");
|
||||
processComputable(infobox as T, "classes");
|
||||
processComputable(infobox as T, "title");
|
||||
processComputable(infobox as T, "display");
|
||||
processComputable(infobox as T, "visibility");
|
||||
setDefault(infobox, "visibility", Visibility.Visible);
|
||||
processComputable(infobox as T, "color");
|
||||
processComputable(infobox as T, "style");
|
||||
processComputable(infobox as T, "titleStyle");
|
||||
processComputable(infobox as T, "bodyStyle");
|
||||
processComputable(infobox as T, "classes");
|
||||
processComputable(infobox as T, "title");
|
||||
processComputable(infobox as T, "display");
|
||||
|
||||
const proxy = createProxy(infobox as unknown as Infobox<T>);
|
||||
return proxy;
|
||||
infobox[GatherProps] = function (this: GenericInfobox) {
|
||||
const {
|
||||
visibility,
|
||||
display,
|
||||
title,
|
||||
color,
|
||||
collapsed,
|
||||
style,
|
||||
titleStyle,
|
||||
bodyStyle,
|
||||
classes,
|
||||
id
|
||||
} = this;
|
||||
return {
|
||||
visibility,
|
||||
display,
|
||||
title,
|
||||
color,
|
||||
collapsed,
|
||||
style,
|
||||
titleStyle,
|
||||
bodyStyle,
|
||||
classes,
|
||||
id
|
||||
};
|
||||
};
|
||||
|
||||
return infobox as unknown as Infobox<T>;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import {
|
|||
CoercableComponent,
|
||||
Component,
|
||||
findFeatures,
|
||||
GatherProps,
|
||||
getUniqueID,
|
||||
makePersistent,
|
||||
Persistent,
|
||||
|
@ -22,7 +23,7 @@ import {
|
|||
processComputable,
|
||||
ProcessedComputable
|
||||
} from "@/util/computed";
|
||||
import { createProxy } from "@/util/proxies";
|
||||
import { createLazyProxy } from "@/util/proxies";
|
||||
import { coerceComponent, isCoercableComponent } from "@/util/vue";
|
||||
import { Unsubscribe } from "nanoevents";
|
||||
import { computed, Ref, unref } from "vue";
|
||||
|
@ -59,6 +60,7 @@ interface BaseMilestone extends Persistent<boolean> {
|
|||
earned: Ref<boolean>;
|
||||
type: typeof MilestoneType;
|
||||
[Component]: typeof MilestoneComponent;
|
||||
[GatherProps]: () => Record<string, unknown>;
|
||||
}
|
||||
|
||||
export type Milestone<T extends MilestoneOptions> = Replace<
|
||||
|
@ -80,52 +82,59 @@ export type GenericMilestone = Replace<
|
|||
>;
|
||||
|
||||
export function createMilestone<T extends MilestoneOptions>(
|
||||
options: T & ThisType<Milestone<T>>
|
||||
optionsFunc: () => T & ThisType<Milestone<T>>
|
||||
): Milestone<T> {
|
||||
const milestone: T & Partial<BaseMilestone> = options;
|
||||
makePersistent<boolean>(milestone, false);
|
||||
milestone.id = getUniqueID("milestone-");
|
||||
milestone.type = MilestoneType;
|
||||
milestone[Component] = MilestoneComponent;
|
||||
return createLazyProxy(() => {
|
||||
const milestone: T & Partial<BaseMilestone> = optionsFunc();
|
||||
makePersistent<boolean>(milestone, false);
|
||||
milestone.id = getUniqueID("milestone-");
|
||||
milestone.type = MilestoneType;
|
||||
milestone[Component] = MilestoneComponent;
|
||||
|
||||
milestone.earned = milestone[PersistentState];
|
||||
processComputable(milestone as T, "visibility");
|
||||
setDefault(milestone, "visibility", Visibility.Visible);
|
||||
const visibility = milestone.visibility as ProcessedComputable<Visibility>;
|
||||
milestone.visibility = computed(() => {
|
||||
switch (settings.msDisplay) {
|
||||
default:
|
||||
case MilestoneDisplay.All:
|
||||
return unref(visibility);
|
||||
case MilestoneDisplay.Configurable:
|
||||
if (
|
||||
unref(proxy.earned) &&
|
||||
!(
|
||||
proxy.display != null &&
|
||||
typeof unref(proxy.display) == "object" &&
|
||||
"optionsDisplay" in (unref(proxy.display) as Record<string, unknown>)
|
||||
)
|
||||
) {
|
||||
milestone.earned = milestone[PersistentState];
|
||||
processComputable(milestone as T, "visibility");
|
||||
setDefault(milestone, "visibility", Visibility.Visible);
|
||||
const visibility = milestone.visibility as ProcessedComputable<Visibility>;
|
||||
milestone.visibility = computed(() => {
|
||||
const display = unref((milestone as GenericMilestone).display);
|
||||
switch (settings.msDisplay) {
|
||||
default:
|
||||
case MilestoneDisplay.All:
|
||||
return unref(visibility);
|
||||
case MilestoneDisplay.Configurable:
|
||||
if (
|
||||
unref(milestone.earned) &&
|
||||
!(
|
||||
display != null &&
|
||||
typeof display == "object" &&
|
||||
"optionsDisplay" in (display as Record<string, unknown>)
|
||||
)
|
||||
) {
|
||||
return Visibility.None;
|
||||
}
|
||||
return unref(visibility);
|
||||
case MilestoneDisplay.Incomplete:
|
||||
if (unref(milestone.earned)) {
|
||||
return Visibility.None;
|
||||
}
|
||||
return unref(visibility);
|
||||
case MilestoneDisplay.None:
|
||||
return Visibility.None;
|
||||
}
|
||||
return unref(visibility);
|
||||
case MilestoneDisplay.Incomplete:
|
||||
if (unref(proxy.earned)) {
|
||||
return Visibility.None;
|
||||
}
|
||||
return unref(visibility);
|
||||
case MilestoneDisplay.None:
|
||||
return Visibility.None;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
processComputable(milestone as T, "shouldEarn");
|
||||
processComputable(milestone as T, "style");
|
||||
processComputable(milestone as T, "classes");
|
||||
processComputable(milestone as T, "display");
|
||||
|
||||
milestone[GatherProps] = function (this: GenericMilestone) {
|
||||
const { visibility, display, style, classes, earned, id } = this;
|
||||
return { visibility, display, style, classes, earned, id };
|
||||
};
|
||||
|
||||
return milestone as unknown as Milestone<T>;
|
||||
});
|
||||
|
||||
processComputable(milestone as T, "shouldEarn");
|
||||
processComputable(milestone as T, "style");
|
||||
processComputable(milestone as T, "classes");
|
||||
processComputable(milestone as T, "display");
|
||||
|
||||
const proxy = createProxy(milestone as unknown as Milestone<T>);
|
||||
return proxy;
|
||||
}
|
||||
|
||||
const toast = useToast();
|
||||
|
@ -150,14 +159,14 @@ globalBus.on("addLayer", layer => {
|
|||
isCoercableComponent(display) ? display : display.requirement
|
||||
);
|
||||
toast(
|
||||
<template>
|
||||
<>
|
||||
<h3>Milestone earned!</h3>
|
||||
<div>
|
||||
{/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
|
||||
{/* @ts-ignore */}
|
||||
<Display />
|
||||
</div>
|
||||
</template>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,14 +4,14 @@ import {
|
|||
Persistent,
|
||||
persistent,
|
||||
PersistentRef,
|
||||
Replace,
|
||||
SetupPersistence
|
||||
PersistentState,
|
||||
Replace
|
||||
} from "@/features/feature";
|
||||
import { globalBus } from "@/game/events";
|
||||
import { GenericLayer } from "@/game/layers";
|
||||
import Decimal from "@/lib/break_eternity";
|
||||
import { Computable, GetComputableType, processComputable } from "@/util/computed";
|
||||
import { createProxy } from "@/util/proxies";
|
||||
import { createLazyProxy } from "@/util/proxies";
|
||||
import { Unsubscribe } from "nanoevents";
|
||||
import { computed, isRef, unref } from "vue";
|
||||
|
||||
|
@ -37,48 +37,46 @@ export type Reset<T extends ResetOptions> = Replace<
|
|||
|
||||
export type GenericReset = Reset<ResetOptions>;
|
||||
|
||||
export function createReset<T extends ResetOptions>(options: T & ThisType<Reset<T>>): Reset<T> {
|
||||
const reset: T & Partial<BaseReset> = options;
|
||||
reset.id = getUniqueID("reset-");
|
||||
reset.type = ResetType;
|
||||
export function createReset<T extends ResetOptions>(
|
||||
optionsFunc: () => T & ThisType<Reset<T>>
|
||||
): Reset<T> {
|
||||
return createLazyProxy(() => {
|
||||
const reset: T & Partial<BaseReset> = optionsFunc();
|
||||
reset.id = getUniqueID("reset-");
|
||||
reset.type = ResetType;
|
||||
|
||||
reset.reset = function () {
|
||||
const handleObject = (obj: Record<string, unknown>) => {
|
||||
Object.keys(obj).forEach(key => {
|
||||
const value = obj[key];
|
||||
if (value && typeof value === "object") {
|
||||
if (SetupPersistence in value && isRef(value)) {
|
||||
if (DefaultValue in value) {
|
||||
(value as PersistentRef).value = (value as PersistentRef)[DefaultValue];
|
||||
} else if (DefaultValue in obj) {
|
||||
(value as PersistentRef).value = (obj as unknown as Persistent)[
|
||||
DefaultValue
|
||||
];
|
||||
}
|
||||
} else {
|
||||
handleObject(value as Record<string, unknown>);
|
||||
reset.reset = function () {
|
||||
const handleObject = (obj: unknown) => {
|
||||
if (obj && typeof obj === "object") {
|
||||
if (PersistentState in obj) {
|
||||
(obj as Persistent)[PersistentState].value = (obj as Persistent)[
|
||||
DefaultValue
|
||||
];
|
||||
} else if (!(obj instanceof Decimal) && !isRef(obj)) {
|
||||
Object.values(obj).forEach(obj =>
|
||||
handleObject(obj as Record<string, unknown>)
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
unref((reset as GenericReset).thingsToReset).forEach(handleObject);
|
||||
globalBus.emit("reset", reset as GenericReset);
|
||||
reset.onReset?.();
|
||||
};
|
||||
unref(proxy.thingsToReset).forEach(handleObject);
|
||||
globalBus.emit("reset", proxy);
|
||||
proxy.onReset?.();
|
||||
};
|
||||
|
||||
processComputable(reset as T, "thingsToReset");
|
||||
processComputable(reset as T, "thingsToReset");
|
||||
|
||||
const proxy = createProxy(reset as unknown as Reset<T>);
|
||||
return proxy;
|
||||
return reset as unknown as Reset<T>;
|
||||
});
|
||||
}
|
||||
|
||||
export function setupAutoReset(
|
||||
layer: GenericLayer,
|
||||
reset: GenericReset,
|
||||
autoActive: Computable<boolean> = true
|
||||
): void {
|
||||
): Unsubscribe {
|
||||
const isActive = typeof autoActive === "function" ? computed(autoActive) : autoActive;
|
||||
layer.on("update", () => {
|
||||
return layer.on("update", () => {
|
||||
if (unref(isActive)) {
|
||||
reset.reset();
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { persistent, State } from "@/features/feature";
|
||||
import Decimal, { DecimalSource, format, formatWhole } from "@/util/bignum";
|
||||
import { computed, Ref, watch } from "vue";
|
||||
import { computed, ComputedRef, ref, Ref, watch } from "vue";
|
||||
import { globalBus } from "@/game/events";
|
||||
|
||||
export type Resource<T = DecimalSource> = Ref<T> & {
|
||||
export interface Resource<T = DecimalSource> extends Ref<T> {
|
||||
displayName: string;
|
||||
precision: number;
|
||||
small: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export function createResource<T extends State>(
|
||||
defaultValue: T | Ref<T>,
|
||||
|
@ -22,27 +22,6 @@ export function createResource<T extends State>(
|
|||
return resource as Resource<T>;
|
||||
}
|
||||
|
||||
function softcap(value: DecimalSource, cap: DecimalSource, power: DecimalSource = 0.5): Decimal {
|
||||
if (Decimal.lte(value, cap)) {
|
||||
return new Decimal(value);
|
||||
} else {
|
||||
return Decimal.pow(value, power).times(Decimal.pow(cap, Decimal.sub(1, power)));
|
||||
}
|
||||
}
|
||||
|
||||
export function addSoftcap(
|
||||
resource: Resource,
|
||||
cap: DecimalSource,
|
||||
power: DecimalSource = 0.5
|
||||
): Resource {
|
||||
return {
|
||||
...resource,
|
||||
get value() {
|
||||
return softcap(resource.value, cap, power);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function trackBest(resource: Resource): Ref<DecimalSource> {
|
||||
const best = persistent(resource.value);
|
||||
watch(resource, amount => {
|
||||
|
@ -63,63 +42,68 @@ export function trackTotal(resource: Resource): Ref<DecimalSource> {
|
|||
return total;
|
||||
}
|
||||
|
||||
export function trackOOMPS(resource: Resource): Ref<string> {
|
||||
let oomps: DecimalSource = 0;
|
||||
let oompsMag = 0;
|
||||
let lastPoints: DecimalSource = 0;
|
||||
export function trackOOMPS(
|
||||
resource: Resource,
|
||||
pointGain?: ComputedRef<DecimalSource>
|
||||
): Ref<string> {
|
||||
const oomps = ref<DecimalSource>(0);
|
||||
const oompsMag = ref(0);
|
||||
const lastPoints = ref<DecimalSource>(0);
|
||||
|
||||
globalBus.on("update", diff => {
|
||||
oompsMag.value = 0;
|
||||
if (Decimal.lte(resource.value, 1e100)) {
|
||||
lastPoints = resource.value;
|
||||
lastPoints.value = resource.value;
|
||||
return;
|
||||
}
|
||||
|
||||
let curr = resource.value;
|
||||
let prev = lastPoints;
|
||||
lastPoints = curr;
|
||||
let prev = lastPoints.value;
|
||||
lastPoints.value = curr;
|
||||
if (Decimal.gt(curr, prev)) {
|
||||
if (Decimal.gte(curr, "10^^8")) {
|
||||
curr = Decimal.slog(curr, 1e10);
|
||||
prev = Decimal.slog(prev, 1e10);
|
||||
oomps = curr.sub(prev).div(diff);
|
||||
oompsMag = -1;
|
||||
oomps.value = curr.sub(prev).div(diff);
|
||||
oompsMag.value = -1;
|
||||
} else {
|
||||
while (
|
||||
Decimal.div(curr, prev).log(10).div(diff).gte("100") &&
|
||||
oompsMag <= 5 &&
|
||||
oompsMag.value <= 5 &&
|
||||
Decimal.gt(prev, 0)
|
||||
) {
|
||||
curr = Decimal.log10(curr);
|
||||
prev = Decimal.log10(prev);
|
||||
oomps = curr.sub(prev).div(diff);
|
||||
oompsMag++;
|
||||
oomps.value = curr.sub(prev).div(diff);
|
||||
oompsMag.value++;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return computed(
|
||||
() =>
|
||||
format(oomps) +
|
||||
|
||||
const oompsString = computed(() => {
|
||||
if (oompsMag.value === 0) {
|
||||
return pointGain
|
||||
? format(pointGain.value, resource.precision, resource.small) +
|
||||
" " +
|
||||
resource.displayName +
|
||||
"/s"
|
||||
: "";
|
||||
}
|
||||
return (
|
||||
format(oomps.value) +
|
||||
" OOM" +
|
||||
(oompsMag < 0 ? "^OOM" : oompsMag > 1 ? "^" + oompsMag : "") +
|
||||
"s"
|
||||
);
|
||||
(oompsMag.value < 0 ? "^OOM" : "^" + oompsMag.value) +
|
||||
"s/sec"
|
||||
);
|
||||
});
|
||||
return oompsString;
|
||||
}
|
||||
|
||||
export function displayResource(resource: Resource, overrideAmount?: DecimalSource): string {
|
||||
const amount = overrideAmount == null ? resource.value : overrideAmount;
|
||||
const amount = overrideAmount ?? resource.value;
|
||||
if (Decimal.eq(resource.precision, 0)) {
|
||||
return formatWhole(amount);
|
||||
}
|
||||
return format(amount, resource.precision, resource.small);
|
||||
}
|
||||
|
||||
// unref may unwrap a resource too far, so this function properly unwraps it
|
||||
export function unwrapResource<T extends State>(
|
||||
resource: Resource<T> | Ref<Resource<T>>
|
||||
): Resource<T> {
|
||||
console.log(resource);
|
||||
if ("displayName" in resource) {
|
||||
return resource;
|
||||
}
|
||||
return resource.value;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
import TabComponent from "@/components/features/Tab.vue";
|
||||
import { Computable, GetComputableType } from "@/util/computed";
|
||||
import { createProxy } from "@/util/proxies";
|
||||
import { CoercableComponent, Component, getUniqueID, Replace, StyleValue } from "./feature";
|
||||
import { createLazyProxy } from "@/util/proxies";
|
||||
import {
|
||||
CoercableComponent,
|
||||
Component,
|
||||
GatherProps,
|
||||
getUniqueID,
|
||||
Replace,
|
||||
StyleValue
|
||||
} from "./feature";
|
||||
|
||||
export const TabType = Symbol("Tab");
|
||||
|
||||
|
@ -15,6 +22,7 @@ interface BaseTab {
|
|||
id: string;
|
||||
type: typeof TabType;
|
||||
[Component]: typeof TabComponent;
|
||||
[GatherProps]: () => Record<string, unknown>;
|
||||
}
|
||||
|
||||
export type Tab<T extends TabOptions> = Replace<
|
||||
|
@ -28,12 +36,18 @@ export type Tab<T extends TabOptions> = Replace<
|
|||
|
||||
export type GenericTab = Tab<TabOptions>;
|
||||
|
||||
export function createTab<T extends TabOptions>(options: T & ThisType<Tab<T>>): Tab<T> {
|
||||
const tab: T & Partial<BaseTab> = options;
|
||||
tab.id = getUniqueID("tab-");
|
||||
tab.type = TabType;
|
||||
tab[Component] = TabComponent;
|
||||
export function createTab<T extends TabOptions>(optionsFunc: () => T & ThisType<Tab<T>>): Tab<T> {
|
||||
return createLazyProxy(() => {
|
||||
const tab: T & Partial<BaseTab> = optionsFunc();
|
||||
tab.id = getUniqueID("tab-");
|
||||
tab.type = TabType;
|
||||
tab[Component] = TabComponent;
|
||||
|
||||
const proxy = createProxy(tab as unknown as Tab<T>);
|
||||
return proxy;
|
||||
tab[GatherProps] = function (this: GenericTab) {
|
||||
const { display } = this;
|
||||
return { display };
|
||||
};
|
||||
|
||||
return tab as unknown as Tab<T>;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -7,15 +7,15 @@ import {
|
|||
processComputable,
|
||||
ProcessedComputable
|
||||
} from "@/util/computed";
|
||||
import { createProxy } from "@/util/proxies";
|
||||
import { createLazyProxy } from "@/util/proxies";
|
||||
import { computed, Ref, unref } from "vue";
|
||||
import {
|
||||
CoercableComponent,
|
||||
Component,
|
||||
GatherProps,
|
||||
getUniqueID,
|
||||
makePersistent,
|
||||
Persistent,
|
||||
PersistentRef,
|
||||
PersistentState,
|
||||
Replace,
|
||||
setDefault,
|
||||
|
@ -75,12 +75,14 @@ export function createTabButton<T extends TabButtonOptions>(
|
|||
processComputable(tabButton as T, "style");
|
||||
processComputable(tabButton as T, "glowColor");
|
||||
|
||||
const proxy = createProxy(tabButton as unknown as TabButton<T>);
|
||||
return proxy;
|
||||
return tabButton as unknown as TabButton<T>;
|
||||
}
|
||||
|
||||
export interface TabFamilyOptions {
|
||||
visibility?: Computable<Visibility>;
|
||||
tabs: Computable<Record<string, GenericTabButton>>;
|
||||
classes?: Computable<Record<string, boolean>>;
|
||||
style?: Computable<StyleValue>;
|
||||
}
|
||||
|
||||
interface BaseTabFamily extends Persistent<string> {
|
||||
|
@ -89,49 +91,69 @@ interface BaseTabFamily extends Persistent<string> {
|
|||
selected: Ref<string>;
|
||||
type: typeof TabFamilyType;
|
||||
[Component]: typeof TabFamilyComponent;
|
||||
[GatherProps]: () => Record<string, unknown>;
|
||||
}
|
||||
|
||||
export type TabFamily<T extends TabFamilyOptions> = Replace<
|
||||
T & BaseTabFamily,
|
||||
{
|
||||
visibility: GetComputableTypeWithDefault<T["visibility"], Visibility.Visible>;
|
||||
tabs: GetComputableType<T["tabs"]>;
|
||||
}
|
||||
>;
|
||||
|
||||
export type GenericTabFamily = TabFamily<TabFamilyOptions>;
|
||||
export type GenericTabFamily = Replace<
|
||||
TabFamily<TabFamilyOptions>,
|
||||
{
|
||||
visibility: ProcessedComputable<Visibility>;
|
||||
}
|
||||
>;
|
||||
|
||||
export function createTabFamily<T extends TabFamilyOptions>(
|
||||
options: T & ThisType<TabFamily<T>>
|
||||
optionsFunc: () => T & ThisType<TabFamily<T>>
|
||||
): TabFamily<T> {
|
||||
if (Object.keys(options.tabs).length === 0) {
|
||||
console.warn("Cannot create tab family with 0 tabs", options);
|
||||
throw "Cannot create tab family with 0 tabs";
|
||||
}
|
||||
return createLazyProxy(() => {
|
||||
const tabFamily: T & Partial<BaseTabFamily> = optionsFunc();
|
||||
|
||||
const tabFamily: T & Partial<BaseTabFamily> = options;
|
||||
tabFamily.id = getUniqueID("tabFamily-");
|
||||
tabFamily.type = TabFamilyType;
|
||||
tabFamily[Component] = TabFamilyComponent;
|
||||
if (Object.keys(tabFamily.tabs).length === 0) {
|
||||
console.warn("Cannot create tab family with 0 tabs", tabFamily);
|
||||
throw "Cannot create tab family with 0 tabs";
|
||||
}
|
||||
|
||||
makePersistent<string>(tabFamily, Object.keys(options.tabs)[0]);
|
||||
tabFamily.selected = tabFamily[PersistentState];
|
||||
tabFamily.activeTab = computed(() => {
|
||||
const tabs = unref(proxy.tabs);
|
||||
if (
|
||||
proxy[PersistentState].value in tabs &&
|
||||
unref(tabs[proxy[PersistentState].value].visibility) === Visibility.Visible
|
||||
) {
|
||||
return unref(tabs[proxy[PersistentState].value].tab);
|
||||
}
|
||||
const firstTab = Object.values(tabs).find(
|
||||
tab => unref(tab.visibility) === Visibility.Visible
|
||||
);
|
||||
if (firstTab) {
|
||||
return unref(firstTab.tab);
|
||||
}
|
||||
return null;
|
||||
tabFamily.id = getUniqueID("tabFamily-");
|
||||
tabFamily.type = TabFamilyType;
|
||||
tabFamily[Component] = TabFamilyComponent;
|
||||
|
||||
makePersistent<string>(tabFamily, Object.keys(tabFamily.tabs)[0]);
|
||||
tabFamily.selected = tabFamily[PersistentState];
|
||||
tabFamily.activeTab = computed(() => {
|
||||
const tabs = unref((tabFamily as GenericTabFamily).tabs);
|
||||
if (
|
||||
tabFamily[PersistentState].value in tabs &&
|
||||
unref(tabs[(tabFamily as GenericTabFamily)[PersistentState].value].visibility) ===
|
||||
Visibility.Visible
|
||||
) {
|
||||
return unref(tabs[(tabFamily as GenericTabFamily)[PersistentState].value].tab);
|
||||
}
|
||||
const firstTab = Object.values(tabs).find(
|
||||
tab => unref(tab.visibility) === Visibility.Visible
|
||||
);
|
||||
if (firstTab) {
|
||||
return unref(firstTab.tab);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
processComputable(tabFamily as T, "visibility");
|
||||
setDefault(tabFamily, "visibility", Visibility.Visible);
|
||||
processComputable(tabFamily as T, "classes");
|
||||
processComputable(tabFamily as T, "style");
|
||||
|
||||
tabFamily[GatherProps] = function (this: GenericTabFamily) {
|
||||
const { visibility, activeTab, selected, tabs, style, classes } = this;
|
||||
return { visibility, activeTab, selected, tabs, style, classes };
|
||||
};
|
||||
|
||||
return tabFamily as unknown as TabFamily<T>;
|
||||
});
|
||||
|
||||
const proxy = createProxy(tabFamily as unknown as TabFamily<T>);
|
||||
return proxy;
|
||||
}
|
||||
|
|
|
@ -18,3 +18,8 @@ export interface Tooltip {
|
|||
yoffset?: ProcessedComputable<string>;
|
||||
force?: ProcessedComputable<boolean>;
|
||||
}
|
||||
|
||||
export function gatherTooltipProps(tooltip: Tooltip) {
|
||||
const { display, top, left, right, bottom, xoffset, yoffset, force } = tooltip;
|
||||
return { display, top, left, right, bottom, xoffset, yoffset, force };
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import TreeComponent from "@/components/features/tree/Tree.vue";
|
|||
import {
|
||||
CoercableComponent,
|
||||
Component,
|
||||
GatherProps,
|
||||
getUniqueID,
|
||||
persistent,
|
||||
Replace,
|
||||
|
@ -20,7 +21,7 @@ import {
|
|||
processComputable,
|
||||
ProcessedComputable
|
||||
} from "@/util/computed";
|
||||
import { createProxy } from "@/util/proxies";
|
||||
import { createLazyProxy } from "@/util/proxies";
|
||||
import { computed, ref, Ref, unref } from "vue";
|
||||
import { Link } from "./links";
|
||||
import { GenericReset } from "./reset";
|
||||
|
@ -74,33 +75,34 @@ export type GenericTreeNode = Replace<
|
|||
>;
|
||||
|
||||
export function createTreeNode<T extends TreeNodeOptions>(
|
||||
options: T & ThisType<TreeNode<T>>
|
||||
optionsFunc: () => T & ThisType<TreeNode<T>>
|
||||
): TreeNode<T> {
|
||||
const treeNode: T & Partial<BaseTreeNode> = options;
|
||||
treeNode.id = getUniqueID("treeNode-");
|
||||
treeNode.type = TreeNodeType;
|
||||
return createLazyProxy(() => {
|
||||
const treeNode: T & Partial<BaseTreeNode> = optionsFunc();
|
||||
treeNode.id = getUniqueID("treeNode-");
|
||||
treeNode.type = TreeNodeType;
|
||||
|
||||
if (treeNode.tooltip) {
|
||||
treeNode.forceTooltip = persistent(false);
|
||||
} else {
|
||||
// If we don't have a tooltip, no point in making this persistent
|
||||
treeNode.forceTooltip = ref(false);
|
||||
}
|
||||
if (treeNode.tooltip) {
|
||||
treeNode.forceTooltip = persistent(false);
|
||||
} else {
|
||||
// If we don't have a tooltip, no point in making this persistent
|
||||
treeNode.forceTooltip = ref(false);
|
||||
}
|
||||
|
||||
processComputable(treeNode as T, "visibility");
|
||||
setDefault(treeNode, "visibility", Visibility.Visible);
|
||||
processComputable(treeNode as T, "canClick");
|
||||
setDefault(treeNode, "canClick", true);
|
||||
processComputable(treeNode as T, "color");
|
||||
processComputable(treeNode as T, "display");
|
||||
processComputable(treeNode as T, "tooltip");
|
||||
processComputable(treeNode as T, "glowColor");
|
||||
processComputable(treeNode as T, "classes");
|
||||
processComputable(treeNode as T, "style");
|
||||
processComputable(treeNode as T, "mark");
|
||||
processComputable(treeNode as T, "visibility");
|
||||
setDefault(treeNode, "visibility", Visibility.Visible);
|
||||
processComputable(treeNode as T, "canClick");
|
||||
setDefault(treeNode, "canClick", true);
|
||||
processComputable(treeNode as T, "color");
|
||||
processComputable(treeNode as T, "display");
|
||||
processComputable(treeNode as T, "tooltip");
|
||||
processComputable(treeNode as T, "glowColor");
|
||||
processComputable(treeNode as T, "classes");
|
||||
processComputable(treeNode as T, "style");
|
||||
processComputable(treeNode as T, "mark");
|
||||
|
||||
const proxy = createProxy(treeNode as unknown as TreeNode<T>);
|
||||
return proxy;
|
||||
return treeNode as unknown as TreeNode<T>;
|
||||
});
|
||||
}
|
||||
|
||||
export interface TreeBranch extends Omit<Link, "startNode" | "endNode"> {
|
||||
|
@ -126,6 +128,7 @@ interface BaseTree {
|
|||
resettingNode: Ref<GenericTreeNode | null>;
|
||||
type: typeof TreeType;
|
||||
[Component]: typeof TreeComponent;
|
||||
[GatherProps]: () => Record<string, unknown>;
|
||||
}
|
||||
|
||||
export type Tree<T extends TreeOptions> = Replace<
|
||||
|
@ -146,33 +149,46 @@ export type GenericTree = Replace<
|
|||
}
|
||||
>;
|
||||
|
||||
export function createTree<T extends TreeOptions>(options: T & ThisType<Tree<T>>): Tree<T> {
|
||||
const tree: T & Partial<BaseTree> = options;
|
||||
tree.id = getUniqueID("tree-");
|
||||
tree.type = TreeType;
|
||||
tree[Component] = TreeComponent;
|
||||
export function createTree<T extends TreeOptions>(
|
||||
optionsFunc: () => T & ThisType<Tree<T>>
|
||||
): Tree<T> {
|
||||
return createLazyProxy(() => {
|
||||
const tree: T & Partial<BaseTree> = optionsFunc();
|
||||
tree.id = getUniqueID("tree-");
|
||||
tree.type = TreeType;
|
||||
tree[Component] = TreeComponent;
|
||||
|
||||
tree.isResetting = ref(false);
|
||||
tree.resettingNode = ref(null);
|
||||
tree.isResetting = ref(false);
|
||||
tree.resettingNode = ref(null);
|
||||
|
||||
tree.reset = function (node) {
|
||||
proxy.isResetting.value = true;
|
||||
proxy.resettingNode.value = node;
|
||||
proxy.resetPropagation?.(proxy, node);
|
||||
proxy.isResetting.value = false;
|
||||
proxy.resettingNode.value = null;
|
||||
};
|
||||
tree.links = computed(() => (proxy.branches == null ? [] : unref(proxy.branches)));
|
||||
tree.reset = function (node) {
|
||||
const genericTree = tree as GenericTree;
|
||||
genericTree.isResetting.value = true;
|
||||
genericTree.resettingNode.value = node;
|
||||
genericTree.resetPropagation?.(genericTree, node);
|
||||
genericTree.onReset?.(node);
|
||||
genericTree.isResetting.value = false;
|
||||
genericTree.resettingNode.value = null;
|
||||
};
|
||||
tree.links = computed(() => {
|
||||
const genericTree = tree as GenericTree;
|
||||
return unref(genericTree.branches) ?? [];
|
||||
});
|
||||
|
||||
processComputable(tree as T, "visibility");
|
||||
setDefault(tree, "visibility", Visibility.Visible);
|
||||
processComputable(tree as T, "nodes");
|
||||
processComputable(tree as T, "leftSideNodes");
|
||||
processComputable(tree as T, "rightSideNodes");
|
||||
processComputable(tree as T, "branches");
|
||||
processComputable(tree as T, "visibility");
|
||||
setDefault(tree, "visibility", Visibility.Visible);
|
||||
processComputable(tree as T, "nodes");
|
||||
processComputable(tree as T, "leftSideNodes");
|
||||
processComputable(tree as T, "rightSideNodes");
|
||||
processComputable(tree as T, "branches");
|
||||
|
||||
const proxy = createProxy(tree as unknown as Tree<T>);
|
||||
return proxy;
|
||||
tree[GatherProps] = function (this: GenericTree) {
|
||||
const { nodes, leftSideNodes, rightSideNodes } = this;
|
||||
return { nodes, leftSideNodes, rightSideNodes };
|
||||
};
|
||||
|
||||
return tree as unknown as Tree<T>;
|
||||
});
|
||||
}
|
||||
|
||||
export type ResetPropagation = {
|
||||
|
@ -213,17 +229,25 @@ export const branchedResetPropagation = function (
|
|||
const nextNodes: GenericTreeNode[] = [];
|
||||
currentNodes.forEach(node => {
|
||||
branches
|
||||
.filter(
|
||||
branch =>
|
||||
branch.startNode === node &&
|
||||
!visitedNodes.includes(unref(branch.endNode))
|
||||
)
|
||||
.forEach(branch => {
|
||||
visitedNodes.push(branch.startNode);
|
||||
nextNodes.push(branch.endNode);
|
||||
.filter(branch => branch.startNode === node || branch.endNode === node)
|
||||
.map(branch => {
|
||||
if (branch.startNode === node) {
|
||||
return branch.endNode;
|
||||
}
|
||||
return branch.startNode;
|
||||
})
|
||||
.filter(node => !visitedNodes.includes(node))
|
||||
.forEach(node => {
|
||||
// Check here instead of in the filter because this check's results may
|
||||
// change as we go through each node
|
||||
if (!nextNodes.includes(node)) {
|
||||
nextNodes.push(node);
|
||||
node.reset?.reset();
|
||||
}
|
||||
});
|
||||
});
|
||||
currentNodes = nextNodes;
|
||||
visitedNodes.push(...currentNodes);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -3,6 +3,7 @@ import {
|
|||
CoercableComponent,
|
||||
Component,
|
||||
findFeatures,
|
||||
GatherProps,
|
||||
getUniqueID,
|
||||
makePersistent,
|
||||
Persistent,
|
||||
|
@ -23,7 +24,7 @@ import {
|
|||
processComputable,
|
||||
ProcessedComputable
|
||||
} from "@/util/computed";
|
||||
import { createProxy } from "@/util/proxies";
|
||||
import { createLazyProxy } from "@/util/proxies";
|
||||
import { computed, Ref, unref } from "vue";
|
||||
|
||||
export const UpgradeType = Symbol("Upgrade");
|
||||
|
@ -42,18 +43,19 @@ export interface UpgradeOptions {
|
|||
>;
|
||||
mark?: Computable<boolean | string>;
|
||||
cost?: Computable<DecimalSource>;
|
||||
resource?: Computable<Resource>;
|
||||
canPurchase?: Computable<boolean>;
|
||||
resource?: Resource;
|
||||
canAfford?: Computable<boolean>;
|
||||
onPurchase?: VoidFunction;
|
||||
}
|
||||
|
||||
interface BaseUpgrade extends Persistent<boolean> {
|
||||
id: string;
|
||||
bought: Ref<boolean>;
|
||||
canAfford: Ref<boolean>;
|
||||
canPurchase: Ref<boolean>;
|
||||
purchase: VoidFunction;
|
||||
type: typeof UpgradeType;
|
||||
[Component]: typeof UpgradeComponent;
|
||||
[GatherProps]: () => Record<string, unknown>;
|
||||
}
|
||||
|
||||
export type Upgrade<T extends UpgradeOptions> = Replace<
|
||||
|
@ -65,8 +67,7 @@ export type Upgrade<T extends UpgradeOptions> = Replace<
|
|||
display: GetComputableType<T["display"]>;
|
||||
mark: GetComputableType<T["mark"]>;
|
||||
cost: GetComputableType<T["cost"]>;
|
||||
resource: GetComputableType<T["resource"]>;
|
||||
canPurchase: GetComputableTypeWithDefault<T["canPurchase"], Ref<boolean>>;
|
||||
canAfford: GetComputableTypeWithDefault<T["canAfford"], Ref<boolean>>;
|
||||
}
|
||||
>;
|
||||
|
||||
|
@ -79,59 +80,96 @@ export type GenericUpgrade = Replace<
|
|||
>;
|
||||
|
||||
export function createUpgrade<T extends UpgradeOptions>(
|
||||
options: T & ThisType<Upgrade<T>>
|
||||
optionsFunc: () => T & ThisType<Upgrade<T>>
|
||||
): Upgrade<T> {
|
||||
const upgrade: T & Partial<BaseUpgrade> = options;
|
||||
makePersistent<boolean>(upgrade, false);
|
||||
upgrade.id = getUniqueID("upgrade-");
|
||||
upgrade.type = UpgradeType;
|
||||
upgrade[Component] = UpgradeComponent;
|
||||
return createLazyProxy(() => {
|
||||
const upgrade: T & Partial<BaseUpgrade> = optionsFunc();
|
||||
makePersistent<boolean>(upgrade, false);
|
||||
upgrade.id = getUniqueID("upgrade-");
|
||||
upgrade.type = UpgradeType;
|
||||
upgrade[Component] = UpgradeComponent;
|
||||
|
||||
if (upgrade.canPurchase == null && (upgrade.resource == null || upgrade.cost == null)) {
|
||||
console.warn(
|
||||
"Error: can't create upgrade without a canPurchase property or a resource and cost property",
|
||||
upgrade
|
||||
);
|
||||
}
|
||||
|
||||
upgrade.bought = upgrade[PersistentState];
|
||||
if (upgrade.canAfford == null) {
|
||||
upgrade.canAfford = computed(
|
||||
() =>
|
||||
proxy.resource != null &&
|
||||
proxy.cost != null &&
|
||||
Decimal.gte(unref<Resource>(proxy.resource).value, unref(proxy.cost))
|
||||
);
|
||||
}
|
||||
if (upgrade.canPurchase == null) {
|
||||
upgrade.canPurchase = computed(() => unref(proxy.canAfford) && !unref(proxy.bought));
|
||||
}
|
||||
upgrade.purchase = function () {
|
||||
if (!unref(proxy.canPurchase)) {
|
||||
return;
|
||||
}
|
||||
if (proxy.resource != null && proxy.cost != null) {
|
||||
proxy.resource.value = Decimal.sub(
|
||||
unref<Resource>(proxy.resource).value,
|
||||
unref(proxy.cost)
|
||||
if (upgrade.canPurchase == null && (upgrade.resource == null || upgrade.cost == null)) {
|
||||
console.warn(
|
||||
"Error: can't create upgrade without a canPurchase property or a resource and cost property",
|
||||
upgrade
|
||||
);
|
||||
}
|
||||
proxy[PersistentState].value = true;
|
||||
proxy.onPurchase?.();
|
||||
};
|
||||
|
||||
processComputable(upgrade as T, "visibility");
|
||||
setDefault(upgrade, "visibility", Visibility.Visible);
|
||||
processComputable(upgrade as T, "classes");
|
||||
processComputable(upgrade as T, "style");
|
||||
processComputable(upgrade as T, "display");
|
||||
processComputable(upgrade as T, "mark");
|
||||
processComputable(upgrade as T, "cost");
|
||||
processComputable(upgrade as T, "resource");
|
||||
processComputable(upgrade as T, "canPurchase");
|
||||
upgrade.bought = upgrade[PersistentState];
|
||||
if (upgrade.canAfford == null) {
|
||||
upgrade.canAfford = computed(() => {
|
||||
const genericUpgrade = upgrade as GenericUpgrade;
|
||||
return (
|
||||
genericUpgrade.resource != null &&
|
||||
genericUpgrade.cost != null &&
|
||||
Decimal.gte(genericUpgrade.resource.value, unref(genericUpgrade.cost))
|
||||
);
|
||||
});
|
||||
} else {
|
||||
processComputable(upgrade as T, "canAfford");
|
||||
}
|
||||
upgrade.canPurchase = computed(
|
||||
() =>
|
||||
unref((upgrade as GenericUpgrade).visibility) === Visibility.Visible &&
|
||||
unref((upgrade as GenericUpgrade).canAfford) &&
|
||||
!unref(upgrade.bought)
|
||||
);
|
||||
upgrade.purchase = function () {
|
||||
const genericUpgrade = upgrade as GenericUpgrade;
|
||||
if (!unref(genericUpgrade.canPurchase)) {
|
||||
return;
|
||||
}
|
||||
if (genericUpgrade.resource != null && genericUpgrade.cost != null) {
|
||||
genericUpgrade.resource.value = Decimal.sub(
|
||||
genericUpgrade.resource.value,
|
||||
unref(genericUpgrade.cost)
|
||||
);
|
||||
}
|
||||
genericUpgrade[PersistentState].value = true;
|
||||
genericUpgrade.onPurchase?.();
|
||||
};
|
||||
|
||||
const proxy = createProxy(upgrade as unknown as Upgrade<T>);
|
||||
return proxy;
|
||||
processComputable(upgrade as T, "visibility");
|
||||
setDefault(upgrade, "visibility", Visibility.Visible);
|
||||
processComputable(upgrade as T, "classes");
|
||||
processComputable(upgrade as T, "style");
|
||||
processComputable(upgrade as T, "display");
|
||||
processComputable(upgrade as T, "mark");
|
||||
processComputable(upgrade as T, "cost");
|
||||
processComputable(upgrade as T, "resource");
|
||||
|
||||
upgrade[GatherProps] = function (this: GenericUpgrade) {
|
||||
const {
|
||||
display,
|
||||
visibility,
|
||||
style,
|
||||
classes,
|
||||
resource,
|
||||
cost,
|
||||
canPurchase,
|
||||
bought,
|
||||
mark,
|
||||
id,
|
||||
purchase
|
||||
} = this;
|
||||
return {
|
||||
display,
|
||||
visibility,
|
||||
style,
|
||||
classes,
|
||||
resource,
|
||||
cost,
|
||||
canPurchase,
|
||||
bought,
|
||||
mark,
|
||||
id,
|
||||
purchase
|
||||
};
|
||||
};
|
||||
|
||||
return upgrade as unknown as Upgrade<T>;
|
||||
});
|
||||
}
|
||||
|
||||
export function setupAutoPurchase(
|
||||
|
|
|
@ -45,6 +45,10 @@ function update() {
|
|||
|
||||
diff = new Decimal(diff).max(0);
|
||||
|
||||
if (player.devSpeed === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add offline time if any
|
||||
if (player.offlineTime != undefined) {
|
||||
if (Decimal.gt(player.offlineTime, modInfo.offlineLimit * 3600)) {
|
||||
|
|
|
@ -15,9 +15,8 @@ import {
|
|||
processComputable,
|
||||
ProcessedComputable
|
||||
} from "@/util/computed";
|
||||
import { createProxy } from "@/util/proxies";
|
||||
import { createLazyProxy } from "@/util/proxies";
|
||||
import { createNanoEvents, Emitter } from "nanoevents";
|
||||
import { customRef, Ref } from "vue";
|
||||
import { globalBus } from "./events";
|
||||
import player from "./player";
|
||||
|
||||
|
@ -33,6 +32,12 @@ export interface LayerEvents {
|
|||
export const layers: Record<string, Readonly<GenericLayer> | undefined> = {};
|
||||
window.layers = layers;
|
||||
|
||||
declare module "@vue/runtime-dom" {
|
||||
interface CSSProperties {
|
||||
"--layer-color"?: string;
|
||||
}
|
||||
}
|
||||
|
||||
export interface Position {
|
||||
x: number;
|
||||
y: number;
|
||||
|
@ -82,39 +87,26 @@ export type GenericLayer = Replace<
|
|||
}
|
||||
>;
|
||||
|
||||
export function createLayer<T extends LayerOptions>(optionsFunc: () => T): Ref<Layer<T>> {
|
||||
let layer: Layer<T> | null = null;
|
||||
export function createLayer<T extends LayerOptions>(optionsFunc: () => T): Layer<T> {
|
||||
return createLazyProxy(() => {
|
||||
const layer = optionsFunc() as T & Partial<BaseLayer>;
|
||||
const emitter = (layer.emitter = createNanoEvents<LayerEvents>());
|
||||
layer.on = emitter.on.bind(emitter);
|
||||
layer.emit = emitter.emit.bind(emitter);
|
||||
|
||||
return customRef(track => {
|
||||
return {
|
||||
get() {
|
||||
if (layer == undefined) {
|
||||
const partialLayer = optionsFunc() as T & Partial<BaseLayer>;
|
||||
const emitter = (partialLayer.emitter = createNanoEvents<LayerEvents>());
|
||||
partialLayer.on = emitter.on.bind(emitter);
|
||||
partialLayer.emit = emitter.emit.bind(emitter);
|
||||
layer.minimized = persistent(false);
|
||||
|
||||
partialLayer.minimized = persistent(false);
|
||||
processComputable(layer as T, "color");
|
||||
processComputable(layer as T, "display");
|
||||
processComputable(layer as T, "name");
|
||||
setDefault(layer, "name", layer.id);
|
||||
processComputable(layer as T, "minWidth");
|
||||
setDefault(layer, "minWidth", 600);
|
||||
processComputable(layer as T, "minimizable");
|
||||
setDefault(layer, "minimizable", true);
|
||||
processComputable(layer as T, "links");
|
||||
|
||||
processComputable(partialLayer as T, "color");
|
||||
processComputable(partialLayer as T, "display");
|
||||
processComputable(partialLayer as T, "name");
|
||||
setDefault(partialLayer, "name", partialLayer.id);
|
||||
processComputable(partialLayer as T, "minWidth");
|
||||
setDefault(partialLayer, "minWidth", 600);
|
||||
processComputable(partialLayer as T, "minimizable");
|
||||
setDefault(partialLayer, "minimizable", true);
|
||||
processComputable(partialLayer as T, "links");
|
||||
|
||||
layer = createProxy(partialLayer as unknown as Layer<T>);
|
||||
}
|
||||
track();
|
||||
return layer;
|
||||
},
|
||||
set() {
|
||||
console.error("Layers are read-only!");
|
||||
}
|
||||
};
|
||||
return layer as unknown as Layer<T>;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -3,3 +3,16 @@ import Toast from "vue-toastification";
|
|||
import "vue-toastification/dist/index.css";
|
||||
|
||||
globalBus.on("setupVue", vue => vue.use(Toast));
|
||||
|
||||
export function getNotifyStyle(color = "white", strength = "8px") {
|
||||
return {
|
||||
transform: "scale(1.05, 1.05)",
|
||||
borderColor: "rgba(0, 0, 0, 0.125)",
|
||||
boxShadow: `-4px -4px 4px rgba(0, 0, 0, 0.25) inset, 0 0 ${strength} ${color}`,
|
||||
zIndex: 1
|
||||
};
|
||||
}
|
||||
|
||||
export function getHighNotifyStyle() {
|
||||
return getNotifyStyle("red", "20px");
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Decimal, { DecimalSource } from "@/util/bignum";
|
||||
import { isPlainObject } from "@/util/common";
|
||||
import { ProxiedWithState, ProxyPath, ProxyState } from "@/util/proxies";
|
||||
import { shallowReactive, unref } from "vue";
|
||||
import { reactive, unref } from "vue";
|
||||
import transientState from "./state";
|
||||
|
||||
export interface PlayerData {
|
||||
|
@ -15,7 +15,6 @@ export interface PlayerData {
|
|||
offlineTime: DecimalSource | null;
|
||||
timePlayed: DecimalSource;
|
||||
keepGoing: boolean;
|
||||
minimized: Record<string, boolean>;
|
||||
modID: string;
|
||||
modVersion: string;
|
||||
layers: Record<string, Record<string, unknown>>;
|
||||
|
@ -23,7 +22,7 @@ export interface PlayerData {
|
|||
|
||||
export type Player = ProxiedWithState<PlayerData>;
|
||||
|
||||
const state = shallowReactive<PlayerData>({
|
||||
const state = reactive<PlayerData>({
|
||||
id: "",
|
||||
devSpeed: null,
|
||||
name: "",
|
||||
|
@ -34,14 +33,13 @@ const state = shallowReactive<PlayerData>({
|
|||
offlineTime: null,
|
||||
timePlayed: new Decimal(0),
|
||||
keepGoing: false,
|
||||
minimized: {},
|
||||
modID: "",
|
||||
modVersion: "",
|
||||
layers: {}
|
||||
});
|
||||
|
||||
export function stringifySave(player: PlayerData): string {
|
||||
return JSON.stringify((player as Player)[ProxyState], (key, value) => unref(value));
|
||||
return JSON.stringify(player, (key, value) => unref(value));
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
@ -53,7 +51,7 @@ const playerHandler: ProxyHandler<Record<PropertyKey, any>> = {
|
|||
}
|
||||
|
||||
const value = target[ProxyState][key];
|
||||
if (isPlainObject(value) && !(value instanceof Decimal)) {
|
||||
if (key !== "value" && isPlainObject(value) && !(value instanceof Decimal)) {
|
||||
if (value !== target[key]?.[ProxyState]) {
|
||||
const path = [...target[ProxyPath], key];
|
||||
target[key] = new Proxy({ [ProxyState]: value, [ProxyPath]: path }, playerHandler);
|
||||
|
@ -109,9 +107,12 @@ const playerHandler: ProxyHandler<Record<PropertyKey, any>> = {
|
|||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
has(target: Record<PropertyKey, any>, key: string) {
|
||||
return Reflect.has(target[ProxyState], key);
|
||||
},
|
||||
getOwnPropertyDescriptor(target, key) {
|
||||
return Object.getOwnPropertyDescriptor(target[ProxyState], key);
|
||||
}
|
||||
};
|
||||
export default window.player = new Proxy(
|
||||
{ [ProxyState]: state, [ProxyPath]: ["player"] },
|
||||
playerHandler
|
||||
) as PlayerData;
|
||||
) as Player;
|
||||
|
|
|
@ -2,7 +2,7 @@ import modInfo from "@/data/modInfo.json";
|
|||
import { Themes } from "@/data/themes";
|
||||
import { globalBus } from "@/game/events";
|
||||
import { hardReset } from "@/util/save";
|
||||
import { shallowReactive, watch } from "vue";
|
||||
import { reactive, watch } from "vue";
|
||||
|
||||
export interface Settings {
|
||||
active: string;
|
||||
|
@ -12,7 +12,7 @@ export interface Settings {
|
|||
unthrottled: boolean;
|
||||
}
|
||||
|
||||
const state = shallowReactive<Partial<Settings>>({
|
||||
const state = reactive<Partial<Settings>>({
|
||||
active: "",
|
||||
saves: [],
|
||||
showTPS: true,
|
||||
|
@ -21,7 +21,7 @@ const state = shallowReactive<Partial<Settings>>({
|
|||
});
|
||||
|
||||
watch(
|
||||
() => state,
|
||||
state,
|
||||
state =>
|
||||
localStorage.setItem(modInfo.id, btoa(unescape(encodeURIComponent(JSON.stringify(state))))),
|
||||
{ deep: true }
|
||||
|
|
|
@ -28,10 +28,16 @@ declare global {
|
|||
toPlaces: (x: DecimalSource, precision: number, maxAccepted: DecimalSource) => string;
|
||||
formatSmall: (x: DecimalSource, precision?: number) => string;
|
||||
invertOOM: (x: DecimalSource) => Decimal;
|
||||
modInfo: typeof modInfo;
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(async () => {
|
||||
console.log(
|
||||
"%cMade in TMT-X%c\nLearn more at www.moddingtree.com",
|
||||
"font-weight: bold; font-size: 24px; color: #A3BE8C; background: #2E3440; padding: 4px 8px; border-radius: 8px;",
|
||||
"padding: 4px;"
|
||||
);
|
||||
await load();
|
||||
const { globalBus, startGameLoop } = await require("./game/events");
|
||||
|
||||
|
@ -45,3 +51,5 @@ requestAnimationFrame(async () => {
|
|||
|
||||
startGameLoop();
|
||||
});
|
||||
|
||||
window.modInfo = modInfo;
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import { computed, Ref } from "vue";
|
||||
import { isFunction } from "./common";
|
||||
|
||||
export const DoNotCache = Symbol("DoNotCache");
|
||||
|
||||
export type Computable<T> = T | Ref<T> | (() => T);
|
||||
export type ProcessedComputable<T> = T | Ref<T>;
|
||||
export type GetComputableType<T> = T extends () => infer S
|
||||
export type GetComputableType<T> = T extends { [DoNotCache]: true }
|
||||
? T
|
||||
: T extends () => infer S
|
||||
? Ref<S>
|
||||
: undefined extends T
|
||||
? undefined
|
||||
|
@ -27,7 +31,8 @@ export function processComputable<T, S extends keyof ComputableKeysOf<T>>(
|
|||
key: S
|
||||
): asserts obj is T & { [K in S]: ProcessedComputable<UnwrapComputableType<T[S]>> } {
|
||||
const computable = obj[key];
|
||||
if (isFunction(computable) && computable.length === 0) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if (isFunction(computable) && computable.length === 0 && !(computable as any)[DoNotCache]) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
obj[key] = computed(computable.bind(obj));
|
||||
|
@ -35,8 +40,11 @@ export function processComputable<T, S extends keyof ComputableKeysOf<T>>(
|
|||
}
|
||||
|
||||
export function convertComputable<T>(obj: Computable<T>): ProcessedComputable<T> {
|
||||
if (isFunction(obj)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if (isFunction(obj) && !(obj as any)[DoNotCache]) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
obj = computed(obj);
|
||||
}
|
||||
return obj;
|
||||
return obj as ProcessedComputable<T>;
|
||||
}
|
||||
|
|
|
@ -1,15 +1,8 @@
|
|||
import { isRef } from "vue";
|
||||
import Decimal from "./bignum";
|
||||
import { isFunction, isPlainObject } from "./common";
|
||||
|
||||
export const ProxyState = Symbol("ProxyState");
|
||||
export const ProxyPath = Symbol("ProxyPath");
|
||||
|
||||
export type Proxied<T> = NonNullable<T> extends Record<PropertyKey, unknown>
|
||||
? {
|
||||
[K in keyof T]: Proxied<T[K]>;
|
||||
}
|
||||
: T;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type ProxiedWithState<T> = NonNullable<T> extends Record<PropertyKey, any>
|
||||
? NonNullable<T> extends Decimal
|
||||
|
@ -22,38 +15,46 @@ export type ProxiedWithState<T> = NonNullable<T> extends Record<PropertyKey, any
|
|||
}
|
||||
: T;
|
||||
|
||||
export function createProxy<T extends Record<string, unknown>>(object: T): T {
|
||||
if (object.isProxy) {
|
||||
console.warn(
|
||||
"Creating a proxy out of a proxy! This may cause unintentional function calls and stack overflows."
|
||||
);
|
||||
// Takes a function that returns an object and pretends to be that object
|
||||
// Note that the object is lazily calculated
|
||||
export function createLazyProxy<T extends object>(objectFunc: () => T): T {
|
||||
const obj: T | Record<string, never> = {};
|
||||
let calculated = false;
|
||||
function calculateObj(): T {
|
||||
if (!calculated) {
|
||||
Object.assign(obj, objectFunc());
|
||||
calculated = true;
|
||||
}
|
||||
return obj as T;
|
||||
}
|
||||
//return new Proxy(object, layerHandler) as T;
|
||||
return object;
|
||||
|
||||
return new Proxy(obj, {
|
||||
get(target, key) {
|
||||
if (key === ProxyState) {
|
||||
return calculateObj();
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return (calculateObj() as any)[key];
|
||||
},
|
||||
set() {
|
||||
console.error("Layers and features are shallow readonly");
|
||||
return false;
|
||||
},
|
||||
has(target, key) {
|
||||
if (key === ProxyState) {
|
||||
return true;
|
||||
}
|
||||
return Reflect.has(calculateObj(), key);
|
||||
},
|
||||
ownKeys() {
|
||||
return Reflect.ownKeys(calculateObj());
|
||||
},
|
||||
getOwnPropertyDescriptor(target, key) {
|
||||
if (!calculated) {
|
||||
Object.assign(obj, objectFunc());
|
||||
calculated = true;
|
||||
}
|
||||
return Object.getOwnPropertyDescriptor(target, key);
|
||||
}
|
||||
}) as T;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const layerHandler: ProxyHandler<Record<PropertyKey, any>> = {
|
||||
get(target, key, receiver: typeof Proxy) {
|
||||
if (key === "isProxy") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
target[key] == null ||
|
||||
isRef(target[key]) ||
|
||||
target[key].isProxy ||
|
||||
target[key] instanceof Decimal ||
|
||||
typeof key === "symbol" ||
|
||||
typeof target[key].render === "function"
|
||||
) {
|
||||
return target[key];
|
||||
} else if (isPlainObject(target[key]) || Array.isArray(target[key])) {
|
||||
target[key] = new Proxy(target[key], layerHandler);
|
||||
return target[key];
|
||||
} else if (isFunction(target[key])) {
|
||||
return target[key].bind(receiver);
|
||||
}
|
||||
return target[key];
|
||||
}
|
||||
};
|
||||
|
|
|
@ -2,6 +2,7 @@ import modInfo from "@/data/modInfo.json";
|
|||
import player, { Player, PlayerData, stringifySave } from "@/game/player";
|
||||
import settings, { loadSettings } from "@/game/settings";
|
||||
import Decimal from "./bignum";
|
||||
import { ProxyState } from "./proxies";
|
||||
|
||||
export function setupInitialStore(player: Partial<PlayerData> = {}): Player {
|
||||
return Object.assign(
|
||||
|
@ -24,7 +25,7 @@ export function setupInitialStore(player: Partial<PlayerData> = {}): Player {
|
|||
}
|
||||
|
||||
export function save(): string {
|
||||
const stringifiedSave = btoa(unescape(encodeURIComponent(stringifySave(player))));
|
||||
const stringifiedSave = btoa(unescape(encodeURIComponent(stringifySave(player[ProxyState]))));
|
||||
localStorage.setItem(player.id, stringifiedSave);
|
||||
return stringifiedSave;
|
||||
}
|
||||
|
@ -55,7 +56,6 @@ export async function load(): Promise<void> {
|
|||
export function newSave(): PlayerData {
|
||||
const id = getUniqueID();
|
||||
const player = setupInitialStore({ id });
|
||||
console.log(player);
|
||||
localStorage.setItem(id, btoa(unescape(encodeURIComponent(stringifySave(player)))));
|
||||
|
||||
settings.saves.push(id);
|
||||
|
|
124
src/util/vue.tsx
124
src/util/vue.tsx
|
@ -3,73 +3,70 @@ import Row from "@/components/system/Row.vue";
|
|||
import {
|
||||
CoercableComponent,
|
||||
Component as ComponentKey,
|
||||
GenericComponent
|
||||
GatherProps,
|
||||
GenericComponent,
|
||||
JSXFunction
|
||||
} from "@/features/feature";
|
||||
import { isArray } from "@vue/shared";
|
||||
import {
|
||||
Component,
|
||||
computed,
|
||||
ComputedRef,
|
||||
DefineComponent,
|
||||
defineComponent,
|
||||
h,
|
||||
isRef,
|
||||
PropType,
|
||||
ref,
|
||||
Ref,
|
||||
ShallowRef,
|
||||
shallowRef,
|
||||
unref,
|
||||
WritableComputedRef
|
||||
watchEffect
|
||||
} from "vue";
|
||||
import { ProcessedComputable } from "./computed";
|
||||
import { DoNotCache, ProcessedComputable } from "./computed";
|
||||
|
||||
export function coerceComponent(component: CoercableComponent, defaultWrapper = "span"): Component {
|
||||
export function coerceComponent(
|
||||
component: CoercableComponent,
|
||||
defaultWrapper = "span"
|
||||
): DefineComponent {
|
||||
if (typeof component === "function") {
|
||||
return defineComponent({ render: component });
|
||||
}
|
||||
if (typeof component === "string") {
|
||||
component = component.trim();
|
||||
if (component.charAt(0) !== "<") {
|
||||
component = `<${defaultWrapper}>${component}</${defaultWrapper}>`;
|
||||
}
|
||||
if (component.length > 0) {
|
||||
component = component.trim();
|
||||
if (component.charAt(0) !== "<") {
|
||||
component = `<${defaultWrapper}>${component}</${defaultWrapper}>`;
|
||||
}
|
||||
|
||||
return defineComponent({ template: component });
|
||||
return defineComponent({ template: component });
|
||||
}
|
||||
return defineComponent({ render: () => ({}) });
|
||||
}
|
||||
return component;
|
||||
}
|
||||
|
||||
export function render(object: { [ComponentKey]: GenericComponent }): DefineComponent {
|
||||
return defineComponent({
|
||||
render() {
|
||||
const component = object[ComponentKey];
|
||||
return h(component, object);
|
||||
export type VueFeature = {
|
||||
[ComponentKey]: GenericComponent;
|
||||
[GatherProps]: () => Record<string, unknown>;
|
||||
};
|
||||
|
||||
export function render(object: VueFeature | CoercableComponent): JSX.Element | DefineComponent {
|
||||
if (isCoercableComponent(object)) {
|
||||
if (typeof object === "function") {
|
||||
return (object as JSXFunction)();
|
||||
}
|
||||
});
|
||||
return coerceComponent(object);
|
||||
}
|
||||
const Component = object[ComponentKey];
|
||||
return <Component {...object[GatherProps]()} />;
|
||||
}
|
||||
|
||||
export function renderRow(
|
||||
objects: { [ComponentKey]: GenericComponent }[],
|
||||
props: Record<string, unknown> | null = null
|
||||
): DefineComponent {
|
||||
return defineComponent({
|
||||
render() {
|
||||
return h(
|
||||
Row as DefineComponent,
|
||||
props,
|
||||
objects.map(obj => h(obj[ComponentKey], obj))
|
||||
);
|
||||
}
|
||||
});
|
||||
export function renderRow(...objects: (VueFeature | CoercableComponent)[]): JSX.Element {
|
||||
return <Row>{objects.map(obj => render(obj))}</Row>;
|
||||
}
|
||||
|
||||
export function renderCol(
|
||||
objects: { [ComponentKey]: GenericComponent }[],
|
||||
props: Record<string, unknown> | null = null
|
||||
): DefineComponent {
|
||||
return defineComponent({
|
||||
render() {
|
||||
return h(
|
||||
Col as DefineComponent,
|
||||
props,
|
||||
objects.map(obj => h(obj[ComponentKey], obj))
|
||||
);
|
||||
}
|
||||
});
|
||||
export function renderCol(...objects: (VueFeature | CoercableComponent)[]): JSX.Element {
|
||||
return <Col>{objects.map(obj => render(obj))}</Col>;
|
||||
}
|
||||
|
||||
export function isCoercableComponent(component: unknown): component is CoercableComponent {
|
||||
|
@ -80,6 +77,9 @@ export function isCoercableComponent(component: unknown): component is Coercable
|
|||
return false;
|
||||
}
|
||||
return "render" in component || "component" in component;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} else if (typeof component === "function" && (component as any)[DoNotCache] === true) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -117,23 +117,25 @@ export function setupHoldToClick(
|
|||
}
|
||||
|
||||
export function computeComponent(
|
||||
component: Ref<ProcessedComputable<CoercableComponent>>
|
||||
): ComputedRef<Component> {
|
||||
return computed(() => {
|
||||
return coerceComponent(unref(unref<ProcessedComputable<CoercableComponent>>(component)));
|
||||
component: Ref<ProcessedComputable<CoercableComponent>>,
|
||||
defaultWrapper = "div"
|
||||
): ShallowRef<Component | JSXFunction | ""> {
|
||||
const comp = shallowRef<Component | JSXFunction | "">();
|
||||
watchEffect(() => {
|
||||
comp.value = coerceComponent(unwrapRef(component), defaultWrapper);
|
||||
});
|
||||
return comp as ShallowRef<Component | JSXFunction | "">;
|
||||
}
|
||||
export function computeOptionalComponent(
|
||||
component: Ref<ProcessedComputable<CoercableComponent | undefined> | undefined>
|
||||
): ComputedRef<Component | undefined> {
|
||||
return computed(() => {
|
||||
let currComponent = unref<ProcessedComputable<CoercableComponent | undefined> | undefined>(
|
||||
component
|
||||
);
|
||||
if (currComponent == null) return;
|
||||
currComponent = unref(currComponent);
|
||||
return currComponent == null ? undefined : coerceComponent(currComponent);
|
||||
component: Ref<ProcessedComputable<CoercableComponent | undefined> | undefined>,
|
||||
defaultWrapper = "div"
|
||||
): ShallowRef<Component | JSXFunction | "" | null> {
|
||||
const comp = shallowRef<Component | JSXFunction | "" | null>(null);
|
||||
watchEffect(() => {
|
||||
const currComponent = unwrapRef(component);
|
||||
comp.value = currComponent == null ? null : coerceComponent(currComponent, defaultWrapper);
|
||||
});
|
||||
return comp;
|
||||
}
|
||||
|
||||
export function wrapRef<T>(ref: Ref<ProcessedComputable<T>>): ComputedRef<T> {
|
||||
|
@ -141,7 +143,15 @@ export function wrapRef<T>(ref: Ref<ProcessedComputable<T>>): ComputedRef<T> {
|
|||
}
|
||||
|
||||
export function unwrapRef<T>(ref: Ref<ProcessedComputable<T>>): T {
|
||||
return unref(unref<ProcessedComputable<T>>(ref));
|
||||
return unref<T>(unref(ref));
|
||||
}
|
||||
|
||||
export function setRefValue<T>(ref: Ref<T | Ref<T>>, value: T) {
|
||||
if (isRef(ref.value)) {
|
||||
ref.value.value = value;
|
||||
} else {
|
||||
ref.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
type PropTypes =
|
||||
|
|
Loading…
Reference in a new issue