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