Added layer features
This commit is contained in:
parent
e6676d48fc
commit
adf7d7a67c
76 changed files with 4568 additions and 801 deletions
805
package-lock.json
generated
805
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -10,8 +10,12 @@
|
|||
"dependencies": {
|
||||
"core-js": "^3.6.5",
|
||||
"vue": "^2.6.11",
|
||||
"vue-fragment": "^1.5.2",
|
||||
"vue-frag": "^1.1.5",
|
||||
"vue-reactive-provide": "^0.3.0",
|
||||
"vue-select": "^3.11.2",
|
||||
"vue-textarea-autosize": "^1.1.1",
|
||||
"vue-transition-expand": "^0.1.0",
|
||||
"vue2-perfect-scrollbar": "^1.5.0",
|
||||
"vuex": "^3.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
56
src/App.vue
56
src/App.vue
|
@ -12,6 +12,8 @@ import Tabs from './components/system/Tabs';
|
|||
import TPS from './components/system/TPS';
|
||||
import themes from './data/themes';
|
||||
import { mapState } from 'vuex';
|
||||
import { player } from './store/proxies';
|
||||
import './main.css';
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
|
@ -21,7 +23,7 @@ export default {
|
|||
computed: {
|
||||
...mapState([ 'showTPS' ]),
|
||||
theme() {
|
||||
return themes[this.$store.state.theme || "default"];
|
||||
return themes[player.theme].variables;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -32,57 +34,7 @@ export default {
|
|||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
* {
|
||||
transition-duration: 0.5s;
|
||||
font-family: "Roboto Mono", monospace;
|
||||
font-weight: bold;
|
||||
margin: auto;
|
||||
text-size-adjust: none;
|
||||
}
|
||||
|
||||
*:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow: hidden;
|
||||
min-width: 640px;
|
||||
transition: none;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
html, body, #app {
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
h1, h2, h3, b, input {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
a,
|
||||
button,
|
||||
.link {
|
||||
display: block;
|
||||
color: var(--link);
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover,
|
||||
button:hover,
|
||||
.link:hover {
|
||||
text-shadow: 5px 0 10px var(--link),
|
||||
-3px 0 12px var(--link);
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
<style scoped>
|
||||
#app {
|
||||
background-color: var(--background);
|
||||
color: var(--color);
|
||||
|
|
20
src/components/common/notify.css
Normal file
20
src/components/common/notify.css
Normal file
|
@ -0,0 +1,20 @@
|
|||
.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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.subtabs > .resetNotify:not(.notify):not(.floating) {
|
||||
box-shadow: 0px 10px 8px -10px #ffffff;
|
||||
}
|
61
src/components/features/Achievement.vue
Normal file
61
src/components/features/Achievement.vue
Normal file
|
@ -0,0 +1,61 @@
|
|||
<template>
|
||||
<tooltip v-if="achievement.unlocked" :text="tooltip">
|
||||
<div :style="style"
|
||||
:class="{ [layer || tab.layer]: true, feature: true, achievement: true, locked: !achievement.earned,
|
||||
bought: achievement.earned }">
|
||||
<component v-if="display" :is="display" />
|
||||
<branch-node :branches="achievement.branches" :id="id" featureType="achievement" />
|
||||
</div>
|
||||
</tooltip>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import { coerceComponent } from '../../util/vue';
|
||||
import './features.css';
|
||||
|
||||
export default {
|
||||
name: 'achievement',
|
||||
inject: [ 'tab' ],
|
||||
props: {
|
||||
layer: String,
|
||||
id: [ Number, String ]
|
||||
},
|
||||
computed: {
|
||||
achievement() {
|
||||
return layers[this.layer || this.tab.layer].achievements[this.id];
|
||||
},
|
||||
style() {
|
||||
return [
|
||||
layers[this.layer || this.tab.layer].componentStyles?.achievement,
|
||||
this.achievement.style,
|
||||
this.achievement.image && this.achievement.earned ? {
|
||||
backgroundImage: `url(${this.achievement.image}`
|
||||
} : null
|
||||
];
|
||||
},
|
||||
display() {
|
||||
if (this.achievement.display) {
|
||||
return coerceComponent(this.achievement.display, 'h3');
|
||||
}
|
||||
return coerceComponent(this.achievement.name, 'h3');
|
||||
},
|
||||
tooltip() {
|
||||
if (this.achievement.earned) {
|
||||
return this.achievement.doneTooltip || this.achievement.tooltip || "You did it!";
|
||||
}
|
||||
return this.achievement.goalTooltip || this.achievement.tooltip || "LOCKED";
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.achievement {
|
||||
height: 90px;
|
||||
width: 90px;
|
||||
font-size: 10px;
|
||||
color: white;
|
||||
text-shadow: 0 0 2px #000000;
|
||||
}
|
||||
</style>
|
37
src/components/features/Achievements.vue
Normal file
37
src/components/features/Achievements.vue
Normal file
|
@ -0,0 +1,37 @@
|
|||
<template>
|
||||
<div v-if="filteredAchievements" class="table">
|
||||
<div v-frag v-if="filteredAchievements.rows && filteredAchievements.cols">
|
||||
<div v-for="row in filteredAchievements.rows" class="row" :key="row">
|
||||
<div v-for="col in filteredAchievements.cols" :key="col">
|
||||
<achievement v-if="filteredAchievements[row * 10 + col] !== undefined" class="align" :id="row * 10 + col" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-frag v-else>
|
||||
<achievement v-for="(achievement, id) in filteredAchievements" :key="id" :id="id" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import { getFiltered } from '../../util/vue';
|
||||
import './table.css';
|
||||
|
||||
export default {
|
||||
name: 'achievements',
|
||||
inject: [ 'tab' ],
|
||||
props: {
|
||||
layer: String,
|
||||
achievements: Array
|
||||
},
|
||||
computed: {
|
||||
filteredAchievements() {
|
||||
return getFiltered(layers[this.layer || this.tab.layer].achievements, this.achievements);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
130
src/components/features/Bar.vue
Normal file
130
src/components/features/Bar.vue
Normal file
|
@ -0,0 +1,130 @@
|
|||
<template>
|
||||
<div v-if="bar.unlocked" :style="style" class="bar">
|
||||
<div class="overlayTextContainer border" :style="borderStyle">
|
||||
<component class="overlayText" :style="textStyle" :is="display" />
|
||||
</div>
|
||||
<div class="border" :style="backgroundStyle">
|
||||
<div class="fill" :style="fillStyle" />
|
||||
</div>
|
||||
<branch-node :branches="bar.branches" :id="id" featureType="bar" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import { UP, DOWN, LEFT, RIGHT, DEFAULT, coerceComponent } from '../../util/vue';
|
||||
import Decimal from '../../util/bignum';
|
||||
|
||||
export default {
|
||||
name: 'bar',
|
||||
inject: [ 'tab' ],
|
||||
props: {
|
||||
layer: String,
|
||||
id: [ Number, String ]
|
||||
},
|
||||
computed: {
|
||||
bar() {
|
||||
return layers[this.layer || this.tab.layer].bars[this.id];
|
||||
},
|
||||
progress() {
|
||||
let progress = this.bar.progress instanceof Decimal ? this.bar.progress.toNumber() : this.bar.progress;
|
||||
return (1 - Math.min(Math.max(progress, 0), 1)) * 100;
|
||||
},
|
||||
style() {
|
||||
return [
|
||||
{ 'width': this.bar.width + "px", 'height': this.bar.height + "px" },
|
||||
layers[this.layer || this.tab.layer].componentStyles?.bar,
|
||||
this.bar.style
|
||||
];
|
||||
},
|
||||
borderStyle() {
|
||||
return [
|
||||
{ 'width': this.bar.width + "px", 'height': this.bar.height + "px" },
|
||||
this.bar.borderStyle
|
||||
];
|
||||
},
|
||||
textStyle() {
|
||||
return [
|
||||
this.bar.style,
|
||||
this.bar.textStyle
|
||||
];
|
||||
},
|
||||
backgroundStyle() {
|
||||
return [
|
||||
{ 'width': this.bar.width + "px", 'height': this.bar.height + "px" },
|
||||
this.bar.style,
|
||||
this.bar.baseStyle,
|
||||
this.bar.borderStyle
|
||||
];
|
||||
},
|
||||
fillStyle() {
|
||||
const fillStyle = { 'width': (this.bar.width + 0.5) + "px", 'height': (this.bar.height + 0.5) + "px" };
|
||||
switch (this.bar.direction) {
|
||||
case UP:
|
||||
fillStyle['clip-path'] = `inset(${this.progress}% 0% 0% 0%)`;
|
||||
fillStyle.width = this.bar.width + 1 + 'px';
|
||||
break;
|
||||
case DOWN:
|
||||
fillStyle['clip-path'] = `inset(0% 0% ${this.progress}% 0%)`;
|
||||
fillStyle.width = this.bar.width + 1 + 'px';
|
||||
break;
|
||||
case RIGHT:
|
||||
fillStyle['clip-path'] = `inset(0% ${this.progress}% 0% 0%)`;
|
||||
break;
|
||||
case LEFT:
|
||||
fillStyle['clip-path'] = `inset(0% 0% 0% ${this.progress} + '%)`;
|
||||
break;
|
||||
case DEFAULT:
|
||||
fillStyle['clip-path'] = 'inset(0% 50% 0% 0%)';
|
||||
break;
|
||||
}
|
||||
return [
|
||||
fillStyle,
|
||||
this.bar.style,
|
||||
this.bar.fillStyle
|
||||
];
|
||||
},
|
||||
display() {
|
||||
return coerceComponent(this.bar.display);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.bar {
|
||||
position: relative;
|
||||
display: table;
|
||||
}
|
||||
|
||||
.overlayTextContainer {
|
||||
position: absolute;
|
||||
border-radius: 10px;
|
||||
vertical-align: middle;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.overlayText {
|
||||
z-index: 6;
|
||||
}
|
||||
|
||||
.border {
|
||||
border: 2px solid;
|
||||
border-radius: 10px;
|
||||
border-color: var(--color);
|
||||
overflow: hidden;
|
||||
-webkit-mask-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAA5JREFUeNpiYGBgAAgwAAAEAAGbA+oJAAAAAElFTkSuQmCC);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.fill {
|
||||
position: absolute;
|
||||
background-color: var(--color);
|
||||
overflow: hidden;
|
||||
margin-left: -0.5px;
|
||||
transition-duration: 0.2s;
|
||||
z-index: 2;
|
||||
}
|
||||
</style>
|
101
src/components/features/Buyable.vue
Normal file
101
src/components/features/Buyable.vue
Normal file
|
@ -0,0 +1,101 @@
|
|||
<template>
|
||||
<div v-if="buyable.unlocked" style="display: grid">
|
||||
<button :style="style" @click="buyable.buy" @mousedown="start" @mouseleave="stop" @mouseup="stop" @touchstart="start"
|
||||
:class="{ feature: true, [layer || tab.layer]: true, buyable: true, can: buyable.canBuy, locked: !buyable.canAfford, bought }"
|
||||
@touchend="stop" @touchcancel="stop">
|
||||
<div v-if="title">
|
||||
<component :is="title" />
|
||||
</div>
|
||||
<component :is="display" style="white-space: pre-line;" />
|
||||
<mark-node :mark="buyable.mark" />
|
||||
<branch-node :branches="buyable.branches" :id="id" featureType="buyable" />
|
||||
</button>
|
||||
<div v-if="(buyable.sellOne !== undefined && buyable.canSellOne !== false) ||
|
||||
(buyable.sellAll !== undefined && buyable.canSellAll !== false)" style="width: 100%">
|
||||
<button @click="buyable.sellAll" v-if="buyable.sellAll !== undefined && buyable.canSellAll !== false"
|
||||
:class="{ 'buyable-button': true, can: buyable.unlocked, locked: !buyable.unlocked, feature: true }"
|
||||
:style="{ 'background-color': buyable.canSellAll ? layerColor : '' }">
|
||||
Sell All
|
||||
</button>
|
||||
<button @click="buyable.sellOne" v-if="buyable.sellOne !== undefined && buyable.canSellOne !== false"
|
||||
:class="{ 'buyable-button': true, can: buyable.unlocked, locked: !buyable.unlocked, feature: true }"
|
||||
:style="{ 'background-color': buyable.canSellOne ? layerColor : '' }">
|
||||
Sell All
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import { player } from '../../store/proxies';
|
||||
import { coerceComponent } from '../../util/vue';
|
||||
import './features.css';
|
||||
|
||||
export default {
|
||||
name: 'buyable',
|
||||
inject: [ 'tab' ],
|
||||
props: {
|
||||
layer: String,
|
||||
id: [ Number, String ],
|
||||
size: [ Number, String ]
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
interval: false,
|
||||
time: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
buyable() {
|
||||
return layers[this.layer || this.tab.layer].buyables[this.id];
|
||||
},
|
||||
bought() {
|
||||
return player[this.layer || this.tab.layer].buyables[this.id].gte(this.buyable.purchaseLimit);
|
||||
},
|
||||
style() {
|
||||
return [
|
||||
this.buyable.canBuy ? { 'background-color': layers[this.layer || this.tab.layer].color } : {},
|
||||
this.size ? { 'height': this.size, 'width': this.size } : {},
|
||||
layers[this.layer || this.tab.layer].componentStyles?.buyable,
|
||||
this.buyable.style
|
||||
];
|
||||
},
|
||||
title() {
|
||||
if (this.buyable.title) {
|
||||
return coerceComponent(this.buyable.title, 'h2');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
display() {
|
||||
return coerceComponent(this.buyable.display, 'div');
|
||||
},
|
||||
layerColor() {
|
||||
return layers[this.layer || this.tab.layer].color;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
start() {
|
||||
if (!this.interval) {
|
||||
this.interval = setInterval(this.buyable.buy, 250);
|
||||
}
|
||||
},
|
||||
stop() {
|
||||
clearInterval(this.interval);
|
||||
this.interval = false;
|
||||
this.time = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.buyable {
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.buyable-button {
|
||||
width: calc(100% - 10px);
|
||||
}
|
||||
</style>
|
73
src/components/features/Buyables.vue
Normal file
73
src/components/features/Buyables.vue
Normal file
|
@ -0,0 +1,73 @@
|
|||
<template>
|
||||
<div v-if="filteredBuyables" class="table">
|
||||
<respec-button v-if="showRespec" style="margin-bottom: 12px;" :confirmRespec="confirmRespec"
|
||||
@set-confirm-respec="setConfirmRespec" @respec="respec" />
|
||||
<div v-frag v-if="filteredBuyables.rows && filteredBuyables.cols">
|
||||
<div v-for="row in filteredBuyables.rows" class="row" :key="row">
|
||||
<div v-for="col in filteredBuyables.cols" :key="col">
|
||||
<buyable v-if="filteredBuyables[row * 10 + col] !== undefined" class="align buyable-container" :style="{ height }"
|
||||
:id="row * 10 + col" :size="height === 'inherit' ? null : height" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<frament v-else>
|
||||
<buyable v-for="(buyable, id) in filteredBuyables" :key="id" class="align buyable-container" :style="{ height }"
|
||||
:id="id" :size="height === 'inherit' ? null : height" />
|
||||
</frament>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import { player } from '../../store/proxies';
|
||||
import { getFiltered } from '../../util/vue';
|
||||
import './table.css';
|
||||
|
||||
export default {
|
||||
name: 'buyables',
|
||||
inject: [ 'tab' ],
|
||||
props: {
|
||||
layer: String,
|
||||
buyables: Array,
|
||||
height: {
|
||||
type: [ Number, String ],
|
||||
default: "inherit"
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredBuyables() {
|
||||
return getFiltered(layers[this.layer || this.tab.layer].buyables, this.buyables);
|
||||
},
|
||||
showRespec() {
|
||||
return layers[this.layer || this.tab.layer].buyables.showRespec;
|
||||
},
|
||||
confirmRespec() {
|
||||
return player[this.layer || this.tab.layer].buyables.confirmRespec;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setConfirmRespec(value) {
|
||||
if (this.confirmRespec != undefined) {
|
||||
this.$emit("set-confirm-respec", value);
|
||||
} else {
|
||||
player[this.layer || this.tab.layer].buyables.confirmRespec = value;
|
||||
}
|
||||
},
|
||||
respec() {
|
||||
// If they're controlling confirmRespec, assume they're also controlling what's being respecced
|
||||
if (this.confirmRespec != undefined) {
|
||||
this.$emit("respec");
|
||||
} else {
|
||||
layers[this.layer || this.tab.layer].buyables.respec();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.buyable-container {
|
||||
margin-left: 7px;
|
||||
margin-right: 7px;
|
||||
}
|
||||
</style>
|
87
src/components/features/Challenge.vue
Normal file
87
src/components/features/Challenge.vue
Normal file
|
@ -0,0 +1,87 @@
|
|||
<template>
|
||||
<div v-if="shown" :style="style" :class="{ challenge: true, [challengeClass]: true }">
|
||||
<div v-if="title"><component :is="title" /></div>
|
||||
<button :class="{ [layer || tab.layer]: true, longUpg: true, can: true }" :style="{ backgroundColor: buttonColor }"
|
||||
@click="toggle">
|
||||
{{ buttonText }}
|
||||
</button>
|
||||
<component v-if="fullDisplay" :is="fullDisplay" />
|
||||
<default-challenge-display v-else :id="id" />
|
||||
<mark-node :mark="challenge.mark" />
|
||||
<branch-node :branches="challenge.branches" :id="id" featureType="challenge" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import { player } from '../../store/proxies';
|
||||
import { coerceComponent } from '../../util/vue';
|
||||
import './features.css';
|
||||
|
||||
export default {
|
||||
name: 'challenge',
|
||||
inject: [ 'tab' ],
|
||||
props: {
|
||||
layer: String,
|
||||
id: [ Number, String ]
|
||||
},
|
||||
computed: {
|
||||
challenge() {
|
||||
return layers[this.layer || this.tab.layer].challenges[this.id];
|
||||
},
|
||||
shown() {
|
||||
return this.challenge.unlocked && !(player.hideChallenges && this.challenge.maxes);
|
||||
},
|
||||
challengeClass() {
|
||||
if (this.challenge.canComplete) {
|
||||
return "canComplete";
|
||||
}
|
||||
if (this.challenge.completed) {
|
||||
return "done";
|
||||
}
|
||||
return "locked";
|
||||
},
|
||||
style() {
|
||||
return [
|
||||
layers[this.layer || this.tab.layer].componentStyles?.challenge,
|
||||
this.challenge.style
|
||||
];
|
||||
},
|
||||
title() {
|
||||
if (this.challenge.title) {
|
||||
return coerceComponent(this.challenge.titleDisplay, 'h3');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
buttonColor() {
|
||||
return layers[this.layer || this.tab.layer].color;
|
||||
},
|
||||
buttonText() {
|
||||
if (this.challenge.active) {
|
||||
return this.challenge.canComplete ? "Finish" : "Exit Early";
|
||||
}
|
||||
if (this.challenge.maxed) {
|
||||
return "Completed";
|
||||
}
|
||||
return "Start";
|
||||
},
|
||||
fullDisplay() {
|
||||
if (this.challenge.fullDisplay) {
|
||||
return coerceComponent(this.challenge.fullDisplay, 'div');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggle() {
|
||||
this.challenge.toggle();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.challenge {
|
||||
margin: var(--feature-margin);
|
||||
}
|
||||
</style>
|
37
src/components/features/Challenges.vue
Normal file
37
src/components/features/Challenges.vue
Normal file
|
@ -0,0 +1,37 @@
|
|||
<template>
|
||||
<div v-if="filteredChallenges" class="table">
|
||||
<div v-frag v-if="filteredChallenges.rows && filteredChallenges.cols">
|
||||
<div v-for="row in filteredChallenges.rows" class="row" :key="row">
|
||||
<div v-for="col in filteredChallenges.cols" :key="col">
|
||||
<challenge v-if="filteredChallenges[row * 10 + col] !== undefined" :id="row * 10 + col" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-frag v-else>
|
||||
<challenge v-for="(challenge, id) in filteredChallenges" :key="id" :id="id" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import { getFiltered } from '../../util/vue';
|
||||
import './table.css';
|
||||
|
||||
export default {
|
||||
name: 'challenges',
|
||||
inject: [ 'tab' ],
|
||||
props: {
|
||||
layer: String,
|
||||
challenges: Array
|
||||
},
|
||||
computed: {
|
||||
filteredChallenges() {
|
||||
return getFiltered(layers[this.layer || this.tab.layer].challenges, this.challenges);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
74
src/components/features/Clickable.vue
Normal file
74
src/components/features/Clickable.vue
Normal file
|
@ -0,0 +1,74 @@
|
|||
<template>
|
||||
<div v-if="clickable.unlocked">
|
||||
<button :class="{ feature: true, [layer || tab.layer]: true, can: clickable.canClick, locked: !clickable.canClick }" :style="style"
|
||||
@click="clickable.click" @mousedown="start" @mouseleave="stop" @mouseup="stop" @touchstart="start"
|
||||
@touchend="stop" @touchcancel="stop">
|
||||
<div v-if="titleDisplay">
|
||||
<component :is="titleDisplay" />
|
||||
</div>
|
||||
<component :is="descriptionDisplay" style="white-space: pre-line;" />
|
||||
<mark-node :mark="clickable.mark" />
|
||||
<branch-node :branches="clickable.branches" :id="id" featureType="clickable" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import { coerceComponent } from '../../util/vue';
|
||||
import './features.css';
|
||||
|
||||
export default {
|
||||
name: 'clickable',
|
||||
inject: [ 'tab' ],
|
||||
props: {
|
||||
id: [ Number, String ],
|
||||
size: {
|
||||
type: [ Number, String ]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
interval: false,
|
||||
time: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
clickable() {
|
||||
return layers[this.layer || this.tab.layer].clickables[this.id];
|
||||
},
|
||||
style() {
|
||||
return [
|
||||
this.clickable.canClick ? { 'background-color': layers[this.layer || this.tab.layer].color } : {},
|
||||
this.size ? {'height': this.size, 'width': this.size} : {},
|
||||
layers[this.layer || this.tab.layer].componentStyles?.clickable,
|
||||
this.clickable.style
|
||||
];
|
||||
},
|
||||
titleDisplay() {
|
||||
if (this.clickable.titleDisplay) {
|
||||
return coerceComponent(this.clickable.titleDisplay, 'h2');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
descriptionDisplay() {
|
||||
return coerceComponent(this.clickable.descriptionDisplay, 'div');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
start() {
|
||||
if (!this.interval) {
|
||||
this.interval = setInterval(this.clickable.click, 250);
|
||||
}
|
||||
},
|
||||
stop() {
|
||||
clearInterval(this.interval);
|
||||
this.interval = false;
|
||||
this.time = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
63
src/components/features/Clickables.vue
Normal file
63
src/components/features/Clickables.vue
Normal file
|
@ -0,0 +1,63 @@
|
|||
<template>
|
||||
<div v-if="filteredClickables" class="table">
|
||||
<master-button v-if="showMasterButton" style="margin-bottom: 12px;" @press="press" />
|
||||
<div v-frag v-if="filteredClickables.rows && filteredClickables.cols">
|
||||
<div v-for="row in filteredClickables.rows" class="row" :key="row">
|
||||
<div v-for="col in filteredClickables.cols" :key="col">
|
||||
<clickable v-if="filteredClickables[row * 10 + col] !== undefined" class="align clickable-container"
|
||||
:style="{ height }" :id="row * 10 + col" :size="height === 'inherit' ? null : height" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-frag v-else>
|
||||
<clickable v-for="(clickable, id) in filteredClickables" :key="id" class="align clickable-container" :style="{ height }"
|
||||
:id="id" :size="height === 'inherit' ? null : height" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import { getFiltered } from '../../util/vue';
|
||||
import './table.css';
|
||||
|
||||
export default {
|
||||
name: 'clickables',
|
||||
inject: [ 'tab' ],
|
||||
props: {
|
||||
layer: String,
|
||||
clickables: Array,
|
||||
showMaster: Boolean,
|
||||
height: {
|
||||
type: [ Number, String ],
|
||||
default: "inherit"
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredClickables() {
|
||||
return getFiltered(layers[this.layer || this.tab.layer].clickables, this.clickables);
|
||||
},
|
||||
showMasterButton() {
|
||||
if (layers[this.layer || this.tab.layer].clickables.masterButtonPress == undefined) {
|
||||
return false;
|
||||
}
|
||||
if (this.showMaster != undefined) {
|
||||
return this.showMaster;
|
||||
}
|
||||
return layers[this.layer || this.tab.layer].clickables.showMaster;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
respec() {
|
||||
layers[this.layer || this.tab.layer].clickables.masterButtonPress();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.clickable-container {
|
||||
margin-left: 7px;
|
||||
margin-right: 7px;
|
||||
}
|
||||
</style>
|
51
src/components/features/DefaultChallengeDisplay.vue
Normal file
51
src/components/features/DefaultChallengeDisplay.vue
Normal file
|
@ -0,0 +1,51 @@
|
|||
<template>
|
||||
<div v-frag>
|
||||
<component :is="challengeDescription" />
|
||||
<div>Goal: <component :is="goalDescription" /></div>
|
||||
<div>Reward: <component :is="rewardDescription" /></div>
|
||||
<component v-if="rewardDisplay" :is="rewardDisplay" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import { coerceComponent } from '../../util/vue';
|
||||
|
||||
export default {
|
||||
name: 'default-challenge-display',
|
||||
inject: [ 'tab' ],
|
||||
props: {
|
||||
layer: String,
|
||||
id: [ Number, String ]
|
||||
},
|
||||
computed: {
|
||||
challenge() {
|
||||
return layers[this.layer || this.tab.layer].challenges[this.id];
|
||||
},
|
||||
challengeDescription() {
|
||||
return coerceComponent(this.challenge.challengeDescription, 'div');
|
||||
},
|
||||
goalDescription() {
|
||||
if (this.challenge.goalDescription) {
|
||||
return coerceComponent(this.challenge.goalDescription);
|
||||
}
|
||||
return coerceComponent(`format(${this.goal}) ${this.currencyDisplayName || 'points'}`);
|
||||
},
|
||||
rewardDescription() {
|
||||
return coerceComponent(this.challenge.goalDescription);
|
||||
},
|
||||
rewardDisplay() {
|
||||
if (this.challenge.rewardDisplay) {
|
||||
return coerceComponent(this.challenge.rewardDisplay);
|
||||
}
|
||||
if (this.challenge.rewardEffect) {
|
||||
return coerceComponent(`Currently: ${this.challenge.rewardEffect}`);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
71
src/components/features/DefaultPrestigeButtonDisplay.vue
Normal file
71
src/components/features/DefaultPrestigeButtonDisplay.vue
Normal file
|
@ -0,0 +1,71 @@
|
|||
<template>
|
||||
<span>
|
||||
{{ resetDescription }}<b>{{ resetGain }}</b>
|
||||
{{ resource }}
|
||||
<br/><br/>
|
||||
{{ nextAt }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import { player } from '../../store/proxies';
|
||||
import { format, formatWhole } from '../../util/bignum';
|
||||
|
||||
export default {
|
||||
name: 'default-prestige-button-display',
|
||||
inject: [ 'tab' ],
|
||||
props: {
|
||||
layer: String
|
||||
},
|
||||
computed: {
|
||||
resetDescription() {
|
||||
if (player[this.layer || this.tab.layer].points.lt(1e3) || layers[this.layer || this.tab.layer].type === "static") {
|
||||
return layers[this.layer || this.tab.layer].resetDescription || "Reset for ";
|
||||
}
|
||||
return "";
|
||||
},
|
||||
resetGain() {
|
||||
return formatWhole(layers[this.layer || this.tab.layer].resetGain);
|
||||
},
|
||||
resource() {
|
||||
return layers[this.layer || this.tab.layer].resource;
|
||||
},
|
||||
showNextAt() {
|
||||
if (layers[this.layer || this.tab.layer].showNextAt != undefined) {
|
||||
return layers[this.layer || this.tab.layer].showNextAt;
|
||||
} else {
|
||||
return layers[this.layer || this.tab.layer].type === "static" ?
|
||||
player[this.layer || this.tab.layer].points.lt(30) : // static
|
||||
player[this.layer || this.tab.layer].points.lt(1e3) && layers[this.layer ||
|
||||
this.tab.layer].resetGain.lt(100); // normal
|
||||
}
|
||||
},
|
||||
nextAt() {
|
||||
if (this.showNextAt) {
|
||||
let prefix;
|
||||
if (layers[this.layer || this.tab.layer].type === "static") {
|
||||
if (layers[this.layer || this.tab.layer].baseAmount.gte(layers[this.layer || this.tab.layer].nextAt) &&
|
||||
layers[this.layer || this.tab.layer].canBuyMax !== false) {
|
||||
prefix = "Next:";
|
||||
} else {
|
||||
prefix = "Req:";
|
||||
}
|
||||
|
||||
const baseAmount = formatWhole(layers[this.layer || this.tab.layer].baseAmount);
|
||||
const nextAt = (layers[this.layer || this.tab.layer].roundUpCost ? formatWhole : format)(layers[this.layer || this.tab.layer].nextAtMax);
|
||||
const baseResource = layers[this.layer || this.tab.layer].baseResource;
|
||||
|
||||
return `${prefix} ${baseAmount} / ${nextAt} ${baseResource}`;
|
||||
} else {
|
||||
return `Next at ${formatWhole(layers[this.layer || this.tab.layer].baseAmount)} ${layers[this.layer || this.tab.layer].baseResource}`;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
53
src/components/features/DefaultUpgradeDisplay.vue
Normal file
53
src/components/features/DefaultUpgradeDisplay.vue
Normal file
|
@ -0,0 +1,53 @@
|
|||
<template>
|
||||
<span>
|
||||
<div v-if="title"><component :is="title" /></div>
|
||||
<component :is="description" />
|
||||
<div v-if="effectDisplay"><br>Currently: <component :is="effectDisplay" /></div>
|
||||
<br>
|
||||
Cost: {{ cost }} {{ costResource }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import { coerceComponent } from '../../util/vue';
|
||||
import { formatWhole } from '../../util/bignum';
|
||||
|
||||
export default {
|
||||
name: 'default-upgrade-display',
|
||||
inject: [ 'tab' ],
|
||||
props: {
|
||||
layer: String,
|
||||
id: [ Number, String ]
|
||||
},
|
||||
computed: {
|
||||
upgrade() {
|
||||
return layers[this.layer || this.tab.layer].upgrades[this.id];
|
||||
},
|
||||
title() {
|
||||
if (this.upgrade.title) {
|
||||
return coerceComponent(this.upgrade.title, 'h2');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
description() {
|
||||
return coerceComponent(this.upgrade.description, 'div');
|
||||
},
|
||||
effectDisplay() {
|
||||
if (this.upgrade.effectDisplay) {
|
||||
return coerceComponent(this.upgrade.effectDisplay);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
cost() {
|
||||
return formatWhole(this.upgrade.cost);
|
||||
},
|
||||
costResource() {
|
||||
return this.upgrade.currencyDisplayName || layers[this.layer || this.tab.layer].resource;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
31
src/components/features/Grid.vue
Normal file
31
src/components/features/Grid.vue
Normal file
|
@ -0,0 +1,31 @@
|
|||
<template>
|
||||
<div v-if="grid" class="table">
|
||||
<div v-for="row in grid.rows" class="row" :key="row">
|
||||
<div v-for="col in grid.cols" :key="col">
|
||||
<gridable class="align" :id="id" :cell="row * 100 + col" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import './table.css';
|
||||
|
||||
export default {
|
||||
name: 'grid',
|
||||
inject: [ 'tab' ],
|
||||
props: {
|
||||
layer: String,
|
||||
id: [ Number, String ]
|
||||
},
|
||||
computed: {
|
||||
grid() {
|
||||
return layers[this.layer || this.tab.layer].grids[this.id];
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
75
src/components/features/Gridable.vue
Normal file
75
src/components/features/Gridable.vue
Normal file
|
@ -0,0 +1,75 @@
|
|||
<template>
|
||||
<button v-if="gridable.unlocked" :class="{ feature: true, tile: true, can: gridable.canClick, locked: !gridable.canClick}"
|
||||
:style="style" @click="gridable.click" @mousedown="start" @mouseleave="stop" @mouseup="stop" @touchstart="start"
|
||||
@touchend="stop" @touchcancel="stop">
|
||||
<div v-if="title"><component :is="title" /></div>
|
||||
<component :is="display" style="white-space: pre-line;" />
|
||||
<branch-node :branches="gridable.branches" :id="id" featureType="gridable" />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import { coerceComponent } from '../../util/vue';
|
||||
import './features.css';
|
||||
|
||||
export default {
|
||||
name: 'gridable',
|
||||
inject: [ 'tab' ],
|
||||
props: {
|
||||
layer: String,
|
||||
id: [ Number, String ],
|
||||
cell: [ Number, String ],
|
||||
size: {
|
||||
type: [ Number, String ]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
interval: false,
|
||||
time: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
gridable() {
|
||||
return layers[this.layer || this.tab.layer].grids[this.id][this.cell];
|
||||
},
|
||||
style() {
|
||||
return [
|
||||
this.gridable.canClick ? { 'background-color': layers[this.layer || this.tab.layer].color } : {},
|
||||
layers[this.layer || this.tab.layer].componentStyles?.gridable,
|
||||
this.gridable.style
|
||||
];
|
||||
},
|
||||
title() {
|
||||
if (this.gridable.title) {
|
||||
return coerceComponent(this.gridable.title, 'h3');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
display() {
|
||||
return coerceComponent(this.gridable.display, 'div');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
start() {
|
||||
if (!this.interval) {
|
||||
this.interval = setInterval(this.gridable.click, 250);
|
||||
}
|
||||
},
|
||||
stop() {
|
||||
clearInterval(this.interval);
|
||||
this.interval = false;
|
||||
this.time = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tile {
|
||||
min-height: 80px;
|
||||
width: 80px;
|
||||
font-size: 10px;
|
||||
}
|
||||
</style>
|
169
src/components/features/Infobox.vue
Normal file
169
src/components/features/Infobox.vue
Normal file
|
@ -0,0 +1,169 @@
|
|||
<template>
|
||||
<div class="infobox" v-if="infobox.unlocked" :style="style" :class="{ collapsed, stacked }">
|
||||
<button class="title" :style="titleStyle" @click="toggle">
|
||||
<span class="toggle">▼</span>
|
||||
<component :is="title" />
|
||||
</button>
|
||||
<transition-expand>
|
||||
<div v-if="!collapsed" class="body" :style="{ backgroundColor: this.borderColor }">
|
||||
<component :is="body" :style="bodyStyle" />
|
||||
</div>
|
||||
</transition-expand>
|
||||
<branch-node :branches="infobox.branches" :id="id" featureType="infobox" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import { player } from '../../store/proxies';
|
||||
import { coerceComponent } from '../../util/vue';
|
||||
import themes from '../../data/themes';
|
||||
|
||||
export default {
|
||||
name: 'infobox',
|
||||
inject: [ 'tab' ],
|
||||
props: {
|
||||
layer: String,
|
||||
id: [ Number, String ]
|
||||
},
|
||||
computed: {
|
||||
infobox() {
|
||||
return layers[this.layer || this.tab.layer].infoboxes[this.id];
|
||||
},
|
||||
borderColor() {
|
||||
return this.infobox.borderColor || layers[this.layer || this.tab.layer].color;
|
||||
},
|
||||
style() {
|
||||
return [
|
||||
{ borderColor: this.borderColor },
|
||||
layers[this.layer || this.tab.layer].componentStyles?.infobox,
|
||||
this.infobox.style
|
||||
];
|
||||
},
|
||||
titleStyle() {
|
||||
return [
|
||||
{ backgroundColor: layers[this.layer || this.tab.layer].color },
|
||||
layers[this.layer || this.tab.layer].componentStyles?.['infobox-title'],
|
||||
this.infobox.titleStyle
|
||||
];
|
||||
},
|
||||
bodyStyle() {
|
||||
return [
|
||||
layers[this.layer || this.tab.layer].componentStyles?.['infobox-body'],
|
||||
this.infobox.bodyStyle
|
||||
];
|
||||
},
|
||||
title() {
|
||||
if (this.infobox.title) {
|
||||
return coerceComponent(this.infobox.title);
|
||||
}
|
||||
return coerceComponent(layers[this.layer || this.tab.layer].name);
|
||||
},
|
||||
body() {
|
||||
return coerceComponent(this.infobox.body);
|
||||
},
|
||||
collapsed() {
|
||||
return player[this.layer || this.tab.layer].infoboxes[this.id];
|
||||
},
|
||||
stacked() {
|
||||
return themes[player.theme].stackedInfoboxes;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggle() {
|
||||
player[this.layer || this.tab.layer].infoboxes[this.id] = !player[this.layer || this.tab.layer].infoboxes[this.id];
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.infobox {
|
||||
position: relative;
|
||||
width: 600px;
|
||||
max-width: 95%;
|
||||
margin-top: 0;
|
||||
text-align: left;
|
||||
border-style: solid;
|
||||
border-width: 0px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.infobox.stacked {
|
||||
border-width: 4px;
|
||||
}
|
||||
|
||||
.infobox:not(.stacked) + .infobox:not(.stacked) {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.infobox + :not(.infobox) {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
color: black;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
padding: 4px;
|
||||
width: auto;
|
||||
text-align: left;
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
.infobox:not(.stacked) .title {
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
}
|
||||
|
||||
.infobox.stacked + .infobox.stacked {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
margin-top: -5px;
|
||||
}
|
||||
|
||||
.stacked .title {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.collapsed:not(.stacked) .title::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 4px;
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.toggle {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.collapsed .toggle {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.body {
|
||||
transition-duration: .5s;
|
||||
border-radius: 5px;
|
||||
border-top-left-radius: 0;
|
||||
}
|
||||
|
||||
.infobox:not(.stacked) .body {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.body > * {
|
||||
padding: 8px;
|
||||
width: 100%;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
border-radius: 5px;
|
||||
border-top-left-radius: 0;
|
||||
background-color: var(--background);
|
||||
}
|
||||
</style>
|
|
@ -16,30 +16,31 @@ import { coerceComponent } from '../../util/vue';
|
|||
|
||||
export default {
|
||||
name: 'main-display',
|
||||
inject: [ 'layer' ],
|
||||
inject: [ 'tab' ],
|
||||
props: {
|
||||
layer: String,
|
||||
precision: Number
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
return layers[this.layer].componentStyles?.['main-display'];
|
||||
return layers[this.layer || this.tab.layer].componentStyles?.['main-display'];
|
||||
},
|
||||
resource() {
|
||||
return layers[this.layer].resource;
|
||||
return layers[this.layer || this.tab.layer].resource;
|
||||
},
|
||||
effectDescription() {
|
||||
return coerceComponent(layers[this.layer].effectDescription);
|
||||
return coerceComponent(layers[this.layer || this.tab.layer].effectDescription);
|
||||
},
|
||||
showPrefix() {
|
||||
return player[this.layer].points.lt('1e1000');
|
||||
return player[this.layer || this.tab.layer].points.lt('1e1000');
|
||||
},
|
||||
color() {
|
||||
return layers[this.layer].color;
|
||||
return layers[this.layer || this.tab.layer].color;
|
||||
},
|
||||
amount() {
|
||||
return this.precision == undefined ?
|
||||
formatWhole(player[this.layer].points) :
|
||||
format(player[this.layer].points, this.precision);
|
||||
formatWhole(player[this.layer || this.tab.layer].points) :
|
||||
format(player[this.layer || this.tab.layer].points, this.precision);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
66
src/components/features/MarkNode.vue
Normal file
66
src/components/features/MarkNode.vue
Normal file
|
@ -0,0 +1,66 @@
|
|||
<template>
|
||||
<div v-if="mark">
|
||||
<div v-if="mark === true" class="mark star"></div>
|
||||
<img v-else class="mark" :src="mark" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'mark-node',
|
||||
props: {
|
||||
mark: [ Boolean, String ]
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mark {
|
||||
position: absolute;
|
||||
left: -25px;
|
||||
top: -10px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
pointer-events: none;
|
||||
margin-left: 0.9em;
|
||||
margin-right: 0.9em;
|
||||
margin-bottom: 1.2em;
|
||||
border-right: 0.3em solid transparent;
|
||||
border-bottom: 0.7em solid transparent;
|
||||
border-left: 0.3em solid transparent;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.star {
|
||||
left: -10px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
z-index: 10000;
|
||||
margin-left: 0.9em;
|
||||
margin-right: 0.9em;
|
||||
margin-bottom: 1.2em;
|
||||
border-right: 0.3em solid transparent;
|
||||
border-bottom: 0.7em solid #ffcc00;
|
||||
border-left: 0.3em solid transparent;
|
||||
font-size: 10px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.star::before,
|
||||
.star::after {
|
||||
content: "";
|
||||
width: 0;
|
||||
height: 0;
|
||||
position: absolute;
|
||||
top: .6em;
|
||||
left: -1em;
|
||||
border-right: 1em solid transparent;
|
||||
border-bottom: 0.7em solid #ffcc00;
|
||||
border-left: 1em solid transparent;
|
||||
transform: rotate(-35deg);
|
||||
}
|
||||
|
||||
.star::after {
|
||||
transform: rotate(35deg);
|
||||
}
|
||||
</style>
|
44
src/components/features/MasterButton.vue
Normal file
44
src/components/features/MasterButton.vue
Normal file
|
@ -0,0 +1,44 @@
|
|||
<template>
|
||||
<button @click="press" :class="{ longUpg: true, can: unlocked, locked: !unlocked }" :style="style">
|
||||
<component :is="masterButtonDisplay" />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import { player } from '../../store/proxies';
|
||||
import { coerceComponent } from '../../util/vue';
|
||||
|
||||
export default {
|
||||
name: 'master-button',
|
||||
inject: [ 'tab' ],
|
||||
props: {
|
||||
layer: String,
|
||||
display: [ String, Object ]
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
return [
|
||||
layers[this.layer || this.tab.layer].componentStyles?.['master-button']
|
||||
];
|
||||
},
|
||||
unlocked() {
|
||||
return player[this.layer || this.tab.layer].unlocked;
|
||||
},
|
||||
masterButtonDisplay() {
|
||||
if (this.display) {
|
||||
return coerceComponent(this.display);
|
||||
}
|
||||
return coerceComponent("Click Me!");
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
press() {
|
||||
this.$emit("press");
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
66
src/components/features/Milestone.vue
Normal file
66
src/components/features/Milestone.vue
Normal file
|
@ -0,0 +1,66 @@
|
|||
<template>
|
||||
<div v-if="milestone.shown" :style="style" :class="{ feature: true, milestone: !milestone.earned, milestoneDone: milestone.earned }">
|
||||
<div v-if="requirementDisplay"><component :is="requirementDisplay" /></div>
|
||||
<div v-if="effectDisplay"><component :is="effectDisplay" /></div>
|
||||
<component v-if="optionsDisplay" :is="optionsDisplay" />
|
||||
<branch-node :branches="milestone.branches" :id="id" featureType="milestone" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import { coerceComponent } from '../../util/vue';
|
||||
import './features.css';
|
||||
|
||||
export default {
|
||||
name: 'milestone',
|
||||
inject: [ 'tab' ],
|
||||
props: {
|
||||
layer: String,
|
||||
id: [ Number, String ]
|
||||
},
|
||||
computed: {
|
||||
milestone() {
|
||||
return layers[this.layer || this.tab.layer].milestones[this.id];
|
||||
},
|
||||
style() {
|
||||
return [
|
||||
layers[this.layer || this.tab.layer].componentStyles?.milestone,
|
||||
this.milestone.style
|
||||
];
|
||||
},
|
||||
requirementDisplay() {
|
||||
if (this.milestone.requirementDisplay) {
|
||||
return coerceComponent(this.milestone.requirementDisplay, 'h3');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
effectDisplay() {
|
||||
if (this.milestone.effectDisplay) {
|
||||
return coerceComponent(this.milestone.effectDisplay, 'b');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
optionsDisplay() {
|
||||
if (this.milestone.optionsDisplay) {
|
||||
return coerceComponent(this.milestone.optionsDisplay, 'div');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.milestone {
|
||||
width: calc(100% - 10px);
|
||||
min-width: 120px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
min-height: 75px;
|
||||
background-color: var(--locked);
|
||||
border-width: 4px;
|
||||
border-radius: 5px;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
</style>
|
28
src/components/features/Milestones.vue
Normal file
28
src/components/features/Milestones.vue
Normal file
|
@ -0,0 +1,28 @@
|
|||
<template>
|
||||
<div v-if="filteredMilestones" class="table">
|
||||
<milestone v-for="(milestone, id) in filteredMilestones" :key="id" :id="id" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import { getFiltered } from '../../util/vue';
|
||||
import './table.css';
|
||||
|
||||
export default {
|
||||
name: 'milestones',
|
||||
inject: [ 'tab' ],
|
||||
props: {
|
||||
layer: String,
|
||||
milestones: Array
|
||||
},
|
||||
computed: {
|
||||
filteredMilestones() {
|
||||
return getFiltered(layers[this.layer || this.tab.layer].milestones, this.milestones);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
56
src/components/features/PrestigeButton.vue
Normal file
56
src/components/features/PrestigeButton.vue
Normal file
|
@ -0,0 +1,56 @@
|
|||
<template>
|
||||
<button :style="style" @click="resetLayer"
|
||||
:class="{ [layer || tab.layer]: true, reset: true, locked: !canReset, can: canReset }" >
|
||||
<component v-if="prestigeButtonDisplay" :is="prestigeButtonDisplay" />
|
||||
<default-prestige-button-display v-else />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import { resetLayer } from '../../util/layers';
|
||||
import { coerceComponent } from '../../util/vue';
|
||||
|
||||
export default {
|
||||
name: 'prestige-button',
|
||||
inject: [ 'tab' ],
|
||||
props: {
|
||||
layer: String
|
||||
},
|
||||
computed: {
|
||||
canReset() {
|
||||
return layers[this.layer || this.tab.layer].canReset;
|
||||
},
|
||||
color() {
|
||||
return layers[this.layer || this.tab.layer].color;
|
||||
},
|
||||
prestigeButtonDisplay() {
|
||||
if (layers[this.layer || this.tab.layer].prestigeButtonDisplay) {
|
||||
return coerceComponent(layers[this.layer || this.tab.layer].prestigeButtonDisplay);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
style() {
|
||||
return [
|
||||
this.canReset ? { 'background-color': this.color } : {},
|
||||
layers[this.layer || this.tab.layer].componentStyles?.['prestige-button']
|
||||
];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetLayer() {
|
||||
resetLayer(this.layer || this.tab.layer);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.reset {
|
||||
min-height: 100px;
|
||||
width: 180px;
|
||||
border-radius: var(--border-radius);
|
||||
border: 4px solid rgba(0, 0, 0, 0.125);
|
||||
margin: 10px;
|
||||
}
|
||||
</style>
|
55
src/components/features/ResourceDisplay.vue
Normal file
55
src/components/features/ResourceDisplay.vue
Normal file
|
@ -0,0 +1,55 @@
|
|||
<template>
|
||||
<div class="resource-display" :class="{ empty }">
|
||||
<div v-if="baseAmount != undefined">You have {{ baseAmount }} {{ baseResource }}</div>
|
||||
<div v-if="passiveGeneration != undefined">You are gaining {{ passiveGeneration }} {{ resource }} per second</div>
|
||||
<spacer v-if="(baseAmount != undefined || passiveGeneration != undefined) && (best != undefined || total != undefined)" />
|
||||
<div v-if="best != undefined">Your best {{ resource }} is {{ best }}</div>
|
||||
<div v-if="total != undefined">You have made a total of {{ total }} {{ resource }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import { player } from '../../store/proxies';
|
||||
import Decimal, { formatWhole } from '../../util/bignum';
|
||||
|
||||
export default {
|
||||
name: 'resource-display',
|
||||
inject: [ 'tab' ],
|
||||
props: {
|
||||
layer: String
|
||||
},
|
||||
computed: {
|
||||
baseAmount() {
|
||||
return layers[this.layer || this.tab.layer].baseAmount ? formatWhole(layers[this.layer || this.tab.layer].baseAmount) : null;
|
||||
},
|
||||
baseResource() {
|
||||
return layers[this.layer || this.tab.layer].baseResource;
|
||||
},
|
||||
passiveGeneration() {
|
||||
return layers[this.layer || this.tab.layer].passiveGeneration ?
|
||||
formatWhole(Decimal.times(layers[this.layer || this.tab.layer].resetGain,
|
||||
layers[this.layer || this.tab.layer].passiveGeneration)) :
|
||||
null;
|
||||
},
|
||||
resource() {
|
||||
return layers[this.layer || this.tab.layer].resource;
|
||||
},
|
||||
best() {
|
||||
return player[this.layer || this.tab.layer].best ? formatWhole(player[this.layer || this.tab.layer].best) : null;
|
||||
},
|
||||
total() {
|
||||
return player[this.layer || this.tab.layer].total ? formatWhole(player[this.layer || this.tab.layer].total) : null;
|
||||
},
|
||||
empty() {
|
||||
return !(this.baseAmount || this.passiveGeneration || this.best || this.total);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.resource-display:not(.empty) {
|
||||
margin: 10px;
|
||||
}
|
||||
</style>
|
111
src/components/features/RespecButton.vue
Normal file
111
src/components/features/RespecButton.vue
Normal file
|
@ -0,0 +1,111 @@
|
|||
<template>
|
||||
<div style="display: flex">
|
||||
<tooltip text="Disable respec confirmation">
|
||||
<Toggle :value="confirmRespec" @change="setConfirmRespec" />
|
||||
</tooltip>
|
||||
<button @click="respec" :class="{ feature: true, respec: true, can: unlocked, locked: !unlocked }"
|
||||
style="margin-right: 18px" :style="style">
|
||||
<component :is="respecButtonDisplay" />
|
||||
</button>
|
||||
<Modal :show="confirming" @close="cancel">
|
||||
<div slot="header">
|
||||
<h2>Confirm Respec</h2>
|
||||
</div>
|
||||
<slot name="respec-warning" slot="body">
|
||||
<div>Are you sure you want to respec? This will force you to do a {{ name }} respec as well!</div>
|
||||
</slot>
|
||||
<div slot="footer">
|
||||
<div class="modal-footer">
|
||||
<div class="modal-flex-grow"></div>
|
||||
<button class="button modal-button danger" @click="cancel">
|
||||
Yes!
|
||||
</button>
|
||||
<button class="button modal-button" @click="cancel">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import { player } from '../../store/proxies';
|
||||
import { coerceComponent } from '../../util/vue';
|
||||
import './features.css';
|
||||
|
||||
export default {
|
||||
name: 'respec-button',
|
||||
inject: [ 'tab' ],
|
||||
data() {
|
||||
return {
|
||||
confirming: false
|
||||
};
|
||||
},
|
||||
props: {
|
||||
layer: String,
|
||||
confirmRespec: Boolean,
|
||||
display: [ String, Object ]
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
return [
|
||||
layers[this.layer || this.tab.layer].componentStyles?.['respec-button']
|
||||
];
|
||||
},
|
||||
unlocked() {
|
||||
return player[this.layer || this.tab.layer].unlocked;
|
||||
},
|
||||
respecButtonDisplay() {
|
||||
if (this.display) {
|
||||
return coerceComponent(this.display);
|
||||
}
|
||||
return coerceComponent("Respec");
|
||||
},
|
||||
name() {
|
||||
return layers[this.layer || this.tab.layer].name || this.layer || this.tab.layer;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setConfirmRespec(value) {
|
||||
this.$emit("set-confirm-respec", value);
|
||||
},
|
||||
respec() {
|
||||
if (this.confirmRespec) {
|
||||
this.confirming = true;
|
||||
} else {
|
||||
this.$emit("respec");
|
||||
}
|
||||
},
|
||||
confirm() {
|
||||
this.$emit("respec");
|
||||
this.confirming = false;
|
||||
},
|
||||
cancel() {
|
||||
this.confirming = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.respec {
|
||||
height: 40px;
|
||||
width: 80px;
|
||||
background: var(--points);
|
||||
border: 2px solid rgba(0, 0, 0, 0.125);
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.modal-flex-grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.modal-button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
61
src/components/features/Upgrade.vue
Normal file
61
src/components/features/Upgrade.vue
Normal file
|
@ -0,0 +1,61 @@
|
|||
<template>
|
||||
<button v-if="upgrade.unlocked" :style="style" @click="buy"
|
||||
:class="{
|
||||
feature: true,
|
||||
[tab.layer]: true,
|
||||
upgrade: true,
|
||||
can: upgrade.canAfford && !upgrade.bought,
|
||||
locked: !upgrade.canAfford && !upgrade.bought,
|
||||
bought: upgrade.bought
|
||||
}">
|
||||
<component v-if="fullDisplay" :is="fullDisplay" />
|
||||
<default-upgrade-display v-else :id="id" />
|
||||
<branch-node :branches="upgrade.branches" :id="id" featureType="upgrade" />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import { coerceComponent } from '../../util/vue';
|
||||
import './features.css';
|
||||
|
||||
export default {
|
||||
name: 'upgrade',
|
||||
inject: [ 'tab' ],
|
||||
props: {
|
||||
layer: String,
|
||||
id: [ Number, String ]
|
||||
},
|
||||
computed: {
|
||||
upgrade() {
|
||||
return layers[this.layer || this.tab.layer].upgrades[this.id];
|
||||
},
|
||||
style() {
|
||||
return [
|
||||
this.upgrade.canAfford && !this.upgrade.bought ? { 'background-color': layers[this.layer || this.tab.layer].color } : {},
|
||||
layers[this.layer || this.tab.layer].componentStyles?.upgrade,
|
||||
this.upgrade.style
|
||||
];
|
||||
},
|
||||
fullDisplay() {
|
||||
if (this.upgrade.fullDisplay) {
|
||||
return coerceComponent(this.upgrade.fullDisplay, 'div');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
buy() {
|
||||
this.upgrade.buy();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.upgrade {
|
||||
min-height: 120px;
|
||||
width: 120px;
|
||||
font-size: 10px;
|
||||
}
|
||||
</style>
|
37
src/components/features/Upgrades.vue
Normal file
37
src/components/features/Upgrades.vue
Normal file
|
@ -0,0 +1,37 @@
|
|||
<template>
|
||||
<div v-if="filteredUpgrades" class="table">
|
||||
<div v-frag v-if="filteredUpgrades.rows && filteredUpgrades.cols">
|
||||
<div v-for="row in filteredUpgrades.rows" class="row" :key="row">
|
||||
<div v-for="col in filteredUpgrades.cols" :key="col">
|
||||
<upgrade v-if="filteredUpgrades[row * 10 + col] !== undefined" class="align" :id="row * 10 + col" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-frag v-else>
|
||||
<upgrade v-for="(upgrade, id) in filteredUpgrades" :key="id" class="align" :id="id" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import { getFiltered } from '../../util/vue';
|
||||
import './table.css';
|
||||
|
||||
export default {
|
||||
name: 'upgrades',
|
||||
inject: [ 'tab' ],
|
||||
props: {
|
||||
layer: String,
|
||||
upgrades: Array
|
||||
},
|
||||
computed: {
|
||||
filteredUpgrades() {
|
||||
return getFiltered(layers[this.layer || this.tab.layer].upgrades, this.upgrades);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
32
src/components/features/features.css
Normal file
32
src/components/features/features.css
Normal file
|
@ -0,0 +1,32 @@
|
|||
.feature,
|
||||
.feature button {
|
||||
position: relative;
|
||||
padding-top: 5px;
|
||||
border-radius: var(--border-radius);
|
||||
border: 2px solid rgba(0, 0, 0, 0.125);
|
||||
margin: var(--feature-margin);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.can,
|
||||
.can button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.can:hover,
|
||||
.can:hover button {
|
||||
transform: scale(1.15, 1.15);
|
||||
box-shadow: 0 0 20px var(--points);
|
||||
}
|
||||
|
||||
.locked,
|
||||
.locked button {
|
||||
background-color: var(--locked);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.bought,
|
||||
.bought button {
|
||||
background-color: var(--bought);
|
||||
cursor: default;
|
||||
}
|
30
src/components/features/table.css
Normal file
30
src/components/features/table.css
Normal file
|
@ -0,0 +1,30 @@
|
|||
.table {
|
||||
display: flex;
|
||||
flex-flow: column wrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.table + .table {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
max-width: 100%;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.col {
|
||||
display: flex;
|
||||
flex-flow: column wrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
margin: 10px 0;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="field">
|
||||
<span class="field-title">{{ title }}</span>
|
||||
<span class="field-title" v-if="title">{{ title }}</span>
|
||||
<v-select :options="options" :value="value" @input="setSelected" />
|
||||
</div>
|
||||
</template>
|
||||
|
@ -15,8 +15,8 @@ export default {
|
|||
props: {
|
||||
title: String,
|
||||
options: Array, // https://vue-select.org/guide/options.html#options-prop
|
||||
value: String,
|
||||
default: String
|
||||
value: [ String, Object ],
|
||||
default: [ String, Object ]
|
||||
},
|
||||
components: {
|
||||
vSelect
|
||||
|
|
23
src/components/fields/Slider.vue
Normal file
23
src/components/fields/Slider.vue
Normal file
|
@ -0,0 +1,23 @@
|
|||
<template>
|
||||
<div class="field">
|
||||
<span class="field-title" v-if="title">{{ title }}</span>
|
||||
<input type="range" :value="value" @input="e => $emit('change', e.target.value)" :min="min" :max="max" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import './fields.css';
|
||||
|
||||
export default {
|
||||
name: 'TextField',
|
||||
props: {
|
||||
title: String,
|
||||
value: Number,
|
||||
min: Number,
|
||||
max: Number
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
24
src/components/fields/Text.vue
Normal file
24
src/components/fields/Text.vue
Normal file
|
@ -0,0 +1,24 @@
|
|||
<template>
|
||||
<div class="field">
|
||||
<span class="field-title" v-if="title">{{ title }}</span>
|
||||
<textarea-autosize v-if="textarea" :placeholder="placeholder" :value="value" @input="value => $emit('change', value)" />
|
||||
<input v-else type="text" :value="value" @input="e => $emit('change', e.target.value)" :placeholder="placeholder" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import './fields.css';
|
||||
|
||||
export default {
|
||||
name: 'TextField',
|
||||
props: {
|
||||
title: String,
|
||||
value: [ String, Object ],
|
||||
textarea: Boolean,
|
||||
placeholder: String
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
|
@ -17,7 +17,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
handleInput(e) {
|
||||
this.$emit('change', e.target.value);
|
||||
this.$emit('change', e.target.checked);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.field {
|
||||
display: flex;
|
||||
position: relative;
|
||||
height: 2em;
|
||||
min-height: 2em;
|
||||
margin: 10px 0;
|
||||
user-select: none;
|
||||
justify-content: space-between;
|
||||
|
|
|
@ -3,40 +3,130 @@
|
|||
|
||||
import Vue from 'vue';
|
||||
|
||||
/* features */
|
||||
import Achievement from './features/Achievement';
|
||||
import Achievements from './features/Achievements';
|
||||
import Bar from './features/Bar';
|
||||
import Buyable from './features/Buyable';
|
||||
import Buyables from './features/Buyables';
|
||||
import Challenge from './features/Challenge';
|
||||
import Challenges from './features/Challenges';
|
||||
import Clickable from './features/Clickable';
|
||||
import Clickables from './features/Clickables';
|
||||
import DefaultChallengeDisplay from './features/DefaultChallengeDisplay';
|
||||
import DefaultPrestigeButtonDisplay from './features/DefaultPrestigeButtonDisplay';
|
||||
import DefaultUpgradeDisplay from './features/DefaultUpgradeDisplay';
|
||||
import Grid from './features/Grid';
|
||||
import Gridable from './features/Gridable';
|
||||
import Infobox from './features/Infobox';
|
||||
import MainDisplay from './features/MainDisplay';
|
||||
import MarkNode from './features/MarkNode';
|
||||
import MasterButton from './features/MasterButton';
|
||||
import Milestone from './features/Milestone';
|
||||
import Milestones from './features/Milestones';
|
||||
import PrestigeButton from './features/PrestigeButton';
|
||||
import ResourceDisplay from './features/ResourceDisplay';
|
||||
import RespecButton from './features/RespecButton';
|
||||
import Upgrade from './features/Upgrade';
|
||||
import Upgrades from './features/Upgrades';
|
||||
/* fields */
|
||||
import Select from './fields/Select';
|
||||
import Slider from './fields/Slider';
|
||||
import Text from './fields/Text';
|
||||
import Toggle from './fields/Toggle';
|
||||
/* system */
|
||||
import Column from './system/Column';
|
||||
import DefaultLayerTab from './system/DefaultLayerTab';
|
||||
import Info from './system/Info';
|
||||
import LayerProvider from './system/LayerProvider';
|
||||
import LayerTab from './system/LayerTab';
|
||||
import Microtab from './system/Microtab';
|
||||
import Modal from './system/Modal';
|
||||
import Nav from './system/Nav';
|
||||
import Options from './system/Options';
|
||||
import Resource from './system/Resource';
|
||||
import Row from './system/Row';
|
||||
import Spacer from './system/Spacer';
|
||||
import Sticky from './system/Sticky';
|
||||
import Subtab from './system/Subtab';
|
||||
import TabButton from './system/TabButton';
|
||||
import Tabs from './system/Tabs';
|
||||
import Tooltip from './system/Tooltip';
|
||||
import TPS from './system/TPS';
|
||||
/* fields */
|
||||
import Select from './fields/Select';
|
||||
import Toggle from './fields/Toggle';
|
||||
/* features */
|
||||
import MainDisplay from './features/MainDisplay';
|
||||
import VerticalRule from './system/VerticalRule';
|
||||
/* tree */
|
||||
import Branches from './tree/Branches';
|
||||
import BranchLine from './tree/BranchLine';
|
||||
import BranchNode from './tree/BranchNode';
|
||||
import Tree from './tree/Tree';
|
||||
import TreeNode from './tree/TreeNode';
|
||||
/* misc */
|
||||
import { Fragment } from 'vue-fragment';
|
||||
import frag from 'vue-frag';
|
||||
import TransitionExpand from 'vue-transition-expand';
|
||||
import 'vue-transition-expand/dist/vue-transition-expand.css';
|
||||
import PerfectScrollbar from 'vue2-perfect-scrollbar';
|
||||
import 'vue2-perfect-scrollbar/dist/vue2-perfect-scrollbar.css';
|
||||
import VueTextareaAutosize from 'vue-textarea-autosize';
|
||||
|
||||
/* features */
|
||||
Vue.component(Achievement.name, Achievement);
|
||||
Vue.component(Achievements.name, Achievements);
|
||||
Vue.component(Bar.name, Bar);
|
||||
Vue.component(Buyable.name, Buyable);
|
||||
Vue.component(Buyables.name, Buyables);
|
||||
Vue.component(Challenge.name, Challenge);
|
||||
Vue.component(Challenges.name, Challenges);
|
||||
Vue.component(Clickable.name, Clickable);
|
||||
Vue.component(Clickables.name, Clickables);
|
||||
Vue.component(DefaultChallengeDisplay.name, DefaultChallengeDisplay);
|
||||
Vue.component(DefaultPrestigeButtonDisplay.name, DefaultPrestigeButtonDisplay);
|
||||
Vue.component(DefaultUpgradeDisplay.name, DefaultUpgradeDisplay);
|
||||
Vue.component(Grid.name, Grid);
|
||||
Vue.component(Gridable.name, Gridable);
|
||||
Vue.component(Infobox.name, Infobox);
|
||||
Vue.component(MainDisplay.name, MainDisplay);
|
||||
Vue.component(MarkNode.name, MarkNode);
|
||||
Vue.component(MasterButton.name, MasterButton);
|
||||
Vue.component(Milestone.name, Milestone);
|
||||
Vue.component(Milestones.name, Milestones);
|
||||
Vue.component(PrestigeButton.name, PrestigeButton);
|
||||
Vue.component(ResourceDisplay.name, ResourceDisplay);
|
||||
Vue.component(RespecButton.name, RespecButton);
|
||||
Vue.component(Upgrade.name, Upgrade);
|
||||
Vue.component(Upgrades.name, Upgrades);
|
||||
/* fields */
|
||||
Vue.component(Select.name, Select);
|
||||
Vue.component(Slider.name, Slider);
|
||||
Vue.component(Text.name, Text);
|
||||
Vue.component(Toggle.name, Toggle);
|
||||
/* system */
|
||||
Vue.component(Column.name, Column);
|
||||
Vue.component(DefaultLayerTab.name, DefaultLayerTab);
|
||||
Vue.component(Info.name, Info);
|
||||
Vue.component(LayerProvider.name, LayerProvider);
|
||||
Vue.component(LayerTab.name, LayerTab);
|
||||
Vue.component(Microtab.name, Microtab);
|
||||
Vue.component(Modal.name, Modal);
|
||||
Vue.component(Nav.name, Nav);
|
||||
Vue.component(Options.name, Options);
|
||||
Vue.component(Resource.name, Resource);
|
||||
Vue.component(Row.name, Row);
|
||||
Vue.component(Spacer.name, Spacer);
|
||||
Vue.component(Sticky.name, Sticky);
|
||||
Vue.component(Subtab.name, Subtab);
|
||||
Vue.component(TabButton.name, TabButton);
|
||||
Vue.component(Tabs.name, Tabs);
|
||||
Vue.component(Tooltip.name, Tooltip);
|
||||
Vue.component(TPS.name, TPS);
|
||||
/* fields */
|
||||
Vue.component(Select.name, Select);
|
||||
Vue.component(Toggle.name, Toggle);
|
||||
/* features */
|
||||
Vue.component(MainDisplay.name, MainDisplay);
|
||||
Vue.component(VerticalRule.name, VerticalRule);
|
||||
/* tree */
|
||||
Vue.component(Branches.name, Branches);
|
||||
Vue.component(BranchLine.name, BranchLine);
|
||||
Vue.component(BranchNode.name, BranchNode);
|
||||
Vue.component(Tree.name, Tree);
|
||||
Vue.component(TreeNode.name, TreeNode);
|
||||
/* misc */
|
||||
Vue.component(Fragment.name, Fragment);
|
||||
Vue.directive('frag', frag);
|
||||
Vue.use(TransitionExpand);
|
||||
Vue.use(PerfectScrollbar);
|
||||
Vue.use(VueTextareaAutosize);
|
||||
|
|
15
src/components/system/Column.vue
Normal file
15
src/components/system/Column.vue
Normal file
|
@ -0,0 +1,15 @@
|
|||
<template>
|
||||
<div class="table">
|
||||
<div class="col">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import '../features/table.css';
|
||||
|
||||
export default {
|
||||
name: 'column'
|
||||
};
|
||||
</script>
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<fragment>
|
||||
<div v-frag>
|
||||
<infobox v-if="infobox != undefined" :id="infobox" />
|
||||
<main-display />
|
||||
<prestige-button v-if="type !== 'none'" />
|
||||
<sticky v-if="showPrestigeButton"><prestige-button /></sticky>
|
||||
<resource-display />
|
||||
<milestones />
|
||||
<component v-if="midsection" :is="midsection" />
|
||||
|
@ -11,25 +11,32 @@
|
|||
<upgrades />
|
||||
<challenges />
|
||||
<achievements />
|
||||
</fragment>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import { coerceComponent } from '../../util/vue';
|
||||
|
||||
export default {
|
||||
name: 'default-layer-tab',
|
||||
inject: [ 'layer' ],
|
||||
inject: [ 'tab' ],
|
||||
props: {
|
||||
layer: String
|
||||
},
|
||||
computed: {
|
||||
infobox() {
|
||||
return layers[this.layer].infoboxes && Object.keys(layers[this.layer].infoboxes)[0];
|
||||
},
|
||||
type() {
|
||||
return layers[this.layer].type;
|
||||
return layers[this.layer || this.tab.layer].infoboxes && Object.keys(layers[this.layer || this.tab.layer].infoboxes)[0];
|
||||
},
|
||||
midsection() {
|
||||
return layers[this.layer].midsection;
|
||||
}
|
||||
if (layers[this.layer || this.tab.layer].midsection) {
|
||||
return coerceComponent(layers[this.layer || this.tab.layer].midsection);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
showPrestigeButton() {
|
||||
return layers[this.layer || this.tab.layer].type !== "none";
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
<br>
|
||||
<div>Time Played: {{ timePlayed }}</div>
|
||||
<div v-if="hotkeys.length > 0">
|
||||
<br>
|
||||
<h4>Hotkeys</h4>
|
||||
<div v-for="key in hotkeys" :key="key.key">
|
||||
{{ key.key }}: {{ key.description }}
|
||||
|
@ -56,6 +57,7 @@
|
|||
<script>
|
||||
import modInfo from '../../data/modInfo';
|
||||
import { formatTime } from '../../util/bignum';
|
||||
import { hotkeys } from '../../store/layers';
|
||||
|
||||
export default {
|
||||
name: 'Info',
|
||||
|
@ -72,7 +74,7 @@ export default {
|
|||
},
|
||||
hotkeys() {
|
||||
// TODO check layer is unlocked and hotkey is unlocked
|
||||
return this.$root.hotkeys.filter(hotkey => hotkey || true);
|
||||
return hotkeys.filter(hotkey => hotkey || true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,19 +1,23 @@
|
|||
<template>
|
||||
<fragment>
|
||||
<div v-frag>
|
||||
<slot />
|
||||
</fragment>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ReactiveProvideMixin } from 'vue-reactive-provide';
|
||||
|
||||
export default {
|
||||
name: 'LayerProvider',
|
||||
mixins: [
|
||||
ReactiveProvideMixin({
|
||||
name: 'tab',
|
||||
props: true
|
||||
})
|
||||
],
|
||||
props: {
|
||||
layer: String,
|
||||
index: Number
|
||||
},
|
||||
provide() {
|
||||
const { layer, index } = this;
|
||||
return { layer, index };
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
<template>
|
||||
<LayerProvider :layer="layer" :index="index">
|
||||
<div :style="styles">
|
||||
<div v-if="subtabs && subtabs.length">
|
||||
<button v-for="subtab in subtabs" @click="selectSubtab(subtab)" :key="subtab">{{ subtab }}</button>
|
||||
</div>
|
||||
<component :is="customComponent" v-if="customComponent" />
|
||||
<default-layer-tab v-else />
|
||||
<div class="layer-tab" :style="style" :class="{ hasSubtabs: subtabs }">
|
||||
<branches>
|
||||
<sticky v-if="subtabs" class="subtabs" :class="{ floating, firstTab }">
|
||||
<tab-button v-for="(subtab, id) in subtabs" @selectTab="selectSubtab(id)" :key="id" :activeTab="id === activeSubtab"
|
||||
:options="subtab" :text="id" />
|
||||
</sticky>
|
||||
<component v-if="display" :is="display" />
|
||||
<default-layer-tab v-else />
|
||||
</branches>
|
||||
</div>
|
||||
</LayerProvider>
|
||||
</template>
|
||||
|
@ -13,46 +16,126 @@
|
|||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import { player } from '../../store/proxies';
|
||||
import { coerceComponent } from '../../util/vue';
|
||||
import { isPlainObject } from '../../util/common';
|
||||
import themes from '../../data/themes';
|
||||
|
||||
export default {
|
||||
name: 'layer-tab',
|
||||
props: {
|
||||
layer: String,
|
||||
index: Number
|
||||
index: Number,
|
||||
forceFirstTab: Boolean
|
||||
},
|
||||
computed: {
|
||||
styles() {
|
||||
const styles = [];
|
||||
floating() {
|
||||
return themes[player.theme].floatingTabs;
|
||||
},
|
||||
style() {
|
||||
const style = [];
|
||||
if (layers[this.layer].style) {
|
||||
styles.push(layers[this.layer].style);
|
||||
style.push(layers[this.layer].style);
|
||||
}
|
||||
if (layers[this.layer].activeSubtab?.style) {
|
||||
styles.push(layers[this.layer].activeSubtab.style);
|
||||
style.push(layers[this.layer].activeSubtab.style);
|
||||
}
|
||||
return styles;
|
||||
return style;
|
||||
},
|
||||
customComponent() {
|
||||
return this.activeSubtab != undefined ? this.activeSubtab.component : layers[this.layer].component;
|
||||
display() {
|
||||
if (layers[this.layer].activeSubtab?.display) {
|
||||
return coerceComponent(layers[this.layer].activeSubtab.display);
|
||||
}
|
||||
if (layers[this.layer].display) {
|
||||
return coerceComponent(layers[this.layer].display);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
subtabs() {
|
||||
if (layers[this.layer].subtabs) {
|
||||
return Object.entries(layers[this.layer].subtabs)
|
||||
.filter(subtab => subtab[1].unlocked)
|
||||
.map(subtab => subtab[0]);
|
||||
.filter(subtab => isPlainObject(subtab[1]) && subtab[1].unlocked !== false)
|
||||
.reduce((acc, curr) => {
|
||||
acc[curr[0]] = curr[1];
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
return null;
|
||||
},
|
||||
activeSubtab() {
|
||||
return player.subtabs[this.layer];
|
||||
return layers[this.layer].activeSubtab.id;
|
||||
},
|
||||
firstTab() {
|
||||
if (this.forceFirstTab != undefined) {
|
||||
return this.forceFirstTab;
|
||||
}
|
||||
return this.index === 0;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectSubtab(subtab) {
|
||||
player.subtabs[this.layer] = subtab;
|
||||
if (player.subtabs[this.layer] == undefined) {
|
||||
player.subtabs[this.layer] = {};
|
||||
}
|
||||
player.subtabs[this.layer].mainTabs = subtab;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.layer-tab {
|
||||
padding-top: 50px;
|
||||
padding-bottom: 20px;
|
||||
min-height: 100%;
|
||||
flex-grow: 1;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.inner-tab > .layer-tab {
|
||||
margin: -50px -10px;
|
||||
padding: 50px 10px;
|
||||
}
|
||||
|
||||
.layer-tab .subtabs {
|
||||
margin-bottom: 24px;
|
||||
display: flex;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.subtabs:not(.floating) {
|
||||
width: calc(100% + 14px);
|
||||
border-top: solid 4px var(--separator);
|
||||
border-bottom: solid 4px var(--separator);
|
||||
height: 50px;
|
||||
margin-left: -7px;
|
||||
margin-right: -7px;
|
||||
box-sizing: border-box;
|
||||
text-align: left;
|
||||
padding-left: 14px;
|
||||
}
|
||||
|
||||
.modal-body .layer-tab {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.modal-body .layer-tab:not(.hasSubtabs) {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.modal-body .subtabs {
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.subtabs:not(.floating):first-child {
|
||||
margin-top: -50px;
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.subtabs:not(.floating):not(.firstTab) {
|
||||
padding-left: 70px;
|
||||
}
|
||||
</style>
|
||||
|
|
29
src/components/system/Microtab.vue
Normal file
29
src/components/system/Microtab.vue
Normal file
|
@ -0,0 +1,29 @@
|
|||
<template>
|
||||
<LayerProvider :layer="layer || tab.layer" :index="tab.index">
|
||||
<component :is="display" />
|
||||
</LayerProvider>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import { coerceComponent } from '../../util/vue';
|
||||
|
||||
export default {
|
||||
name: 'microtab',
|
||||
inject: [ 'tab' ],
|
||||
props: {
|
||||
layer: String,
|
||||
family: String,
|
||||
id: String
|
||||
},
|
||||
computed: {
|
||||
display() {
|
||||
const family = layers[this.layer || this.tab.layer].microtabs[this.family];
|
||||
return coerceComponent((this.id !== undefined ? family[this.id] : family.activeMicrotab).display);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
|
@ -3,24 +3,23 @@
|
|||
<div class="modal-mask" v-show="show" v-on:pointerdown.self="$emit('close')">
|
||||
<div class="modal-wrapper">
|
||||
<div class="modal-container">
|
||||
|
||||
<div class="modal-header">
|
||||
<slot name="header">
|
||||
default header
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<slot name="body">
|
||||
default body
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<perfect-scrollbar class="modal-body">
|
||||
<branches>
|
||||
<slot name="body">
|
||||
default body
|
||||
</slot>
|
||||
</branches>
|
||||
</perfect-scrollbar>
|
||||
<div class="modal-footer">
|
||||
<slot name="footer">
|
||||
<div class="modal-default-footer">
|
||||
<div class="modal-default-flex-grow"></div>
|
||||
<button class="modal-default-button" @click="$emit('close')">
|
||||
<button class="button modal-default-button" @click="$emit('close')">
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
|
@ -61,18 +60,31 @@ export default {
|
|||
}
|
||||
|
||||
.modal-container {
|
||||
width: 400px;
|
||||
max-width: calc(95vw - 40px);
|
||||
width: 640px;
|
||||
max-width: 95vw;
|
||||
max-height: 95vh;
|
||||
background-color: var(--background);
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
transition: all 0.3s ease;
|
||||
text-align: left;
|
||||
border: var(--modal-border);
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
margin: 20px 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modal-default-footer {
|
||||
|
@ -97,3 +109,9 @@ export default {
|
|||
transform: scale(1.1);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.modal-body > .ps__rail-y {
|
||||
z-index: 100;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div>
|
||||
<div v-frag>
|
||||
<div class="nav" v-if="useHeader">
|
||||
<img v-if="banner" :src="banner" height="100%" :alt="title" />
|
||||
<div v-else class="title">{{ title }}</div>
|
||||
|
@ -79,8 +79,9 @@ export default {
|
|||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
height: 50px;
|
||||
height: 46px;
|
||||
width: 100%;
|
||||
border-bottom: 4px solid var(--separator);
|
||||
}
|
||||
|
||||
.title {
|
||||
|
|
|
@ -5,15 +5,17 @@
|
|||
</div>
|
||||
<div slot="body">
|
||||
<div class="actions">
|
||||
<button @click="save">Manually Save</button>
|
||||
<button @click="exportSave">Export</button>
|
||||
<button @click="importSave" class="danger">Import</button>
|
||||
<button @click="hardReset" class="danger">Hard Reset</button>
|
||||
<button class="button" @click="save">Manually Save</button>
|
||||
<button @click="exportSave" class="button" >Export</button>
|
||||
<button @click="importSave" class="button danger">Import</button>
|
||||
<button @click="hardReset" class="button danger">Hard Reset</button>
|
||||
</div>
|
||||
<Select title="Theme" :options="themes" :value="theme" @change="setTheme" default="classic" />
|
||||
<Select title="Show Milestones" :options="msDisplayOptions" :value="msDisplay" @change="setMSDisplay" default="all" />
|
||||
<Toggle title="Autosave" :value="autosave" @change="toggleOption('autosave')" />
|
||||
<Toggle title="Offline Production" :value="offlineProd" @change="toggleOption('offlineProd')" />
|
||||
<Toggle title="Show TPS" :value="showTPS" @change="toggleOption('showTPS')" />
|
||||
<Select title="Theme" :options="themes" :value="theme" @change="setTheme" default="default" />
|
||||
<Toggle title="Hide Maxed Challenges" :value="hideChallenges" @change="toggleOption('hideChallenges')" />
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
@ -31,10 +33,20 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
themes: Object.keys(themes).map(theme => ({ label: camelToTitle(theme), value: theme }))
|
||||
themes: Object.keys(themes).map(theme => ({ label: camelToTitle(theme), value: theme })),
|
||||
msDisplayOptions: [ "all", "last", "configurable", "incomplete", "none" ]
|
||||
.map(option => ({ label: camelToTitle(option), value: option }))
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState([ "autosave", "offlineProd", "showTPS", "hideChallenges" ]),
|
||||
theme() {
|
||||
return { label: camelToTitle(player.theme), value: player.theme };
|
||||
},
|
||||
msDisplay() {
|
||||
return { label: camelToTitle(player.msDisplay), value: player.msDisplay };
|
||||
}
|
||||
},
|
||||
computed: mapState([ "autosave", "offlineProd", "showTPS", "theme" ]),
|
||||
methods: {
|
||||
toggleOption(option) {
|
||||
player[option] = !player[option];
|
||||
|
@ -42,6 +54,9 @@ export default {
|
|||
setTheme(theme) {
|
||||
player.theme = theme;
|
||||
},
|
||||
setMSDisplay(msDisplay) {
|
||||
player.msDisplay = msDisplay;
|
||||
},
|
||||
save() {
|
||||
console.warn("Not yet implemented!");
|
||||
},
|
||||
|
|
15
src/components/system/Row.vue
Normal file
15
src/components/system/Row.vue
Normal file
|
@ -0,0 +1,15 @@
|
|||
<template>
|
||||
<div class="table">
|
||||
<div class="row">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import '../features/table.css';
|
||||
|
||||
export default {
|
||||
name: 'row'
|
||||
};
|
||||
</script>
|
21
src/components/system/Spacer.vue
Normal file
21
src/components/system/Spacer.vue
Normal file
|
@ -0,0 +1,21 @@
|
|||
<template>
|
||||
<div :style="{ width: spacingWidth, height: spacingHeight }"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'spacer',
|
||||
props: {
|
||||
width: String,
|
||||
height: String
|
||||
},
|
||||
computed: {
|
||||
spacingWidth() {
|
||||
return this.width || '8px';
|
||||
},
|
||||
spacingHeight() {
|
||||
return this.height || '17px';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
64
src/components/system/Sticky.vue
Normal file
64
src/components/system/Sticky.vue
Normal file
|
@ -0,0 +1,64 @@
|
|||
<template>
|
||||
<div class="sticky" :style="{ top }" ref="sticky" data-v-sticky>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'sticky',
|
||||
data() {
|
||||
return {
|
||||
top: 0,
|
||||
observer: null
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.sticky == undefined) {
|
||||
this.$nextTick(this.mounted);
|
||||
} else {
|
||||
this.updateTop();
|
||||
this.observer = new ResizeObserver(this.updateTop);
|
||||
this.observer.observe(this.$refs.sticky.parentElement);
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
updateTop() {
|
||||
let el = this.$refs.sticky;
|
||||
if (el == undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
let top = 0;
|
||||
while (el.previousSibling) {
|
||||
if (el.previousSibling.dataset && 'vSticky' in el.previousSibling.dataset) {
|
||||
top += el.previousSibling.offsetHeight;
|
||||
}
|
||||
el = el.previousSibling;
|
||||
}
|
||||
this.top = top + "px";
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sticky {
|
||||
position: sticky;
|
||||
background: var(--background);
|
||||
margin-left: -7px;
|
||||
margin-right: -7px;
|
||||
padding-left: 7px;
|
||||
padding-right: 7px;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.modal-body .sticky {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
</style>
|
27
src/components/system/Subtab.vue
Normal file
27
src/components/system/Subtab.vue
Normal file
|
@ -0,0 +1,27 @@
|
|||
<template>
|
||||
<LayerProvider :layer="layer || tab.layer" :index="tab.index">
|
||||
<component :is="display" />
|
||||
</LayerProvider>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import { coerceComponent } from '../../util/vue';
|
||||
|
||||
export default {
|
||||
name: 'subtab',
|
||||
inject: [ 'tab' ],
|
||||
props: {
|
||||
layer: String,
|
||||
id: String
|
||||
},
|
||||
computed: {
|
||||
display() {
|
||||
return coerceComponent(layers[this.layer || this.tab.layer].subtabs[this.id].display);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
81
src/components/system/TabButton.vue
Normal file
81
src/components/system/TabButton.vue
Normal file
|
@ -0,0 +1,81 @@
|
|||
<template>
|
||||
<button @click="$emit('selectTab')" class="tabButton" :style="style"
|
||||
:class="{ notify: options.notify, resetNotify: options.resetNotify, floating, activeTab }">
|
||||
{{ text }}
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import { player } from '../../store/proxies';
|
||||
import themes from '../../data/themes';
|
||||
import '../common/notify.css';
|
||||
|
||||
export default {
|
||||
name: 'tab-button',
|
||||
props: {
|
||||
layer: String,
|
||||
text: String,
|
||||
options: Object,
|
||||
activeTab: Boolean
|
||||
},
|
||||
inject: [ 'tab' ],
|
||||
computed: {
|
||||
floating() {
|
||||
return themes[player.theme].floatingTabs;
|
||||
},
|
||||
style() {
|
||||
return [
|
||||
(this.floating || this.activeTab) && { 'border-color': layers[this.layer || this.tab.layer].color },
|
||||
layers[this.layer || this.tab.layer].componentStyles?.['tab-button'],
|
||||
this.options.resetNotify && this.options.glowColor &&
|
||||
{
|
||||
boxShadow: this.floating ?
|
||||
`-2px -4px 4px rgba(0, 0, 0, 0) inset, 0 0 8px ${this.options.glowColor}` :
|
||||
`0px 10px 7px -10px ${this.options.glowColor}`
|
||||
},
|
||||
this.options.notify && this.options.glowColor &&
|
||||
{
|
||||
boxShadow: this.floating ?
|
||||
`-2px -4px 4px rgba(0, 0, 0, 0) inset, 0 0 20px ${this.options.glowColor}` :
|
||||
`0px 15px 7px -10px ${this.options.glowColor}`
|
||||
},
|
||||
this.options.buttonStyle
|
||||
];
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tabButton {
|
||||
background-color: transparent;
|
||||
color: var(--color);
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
padding: 5px 20px;
|
||||
margin: 5px;
|
||||
border-radius: 5px;
|
||||
border: 2px solid;
|
||||
}
|
||||
|
||||
.tabButton:hover {
|
||||
transform: scale(1.1, 1.1);
|
||||
text-shadow: 0 0 7px var(--color);
|
||||
}
|
||||
|
||||
.tabButton:not(.floating) {
|
||||
height: 50px;
|
||||
margin: 0;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-top: none;
|
||||
border-bottom-width: 4px;
|
||||
border-radius: 0;
|
||||
transform: unset;
|
||||
}
|
||||
|
||||
.tabButton:not(.floating):not(.activeTab) {
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
</style>
|
|
@ -1,15 +1,21 @@
|
|||
<template>
|
||||
<div class="tabs">
|
||||
<div v-for="(tab, index) in tabs" class="tab" :key="index">
|
||||
<button v-if="index > 0" class="goBack" @click="goBack(index)">←</button>
|
||||
<LayerProvider :layer="tab" :index="index" v-if="tab in layers && layers[tab].component != undefined">
|
||||
<component :is="layers[tab].component" />
|
||||
</LayerProvider>
|
||||
<layer-tab :layer="tab" :index="index" v-else-if="tab in layers" />
|
||||
<component :is="tab" :index="index" v-else />
|
||||
<div class="separator" v-if="index !== tabs.length - 1"></div>
|
||||
<perfect-scrollbar class="tabs-container">
|
||||
<div class="tabs">
|
||||
<div v-for="(tab, index) in tabs" :key="index" class="tab">
|
||||
<button v-if="index > 0" class="goBack" @click="goBack(index)">←</button>
|
||||
<perfect-scrollbar>
|
||||
<div class="inner-tab">
|
||||
<LayerProvider :layer="tab" :index="index" v-if="tab in components && components[tab]">
|
||||
<component :is="components[tab]" />
|
||||
</LayerProvider>
|
||||
<layer-tab :layer="tab" :index="index" v-else-if="tab in components" />
|
||||
<component :is="tab" :index="index" v-else />
|
||||
</div>
|
||||
</perfect-scrollbar>
|
||||
<div class="separator" v-if="index !== tabs.length - 1"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</perfect-scrollbar>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -21,8 +27,11 @@ export default {
|
|||
name: 'Tabs',
|
||||
computed: {
|
||||
...mapState([ 'tabs' ]),
|
||||
layers() {
|
||||
return layers;
|
||||
components() {
|
||||
return Object.keys(layers).reduce((acc, curr) => {
|
||||
acc[curr] = layers[curr].component || false;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -34,18 +43,33 @@ export default {
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tabs-container {
|
||||
width: 100vw;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tab {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0 10px;
|
||||
padding-top: 50px;
|
||||
min-width: 640px;
|
||||
}
|
||||
|
||||
.tab .ps {
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.inner-tab {
|
||||
padding: 50px 10px;
|
||||
min-height: calc(100% - 100px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.separator {
|
||||
|
@ -55,6 +79,7 @@ export default {
|
|||
bottom: 0;
|
||||
width: 6px;
|
||||
background: var(--separator);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.goBack {
|
||||
|
@ -67,6 +92,7 @@ export default {
|
|||
font-size: 40px;
|
||||
cursor: pointer;
|
||||
line-height: 40px;
|
||||
z-index: 7;
|
||||
}
|
||||
|
||||
.goBack:hover {
|
||||
|
@ -74,3 +100,25 @@ export default {
|
|||
text-shadow: 0 0 7px var(--color);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.tabs-container > .ps__rail-x {
|
||||
z-index: 120;
|
||||
}
|
||||
|
||||
.tab hr {
|
||||
height: 4px;
|
||||
border: none;
|
||||
background: var(--separator);
|
||||
margin: 7px -10px;
|
||||
}
|
||||
|
||||
.tab .modal-body hr {
|
||||
margin: 7px 0;
|
||||
}
|
||||
|
||||
.tab > .ps > .ps__rail-y {
|
||||
margin-right: 6px;
|
||||
z-index: 10;
|
||||
}
|
||||
</style>
|
||||
|
|
90
src/components/system/Tooltip.vue
Normal file
90
src/components/system/Tooltip.vue
Normal file
|
@ -0,0 +1,90 @@
|
|||
<template>
|
||||
<div :tooltip="text">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tooltip',
|
||||
props: {
|
||||
text: String
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
[tooltip] {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
[tooltip]:before,
|
||||
[tooltip]:after {
|
||||
visibility: hidden;
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
|
||||
filter: progid: DXImageTransform.Microsoft.Alpha(Opacity=0);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
white-space: pre-wrap;
|
||||
z-index: 100 !important;
|
||||
}
|
||||
|
||||
[tooltip]:before {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
margin-bottom: 5px;
|
||||
margin-left: -80px;
|
||||
padding: 7px;
|
||||
width: 160px;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
background-color: var(--background-tooltip);
|
||||
color: var(--points);
|
||||
content: attr(tooltip);
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
line-height: 1.2;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
[tooltip]:after {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
margin-left: -5px;
|
||||
width: 0;
|
||||
border-top: 5px solid var(--background-tooltip);
|
||||
border-right: 5px solid transparent;
|
||||
border-left: 5px solid transparent;
|
||||
content: " ";
|
||||
font-size: 0;
|
||||
line-height: 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
[tooltip]:hover:before,
|
||||
[tooltip].forceTooltip:before,
|
||||
[tooltip]:hover:after,
|
||||
[tooltip].forceTooltip:after {
|
||||
animation: tooltip 0.25s linear 1;
|
||||
animation-fill-mode: forwards;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
@keyframes tooltip {
|
||||
0% {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
|
||||
filter: progid: DXImageTransform.Microsoft.Alpha(Opacity=0);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
100% {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
|
||||
filter: progid: DXImageTransform.Microsoft.Alpha(Opacity=100);
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
</style>
|
21
src/components/system/VerticalRule.vue
Normal file
21
src/components/system/VerticalRule.vue
Normal file
|
@ -0,0 +1,21 @@
|
|||
<template>
|
||||
<div class="vr" :style="{ height }"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'vr',
|
||||
props: {
|
||||
height: String
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.vr {
|
||||
width: 4px;
|
||||
background: var(--separator);
|
||||
height: 100%;
|
||||
margin: 0 7px;
|
||||
}
|
||||
</style>
|
48
src/components/tree/BranchLine.vue
Normal file
48
src/components/tree/BranchLine.vue
Normal file
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<line :stroke="stroke" :stroke-width="strokeWidth" v-bind="typeof options === 'string' ? [] : options"
|
||||
:x1="startPosition.x" :y1="startPosition.y" :x2="endPosition.x" :y2="endPosition.y" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'branch-line',
|
||||
props: {
|
||||
options: [ String, Object ],
|
||||
startNode: Object,
|
||||
endNode: Object
|
||||
},
|
||||
computed: {
|
||||
stroke() {
|
||||
if (typeof this.options === 'string' || !('stroke' in this.options)) {
|
||||
return 'white';
|
||||
}
|
||||
return this.options.stroke;
|
||||
},
|
||||
strokeWidth() {
|
||||
if (typeof this.options === 'string' || !('stroke-width' in this.options)) {
|
||||
return '15px';
|
||||
}
|
||||
return this.options['stroke-width'];
|
||||
},
|
||||
startPosition() {
|
||||
const position = { x: this.startNode.x || 0, y: this.startNode.y || 0 };
|
||||
if (typeof this.options !== 'string' && 'startOffset' in this.options) {
|
||||
position.x += this.options.startOffset.x || 0;
|
||||
position.y += this.options.startOffset.y || 0;
|
||||
}
|
||||
return position;
|
||||
},
|
||||
endPosition() {
|
||||
const position = { x: this.endNode.x || 0, y: this.endNode.y || 0 };
|
||||
if (typeof this.options !== 'string' && 'endOffset' in this.options) {
|
||||
position.x += this.options.endOffset.x || 0;
|
||||
position.y += this.options.endOffset.y || 0;
|
||||
}
|
||||
return position;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
77
src/components/tree/BranchNode.vue
Normal file
77
src/components/tree/BranchNode.vue
Normal file
|
@ -0,0 +1,77 @@
|
|||
<template>
|
||||
<div class="branch"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'branch-node',
|
||||
props: {
|
||||
featureType: String,
|
||||
id: [ Number, String ],
|
||||
branches: Array
|
||||
},
|
||||
inject: [ 'registerNode', 'unregisterNode', 'registerBranch', 'unregisterBranch' ],
|
||||
mounted() {
|
||||
const id = `${this.featureType}@${this.id}`;
|
||||
if (this.registerNode) {
|
||||
this.registerNode(id, this);
|
||||
}
|
||||
if (this.registerBranch) {
|
||||
this.branches?.map(this.handleBranch).forEach(branch => this.registerBranch(id, branch));
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
const id = `${this.featureType}@${this.id}`;
|
||||
if (this.unregisterNode) {
|
||||
this.unregisterNode(id);
|
||||
}
|
||||
if (this.unregisterBranch) {
|
||||
this.branches?.map(this.handleBranch).forEach(branch => this.unregisterBranch(id, branch));
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
featureType(newValue, oldValue) {
|
||||
if (this.registerNode && this.unregisterNode) {
|
||||
this.unregisterNode(`${oldValue}@${this.id}`);
|
||||
this.registerNode(`${newValue}@${this.id}`, this);
|
||||
}
|
||||
},
|
||||
id(newValue, oldValue) {
|
||||
if (this.registerNode && this.unregisterNode) {
|
||||
this.unregisterNode(`${this.featureType}@${oldValue}`);
|
||||
this.registerNode(`${this.featureType}@${newValue}`, this);
|
||||
}
|
||||
},
|
||||
branches(newValue, oldValue) {
|
||||
if (this.registerBranch && this.unregisterBranch) {
|
||||
const id = `${this.featureType}@${this.id}`;
|
||||
oldValue?.map(this.handleBranch).forEach(branch => this.unregisterBranch(id, branch));
|
||||
newValue?.map(this.handleBranch).forEach(branch => this.registerBranch(id, branch));
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleBranch(branch) {
|
||||
if (typeof branch === 'string') {
|
||||
return branch.includes('@') ? branch : `${this.featureType}@${branch}`;
|
||||
}
|
||||
if (!branch.target.includes('@')) {
|
||||
return { ...branch, target: `${branch.featureType || this.featureType}@${branch.target}` };
|
||||
}
|
||||
return branch;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.branch {
|
||||
position: absolute;
|
||||
z-index: -10;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
107
src/components/tree/Branches.vue
Normal file
107
src/components/tree/Branches.vue
Normal file
|
@ -0,0 +1,107 @@
|
|||
<template>
|
||||
<div v-frag>
|
||||
<slot />
|
||||
<div ref="resizeListener" class="resize-listener" />
|
||||
<svg>
|
||||
<branch-line v-for="(branch, index) in branches" :key="index"
|
||||
:startNode="nodes[branch.start]" :endNode="nodes[branch.end]" :options="branch.options" />
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue';
|
||||
|
||||
const observerOptions = {
|
||||
attributes: true,
|
||||
childList: true,
|
||||
subtree: true
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'branches',
|
||||
data() {
|
||||
return {
|
||||
observer: new MutationObserver(this.updateNodes),
|
||||
resizeObserver: new ResizeObserver(this.updateNodes),
|
||||
nodes: {},
|
||||
links: []
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.resizeListener == undefined) {
|
||||
this.mounted();
|
||||
} else {
|
||||
// ResizeListener exists because ResizeObserver's don't work when told to observe an SVG element
|
||||
this.resizeObserver.observe(this.$refs.resizeListener);
|
||||
this.updateNodes();
|
||||
}
|
||||
});
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
registerNode: this.registerNode,
|
||||
unregisterNode: this.unregisterNode,
|
||||
registerBranch: this.registerBranch,
|
||||
unregisterBranch: this.unregisterBranch
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
branches() {
|
||||
return this.links.filter(link => link.start in this.nodes && link.end in this.nodes &&
|
||||
this.nodes[link.start].x != undefined && this.nodes[link.start].y != undefined &&
|
||||
this.nodes[link.end].x != undefined && this.nodes[link.end].y != undefined);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateNodes() {
|
||||
if (this.$refs.resizeListener != undefined) {
|
||||
const containerRect = this.$refs.resizeListener.getBoundingClientRect();
|
||||
Object.keys(this.nodes).forEach(id => this.updateNode(id, containerRect));
|
||||
}
|
||||
},
|
||||
updateNode(id, containerRect) {
|
||||
const linkStartRect = this.nodes[id].element.getBoundingClientRect();
|
||||
Vue.set(this.nodes[id], 'x', linkStartRect.x + linkStartRect.width / 2 - containerRect.x);
|
||||
Vue.set(this.nodes[id], 'y', linkStartRect.y + linkStartRect.height / 2 - containerRect.y);
|
||||
},
|
||||
registerNode(id, component) {
|
||||
const element = component.$el.parentElement;
|
||||
Vue.set(this.nodes, id, { component, element });
|
||||
this.observer.observe(element, observerOptions);
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.resizeListener != undefined) {
|
||||
this.updateNode(id, this.$refs.resizeListener.getBoundingClientRect());
|
||||
}
|
||||
});
|
||||
},
|
||||
unregisterNode(id) {
|
||||
delete this.nodes[id];
|
||||
},
|
||||
registerBranch(start, options) {
|
||||
const end = typeof options === 'string' ? options : options.target;
|
||||
this.links.push({ start, end, options });
|
||||
Vue.set(this, 'links', this.links);
|
||||
},
|
||||
unregisterBranch(start, options) {
|
||||
const index = this.links.findIndex(l => l.start === start && l.options === options);
|
||||
this.links.splice(index, 1);
|
||||
Vue.set(this, 'links', this.links);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
svg,
|
||||
.resize-listener {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
78
src/components/tree/Tree.vue
Normal file
78
src/components/tree/Tree.vue
Normal file
|
@ -0,0 +1,78 @@
|
|||
<template>
|
||||
<div v-frag>
|
||||
<span class="row" v-for="(row, index) in rows" :key="index">
|
||||
<tree-node v-for="(node, nodeIndex) in row" :key="nodeIndex" :id="node" @show-modal="openModal" />
|
||||
</span>
|
||||
<span class="side-nodes" v-if="rows.side">
|
||||
<tree-node v-for="(node, nodeIndex) in rows.side" :key="nodeIndex" :id="node" @show-modal="openModal" small />
|
||||
</span>
|
||||
<modal :show="showModal" @close="closeModal">
|
||||
<div slot="header"><h2 v-if="modalHeader">{{ modalHeader }}</h2></div>
|
||||
<layer-tab slot="body" v-if="modal" :layer="modal" :index="tab.index" :forceFirstTab="true" />
|
||||
</modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import '../features/table.css';
|
||||
|
||||
export default {
|
||||
name: 'tree',
|
||||
data() {
|
||||
return {
|
||||
showModal: false,
|
||||
modal: null
|
||||
};
|
||||
},
|
||||
props: {
|
||||
nodes: Array
|
||||
},
|
||||
inject: [ 'tab' ],
|
||||
computed: {
|
||||
modalHeader() {
|
||||
if (this.modal == null) {
|
||||
return null;
|
||||
}
|
||||
return layers[this.modal].name;
|
||||
},
|
||||
rows() {
|
||||
if (this.nodes != undefined) {
|
||||
return this.nodes;
|
||||
}
|
||||
return Object.keys(layers).reduce((acc, curr) => {
|
||||
if (!(layers[curr].displayRow in acc)) {
|
||||
acc[layers[curr].displayRow] = [];
|
||||
}
|
||||
if (layers[curr].position != undefined) {
|
||||
acc[layers[curr].displayRow][layers[curr].position] = curr;
|
||||
} else {
|
||||
acc[layers[curr].displayRow].push(curr);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openModal(id) {
|
||||
this.showModal = true;
|
||||
this.modal = id;
|
||||
},
|
||||
closeModal() {
|
||||
this.showModal = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.row {
|
||||
margin: 50px auto;
|
||||
}
|
||||
|
||||
.side-nodes {
|
||||
position: absolute;
|
||||
right: 55px;
|
||||
top: 65px;
|
||||
}
|
||||
</style>
|
138
src/components/tree/TreeNode.vue
Normal file
138
src/components/tree/TreeNode.vue
Normal file
|
@ -0,0 +1,138 @@
|
|||
<template>
|
||||
<tooltip :text="tooltip" :class="{
|
||||
forceTooltip,
|
||||
ghost: layer.layerShown === 'ghost',
|
||||
treeNode: true,
|
||||
[id]: true,
|
||||
hidden: !layer.layerShown,
|
||||
locked: !unlocked,
|
||||
notify: layer.notify && unlocked,
|
||||
resetNotify: layer.resetNotify,
|
||||
can: unlocked,
|
||||
small
|
||||
}">
|
||||
<LayerProvider :index="tab.index" :layer="id">
|
||||
<button v-if="layer.shown" @click="clickTab" :style="style">
|
||||
<component :is="display" />
|
||||
<branch-node :branches="layer.branches" :id="id" featureType="tree-node" />
|
||||
</button>
|
||||
<mark-node :mark="layer.mark" />
|
||||
</LayerProvider>
|
||||
</tooltip>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { layers } from '../../store/layers';
|
||||
import { player } from '../../store/proxies';
|
||||
import { coerceComponent } from '../../util/vue';
|
||||
import { formatWhole } from '../../util/bignum';
|
||||
|
||||
export default {
|
||||
name: 'tree-node',
|
||||
props: {
|
||||
id: [ String, Number ],
|
||||
small: Boolean
|
||||
},
|
||||
inject: [ 'tab' ],
|
||||
computed: {
|
||||
layer() {
|
||||
return layers[this.id];
|
||||
},
|
||||
unlocked() {
|
||||
if (this.layer.canClick != undefined) {
|
||||
return this.layer.canClick;
|
||||
}
|
||||
return this.layer.unlocked;
|
||||
},
|
||||
style() {
|
||||
return [
|
||||
this.unlocked ? { backgroundColor: this.layer.color } : null,
|
||||
this.layer.notify && this.unlocked ?
|
||||
{ boxShadow: `-4px -4px 4px rgba(0, 0, 0, 0.25) inset, 0 0 20px ${this.layer.trueGlowColor}` } : null,
|
||||
this.layer.nodeStyle
|
||||
];
|
||||
},
|
||||
display() {
|
||||
if (this.layer.display != undefined) {
|
||||
return coerceComponent(this.layer.display);
|
||||
} else if (this.layer.image != undefined) {
|
||||
return coerceComponent(`<img src=${this.layer.image}/>`);
|
||||
} else {
|
||||
return coerceComponent(this.layer.symbol);
|
||||
}
|
||||
},
|
||||
forceTooltip() {
|
||||
return player[this.id].forceTooltip;
|
||||
},
|
||||
tooltip() {
|
||||
if (this.layer.canClick != undefined) {
|
||||
if (this.layer.canClick) {
|
||||
return this.layer.tooltip || 'I am a button!';
|
||||
} else {
|
||||
return this.layer.tooltipLocked || this.layer.tooltip || 'I am a button!';
|
||||
}
|
||||
}
|
||||
if (this.layer.unlocked) {
|
||||
return this.layer.tooltip || `${formatWhole(player[this.id].points)} ${this.layer.resource}`;
|
||||
} else {
|
||||
return this.layer.tooltipLocked ||
|
||||
`Reach ${formatWhole(this.layer.requires)} ${this.layer.baseResource} to unlock (You have ${formatWhole(this.layer.baseAmount)} ${this.layer.baseResource})`;
|
||||
}
|
||||
},
|
||||
components() {
|
||||
return Object.keys(layers).reduce((acc, curr) => {
|
||||
acc[curr] = layers[curr].component || false;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clickTab(e) {
|
||||
if (e.shiftKey) {
|
||||
player[this.id].forceTooltip = !player[this.id].forceTooltip;
|
||||
} else if (this.layer.onClick != undefined) {
|
||||
this.layer.onClick();
|
||||
} else if (this.layer.modal) {
|
||||
this.$emit('show-modal', this.id);
|
||||
} else {
|
||||
player.tabs = [...player.tabs.slice(0, this.tab.index + 1), this.id];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.treeNode {
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
border-radius: 50%;
|
||||
padding: 0;
|
||||
margin: 0 10px 0 10px;
|
||||
}
|
||||
|
||||
.treeNode button {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 2px solid rgba(0, 0, 0, 0.125);
|
||||
border-radius: inherit;
|
||||
font-size: 40px;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.25);
|
||||
box-shadow: -4px -4px 4px rgba(0, 0, 0, 0.25) inset, 0px 0px 20px var(--background);
|
||||
}
|
||||
|
||||
.treeNode.small {
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.treeNode.small button {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.ghost {
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
70
src/data/layers/aca/a.js
Normal file
70
src/data/layers/aca/a.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
import Decimal from '../../../util/bignum';
|
||||
import { player } from '../../../store/proxies';
|
||||
|
||||
export default {
|
||||
id: "a",
|
||||
startData() { return {
|
||||
unlocked: true,
|
||||
points: new Decimal(0),
|
||||
}},
|
||||
color: "yellow",
|
||||
modal: true,
|
||||
name: "Achievements",
|
||||
resource: "achievement power",
|
||||
row: "side",
|
||||
tooltip() { // Optional, tooltip displays when the layer is locked
|
||||
return ("Achievements")
|
||||
},
|
||||
achievementPopups: true,
|
||||
achievements: {
|
||||
11: {
|
||||
image: "https://unsoftcapped2.github.io/The-Modding-Tree-2/discord.png",
|
||||
name: "Get me!",
|
||||
done() {return true}, // This one is a freebie
|
||||
goalTooltip: "How did this happen?", // Shows when achievement is not completed
|
||||
doneTooltip: "You did it!", // Showed when the achievement is completed
|
||||
},
|
||||
12: {
|
||||
name: "Impossible!",
|
||||
done() {return false},
|
||||
goalTooltip: "Mwahahaha!", // Shows when achievement is not completed
|
||||
doneTooltip: "HOW????", // Showed when the achievement is completed
|
||||
style: {'color': '#04e050'},
|
||||
},
|
||||
13: {
|
||||
name: "EIEIO",
|
||||
done() {return player.f.points.gte(1)},
|
||||
tooltip: "Get a farm point.\n\nReward: The dinosaur is now your friend (you can max Farm Points).", // Showed when the achievement is completed
|
||||
onComplete() {console.log("Bork bork bork!")}
|
||||
},
|
||||
},
|
||||
midsection: "<grid id='test' />",
|
||||
grids: {
|
||||
test: {
|
||||
maxRows: 3,
|
||||
rows: 2,
|
||||
cols: 2,
|
||||
getStartData(cell) {
|
||||
return cell
|
||||
},
|
||||
getUnlocked() { // Default
|
||||
return true
|
||||
},
|
||||
getCanClick() {
|
||||
return player.points.eq(10)
|
||||
},
|
||||
getStyle(cell, data) {
|
||||
return {'background-color': '#'+ (data*1234%999999)}
|
||||
},
|
||||
onClick() { // Don't forget onHold
|
||||
this.data++
|
||||
},
|
||||
getTitle(cell) {
|
||||
return "Gridable #" + cell
|
||||
},
|
||||
getDisplay(cell, data) {
|
||||
return data
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
410
src/data/layers/aca/c.js
Normal file
410
src/data/layers/aca/c.js
Normal file
|
@ -0,0 +1,410 @@
|
|||
import Decimal, { format, formatWhole } from '../../../util/bignum';
|
||||
import { player, tmp } from '../../../store/proxies';
|
||||
import { layers } from '../../../store/layers';
|
||||
import { hasUpgrade, hasMilestone, getBuyableAmount, setBuyableAmount, upgradeEffect, buyableEffect, challengeCompletions } from '../../../util/features';
|
||||
import { resetLayer, resetLayerData } from '../../../util/layers';
|
||||
import { UP, RIGHT } from '../../../util/vue';
|
||||
|
||||
export default {
|
||||
id: "c", // This is assigned automatically, both to the layer and all upgrades, etc. Shown here so you know about it
|
||||
name: "Candies", // This is optional, only used in a few places, If absent it just uses the layer id.
|
||||
symbol: "C", // This appears on the layer's node. Default is the id with the first letter capitalized
|
||||
position: 0, // Horizontal position within a row. By default it uses the layer id and sorts in alphabetical order
|
||||
startData() { return {
|
||||
unlocked: true,
|
||||
points: new Decimal(0),
|
||||
best: new Decimal(0),
|
||||
total: new Decimal(0),
|
||||
beep: false,
|
||||
thingy: "pointy",
|
||||
otherThingy: 10,
|
||||
spentOnBuyables: new Decimal(0)
|
||||
}},
|
||||
color: "#4BDC13",
|
||||
requires: new Decimal(10), // Can be a function that takes requirement increases into account
|
||||
resource: "lollipops", // Name of prestige currency
|
||||
baseResource: "points", // Name of resource prestige is based on
|
||||
baseAmount() {return player.points}, // Get the current amount of baseResource
|
||||
type: "normal", // normal: cost to gain currency depends on amount gained. static: cost depends on how much you already have
|
||||
exponent: 0.5, // Prestige currency exponent
|
||||
base: 5, // Only needed for static layers, base of the formula (b^(x^exp))
|
||||
roundUpCost: false, // True if the cost needs to be rounded up (use when baseResource is static?)
|
||||
|
||||
// For normal layers, gain beyond [softcap] points is put to the [softcapPower]th power
|
||||
softcap: new Decimal(1e100),
|
||||
softcapPower: new Decimal(0.5),
|
||||
canBuyMax() {}, // Only needed for static layers with buy max
|
||||
gainMult() { // Calculate the multiplier for main currency from bonuses
|
||||
let mult = new Decimal(1)
|
||||
if (hasUpgrade(this.layer, 166)) mult = mult.times(2) // These upgrades don't exist
|
||||
if (hasUpgrade(this.layer, 120)) mult = mult.times(upgradeEffect(this.layer, 120))
|
||||
return mult
|
||||
},
|
||||
gainExp() { // Calculate the exponent on main currency from bonuses
|
||||
return new Decimal(1)
|
||||
},
|
||||
row: 0, // Row the layer is in on the tree (0 is the first row)
|
||||
effect() {
|
||||
return { // Formulas for any boosts inherent to resources in the layer. Can return a single value instead of an object if there is just one effect
|
||||
waffleBoost: Decimal.pow(player[this.layer].points, 0.2),
|
||||
icecreamCap: (player[this.layer].points * 10)
|
||||
}},
|
||||
effectDescription() { // Optional text to describe the effects
|
||||
let eff = this.effect;
|
||||
const waffleBoost = eff.waffleBoost.times(buyableEffect(this.layer, 11).first)
|
||||
return "which are boosting waffles by "+format(waffleBoost)+" and increasing the Ice Cream cap by "+format(eff.icecreamCap)
|
||||
},
|
||||
infoboxes:{
|
||||
coolInfo: {
|
||||
title: "Lore",
|
||||
titleStyle: {'color': '#FE0000'},
|
||||
body: "DEEP LORE!",
|
||||
bodyStyle: {'background-color': "#0000EE"}
|
||||
}
|
||||
},
|
||||
milestones: {
|
||||
0: {requirementDisplay: "3 Lollipops",
|
||||
done() {return player[this.layer].best.gte(3)}, // Used to determine when to give the milestone
|
||||
effectDisplay: "Unlock the next milestone",
|
||||
},
|
||||
1: {requirementDisplay: "4 Lollipops",
|
||||
unlocked() {return hasMilestone(this.layer, 0)},
|
||||
done() {return player[this.layer].best.gte(4)},
|
||||
effectDisplay: "You can toggle beep and boop (which do nothing)",
|
||||
optionsDisplay: `
|
||||
<div>
|
||||
<Toggle :value="player.c.beep" @change="value => player.c.beep = value" />
|
||||
<Toggle :value="player.f.boop" @change="value => player.f.boop = value" />
|
||||
</div>
|
||||
`,
|
||||
style() {
|
||||
if(hasMilestone(this.layer, this.id)) return {
|
||||
'background-color': '#1111DD'
|
||||
}},
|
||||
|
||||
},
|
||||
},
|
||||
challenges: {
|
||||
|
||||
11: {
|
||||
name: "Fun",
|
||||
completionLimit: 3,
|
||||
challengeDescription() {return "Makes the game 0% harder<br>"+challengeCompletions(this.layer, this.id) + "/" + this.completionLimit + " completions"},
|
||||
unlocked() { return player[this.layer].best.gt(0) },
|
||||
goalDescription: 'Have 20 points I guess',
|
||||
canComplete() {
|
||||
return player.points.gte(20)
|
||||
},
|
||||
rewardEffect() {
|
||||
let ret = player[this.layer].points.add(1).tetrate(0.02)
|
||||
return ret;
|
||||
},
|
||||
rewardDisplay() { return format(this.rewardEffect)+"x" },
|
||||
countsAs: [12, 21], // Use this for if a challenge includes the effects of other challenges. Being in this challenge "counts as" being in these.
|
||||
rewardDescription: "Says hi",
|
||||
onComplete() {console.log("hiii")}, // Called when you successfully complete the challenge
|
||||
onEnter() {console.log("So challenging")},
|
||||
onExit() {console.log("Sweet freedom!")},
|
||||
|
||||
},
|
||||
},
|
||||
upgrades: {
|
||||
|
||||
11: {
|
||||
title: "Generator of Genericness",
|
||||
description: "Gain 1 Point every second.",
|
||||
cost: new Decimal(1),
|
||||
unlocked() { return player[this.layer].unlocked }, // The upgrade is only visible when this is true
|
||||
},
|
||||
12: {
|
||||
description: "Point generation is faster based on your unspent Lollipops.",
|
||||
cost: new Decimal(1),
|
||||
unlocked() { return (hasUpgrade(this.layer, 11))},
|
||||
effect() { // Calculate bonuses from the upgrade. Can return a single value or an object with multiple values
|
||||
let ret = player[this.layer].points.add(1).pow(player[this.layer].upgrades.includes(24)?1.1:(player[this.layer].upgrades.includes(14)?0.75:0.5))
|
||||
if (ret.gte("1e20000000")) ret = ret.sqrt().times("1e10000000")
|
||||
return ret;
|
||||
},
|
||||
effectDisplay() { return format(this.effect)+"x" }, // Add formatting to the effect
|
||||
},
|
||||
13: {
|
||||
unlocked() { return (hasUpgrade(this.layer, 12))},
|
||||
onPurchase() { // This function triggers when the upgrade is purchased
|
||||
player[this.layer].unlockOrder = 0
|
||||
},
|
||||
style() {
|
||||
if (hasUpgrade(this.layer, this.id)) return {
|
||||
'background-color': '#1111dd'
|
||||
}
|
||||
else if (!this.canAfford) {
|
||||
return {
|
||||
'background-color': '#dd1111'
|
||||
}
|
||||
} // Otherwise use the default
|
||||
},
|
||||
canAfford(){return player.points.lte(7)},
|
||||
pay(){player.points = player.points.add(7)},
|
||||
fullDisplay: "Only buyable with less than 7 points, and gives you 7 more. Unlocks a secret subtab."
|
||||
},
|
||||
22: {
|
||||
title: "This upgrade doesn't exist",
|
||||
description: "Or does it?.",
|
||||
currencyLocation() {return player[this.layer].buyables}, // The object in player data that the currency is contained in
|
||||
currencyDisplayName: "exhancers", // Use if using a nonstandard currency
|
||||
currencyInternalName: 11, // Use if using a nonstandard currency
|
||||
|
||||
cost: new Decimal(3),
|
||||
unlocked() { return player[this.layer].unlocked }, // The upgrade is only visible when this is true
|
||||
},
|
||||
},
|
||||
buyables: {
|
||||
showRespec: true,
|
||||
respec() { // Optional, reset things and give back your currency. Having this function makes a respec button appear
|
||||
player[this.layer].points = player[this.layer].points.add(player[this.layer].spentOnBuyables) // A built-in thing to keep track of this but only keeps a single value
|
||||
this.reset;
|
||||
resetLayer(this.layer, true) // Force a reset
|
||||
},
|
||||
respecText: "Respec Thingies", // Text on Respec button, optional
|
||||
respecMessage: "Are you sure? Respeccing these doesn't accomplish much.",
|
||||
11: {
|
||||
title: "Exhancers", // Optional, displayed at the top in a larger font
|
||||
cost(x) { // cost for buying xth buyable, can be an object if there are multiple currencies
|
||||
if (x.gte(25)) x = x.pow(2).div(25)
|
||||
let cost = Decimal.pow(2, x.pow(1.5))
|
||||
return cost.floor()
|
||||
},
|
||||
effect(x) { // Effects of owning x of the items, x is a decimal
|
||||
let eff = {}
|
||||
if (x.gte(0)) eff.first = Decimal.pow(25, x.pow(1.1))
|
||||
else eff.first = Decimal.pow(1/25, x.times(-1).pow(1.1))
|
||||
|
||||
if (x.gte(0)) eff.second = x.pow(0.8)
|
||||
else eff.second = x.times(-1).pow(0.8).times(-1)
|
||||
return eff;
|
||||
},
|
||||
display() { // Everything else displayed in the buyable button after the title
|
||||
let data = tmp[this.layer].buyables[this.id]
|
||||
return "Cost: " + format(data.cost) + " lollipops\n\
|
||||
Amount: " + player[this.layer].buyables[this.id] + "/4\n\
|
||||
Adds + " + format(data.effect.first) + " things and multiplies stuff by " + format(data.effect.second)
|
||||
},
|
||||
unlocked() { return player[this.layer].unlocked },
|
||||
canAfford() {
|
||||
return player[this.layer].points.gte(tmp[this.layer].buyables[this.id].cost)},
|
||||
buy() {
|
||||
let cost = tmp[this.layer].buyables[this.id].cost
|
||||
player[this.layer].points = player[this.layer].points.sub(cost)
|
||||
player[this.layer].buyables[this.id] = player[this.layer].buyables[this.id].add(1)
|
||||
player[this.layer].spentOnBuyables = player[this.layer].spentOnBuyables.add(cost) // This is a built-in system that you can use for respeccing but it only works with a single Decimal value
|
||||
},
|
||||
buyMax() {}, // You'll have to handle this yourself if you want
|
||||
style: {'height':'222px'},
|
||||
purchaseLimit: new Decimal(4),
|
||||
sellOne() {
|
||||
let amount = getBuyableAmount(this.layer, this.id)
|
||||
if (amount.lte(0)) return // Only sell one if there is at least one
|
||||
setBuyableAmount(this.layer, this.id, amount.sub(1))
|
||||
player[this.layer].points = player[this.layer].points.add(this.cost)
|
||||
},
|
||||
},
|
||||
},
|
||||
doReset(resettingLayer){ // Triggers when this layer is being reset, along with the layer doing the resetting. Not triggered by lower layers resetting, but is by layers on the same row.
|
||||
if(layers[resettingLayer].row > this.row) resetLayerData(this.layer, ["points"]) // This is actually the default behavior
|
||||
},
|
||||
layerShown() {return true}, // Condition for when layer appears on the tree
|
||||
automate() {
|
||||
}, // Do any automation inherent to this layer if appropriate
|
||||
resetsNothing() {return false},
|
||||
onPrestige() {
|
||||
return
|
||||
}, // Useful for if you gain secondary resources or have other interesting things happen to this layer when you reset it. You gain the currency after this function ends.
|
||||
|
||||
hotkeys: [
|
||||
{key: "c", description: "reset for lollipops or whatever", onPress(){if (layers[this.layer].canReset) resetLayer(this.layer)}},
|
||||
{key: "ctrl+c", description: "respec things", onPress(){layers[this.layer].buyables.respec()}, unlocked() {return hasUpgrade('c', '22')}} ,
|
||||
],
|
||||
increaseUnlockOrder: [], // Array of layer names to have their order increased when this one is first unlocked
|
||||
|
||||
microtabs: {
|
||||
stuff: {
|
||||
first: {
|
||||
content: ["upgrades", ["display-text", function() {return "confirmed"}]]
|
||||
},
|
||||
second: {
|
||||
embedLayer: "f",
|
||||
|
||||
content: [["upgrade", 11],
|
||||
["row", [["upgrade", 11], "blank", "blank", ["upgrade", 11],]],
|
||||
|
||||
["display-text", function() {return "double confirmed"}]]
|
||||
},
|
||||
},
|
||||
otherStuff: {
|
||||
// There could be another set of microtabs here
|
||||
}
|
||||
},
|
||||
|
||||
bars: {
|
||||
longBoi: {
|
||||
fillStyle: {'background-color' : "#FFFFFF"},
|
||||
baseStyle: {'background-color' : "#696969"},
|
||||
textStyle: {'color': '#04e050'},
|
||||
|
||||
borderStyle() {return {}},
|
||||
direction: RIGHT,
|
||||
width: 300,
|
||||
height: 30,
|
||||
progress() {
|
||||
return (player.points.add(1).log(10).div(10)).toNumber()
|
||||
},
|
||||
display() {
|
||||
return format(player.points) + " / 1e10 points"
|
||||
},
|
||||
unlocked: true,
|
||||
|
||||
},
|
||||
tallBoi: {
|
||||
fillStyle: {'background-color' : "#4BEC13"},
|
||||
baseStyle: {'background-color' : "#000000"},
|
||||
textStyle: {'text-shadow': '0px 0px 2px #000000'},
|
||||
|
||||
borderStyle() {return {'border-width': "7px"}},
|
||||
direction: UP,
|
||||
width: 50,
|
||||
height: 200,
|
||||
progress() {
|
||||
return player.points.div(100)
|
||||
},
|
||||
display() {
|
||||
return formatWhole((player.points.div(1)).min(100)) + "%"
|
||||
},
|
||||
unlocked: true,
|
||||
|
||||
},
|
||||
flatBoi: {
|
||||
fillStyle: {'background-color' : "#FE0102"},
|
||||
baseStyle: {'background-color' : "#222222"},
|
||||
textStyle: {'text-shadow': '0px 0px 2px #000000'},
|
||||
|
||||
borderStyle() {return {}},
|
||||
direction: UP,
|
||||
width: 100,
|
||||
height: 30,
|
||||
progress() {
|
||||
return player.c.points.div(50)
|
||||
},
|
||||
unlocked: true,
|
||||
|
||||
},
|
||||
},
|
||||
|
||||
// Optional, lets you format the tab yourself by listing components. You can create your own components in v.js.
|
||||
subtabs: {
|
||||
"main tab": {
|
||||
buttonStyle() {return {'color': 'orange'}},
|
||||
notify: true,
|
||||
display: `
|
||||
<div v-frag>
|
||||
<main-display />
|
||||
<sticky><prestige-button /></sticky>
|
||||
<resource-display />
|
||||
<spacer height="5px" />
|
||||
<button onclick='console.log("yeet")'>'HI'</button>
|
||||
<div>Name your points!</div>
|
||||
<TextField :value="player.c.thingy" @change="value => player.c.thingy = value" />
|
||||
<sticky style="color: red; font-size: 32px; font-family: Comic Sans MS;">I have {{ format(player.points) }} {{ player.c.thingy }} points!</sticky>
|
||||
<hr />
|
||||
<milestones />
|
||||
<spacer />
|
||||
<upgrades />
|
||||
<challenges />
|
||||
</div>
|
||||
`,
|
||||
glowColor: "blue",
|
||||
|
||||
},
|
||||
thingies: {
|
||||
resetNotify: true,
|
||||
style() {return {'background-color': '#222222', '--background': '#222222'}},
|
||||
buttonStyle() {return {'border-color': 'orange'}},
|
||||
display: `
|
||||
<div v-frag>
|
||||
<buyables />
|
||||
<spacer />
|
||||
<row style="width: 600px; height: 350px; background-color: green; border-style: solid;">
|
||||
<Toggle :value="player.c.beep" @change="value => player.c.beep = value" />
|
||||
<spacer width="30px" height="10px" />
|
||||
<div>Beep</div>
|
||||
<spacer />
|
||||
<vr height="200px"/>
|
||||
<column>
|
||||
<prestige-button style="width: 150px; height: 80px" />
|
||||
<prestige-button style="width: 100px; height: 150px" />
|
||||
</column>
|
||||
</row>
|
||||
<spacer />
|
||||
<img src="https://unsoftcapped2.github.io/The-Modding-Tree-2/discord.png" />
|
||||
</div>
|
||||
`
|
||||
},
|
||||
jail: {
|
||||
display: `
|
||||
<div v-frag>
|
||||
<infobox id="coolInfo" />
|
||||
<bar id="longBoi" />
|
||||
<spacer />
|
||||
<row>
|
||||
<column style="background-color: #555555; padding: 15px">
|
||||
<div style="color: teal">Sugar level:</div><spacer /><bar id="tallBoi" />
|
||||
</column>
|
||||
<spacer />
|
||||
<column>
|
||||
<div>idk</div>
|
||||
<spacer width="0" height="50px" />
|
||||
<bar id="flatBoi" />
|
||||
</column>
|
||||
</row>
|
||||
<spacer />
|
||||
<div>It's jail because "bars"! So funny! Ha ha!</div>
|
||||
<tree :nodes="[['f', 'c'], ['g', 'spook', 'h']]" />
|
||||
</div>
|
||||
`
|
||||
},
|
||||
illuminati: {
|
||||
unlocked() {return (hasUpgrade("c", 13))},
|
||||
display: `
|
||||
<div v-frag>
|
||||
<h1> C O N F I R M E D </h1>
|
||||
<spacer />
|
||||
<microtab family="stuff" style="width: 600px; height: 350px; background-color: brown; border-style: solid" />
|
||||
<div>Adjust how many points H gives you!</div>
|
||||
<Slider :value="player.c.otherThingy" @change="value => player.c.otherThingy = value" :min="1" :max="30" />
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
},
|
||||
style() {return {
|
||||
//'background-color': '#3325CC'
|
||||
}},
|
||||
nodeStyle() {return { // Style on the layer node
|
||||
'color': '#3325CC',
|
||||
'text-decoration': 'underline'
|
||||
}},
|
||||
glowColor: "orange", // If the node is highlighted, it will be this color (default is red)
|
||||
componentStyles: {
|
||||
"challenge"() {return {'height': '200px'}},
|
||||
"prestige-button"() {return {'color': '#AA66AA'}},
|
||||
},
|
||||
tooltip() { // Optional, tooltip displays when the layer is unlocked
|
||||
let tooltip = formatWhole(player[this.layer].points) + " " + this.resource
|
||||
if (player[this.layer].buyables[11].gt(0)) tooltip += "<br><i><br><br><br>" + formatWhole(player[this.layer].buyables[11]) + " Exhancers</i>"
|
||||
return tooltip
|
||||
},
|
||||
shouldNotify() { // Optional, layer will be highlighted on the tree if true.
|
||||
// Layer will automatically highlight if an upgrade is purchasable.
|
||||
return (player.c.buyables[11] == 1)
|
||||
},
|
||||
mark: "https://unsoftcapped2.github.io/The-Modding-Tree-2/discord.png",
|
||||
resetDescription: "Melt your points into ",
|
||||
};
|
106
src/data/layers/aca/f.js
Normal file
106
src/data/layers/aca/f.js
Normal file
|
@ -0,0 +1,106 @@
|
|||
import Decimal, { formatWhole } from '../../../util/bignum';
|
||||
import { player, tmp } from '../../../store/proxies';
|
||||
import { getClickableState } from '../../../util/features';
|
||||
|
||||
export default {
|
||||
id: "f",
|
||||
infoboxes:{
|
||||
coolInfo: {
|
||||
title: "Lore",
|
||||
titleStyle: {'color': '#FE0000'},
|
||||
body: "DEEP LORE!",
|
||||
bodyStyle: {'background-color': "#0000EE"}
|
||||
}
|
||||
},
|
||||
|
||||
startData() { return {
|
||||
unlocked: false,
|
||||
points: new Decimal(0),
|
||||
boop: false,
|
||||
clickables: {[11]: "Start"}, // Optional default Clickable state
|
||||
}},
|
||||
color: "#FE0102",
|
||||
requires() {return new Decimal(10)},
|
||||
resource: "farm points",
|
||||
baseResource: "points",
|
||||
baseAmount() {return player.points},
|
||||
type: "static",
|
||||
exponent: 0.5,
|
||||
base: 3,
|
||||
roundUpCost: true,
|
||||
canBuyMax() {return false},
|
||||
//directMult() {return new Decimal(player.c.otherThingy)},
|
||||
|
||||
row: 1,
|
||||
layerShown() {return true},
|
||||
branches: [{ target: "c", 'stroke-width': '25px', 'stroke': 'blue', style: 'filter: blur(5px)' }], // When this layer appears, a branch will appear from this layer to any layers here. Each entry can be a pair consisting of a layer id and a color.
|
||||
|
||||
tooltipLocked() { // Optional, tooltip displays when the layer is locked
|
||||
return ("This weird farmer dinosaur will only see you if you have at least " + this.requires + " points. You only have " + formatWhole(player.points))
|
||||
},
|
||||
midsection: '<div><br/><img src="https://images.beano.com/store/24ab3094eb95e5373bca1ccd6f330d4406db8d1f517fc4170b32e146f80d?auto=compress%2Cformat&dpr=1&w=390" /><div>Bork Bork!</div></div>',
|
||||
// The following are only currently used for "custom" Prestige type:
|
||||
prestigeButtonDisplay() { //Is secretly HTML
|
||||
if (!this.canBuyMax) return "Hi! I'm a <u>weird dinosaur</u> and I'll give you a Farm Point in exchange for all of your points and lollipops! (At least " + formatWhole(tmp[this.layer].nextAt) + " points)"
|
||||
if (this.canBuyMax) return "Hi! I'm a <u>weird dinosaur</u> and I'll give you <b>" + formatWhole(tmp[this.layer].resetGain) + "</b> Farm Points in exchange for all of your points and lollipops! (You'll get another one at " + formatWhole(tmp[this.layer].nextAtDisp) + " points)"
|
||||
},
|
||||
canReset() {
|
||||
return tmp[this.layer].baseAmount.gte(tmp[this.layer].nextAt)
|
||||
},
|
||||
// This is also non minimal, a Clickable!
|
||||
clickables: {
|
||||
|
||||
masterButtonPress() {
|
||||
if (getClickableState(this.layer, 11) == "Borkened...")
|
||||
player[this.layer].clickables[11] = "Start"
|
||||
},
|
||||
masterButtonText() {return (getClickableState(this.layer, 11) == "Borkened...") ? "Fix the clickable!" : "Does nothing"}, // Text on Respec button, optional
|
||||
11: {
|
||||
title: "Clicky clicky!", // Optional, displayed at the top in a larger font
|
||||
display() { // Everything else displayed in the buyable button after the title
|
||||
let data = getClickableState(this.layer, this.id)
|
||||
return "Current state:<br>" + data
|
||||
},
|
||||
unlocked() { return player[this.layer].unlocked },
|
||||
canClick() {
|
||||
return getClickableState(this.layer, this.id) !== "Borkened..."},
|
||||
onClick() {
|
||||
switch(getClickableState(this.layer, this.id)){
|
||||
case "Start":
|
||||
player[this.layer].clickables[this.id] = "A new state!"
|
||||
break;
|
||||
case "A new state!":
|
||||
player[this.layer].clickables[this.id] = "Keep going!"
|
||||
break;
|
||||
case "Keep going!":
|
||||
player[this.layer].clickables[this.id] = "Maybe that's a bit too far..."
|
||||
break;
|
||||
case "Maybe that's a bit too far...":
|
||||
//makeParticles(coolParticle, 4)
|
||||
player[this.layer].clickables[this.id] = "Borkened..."
|
||||
break;
|
||||
default:
|
||||
player[this.layer].clickables[this.id] = "Start"
|
||||
break;
|
||||
}
|
||||
},
|
||||
onHold(){
|
||||
console.log("Clickkkkk...")
|
||||
},
|
||||
style() {
|
||||
switch(getClickableState(this.layer, this.id)){
|
||||
case "Start":
|
||||
return {'background-color': 'green'}
|
||||
case "A new state!":
|
||||
return {'background-color': 'yellow'}
|
||||
case "Keep going!":
|
||||
return {'background-color': 'orange'}
|
||||
case "Maybe that's a bit too far...":
|
||||
return {'background-color': 'red'}
|
||||
default:
|
||||
return {}
|
||||
}},
|
||||
},
|
||||
},
|
||||
|
||||
}
|
|
@ -2,7 +2,7 @@ import Decimal, { format } from '../../util/bignum';
|
|||
import { player } from '../../store/proxies';
|
||||
import { layers } from '../../store/layers';
|
||||
import { hasUpgrade, hasMilestone, getBuyableAmount, setBuyableAmount, hasChallenge } from '../../util/features';
|
||||
import { canReset, doReset } from '../../util/layers';
|
||||
import { resetLayer } from '../../util/layers';
|
||||
|
||||
export default {
|
||||
id: "i",
|
||||
|
@ -26,13 +26,13 @@ export default {
|
|||
getResetGain(){
|
||||
|
||||
if (hasMilestone("p",12)){return getBuyableAmount("p",21).div(2).floor().times(2).times(5).sub(30).sub(player.i.points)}
|
||||
return (player.p.buyables[21].gte(layers.i.requires())?1:0)}, // Prestige currency exponent
|
||||
return (player.p.buyables[21].gte(layers.i.requires)?1:0)}, // Prestige currency exponent
|
||||
getNextAt(){return new Decimal(100)},
|
||||
canReset(){return player.p.buyables[21].gte(layers.i.requires())},
|
||||
prestigeButtonText(){return "Reset everything for +"+format(layers.i.getResetGain())+" Infinity.<br>You need "+format(layers.i.requires())+" pointy points to reset."},
|
||||
canReset(){return player.p.buyables[21].gte(layers.i.requires)},
|
||||
prestigeButtonText(){return "Reset everything for +"+format(layers.i.getResetGain)+" Infinity.<br>You need "+format(layers.i.requires())+" pointy points to reset."},
|
||||
row: 1, // Row the layer is in on the tree (0 is the first row)
|
||||
hotkeys: [
|
||||
{key: "i", description: "I: Infinity", onPress(){if (canReset(this.layer)) doReset(this.layer)}},
|
||||
{key: "i", description: "I: Infinity", onPress(){if (layers.i.canReset) resetLayer(this.layer)}},
|
||||
],
|
||||
layerShown(){return player[this.layer].unlocked||new Decimal(player.p.buyables[21]).gte(8)},
|
||||
milestones: {
|
||||
|
|
|
@ -2,7 +2,7 @@ import Decimal, { format } from '../../util/bignum';
|
|||
import { player } from '../../store/proxies';
|
||||
import { layers } from '../../store/layers';
|
||||
import { hasUpgrade, hasMilestone, getBuyableAmount, setBuyableAmount, hasChallenge } from '../../util/features';
|
||||
import { canReset, doReset } from '../../util/layers';
|
||||
import { resetLayer } from '../../util/layers';
|
||||
|
||||
export default {
|
||||
id: "p",
|
||||
|
@ -47,7 +47,7 @@ export default {
|
|||
},
|
||||
row: 0, // Row the layer is in on the tree (0 is the first row)
|
||||
hotkeys: [
|
||||
{key: "p", description: "P: Reset for prestige points", onPress(){if (canReset(this.layer)) doReset(this.layer)}},
|
||||
{key: "p", description: "P: Reset for prestige points", onPress(){if (layers.p.canReset) resetLayer(this.layer)}},
|
||||
],
|
||||
layerShown(){return true},
|
||||
upgrades:{
|
||||
|
@ -375,7 +375,7 @@ export default {
|
|||
cost(){return new Decimal(1)},
|
||||
canAfford(){return getBuyableAmount(this.layer,22).gte(this.cost())},
|
||||
pay(){setBuyableAmount(this.layer,22,getBuyableAmount(this.layer,22).sub(this.cost()))},
|
||||
unlocked(){return (hasMilestone("i",5)&&player.subtabs.p.mainTabs!="Upgrades")},
|
||||
unlocked(){return (hasMilestone("i",5)&&layers.p.activeSubtab!="Upgrades")},
|
||||
},
|
||||
212:{
|
||||
title: "Pointy",
|
||||
|
@ -383,7 +383,7 @@ export default {
|
|||
cost(){return new Decimal(2)},
|
||||
canAfford(){return getBuyableAmount(this.layer,22).gte(this.cost())},
|
||||
pay(){setBuyableAmount(this.layer,22,getBuyableAmount(this.layer,22).sub(this.cost()))},
|
||||
unlocked(){return (hasMilestone("i",5)&&player.subtabs.p.mainTabs!="Upgrades"&&hasUpgrade(this.layer,211))},
|
||||
unlocked(){return (hasMilestone("i",5)&&layers.p.activeSubtab!="Upgrades"&&hasUpgrade(this.layer,211))},
|
||||
},
|
||||
213:{
|
||||
title: "Time",
|
||||
|
@ -391,7 +391,7 @@ export default {
|
|||
cost(){return new Decimal(6)},
|
||||
canAfford(){return getBuyableAmount(this.layer,22).gte(this.cost())},
|
||||
pay(){setBuyableAmount(this.layer,22,getBuyableAmount(this.layer,22).sub(this.cost()))},
|
||||
unlocked(){return (hasMilestone("i",5)&&player.subtabs.p.mainTabs!="Upgrades"&&hasUpgrade(this.layer,212))},
|
||||
unlocked(){return (hasMilestone("i",5)&&layers.p.activeSubtab!="Upgrades"&&hasUpgrade(this.layer,212))},
|
||||
},
|
||||
214:{
|
||||
title: "^0",
|
||||
|
@ -399,7 +399,7 @@ export default {
|
|||
cost(){return new Decimal(11)},
|
||||
canAfford(){return getBuyableAmount(this.layer,22).gte(this.cost())},
|
||||
pay(){setBuyableAmount(this.layer,22,getBuyableAmount(this.layer,22).sub(this.cost()))},
|
||||
unlocked(){return (hasMilestone("i",5)&&player.subtabs.p.mainTabs!="Upgrades"&&hasUpgrade(this.layer,213))},
|
||||
unlocked(){return (hasMilestone("i",5)&&layers.p.activeSubtab!="Upgrades"&&hasUpgrade(this.layer,213))},
|
||||
},
|
||||
215:{
|
||||
title: "bulk",
|
||||
|
@ -407,7 +407,7 @@ export default {
|
|||
cost(){return new Decimal(27)},
|
||||
canAfford(){return getBuyableAmount(this.layer,22).gte(this.cost())},
|
||||
pay(){setBuyableAmount(this.layer,22,getBuyableAmount(this.layer,22).sub(this.cost()))},
|
||||
unlocked(){return (hasMilestone("i",5)&&player.subtabs.p.mainTabs!="Upgrades"&&hasUpgrade(this.layer,214))},
|
||||
unlocked(){return (hasMilestone("i",5)&&layers.p.activeSubtab!="Upgrades"&&hasUpgrade(this.layer,214))},
|
||||
},
|
||||
221:{
|
||||
title: "^-1",
|
||||
|
@ -415,7 +415,7 @@ export default {
|
|||
cost(){return new Decimal(28)},
|
||||
canAfford(){return getBuyableAmount(this.layer,22).gte(this.cost())},
|
||||
pay(){setBuyableAmount(this.layer,22,getBuyableAmount(this.layer,22).sub(this.cost()))},
|
||||
unlocked(){return (hasMilestone("i",5)&&player.subtabs.p.mainTabs!="Upgrades"&&hasUpgrade(this.layer,215))},
|
||||
unlocked(){return (hasMilestone("i",5)&&layers.p.activeSubtab!="Upgrades"&&hasUpgrade(this.layer,215))},
|
||||
},
|
||||
222:{
|
||||
title: "???",
|
||||
|
@ -423,7 +423,7 @@ export default {
|
|||
cost(){return new Decimal(90)},
|
||||
canAfford(){return getBuyableAmount(this.layer,22).gte(this.cost())},
|
||||
pay(){setBuyableAmount(this.layer,22,getBuyableAmount(this.layer,22).sub(this.cost()))},
|
||||
unlocked(){return (hasMilestone("i",5)&&player.subtabs.p.mainTabs!="Upgrades"&&hasUpgrade(this.layer,221))},
|
||||
unlocked(){return (hasMilestone("i",5)&&layers.p.activeSubtab!="Upgrades"&&hasUpgrade(this.layer,221))},
|
||||
},
|
||||
223:{
|
||||
title: "more automation",
|
||||
|
@ -431,7 +431,7 @@ export default {
|
|||
cost(){return new Decimal(96)},
|
||||
canAfford(){return getBuyableAmount(this.layer,22).gte(this.cost())},
|
||||
pay(){setBuyableAmount(this.layer,22,getBuyableAmount(this.layer,22).sub(this.cost()))},
|
||||
unlocked(){return (hasMilestone("i",5)&&player.subtabs.p.mainTabs!="Upgrades"&&hasUpgrade(this.layer,222))},
|
||||
unlocked(){return (hasMilestone("i",5)&&layers.p.activeSubtab!="Upgrades"&&hasUpgrade(this.layer,222))},
|
||||
},
|
||||
224:{
|
||||
title: "Generation",
|
||||
|
@ -439,7 +439,7 @@ export default {
|
|||
cost(){return new Decimal(100)},
|
||||
canAfford(){return getBuyableAmount(this.layer,22).gte(this.cost())},
|
||||
pay(){setBuyableAmount(this.layer,22,getBuyableAmount(this.layer,22).sub(this.cost()))},
|
||||
unlocked(){return (hasMilestone("i",5)&&player.subtabs.p.mainTabs!="Upgrades"&&hasUpgrade(this.layer,223))},
|
||||
unlocked(){return (hasMilestone("i",5)&&layers.p.activeSubtab!="Upgrades"&&hasUpgrade(this.layer,223))},
|
||||
},
|
||||
225:{
|
||||
title: "Boosters",
|
||||
|
@ -447,7 +447,7 @@ export default {
|
|||
cost(){return new Decimal(135)},
|
||||
canAfford(){return getBuyableAmount(this.layer,22).gte(this.cost())},
|
||||
pay(){setBuyableAmount(this.layer,22,getBuyableAmount(this.layer,22).sub(this.cost()))},
|
||||
unlocked(){return (hasMilestone("i",5)&&player.subtabs.p.mainTabs!="Upgrades"&&hasUpgrade(this.layer,224))},
|
||||
unlocked(){return (hasMilestone("i",5)&&layers.p.activeSubtab!="Upgrades"&&hasUpgrade(this.layer,224))},
|
||||
},
|
||||
231:{
|
||||
title: "Blue",
|
||||
|
@ -455,7 +455,7 @@ export default {
|
|||
cost(){return new Decimal(4)},
|
||||
canAfford(){return getBuyableAmount(this.layer,23).gte(this.cost())},
|
||||
pay(){setBuyableAmount(this.layer,23,getBuyableAmount(this.layer,23).sub(this.cost()))},
|
||||
unlocked(){return (player.subtabs.p.mainTabs!="Upgrades"&&hasMilestone(this.layer,11))},
|
||||
unlocked(){return (layers.p.activeSubtab!="Upgrades"&&hasMilestone(this.layer,11))},
|
||||
currencyDisplayName: "pointy boosters"
|
||||
},
|
||||
232:{
|
||||
|
@ -464,7 +464,7 @@ export default {
|
|||
cost(){return new Decimal(5)},
|
||||
canAfford(){return getBuyableAmount(this.layer,23).gte(this.cost())},
|
||||
pay(){setBuyableAmount(this.layer,23,getBuyableAmount(this.layer,23).sub(this.cost()))},
|
||||
unlocked(){return (player.subtabs.p.mainTabs!="Upgrades"&&hasMilestone(this.layer,12))},
|
||||
unlocked(){return (layers.p.activeSubtab!="Upgrades"&&hasMilestone(this.layer,12))},
|
||||
currencyDisplayName: "pointy boosters"
|
||||
},
|
||||
233:{
|
||||
|
@ -473,7 +473,7 @@ export default {
|
|||
cost(){return new Decimal(5)},
|
||||
canAfford(){return getBuyableAmount(this.layer,23).gte(this.cost())},
|
||||
pay(){setBuyableAmount(this.layer,23,getBuyableAmount(this.layer,23).sub(this.cost()))},
|
||||
unlocked(){return (player.subtabs.p.mainTabs!="Upgrades"&&hasMilestone(this.layer,12))},
|
||||
unlocked(){return (layers.p.activeSubtab!="Upgrades"&&hasMilestone(this.layer,12))},
|
||||
currencyDisplayName: "pointy boosters"
|
||||
},
|
||||
234:{
|
||||
|
@ -482,7 +482,7 @@ export default {
|
|||
cost(){return new Decimal(6)},
|
||||
canAfford(){return getBuyableAmount(this.layer,23).gte(this.cost())},
|
||||
pay(){setBuyableAmount(this.layer,23,getBuyableAmount(this.layer,23).sub(this.cost()))},
|
||||
unlocked(){return (player.subtabs.p.mainTabs!="Upgrades"&&hasMilestone(this.layer,12))},
|
||||
unlocked(){return (layers.p.activeSubtab!="Upgrades"&&hasMilestone(this.layer,12))},
|
||||
currencyDisplayName: "pointy boosters"
|
||||
},
|
||||
235:{
|
||||
|
@ -491,7 +491,7 @@ export default {
|
|||
cost(){return new Decimal(8)},
|
||||
canAfford(){return getBuyableAmount(this.layer,23).gte(this.cost())},
|
||||
pay(){setBuyableAmount(this.layer,23,getBuyableAmount(this.layer,23).sub(this.cost()))},
|
||||
unlocked(){return (player.subtabs.p.mainTabs!="Upgrades"&&hasMilestone(this.layer,12))},
|
||||
unlocked(){return (layers.p.activeSubtab!="Upgrades"&&hasMilestone(this.layer,12))},
|
||||
currencyDisplayName: "pointy boosters"
|
||||
},
|
||||
},
|
||||
|
@ -754,55 +754,55 @@ challenges:{
|
|||
requirementDescription: "1 reset",
|
||||
effectDescription: "Add 0.01 to base point gain and prestige requirement, and <b>1</b> doesn't reset upgrades",
|
||||
done() { return getBuyableAmount("p",11).gte(1) },
|
||||
unlocked(){return player.subtabs.p.mainTabs!="Pointy points"}
|
||||
unlocked(){return layers.p.activeSubtab!="Pointy points"}
|
||||
},
|
||||
1: {
|
||||
requirementDescription: "2 resets",
|
||||
effectDescription: "<b>2</b> and <b>3</b> don't reset upgrades, and start with the first 8 upgrades on reset",
|
||||
done() { return getBuyableAmount("p",11).gte(2) },
|
||||
unlocked(){return hasMilestone(this.layer,this.id-1)&&player.subtabs.p.mainTabs!="Pointy points"}
|
||||
unlocked(){return hasMilestone(this.layer,this.id-1)&&layers.p.activeSubtab!="Pointy points"}
|
||||
},
|
||||
2: {
|
||||
requirementDescription: "3 resets",
|
||||
effectDescription: "<b>4</b> doesn't reset upgrades, and permanently fix the bug where you can't buy upgrades when you have 1 prestige point",
|
||||
done() { return getBuyableAmount("p",11).gte(3) },
|
||||
unlocked(){return hasMilestone(this.layer,this.id-1)&&player.subtabs.p.mainTabs!="Pointy points"}
|
||||
unlocked(){return hasMilestone(this.layer,this.id-1)&&layers.p.activeSubtab!="Pointy points"}
|
||||
},
|
||||
3: {
|
||||
requirementDescription: "4 resets",
|
||||
effectDescription: "Don't reset challenges, add 1 to <b>Point</b> maximum completions, and start with 24 upgrades",
|
||||
done() { return getBuyableAmount("p",11).gte(4) },
|
||||
unlocked(){return hasMilestone(this.layer,this.id-1)&&player.subtabs.p.mainTabs!="Pointy points"}
|
||||
unlocked(){return hasMilestone(this.layer,this.id-1)&&layers.p.activeSubtab!="Pointy points"}
|
||||
},
|
||||
4: {
|
||||
requirementDescription: "5 resets",
|
||||
effectDescription: "Each useless upgrade adds 0.1 to base point gain",
|
||||
done() { return getBuyableAmount("p",11).gte(5) },
|
||||
unlocked(){return hasMilestone(this.layer,this.id-1)&&player.subtabs.p.mainTabs!="Pointy points"}
|
||||
unlocked(){return hasMilestone(this.layer,this.id-1)&&layers.p.activeSubtab!="Pointy points"}
|
||||
},
|
||||
5: {
|
||||
requirementDescription: "6 resets",
|
||||
effectDescription: "Unlock something",
|
||||
done() { return getBuyableAmount("p",11).gte(6) },
|
||||
unlocked(){return hasMilestone(this.layer,this.id-1)&&player.subtabs.p.mainTabs!="Pointy points"}
|
||||
unlocked(){return hasMilestone(this.layer,this.id-1)&&layers.p.activeSubtab!="Pointy points"}
|
||||
},
|
||||
6: {
|
||||
requirementDescription: "1 pointy point",
|
||||
effectDescription: "Unlock the upgrade tree",
|
||||
done() { return getBuyableAmount("p",21).gte(1) },
|
||||
unlocked(){return hasMilestone(this.layer,this.id-1)&&(hasUpgrade(this.layer,104)||player.i.unlocked)&&player.subtabs.p.mainTabs!="Pointy points"}
|
||||
unlocked(){return hasMilestone(this.layer,this.id-1)&&(hasUpgrade(this.layer,104)||player.i.unlocked)&&layers.p.activeSubtab!="Pointy points"}
|
||||
},
|
||||
7: {
|
||||
requirementDescription: "7 pointy points",
|
||||
effectDescription: "You can now buy both first and second row upgrade tree upgrades",
|
||||
done() { return getBuyableAmount("p",21).gte(7) },
|
||||
unlocked(){return hasMilestone(this.layer,this.id-1)&&(hasUpgrade(this.layer,111)||player.i.unlocked)&&player.subtabs.p.mainTabs!="Pointy points"}
|
||||
unlocked(){return hasMilestone(this.layer,this.id-1)&&(hasUpgrade(this.layer,111)||player.i.unlocked)&&layers.p.activeSubtab!="Pointy points"}
|
||||
},
|
||||
8: {
|
||||
requirementDescription: "8 pointy points",
|
||||
effectDescription: "Unlock another layer",
|
||||
done() { return getBuyableAmount("p",21).gte(8) },
|
||||
unlocked(){return hasMilestone(this.layer,this.id-1)&&(hasUpgrade(this.layer,141)||hasUpgrade(this.layer,143)||hasUpgrade(this.layer,142)||player.i.unlocked)&&player.subtabs.p.mainTabs!="Pointy points"}
|
||||
unlocked(){return hasMilestone(this.layer,this.id-1)&&(hasUpgrade(this.layer,141)||hasUpgrade(this.layer,143)||hasUpgrade(this.layer,142)||player.i.unlocked)&&layers.p.activeSubtab!="Pointy points"}
|
||||
},
|
||||
11: {
|
||||
requirementDescription: "3 boosters",
|
||||
|
|
|
@ -5,10 +5,43 @@ import Decimal from '../util/bignum';
|
|||
import modInfo from './modInfo';
|
||||
|
||||
// Import initial layers
|
||||
import demo from './layers/demo.js';
|
||||
import demoInfinity from './layers/demo-infinity.js';
|
||||
import f from './layers/aca/f.js';
|
||||
import c from './layers/aca/c.js';
|
||||
import a from './layers/aca/a.js';
|
||||
const g = {
|
||||
id: "g",
|
||||
symbol: "TH",
|
||||
branches: ["c"],
|
||||
color: '#6d3678',
|
||||
layerShown: true,
|
||||
canClick() {return player.points.gte(10)},
|
||||
tooltip: "Thanos your points",
|
||||
onClick() {
|
||||
player.points = player.points.div(2);
|
||||
console.log(this.layer);
|
||||
}
|
||||
};
|
||||
const h = {
|
||||
id: "h",
|
||||
branches: ["g", { target: 'flatBoi', featureType: 'bar', endOffset: { x: () => -50 + 100 * layers.c.bars.flatBoi.progress.toNumber() } }],
|
||||
layerShown: true,
|
||||
tooltip() {return "Restore your points to " + player.c.otherThingy},
|
||||
row: "side",
|
||||
canClick() {return player.points.lt(player.c.otherThingy)},
|
||||
onClick() {player.points = new Decimal(player.c.otherThingy)}
|
||||
};
|
||||
const spook = {
|
||||
id: "spook",
|
||||
row: 1,
|
||||
layerShown: "ghost",
|
||||
};
|
||||
|
||||
export const initialLayers = [ demo, demoInfinity ];
|
||||
const main = {
|
||||
id: 'main',
|
||||
display: '<tree />'
|
||||
}
|
||||
|
||||
export const initialLayers = [ main, f, c, a, g, h, spook ];
|
||||
|
||||
export function update(delta) {
|
||||
let gain = new Decimal(3.19)
|
||||
|
|
|
@ -10,10 +10,10 @@
|
|||
|
||||
"allowSmall": false,
|
||||
"defaultDecimalsShown": 2,
|
||||
"useHeader": false,
|
||||
"useHeader": true,
|
||||
"banner": null,
|
||||
"logo": null,
|
||||
"initialTabs": ["tree-tab", "p", "i"],
|
||||
"initialTabs": [ "main", "c" ],
|
||||
|
||||
"maxTickLength": 3600
|
||||
}
|
||||
|
|
|
@ -1,35 +1,52 @@
|
|||
const defaultTheme = {
|
||||
"--background": "#0f0f0f",
|
||||
"--background-tooltip": "rgba(0, 0, 0, 0.75)",
|
||||
"--secondary-background": "#0f0f0f",
|
||||
"--color": "#dfdfdf",
|
||||
"--points": "#ffffff",
|
||||
"--locked": "#bf8f8f",
|
||||
"--link": "#02f2f2",
|
||||
"--separator": "#dfdfdf",
|
||||
"--border-radius": "25%",
|
||||
"--danger": "rgb(220, 53, 69)",
|
||||
"--modal-border": "solid 2px var(--color)"
|
||||
variables: {
|
||||
"--background": "#0f0f0f",
|
||||
"--background-tooltip": "rgba(0, 0, 0, 0.75)",
|
||||
"--secondary-background": "#0f0f0f",
|
||||
"--color": "#dfdfdf",
|
||||
"--points": "#ffffff",
|
||||
"--locked": "#bf8f8f",
|
||||
"--bought": "#77bf5f",
|
||||
"--link": "#02f2f2",
|
||||
"--separator": "#dfdfdf",
|
||||
"--border-radius": "25%",
|
||||
"--danger": "rgb(220, 53, 69)",
|
||||
"--modal-border": "solid 2px var(--color)",
|
||||
"--feature-margin": "0px",
|
||||
},
|
||||
stackedInfoboxes: false,
|
||||
floatingTabs: true
|
||||
};
|
||||
|
||||
export default {
|
||||
default: defaultTheme,
|
||||
classic: defaultTheme,
|
||||
paper: {
|
||||
...defaultTheme,
|
||||
"--background": "#2a323d",
|
||||
"--secondary-background": "#333c4a",
|
||||
"--separator": "#333c4a",
|
||||
"--border-radius": "4px",
|
||||
"--modal-border": ""
|
||||
variables: {
|
||||
...defaultTheme.variables,
|
||||
"--background": "#2a323d",
|
||||
"--secondary-background": "#333c4a",
|
||||
"--locked": "#3a3e45",
|
||||
"--bought": "#5C8A58",
|
||||
"--separator": "#333c4a",
|
||||
"--border-radius": "4px",
|
||||
"--modal-border": "",
|
||||
"--feature-margin": "5px",
|
||||
},
|
||||
stackedInfoboxes: true,
|
||||
floatingTabs: false
|
||||
},
|
||||
aqua: {
|
||||
...defaultTheme,
|
||||
"--background": "#001f3f",
|
||||
"--background-tooltip": "rgba(0, 15, 31, 0.75)",
|
||||
"--secondary-background": "#001f3f",
|
||||
"--color": "#bfdfff",
|
||||
"--points": "#dfefff",
|
||||
"--locked": "#c4a7b3",
|
||||
"--separator": "#bfdfff"
|
||||
variables: {
|
||||
...defaultTheme.variables,
|
||||
"--background": "#001f3f",
|
||||
"--background-tooltip": "rgba(0, 15, 31, 0.75)",
|
||||
"--secondary-background": "#001f3f",
|
||||
"--color": "#bfdfff",
|
||||
"--points": "#dfefff",
|
||||
"--locked": "#c4a7b3",
|
||||
"--separator": "#bfdfff"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
49
src/main.css
Normal file
49
src/main.css
Normal file
|
@ -0,0 +1,49 @@
|
|||
* {
|
||||
transition-duration: 0.5s;
|
||||
font-family: "Roboto Mono", monospace;
|
||||
font-weight: bold;
|
||||
margin: auto;
|
||||
text-size-adjust: none;
|
||||
}
|
||||
|
||||
*:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow: hidden;
|
||||
min-width: 640px;
|
||||
transition: none;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
html, body, #app {
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
h1, h2, h3, b, input {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
a,
|
||||
.button,
|
||||
.link {
|
||||
display: block;
|
||||
color: var(--link);
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover,
|
||||
.button:hover,
|
||||
.link:hover {
|
||||
text-shadow: 5px 0 10px var(--link),
|
||||
-3px 0 12px var(--link);
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
}
|
23
src/main.js
23
src/main.js
|
@ -1,20 +1,25 @@
|
|||
import Vue from 'vue';
|
||||
import App from './App';
|
||||
import store from './store';
|
||||
import { layers, hotkeys } from './store/layers';
|
||||
import { addLayer} from './store/layers';
|
||||
import { setVue } from './util/vue';
|
||||
import './components/index';
|
||||
|
||||
// Setup
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
// Create Vue
|
||||
const vue = window.vue = new Vue({
|
||||
store,
|
||||
render: h => h(App),
|
||||
data: { layers, hotkeys }
|
||||
}).$mount('#app');
|
||||
requestAnimationFrame(async () => {
|
||||
// Add layers on second frame so dependencies can resolve
|
||||
const { initialLayers } = await import('./data/mod');
|
||||
initialLayers.forEach(addLayer);
|
||||
|
||||
setVue(vue);
|
||||
// Create Vue
|
||||
const vue = window.vue = new Vue({
|
||||
store,
|
||||
render: h => h(App)
|
||||
});
|
||||
setVue(vue);
|
||||
vue.$mount('#app');
|
||||
|
||||
// Start game loop
|
||||
// Start game loop
|
||||
});
|
||||
|
|
|
@ -1,37 +1,49 @@
|
|||
import { isPlainObject } from '../util/common';
|
||||
import { isFunction, isPlainObject } from '../util/common';
|
||||
import { createProxy, createGridProxy, player } from './proxies';
|
||||
import Decimal from '../util/bignum';
|
||||
import store from './index';
|
||||
|
||||
// Add layers on second frame so dependencies can resolve
|
||||
requestAnimationFrame(async () => {
|
||||
const { initialLayers } = await import('../data/mod');
|
||||
initialLayers.forEach(addLayer);
|
||||
});
|
||||
import { resetLayer, noCache, getStartingBuyables, getStartingClickables, getStartingChallenges } from '../util/layers';
|
||||
|
||||
export const layers = {};
|
||||
export const hotkeys = [];
|
||||
window.layers = layers;
|
||||
|
||||
export function addLayer(layer) {
|
||||
// Check for required properties
|
||||
if (requiredProperties.some(prop => !(prop in layer))) {
|
||||
console.error(`Cannot add layer without a ${requiredProperties.find(prop => !(prop in layer))} property!`, layer);
|
||||
if (!('id' in layer)) {
|
||||
console.error(`Cannot add layer without a "id" property!`, layer);
|
||||
return;
|
||||
}
|
||||
if (layer.type === "static" || layer.type === "normal") {
|
||||
const missingProperty = [ 'baseAmount', 'requires' ].find(prop => !(prop in layer));
|
||||
if (missingProperty) {
|
||||
console.error(`Cannot add layer without a "${missingProperty}" property!`, layer);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (layer.type === "static" && (layer.base == undefined || Decimal.lte(layer.base, 1))) {
|
||||
layer.base = 2;
|
||||
}
|
||||
|
||||
// Set default property values
|
||||
layer = Object.assign({}, defaultLayerProperties, layer);
|
||||
layer.layer = layer.id;
|
||||
if (layer.shown == undefined) {
|
||||
layer.shown = true;
|
||||
}
|
||||
if (layer.onClick != undefined) {
|
||||
layer.onClick.forceCached = false;
|
||||
}
|
||||
|
||||
const getters = {};
|
||||
|
||||
// Process each feature
|
||||
for (let property in gridProperties) {
|
||||
for (let property of gridProperties) {
|
||||
if (layer[property]) {
|
||||
setRowCol(layer[property]);
|
||||
}
|
||||
}
|
||||
for (let property in featureProperties) {
|
||||
for (let property of featureProperties) {
|
||||
if (layer[property]) {
|
||||
setupFeature(layer.id, layer[property]);
|
||||
}
|
||||
|
@ -45,6 +57,69 @@ export function addLayer(layer) {
|
|||
layer.upgrades[id].bought = function() {
|
||||
return !this.deactivated && player[layer.id].upgrades.some(upgrade => upgrade == id);
|
||||
}
|
||||
if (layer.upgrades[id].canAfford == undefined) {
|
||||
layer.upgrades[id].canAfford = function() {
|
||||
if (this.currencyInternalName) {
|
||||
let name = this.currencyInternalName;
|
||||
if (this.currencyLocation) {
|
||||
return !(this.currencyLocation[name].lt(this.cost));
|
||||
} else if (this.currencyLayer) {
|
||||
let lr = this.currencyLayer;
|
||||
return !(player[lr][name].lt(this.cost));
|
||||
} else {
|
||||
return !(player[name].lt(this.cost));
|
||||
}
|
||||
} else {
|
||||
return !(player[this.layer].points.lt(this.cost))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (layer.upgrades[id].pay == undefined) {
|
||||
layer.upgrades[id].pay = noCache(function() {
|
||||
if (this.bought || !this.canAfford) {
|
||||
return;
|
||||
}
|
||||
if (this.currencyInternalName) {
|
||||
let name = this.currencyInternalName
|
||||
if (this.currencyLocation) {
|
||||
if (this.currencyLocation[name].lt(this.cost)) {
|
||||
return;
|
||||
}
|
||||
this.currencyLocation[name] = this.currencyLocation[name].sub(this.cost);
|
||||
} else if (this.currencyLayer) {
|
||||
let lr = this.currencyLayer;
|
||||
if (player[lr][name].lt(this.cost)) {
|
||||
return;
|
||||
}
|
||||
player[lr][name] = player[lr][name].sub(this.cost);
|
||||
} else {
|
||||
if (player[name].lt(this.cost)) {
|
||||
return;
|
||||
}
|
||||
player[name] = player[name].sub(this.cost);
|
||||
}
|
||||
} else {
|
||||
if (player[this.layer].points.lt(this.cost)) {
|
||||
return;
|
||||
}
|
||||
player[this.layer].points = player[this.layer].points.sub(this.cost);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
layer.upgrades[id].pay.forceCached = false;
|
||||
}
|
||||
if (layer.upgrades[id].buy == undefined) {
|
||||
layer.upgrades[id].buy = noCache(function() {
|
||||
if (this.bought || !this.canAfford) {
|
||||
return;
|
||||
}
|
||||
this.pay();
|
||||
player[this.layer].upgrades.push(this.id);
|
||||
this.onPurchase?.();
|
||||
});
|
||||
} else {
|
||||
layer.upgrades[id].buy.forceCached = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -62,7 +137,7 @@ export function addLayer(layer) {
|
|||
}
|
||||
if (layer.challenges) {
|
||||
if (player[layer.id].challenges == undefined) {
|
||||
player[layer.id].challenges = {};
|
||||
player[layer.id].challenges = getStartingChallenges(layer);
|
||||
}
|
||||
for (let id in layer.challenges) {
|
||||
if (isPlainObject(layer.challenges[id])) {
|
||||
|
@ -75,21 +150,73 @@ export function addLayer(layer) {
|
|||
layer.challenges[id].maxed = function() {
|
||||
return !this.deactivated && Decimal.gte(player[layer.id].challenges[id], this.completionLimit);
|
||||
}
|
||||
if (layer.challenges[id].marked == undefined) {
|
||||
layer.challenges[id].marked = function() {
|
||||
if (layer.challenges[id].mark == undefined) {
|
||||
layer.challenges[id].mark = function() {
|
||||
return this.maxed;
|
||||
}
|
||||
}
|
||||
layer.challenges[id].active = function() {
|
||||
// TODO search for other rows that "count as" this challenge as well
|
||||
return !this.deactivated && (player[layer.id].activeChallenge === id || layers[layer.id].challenges[player[layer.id].activeChallenge]?.countsAs?.includes(id));
|
||||
return !this.deactivated && player[layer.id].activeChallenge === id;
|
||||
}
|
||||
if (layer.challenges[id].canComplete == undefined) {
|
||||
layer.challenges[id].canComplete = function() {
|
||||
if (this.active) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.currencyInternalName) {
|
||||
let name = this.currencyInternalName;
|
||||
if (this.currencyLocation) {
|
||||
return !(this.currencyLocation[name].lt(this.goal));
|
||||
} else if (this.currencyLayer) {
|
||||
let lr = this.currencyLayer;
|
||||
return !(player[lr][name].lt(this.goal));
|
||||
} else {
|
||||
return !(player[name].lt(this.goal));
|
||||
}
|
||||
} else {
|
||||
return !(player.points.lt(this.goal));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (layer.challenges[id].completionLimit == undefined) {
|
||||
layer.challenges[id].completionLimit = new Decimal(1);
|
||||
}
|
||||
layer.challenges[id].toggle = function() {
|
||||
let exiting = player[layer.id].activeChallenge === id;
|
||||
if (exiting) {
|
||||
if (this.canComplete && !this.maxed) {
|
||||
let completions = this.canComplete;
|
||||
if (completions === true) {
|
||||
completions = 1;
|
||||
}
|
||||
player[layer.id].challenges[id] =
|
||||
Decimal.min(player[layer.id].challenges[id].add(completions), this.completionLimit);
|
||||
this.onComplete?.();
|
||||
}
|
||||
player[layer.id].activeChallenge = null;
|
||||
this.onExit?.();
|
||||
resetLayer(layer.id, true);
|
||||
} else if (!exiting && this.canStart) {
|
||||
resetLayer(layer.id, true);
|
||||
player[layer.id].activeChallenge = id;
|
||||
this.onEnter?.();
|
||||
}
|
||||
}
|
||||
if (layer.challenges[id].canStart == undefined) {
|
||||
layer.challenges[id].canStart = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (layer.buyables) {
|
||||
if (player[layer.id].buyables == undefined) {
|
||||
player[layer.id].buyables = {};
|
||||
player[layer.id].buyables = getStartingBuyables(layer);
|
||||
}
|
||||
if (layer.buyables.reset == undefined) {
|
||||
layer.buyables.reset = noCache(function() {
|
||||
player[this.layer].buyables = getStartingBuyables(layer);
|
||||
});
|
||||
}
|
||||
for (let id in layer.buyables) {
|
||||
if (isPlainObject(layer.buyables[id])) {
|
||||
|
@ -100,17 +227,32 @@ export function addLayer(layer) {
|
|||
player[layer.id].buyables[id] = amount;
|
||||
}
|
||||
layer.buyables[id].canBuy = function() {
|
||||
return !this.deactivated && this.unlocked !== false && this.canAfford !== false && Decimal.lt(player[layer.id].buyables[id], this.purchaseLimit);
|
||||
return !this.deactivated && this.unlocked !== false && this.canAfford !== false &&
|
||||
Decimal.lt(player[layer.id].buyables[id], this.purchaseLimit);
|
||||
}
|
||||
if (layer.buyables[id].purchaseLimit == undefined) {
|
||||
layer.buyables[id].purchaseLimit = new Decimal(Infinity);
|
||||
}
|
||||
if (layer.buyables[id].cost != undefined && layer.buyables[id].buy == undefined) {
|
||||
layer.buyables[id].buy = noCache(function() {
|
||||
player[this.layer].points = player[this.layer].points.sub(this.cost());
|
||||
this.amount = this.amount.add(1);
|
||||
});
|
||||
} else {
|
||||
layer.buyables[id].buy.forceCached = false;
|
||||
}
|
||||
if (layer.buyables[id].sellOne != undefined) {
|
||||
layer.buyables[id].sellOne.forceCached = false;
|
||||
}
|
||||
if (layer.buyables[id].sellAll != undefined) {
|
||||
layer.buyables[id].sellAll.forceCached = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (layer.clickables) {
|
||||
if (player[layer.id].clickables == undefined) {
|
||||
player[layer.id].clickables = {};
|
||||
player[layer.id].clickables = getStartingClickables(layer);
|
||||
}
|
||||
for (let id in layer.clickables) {
|
||||
if (isPlainObject(layer.clickables[id])) {
|
||||
|
@ -129,6 +271,26 @@ export function addLayer(layer) {
|
|||
}
|
||||
for (let id in layer.milestones) {
|
||||
if (isPlainObject(layer.milestones[id])) {
|
||||
layer.milestones[id].shown = function() {
|
||||
if (!this.unlocked) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (player.msDisplay) {
|
||||
default:
|
||||
case "all":
|
||||
return true;
|
||||
case "last":
|
||||
return this.optionsDisplay || !this.earned ||
|
||||
player[this.layer].milestones[player[this.layer].milestones.length - 1] === this.id;
|
||||
case "configurable":
|
||||
return this.optionsDisplay || !this.earned;
|
||||
case "incomplete":
|
||||
return !this.earned;
|
||||
case "none":
|
||||
return false;
|
||||
}
|
||||
}
|
||||
layer.milestones[id].earned = function() {
|
||||
return !this.deactivated && player[layer.id].milestones.some(milestone => milestone == id);
|
||||
}
|
||||
|
@ -150,23 +312,62 @@ export function addLayer(layer) {
|
|||
if (layer.grids[id].getCanClick == undefined) {
|
||||
layer.grids[id].getCanClick = true;
|
||||
}
|
||||
if (layer.grids[id].getStartData == undefined) {
|
||||
layer.grids[id].getStartData = "";
|
||||
}
|
||||
layer.grids[id].data = function(cell) {
|
||||
return player[layer.id].grids[id][cell];
|
||||
if (player[layer.id].grids[id][cell] != undefined) {
|
||||
return player[layer.id].grids[id][cell];
|
||||
}
|
||||
if (isFunction(this.getStartData)) {
|
||||
return this.getStartData(cell);
|
||||
}
|
||||
return this.getStartData;
|
||||
}
|
||||
layer.grids[id].dataSet = function(cell, data) {
|
||||
player[layer.id].grids[id][cell] = data;
|
||||
}
|
||||
createGridProxy(layer.grids[id], getters, `${layer.id}/grids-${id}-`);
|
||||
layer.grids[id] = createGridProxy(layer.grids[id], getters, `${layer.id}/grids-${id}-`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (layer.subtabs) {
|
||||
for (let id in layer.subtabs) {
|
||||
if (isPlainObject(layer.subtabs[id])) {
|
||||
layer.subtabs[id].active = function() {
|
||||
return player.subtabs[this.layer]?.mainTabs === this.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
layer.activeSubtab = function() {
|
||||
if (this.subtabs != undefined) {
|
||||
if (player.subtabs[layer.id] in this.subtabs && this.subtabs[player.subtabs[layer.id]].unlocked !== false) {
|
||||
return player.subtabs[layer.id];
|
||||
if (this.subtabs[player.subtabs[layer.id]?.mainTabs] &&
|
||||
this.subtabs[player.subtabs[layer.id].mainTabs].unlocked !== false) {
|
||||
return this.subtabs[player.subtabs[layer.id].mainTabs];
|
||||
}
|
||||
return Object.keys(this.subtabs).find(subtab => this.subtabs[subtab].unlocked !== false);
|
||||
// Default to first unlocked tab
|
||||
return Object.values(this.subtabs).find(subtab => subtab.unlocked !== false);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (layer.microtabs) {
|
||||
for (let family in layer.microtabs) {
|
||||
for (let id in layer.microtabs[family]) {
|
||||
if (isPlainObject(layer.microtabs[family][id])) {
|
||||
layer.microtabs[family][id].layer = layer.id;
|
||||
layer.microtabs[family][id].family = family;
|
||||
layer.microtabs[family][id].id = id;
|
||||
layer.microtabs[family][id].active = function() {
|
||||
return player.subtabs[this.layer]?.[this.family] === this.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
layer.microtabs[family].activeMicrotab = function() {
|
||||
if (this[player.subtabs[layer.id][family]]?.unlocked !== false) {
|
||||
return this[player.subtabs[layer.id][family]];
|
||||
}
|
||||
// Default to first unlocked tab
|
||||
return Object.values(this).find(microtab => microtab.unlocked !== false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -205,14 +406,176 @@ export function reloadLayer(layer) {
|
|||
addLayer(layer);
|
||||
}
|
||||
|
||||
const requiredProperties = [ 'id' ];
|
||||
const defaultLayerProperties = {
|
||||
export const defaultLayerProperties = {
|
||||
type: "none",
|
||||
layerShown: true,
|
||||
glowColor: "red"
|
||||
glowColor: "red",
|
||||
displayRow() {
|
||||
return this.row;
|
||||
},
|
||||
symbol() {
|
||||
return this.id;
|
||||
},
|
||||
unlocked() {
|
||||
if (player[this.id].unlocked) {
|
||||
return true;
|
||||
}
|
||||
if (this.type !== "none" && this.canReset && this.layerShown) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
trueGlowColor() {
|
||||
if (this.subtabs) {
|
||||
for (let subtab of Object.values(this.subtabs)) {
|
||||
if (subtab.notify) {
|
||||
return subtab.glowColor || "red";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.microtabs) {
|
||||
for (let microtab of Object.values(this.microtabs)) {
|
||||
if (microtab.notify) {
|
||||
return microtab.glowColor || "red";
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.glowColor || "red";
|
||||
},
|
||||
resetGain() {
|
||||
if (this.type === "none" || this.type === "custom") {
|
||||
return new Decimal(0);
|
||||
}
|
||||
if (this.gainExp?.eq(0)) {
|
||||
return new Decimal(0);
|
||||
}
|
||||
if (this.baseAmount.lt(this.requires)) {
|
||||
return new Decimal(0);
|
||||
}
|
||||
if (this.type === "static") {
|
||||
if (!this.canBuyMax) {
|
||||
return new Decimal(1);
|
||||
}
|
||||
let gain = this.baseAmount.div(this.requires).div(this.gainMult || 1).max(1).log(this.base)
|
||||
.times(this.gainExp || 1).pow(Decimal.pow(this.exponent || 1, -1));
|
||||
gain = gain.times(this.directMult || 1);
|
||||
return gain.floor().sub(player[this.layer].points).add(1).max(1);
|
||||
}
|
||||
if (this.type === "normal") {
|
||||
let gain = this.baseAmount.div(this.requires).pow(this.exponent || 1).times(this.gainMult || 1)
|
||||
.pow(this.gainExp || 1);
|
||||
if (this.softcap && gain.gte(this.softcap)) {
|
||||
gain = gain.pow(this.softcapPower).times(this.softcap.pow(Decimal.sub(1, this.softcapPower)));
|
||||
}
|
||||
gain = gain.times(this.directMult || 1);
|
||||
return gain.floor().max(0);
|
||||
}
|
||||
// Unknown prestige type
|
||||
return new Decimal(0);
|
||||
},
|
||||
nextAt() {
|
||||
if (this.type === "none" || this.type === "custom") {
|
||||
return new Decimal(Infinity);
|
||||
}
|
||||
if (this.gainMult?.lte(0) || this.gainExp?.lte(0)) {
|
||||
return new Decimal(Infinity);
|
||||
}
|
||||
if (this.type === "static") {
|
||||
const amount = player[this.layer].points.div(this.directMult || 1);
|
||||
const extraCost = Decimal.pow(this.base, amount.pow(this.exponent || 1).div(this.gainExp || 1))
|
||||
.times(this.gainMult || 1);
|
||||
let cost = extraCost.times(this.requires).max(this.requires);
|
||||
if (this.roundUpCost) {
|
||||
cost = cost.ceil();
|
||||
}
|
||||
return cost;
|
||||
}
|
||||
if (this.type === "normal") {
|
||||
let next = this.resetGain.div(this.directMult || 1);
|
||||
if (this.softcap && next.gte(this.softcap)) {
|
||||
next = next.div(this.softcap.pow(Decimal.sub(1, this.softcapPower)))
|
||||
.pow(Decimal.div(1, this.softcapPower));
|
||||
}
|
||||
next = next.root(this.gainExp || 1).div(this.gainMult || 1).root(this.exponent || 1)
|
||||
.times(this.requires).max(this.requires);
|
||||
if (this.roundUpCost) {
|
||||
next = next.ceil();
|
||||
}
|
||||
return next;
|
||||
}
|
||||
// Unknown prestige type
|
||||
return new Decimal(0);
|
||||
},
|
||||
nextAtMax() {
|
||||
if (!this.canBuyMax || this.type !== "static") {
|
||||
return this.nextAt;
|
||||
}
|
||||
const amount = player[this.layer].points.plus(this.resetGain).div(this.directMult || 1);
|
||||
const extraCost = Decimal.pow(this.base, amount.pow(this.exponent || 1).div(this.gainExp || 1))
|
||||
.times(this.gainMult || 1);
|
||||
let cost = extraCost.times(this.requires).max(this.requires);
|
||||
if (this.roundUpCost) {
|
||||
cost = cost.ceil();
|
||||
}
|
||||
return cost;
|
||||
},
|
||||
canReset() {
|
||||
if (this.type === "normal") {
|
||||
return this.baseAmount.gte(this.requires);
|
||||
}
|
||||
if (this.type === "static") {
|
||||
return this.baseAmount.gte(this.nextAt);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
notify() {
|
||||
if (this.upgrades) {
|
||||
if (Object.values(this.upgrades).some(upgrade => upgrade.canAfford && !upgrade.bought && upgrade.unlocked)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (player[this.layer].activeChallenge && this.challenges[player[this.layer].activeChallenge].canComplete) {
|
||||
return true;
|
||||
}
|
||||
if (this.subtabs) {
|
||||
if (Object.values(this.subtabs).some(subtab => subtab.notify)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (this.microtabs) {
|
||||
if (Object.values(this.microtabs).some(subtab => subtab.notify)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
resetNotify() {
|
||||
if (this.subtabs) {
|
||||
if (Object.values(this.subtabs).some(subtab => subtab.prestigeNotify)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (this.microtabs) {
|
||||
if (Object.values(this.microtabs).some(microtab => microtab.prestigeNotify)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (this.autoPrestige || this.passiveGeneration) {
|
||||
return false;
|
||||
}
|
||||
if (this.type === "static") {
|
||||
return this.canReset;
|
||||
}
|
||||
if (this.type === "normal") {
|
||||
return this.canReset && this.resetGain.gte(player[this.layer].points.div(10));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const gridProperties = [ 'upgrades', 'achievements', 'challenges', 'buyables', 'clickables' ];
|
||||
const featureProperties = [ 'upgrades', 'achievements', 'challenges', 'buyables', 'clickables', 'milestones', 'bars', 'infoboxes', 'grids', 'hotkeys', 'subtabs' ];
|
||||
const featureProperties = [ 'upgrades', 'achievements', 'challenges', 'buyables', 'clickables', 'milestones', 'bars',
|
||||
'infoboxes', 'grids', 'hotkeys', 'subtabs' ];
|
||||
|
||||
function setRowCol(features) {
|
||||
if (features.rows && features.cols) {
|
||||
|
@ -235,11 +598,15 @@ function setRowCol(features) {
|
|||
}
|
||||
|
||||
function setupFeature(layer, features) {
|
||||
features.layer = layer;
|
||||
for (let id in features) {
|
||||
const feature = features[id];
|
||||
if (isPlainObject(feature)) {
|
||||
feature.id = id;
|
||||
feature.layer = layer;
|
||||
if (feature.unlocked == undefined) {
|
||||
feature.unlocked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ const playerHandler = {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!target[key].isProxy && !(target[key] instanceof Decimal) && isPlainObject(target[key])) {
|
||||
if (!target[key].isProxy && !(target[key] instanceof Decimal) && (isPlainObject(target[key]) || Array.isArray(target[key]))) {
|
||||
// Note that player isn't pre-created since it (shouldn't) have functions or getters
|
||||
// so creating proxies as they're requested is A-OK
|
||||
target[key] = new Proxy(target[key], playerHandler);
|
||||
|
@ -41,6 +41,9 @@ const playerHandler = {
|
|||
export const player = window.player = new Proxy(store.state, playerHandler);
|
||||
|
||||
export function createProxy(object, getters, prefix) {
|
||||
if (object.isProxy) {
|
||||
console.warn("Creating a proxy out of a proxy! This may cause unintentional function calls and stack overflows.");
|
||||
}
|
||||
const objectProxy = new Proxy(object, getHandler(prefix));
|
||||
travel(createProxy, object, objectProxy, getters, prefix);
|
||||
return objectProxy;
|
||||
|
@ -48,6 +51,9 @@ export function createProxy(object, getters, prefix) {
|
|||
|
||||
// TODO cache grid values? Currently they'll be calculated every render they're visible
|
||||
export function createGridProxy(object, getters, prefix) {
|
||||
if (object.isProxy) {
|
||||
console.warn("Creating a proxy out of a proxy! This may cause unintentional function calls and stack overflows.");
|
||||
}
|
||||
const objectProxy = new Proxy(object, getGridHandler(prefix));
|
||||
travel(createGridProxy, object, objectProxy, getters, prefix);
|
||||
return objectProxy;
|
||||
|
@ -59,14 +65,13 @@ function travel(callback, object, objectProxy, getters, prefix) {
|
|||
continue;
|
||||
}
|
||||
if (isFunction(object[key])) {
|
||||
// Skip any functions that require a parameter, since they can't be cached through vuex
|
||||
if (object[key].length !== 0) {
|
||||
if ((object[key].length !== 0 && object[key].forceCached !== true) || object[key].forceCached === false) {
|
||||
continue;
|
||||
}
|
||||
getters[`${prefix}${key}`] = () => {
|
||||
return object[key].call(objectProxy);
|
||||
}
|
||||
} else if (isPlainObject(object[key])) {
|
||||
} else if ((isPlainObject(object[key]) || Array.isArray(object[key])) && !(object[key] instanceof Decimal)) {
|
||||
object[key] = callback(object[key], getters, `${prefix}${key}-`);
|
||||
}
|
||||
}
|
||||
|
@ -83,9 +88,9 @@ function getHandler(prefix) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (target[key].isProxy) {
|
||||
if (target[key].isProxy || target[key] instanceof Decimal) {
|
||||
return target[key];
|
||||
} else if (isPlainObject(target[key])) {
|
||||
} else if ((isPlainObject(target[key]) || Array.isArray(target[key])) && key.slice(0, 2) !== '__') {
|
||||
console.warn("Creating proxy outside `createProxy`. This may cause issues when calling proxied functions.",
|
||||
target, key);
|
||||
target[key] = new Proxy(target[key], getHandler(`${prefix}${key}-`));
|
||||
|
@ -103,6 +108,7 @@ function getHandler(prefix) {
|
|||
set(target, key, value, receiver) {
|
||||
if (`${key}Set` in target && isFunction(target[`${key}Set`]) && target[`${key}Set`].length < 2) {
|
||||
target[`${key}Set`].call(receiver, value);
|
||||
return true;
|
||||
} else {
|
||||
console.warn(`No setter for "${key}".`, target);
|
||||
}
|
||||
|
@ -117,9 +123,9 @@ function getGridHandler(prefix) {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (target[key].isProxy) {
|
||||
if (target[key] && (target[key].isProxy || target[key] instanceof Decimal)) {
|
||||
return target[key];
|
||||
} else if (isPlainObject(target[key])) {
|
||||
} else if (isPlainObject(target[key]) || Array.isArray(target[key])) {
|
||||
console.warn("Creating proxy outside `createProxy`. This may cause issues when calling proxied functions.",
|
||||
target, key);
|
||||
target[key] = new Proxy(target[key], getHandler(`${prefix}${key}-`));
|
||||
|
@ -132,7 +138,7 @@ function getGridHandler(prefix) {
|
|||
return target[key].bind(receiver);
|
||||
}
|
||||
}
|
||||
if (!isNaN(key) && parseInt(key) > 100) {
|
||||
if (!isNaN(key)) {
|
||||
target[key] = new Proxy(target, getCellHandler(key));
|
||||
}
|
||||
return target[key];
|
||||
|
@ -140,6 +146,7 @@ function getGridHandler(prefix) {
|
|||
set(target, key, value, receiver) {
|
||||
if (`${key}Set` in target && isFunction(target[`${key}Set`]) && target[`${key}Set`].length < 2) {
|
||||
target[`${key}Set`].call(receiver, value);
|
||||
return true;
|
||||
} else {
|
||||
console.warn(`No setter for "${key}".`, target);
|
||||
}
|
||||
|
@ -156,31 +163,36 @@ function getCellHandler(id) {
|
|||
|
||||
let prop = target[key];
|
||||
if (isFunction(prop)) {
|
||||
// TODO explicitly list functions that don't receive cell data?
|
||||
if (prop.length < 2) {
|
||||
return prop.call(receiver, id);
|
||||
} else {
|
||||
return prop.call(receiver, receiver.data, id);
|
||||
}
|
||||
return prop.call(receiver, id, target.data(id));
|
||||
} else if (prop != undefined) {
|
||||
return prop;
|
||||
}
|
||||
|
||||
prop = target[`get${key.slice(0, 1).toUpperCase() + key.slice(1)}`];
|
||||
if (isFunction(prop)) {
|
||||
// TODO explicitly list functions that don't receive cell data?
|
||||
if (prop.length < 2) {
|
||||
return prop.call(receiver, id);
|
||||
} else {
|
||||
return prop.call(receiver, receiver.data, id);
|
||||
}
|
||||
} else {
|
||||
if (key.slice == undefined) {
|
||||
return prop;
|
||||
}
|
||||
|
||||
key = key.slice(0, 1).toUpperCase() + key.slice(1);
|
||||
prop = target[`get${key}`];
|
||||
if (isFunction(prop)) {
|
||||
return prop.call(receiver, id, target.data(id));
|
||||
} else if (prop != undefined) {
|
||||
return prop;
|
||||
}
|
||||
|
||||
prop = target[`on${key}`];
|
||||
if (isFunction(prop)) {
|
||||
return () => prop.call(receiver, id, target.data(id));
|
||||
} else if (prop != undefined) {
|
||||
return prop;
|
||||
}
|
||||
|
||||
return target[key];
|
||||
},
|
||||
set(target, key, value, receiver) {
|
||||
if (`${key}Set` in target && isFunction(target[`${key}Set`]) && target[`${key}Set`].length < 3) {
|
||||
target[`${key}Set`].call(receiver, id, value);
|
||||
return true;
|
||||
} else {
|
||||
console.warn(`No setter for "${key}".`, target);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import Decimal from './bignum';
|
||||
|
||||
// Reference: https://stackoverflow.com/questions/7225407/convert-camelcasetext-to-sentence-case-text
|
||||
export function camelToTitle(camel) {
|
||||
let title = camel.replace(/([A-Z])/g, " $1");
|
||||
|
@ -12,3 +14,11 @@ export function isPlainObject(object) {
|
|||
export function isFunction(func) {
|
||||
return typeof func === 'function';
|
||||
}
|
||||
|
||||
export function softcap(value, cap, power = 0.5) {
|
||||
if (value.lte(cap)) {
|
||||
return value;
|
||||
} else {
|
||||
return value.pow(power).times(cap.pow(Decimal.sub(1, power)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +1,35 @@
|
|||
import { layers } from '../store/layers';
|
||||
|
||||
export function hasUpgrade(layer, id) {
|
||||
return layers[layer].upgrades[id].bought;
|
||||
return layers[layer]?.upgrades?.[id]?.bought;
|
||||
}
|
||||
|
||||
export function hasMilestone(layer, id) {
|
||||
return layers[layer].milestones[id].earned;
|
||||
return layers[layer]?.milestones?.[id]?.earned;
|
||||
}
|
||||
|
||||
export function hasAchievement(layer, id) {
|
||||
return layers[layer].achievements[id].earned;
|
||||
return layers[layer]?.achievements?.[id]?.earned;
|
||||
}
|
||||
|
||||
export function hasChallenge(layer, id) {
|
||||
return layers[layer].challenges[id].completed;
|
||||
return layers[layer]?.challenges?.[id]?.completed;
|
||||
}
|
||||
|
||||
export function maxedChallenge(layer, id) {
|
||||
return layers[layer].challenges[id].maxed;
|
||||
return layers[layer]?.challenges?.[id]?.maxed;
|
||||
}
|
||||
|
||||
export function challengeCompletions(layer, id) {
|
||||
return layers[layer].challenges[id].completions;
|
||||
return layers[layer]?.challenges?.[id]?.completions;
|
||||
}
|
||||
|
||||
export function inChallenge(layer, id) {
|
||||
return layers[layer].challenges[id].active;
|
||||
return layers[layer]?.challenges?.[id]?.active;
|
||||
}
|
||||
|
||||
export function getBuyableAmount(layer, id) {
|
||||
return layers[layer].buyables[id].amount;
|
||||
return layers[layer]?.buyables?.[id]?.amount;
|
||||
}
|
||||
|
||||
export function setBuyableAmount(layer, id, amt) {
|
||||
|
@ -37,7 +37,7 @@ export function setBuyableAmount(layer, id, amt) {
|
|||
}
|
||||
|
||||
export function getClickableState(layer, id) {
|
||||
return layers[layer].clickables[id].state;
|
||||
return layers[layer]?.clickables?.[id]?.state;
|
||||
}
|
||||
|
||||
export function setClickableState(layer, id, state) {
|
||||
|
@ -45,7 +45,7 @@ export function setClickableState(layer, id, state) {
|
|||
}
|
||||
|
||||
export function getGridData(layer, id, cell) {
|
||||
return layers[layer].grids[id][cell];
|
||||
return layers[layer]?.grids?.[id]?.[cell];
|
||||
}
|
||||
|
||||
export function setGridData(layer, id, cell, data) {
|
||||
|
@ -53,25 +53,25 @@ export function setGridData(layer, id, cell, data) {
|
|||
}
|
||||
|
||||
export function upgradeEffect(layer, id) {
|
||||
return layers[layer].upgrades[id].effect;
|
||||
return layers[layer]?.upgrades?.[id]?.effect;
|
||||
}
|
||||
|
||||
export function challengeEffect(layer, id) {
|
||||
return layers[layer].challenges[id].rewardEffect;
|
||||
return layers[layer]?.challenges?.[id]?.rewardEffect;
|
||||
}
|
||||
|
||||
export function buyableEffect(layer, id) {
|
||||
return layers[layer].buyables[id].effect;
|
||||
return layers[layer]?.buyables?.[id]?.effect;
|
||||
}
|
||||
|
||||
export function clickableEffect(layer, id) {
|
||||
return layers[layer].clickables[id].effect;
|
||||
return layers[layer]?.clickables?.[id]?.effect;
|
||||
}
|
||||
|
||||
export function achievementEffect(layer, id) {
|
||||
return layers[layer].achievements[id].effect;
|
||||
return layers[layer]?.achievements?.[id]?.effect;
|
||||
}
|
||||
|
||||
export function gridEffect(layer, id, cell) {
|
||||
return layers[layer].grids[id][cell].effect;
|
||||
return layers[layer]?.grids?.[id]?.[cell]?.effect;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,49 @@
|
|||
export function canReset(layer) {
|
||||
console.warn("Not yet implemented!", layer);
|
||||
import Decimal from './bignum';
|
||||
import { isPlainObject } from './common';
|
||||
|
||||
// TODO make layer.reset(force = false)
|
||||
export function resetLayer(layer, force = false) {
|
||||
console.warn("Not yet implemented!", layer, force);
|
||||
}
|
||||
|
||||
export function doReset(layer) {
|
||||
console.warn("Not yet implemented!", layer);
|
||||
// TODO make layer.resetData(keep = [])
|
||||
export function resetLayerData(layer, keep = []) {
|
||||
console.warn("Not yet implemented!", layer, keep);
|
||||
}
|
||||
|
||||
export function layerDataReset(layer) {
|
||||
console.warn("Not yet implemented!", layer);
|
||||
export function cache(func) {
|
||||
func.forceCached = true;
|
||||
return func;
|
||||
}
|
||||
|
||||
export function noCache(func) {
|
||||
func.forceCached = false;
|
||||
return func;
|
||||
}
|
||||
|
||||
export function getStartingBuyables(layer) {
|
||||
return layer.buyables && Object.keys(layer.buyables).reduce((acc, curr) => {
|
||||
if (isPlainObject(layer.buyables[curr])) {
|
||||
acc[curr] = new Decimal(0);
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
export function getStartingClickables(layer) {
|
||||
return layer.clickables && Object.keys(layer.clickables).reduce((acc, curr) => {
|
||||
if (isPlainObject(layer.clickables[curr])) {
|
||||
acc[curr] = "";
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
export function getStartingChallenges(layer) {
|
||||
return layer.challenges && Object.keys(layer.challenges).reduce((acc, curr) => {
|
||||
if (isPlainObject(layer.challenges[curr])) {
|
||||
acc[curr] = new Decimal(0);
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import modInfo from '../data/modInfo';
|
||||
import { getStartingData, initialLayers } from '../data/mod';
|
||||
import { getStartingBuyables, getStartingClickables, getStartingChallenges } from './layers';
|
||||
|
||||
export function getInitialStore() {
|
||||
return {
|
||||
|
@ -12,11 +13,22 @@ export function getInitialStore() {
|
|||
hasNaN: false,
|
||||
lastTenTicks: [],
|
||||
showTPS: true,
|
||||
msDisplay: "all",
|
||||
hideChallenges: false,
|
||||
theme: "paper",
|
||||
subtabs: {},
|
||||
...getStartingData(),
|
||||
...initialLayers.reduce((acc, layer) => {
|
||||
acc[layer.id] = layer.startData();
|
||||
acc[layer.id] = {
|
||||
upgrades: [],
|
||||
achievements: [],
|
||||
milestones: [],
|
||||
infoboxes: {},
|
||||
buyables: getStartingBuyables(layer),
|
||||
clickables: getStartingClickables(layer),
|
||||
challenges: getStartingChallenges(layer),
|
||||
...layer.startData?.()
|
||||
};
|
||||
return acc;
|
||||
}, {})
|
||||
}
|
||||
|
|
|
@ -8,25 +8,44 @@ export function setVue(vm) {
|
|||
}
|
||||
|
||||
// Pass in various data that the template could potentially use
|
||||
const defaultComputed = {
|
||||
const computed = {
|
||||
player() { return player; },
|
||||
layers() { return layers; }
|
||||
};
|
||||
const defaultData = function() {
|
||||
const data = function() {
|
||||
return { Decimal, ...numberUtils };
|
||||
}
|
||||
export function coerceComponent(component) {
|
||||
if (typeof component === 'string' && !(component in vue.$options.components)) {
|
||||
let computed = defaultComputed;
|
||||
let data = defaultData;
|
||||
if (component.charAt(0) !== '<') {
|
||||
// Not a template string, so make it one and remove the data
|
||||
component = `<span>${component}</span>`;
|
||||
computed = null;
|
||||
data = null;
|
||||
}
|
||||
export function coerceComponent(component, defaultWrapper = 'span') {
|
||||
if (typeof component === 'number') {
|
||||
component = "" + component;
|
||||
}
|
||||
if (typeof component === 'string') {
|
||||
component = component.trim();
|
||||
if (!(component in vue.$options.components)) {
|
||||
if (component.charAt(0) !== '<') {
|
||||
component = `<${defaultWrapper}>${component}</${defaultWrapper}>`;
|
||||
}
|
||||
|
||||
return { template: component, computed, data };
|
||||
return { template: component, computed, data, inject: [ 'tab' ] };
|
||||
}
|
||||
}
|
||||
return component;
|
||||
}
|
||||
|
||||
export function getFiltered(objects, filter = null) {
|
||||
if (filter) {
|
||||
return Object.keys(objects)
|
||||
.filter(key => key in filter)
|
||||
.reduce((acc, curr) => {
|
||||
acc[curr] = objects[curr];
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
return objects;
|
||||
}
|
||||
|
||||
export const UP = 'UP';
|
||||
export const DOWN = 'DOWN';
|
||||
export const LEFT = 'LEFT';
|
||||
export const RIGHT = 'RIGHT';
|
||||
export const DEFAULT = 'DEFAULT';
|
||||
|
|
Loading…
Reference in a new issue