Vue 3 Migration

This commit is contained in:
thepaperpilot 2021-07-24 17:08:52 -05:00
parent 8103377ab0
commit 8fe91e88c9
75 changed files with 40190 additions and 28102 deletions

15063
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -8,30 +8,24 @@
"lint": "vue-cli-service lint" "lint": "vue-cli-service lint"
}, },
"dependencies": { "dependencies": {
"@ivanv/vue-collapse-transition": "^1.0.2",
"core-js": "^3.6.5", "core-js": "^3.6.5",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"portal-vue": "^2.1.7", "vue": "^3.1.4",
"simplebar-vue": "^1.6.4", "vue-next-select": "^2.7.0",
"vue": "^2.6.11",
"vue-frag": "^1.1.5",
"vue-reactive-provide": "^0.3.0",
"vue-select": "^3.11.2",
"vue-sortable": "github:Netbel/vue-sortable#master-fix", "vue-sortable": "github:Netbel/vue-sortable#master-fix",
"vue-textarea-autosize": "^1.1.1", "vue-textarea-autosize": "^1.1.1",
"vue-transition-expand": "^0.1.0", "vue-transition-expand": "^0.1.0"
"vuex": "^3.4.0"
}, },
"devDependencies": { "devDependencies": {
"@types/vue-select": "^3.11.1",
"@vue/cli-plugin-babel": "~4.5.0", "@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0", "@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0", "@vue/cli-service": "~4.5.0",
"@vue/compiler-sfc": "^3.0.0-beta.1",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"eslint": "^6.7.2", "eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2", "eslint-plugin-vue": "^7.0.0-alpha.0",
"raw-loader": "^4.0.2", "raw-loader": "^4.0.2"
"vue-template-compiler": "^2.6.11"
}, },
"eslintConfig": { "eslintConfig": {
"root": true, "root": true,
@ -39,7 +33,7 @@
"node": true "node": true
}, },
"extends": [ "extends": [
"plugin:vue/essential", "plugin:vue/vue3-essential",
"eslint:recommended" "eslint:recommended"
], ],
"parserOptions": { "parserOptions": {

View file

@ -1,29 +1,23 @@
<template> <template>
<div id="app" @mousemove="updateMouse" :style="theme" :class="{ useHeader }"> <div id="modal-root" :style="theme" />
<div class="app" @mousemove="updateMouse" :style="theme" :class="{ useHeader }">
<Nav v-if="useHeader" /> <Nav v-if="useHeader" />
<Tabs /> <Tabs />
<TPS v-if="showTPS" /> <TPS v-if="showTPS" />
<GameOverScreen /> <GameOverScreen />
<NaNScreen /> <NaNScreen />
<portal-target name="modal-root" multiple />
</div> </div>
</template> </template>
<script> <script>
import Nav from './components/system/Nav';
import Tabs from './components/system/Tabs';
import TPS from './components/system/TPS';
import themes from './data/themes'; import themes from './data/themes';
import { mapState } from 'vuex'; import player from './game/player';
import { player } from './store/proxies';
import modInfo from './data/modInfo.json'; import modInfo from './data/modInfo.json';
import { mapState } from './util/vue';
import './main.css'; import './main.css';
export default { export default {
name: 'App', name: 'App',
components: {
Nav, Tabs, TPS
},
data() { data() {
return { useHeader: modInfo.useHeader }; return { useHeader: modInfo.useHeader };
}, },
@ -42,10 +36,18 @@ export default {
</script> </script>
<style scoped> <style scoped>
#app { .app {
background-color: var(--background); background-color: var(--background);
color: var(--color); color: var(--color);
display: flex; display: flex;
flex-flow: column; flex-flow: column;
min-height: 100%;
height: 100%;
}
#modal-root {
position: absolute;
min-height: 100%;
height: 100%;
} }
</style> </style>

View file

@ -1,11 +0,0 @@
#app .simplebar-scrollbar:before {
background: #eee;
}
.simplebar-horizontal.simplebar-hover,
.simplebar-horizontal.simplebar-hover .simplebar-scrollbar {
height: 22px;
}
.simplebar-vertical.simplebar-hover {
width: 22px;
}

View file

@ -10,7 +10,7 @@
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { coerceComponent } from '../../util/vue'; import { coerceComponent } from '../../util/vue';
export default { export default {

View file

@ -1,20 +1,20 @@
<template> <template>
<div v-if="filteredAchievements" class="table"> <div v-if="filteredAchievements" class="table">
<div v-frag v-if="filteredAchievements.rows && filteredAchievements.cols"> <template v-if="filteredAchievements.rows && filteredAchievements.cols">
<div v-for="row in filteredAchievements.rows" class="row" :key="row"> <div v-for="row in filteredAchievements.rows" class="row" :key="row">
<div v-for="col in filteredAchievements.cols" :key="col"> <div v-for="col in filteredAchievements.cols" :key="col">
<achievement v-if="filteredAchievements[row * 10 + col] !== undefined" class="align" :id="row * 10 + col" /> <achievement v-if="filteredAchievements[row * 10 + col] !== undefined" class="align" :id="row * 10 + col" />
</div> </div>
</div> </div>
</div> </template>
<div v-frag v-else> <template v-frag v-else>
<achievement v-for="(achievement, id) in filteredAchievements" :key="id" :id="id" /> <achievement v-for="(achievement, id) in filteredAchievements" :key="id" :id="id" />
</div> </template>
</div> </div>
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { getFiltered } from '../../util/vue'; import { getFiltered } from '../../util/vue';
export default { export default {

View file

@ -11,7 +11,7 @@
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { UP, DOWN, LEFT, RIGHT, DEFAULT, coerceComponent } from '../../util/vue'; import { UP, DOWN, LEFT, RIGHT, DEFAULT, coerceComponent } from '../../util/vue';
import Decimal from '../../util/bignum'; import Decimal from '../../util/bignum';

View file

@ -27,8 +27,8 @@
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { player } from '../../store/proxies'; import player from '../../game/player';
import { coerceComponent } from '../../util/vue'; import { coerceComponent } from '../../util/vue';
export default { export default {

View file

@ -2,15 +2,15 @@
<div v-if="filteredBuyables" class="table"> <div v-if="filteredBuyables" class="table">
<respec-button v-if="showRespec" style="margin-bottom: 12px;" :confirmRespec="confirmRespec" <respec-button v-if="showRespec" style="margin-bottom: 12px;" :confirmRespec="confirmRespec"
@set-confirm-respec="setConfirmRespec" @respec="respec" /> @set-confirm-respec="setConfirmRespec" @respec="respec" />
<div v-frag v-if="filteredBuyables.rows && filteredBuyables.cols"> <template v-if="filteredBuyables.rows && filteredBuyables.cols">
<div v-for="row in filteredBuyables.rows" class="row" :key="row"> <div v-for="row in filteredBuyables.rows" class="row" :key="row">
<div v-for="col in filteredBuyables.cols" :key="col"> <div v-for="col in filteredBuyables.cols" :key="col">
<buyable v-if="filteredBuyables[row * 10 + col] !== undefined" class="align buyable-container" :style="{ height }" <buyable v-if="filteredBuyables[row * 10 + col] !== undefined" class="align buyable-container" :style="{ height }"
:id="row * 10 + col" :size="height === 'inherit' ? null : height" /> :id="row * 10 + col" :size="height === 'inherit' ? null : height" />
</div> </div>
</div> </div>
</div> </template>
<row v-frag v-else> <row v-else>
<buyable v-for="(buyable, id) in filteredBuyables" :key="id" class="align buyable-container" <buyable v-for="(buyable, id) in filteredBuyables" :key="id" class="align buyable-container"
:style="{ height }" :id="id" :size="height === 'inherit' ? null : height" /> :style="{ height }" :id="id" :size="height === 'inherit' ? null : height" />
</row> </row>
@ -18,8 +18,8 @@
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { player } from '../../store/proxies'; import player from '../../game/player';
import { getFiltered } from '../../util/vue'; import { getFiltered } from '../../util/vue';
export default { export default {
@ -33,6 +33,7 @@ export default {
default: "inherit" default: "inherit"
} }
}, },
emits: [ 'set-confirm-respec' ],
computed: { computed: {
filteredBuyables() { filteredBuyables() {
return getFiltered(layers[this.layer || this.tab.layer].buyables, this.buyables); return getFiltered(layers[this.layer || this.tab.layer].buyables, this.buyables);

View file

@ -20,7 +20,7 @@
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { coerceComponent } from '../../util/vue'; import { coerceComponent } from '../../util/vue';
export default { export default {

View file

@ -1,20 +1,20 @@
<template> <template>
<div v-if="filteredChallenges" class="table"> <div v-if="filteredChallenges" class="table">
<div v-frag v-if="filteredChallenges.rows && filteredChallenges.cols"> <template v-if="filteredChallenges.rows && filteredChallenges.cols">
<div v-for="row in filteredChallenges.rows" class="row" :key="row"> <div v-for="row in filteredChallenges.rows" class="row" :key="row">
<div v-for="col in filteredChallenges.cols" :key="col"> <div v-for="col in filteredChallenges.cols" :key="col">
<challenge v-if="filteredChallenges[row * 10 + col] !== undefined" :id="row * 10 + col" /> <challenge v-if="filteredChallenges[row * 10 + col] !== undefined" :id="row * 10 + col" />
</div> </div>
</div> </div>
</div> </template>
<div v-frag v-else> <row v-else>
<challenge v-for="(challenge, id) in filteredChallenges" :key="id" :id="id" /> <challenge v-for="(challenge, id) in filteredChallenges" :key="id" :id="id" />
</div> </row>
</div> </div>
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { getFiltered } from '../../util/vue'; import { getFiltered } from '../../util/vue';
export default { export default {

View file

@ -14,7 +14,7 @@
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { coerceComponent } from '../../util/vue'; import { coerceComponent } from '../../util/vue';
export default { export default {

View file

@ -1,23 +1,23 @@
<template> <template>
<div v-if="filteredClickables" class="table"> <div v-if="filteredClickables" class="table">
<master-button v-if="showMasterButton" style="margin-bottom: 12px;" @press="press" /> <master-button v-if="showMasterButton" style="margin-bottom: 12px;" @press="press" />
<div v-frag v-if="filteredClickables.rows && filteredClickables.cols"> <template v-if="filteredClickables.rows && filteredClickables.cols">
<div v-for="row in filteredClickables.rows" class="row" :key="row"> <div v-for="row in filteredClickables.rows" class="row" :key="row">
<div v-for="col in filteredClickables.cols" :key="col"> <div v-for="col in filteredClickables.cols" :key="col">
<clickable v-if="filteredClickables[row * 10 + col] !== undefined" class="align clickable-container" <clickable v-if="filteredClickables[row * 10 + col] !== undefined" class="align clickable-container"
:style="{ height }" :id="row * 10 + col" :size="height === 'inherit' ? null : height" /> :style="{ height }" :id="row * 10 + col" :size="height === 'inherit' ? null : height" />
</div> </div>
</div> </div>
</div> </template>
<div v-frag v-else> <row v-else>
<clickable v-for="(clickable, id) in filteredClickables" :key="id" class="align clickable-container" :style="{ height }" <clickable v-for="(clickable, id) in filteredClickables" :key="id" class="align clickable-container" :style="{ height }"
:id="id" :size="height === 'inherit' ? null : height" /> :id="id" :size="height === 'inherit' ? null : height" />
</div> </row>
</div> </div>
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { getFiltered } from '../../util/vue'; import { getFiltered } from '../../util/vue';
export default { export default {

View file

@ -1,14 +1,12 @@
<template> <template>
<div v-frag> <component :is="challengeDescription" v-bind="$attrs" />
<component :is="challengeDescription" />
<div>Goal: <component :is="goalDescription" /></div> <div>Goal: <component :is="goalDescription" /></div>
<div>Reward: <component :is="rewardDescription" /></div> <div>Reward: <component :is="rewardDescription" /></div>
<component v-if="rewardDisplay" :is="rewardDisplay" /> <component v-if="rewardDisplay" :is="rewardDisplay" />
</div>
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { coerceComponent } from '../../util/vue'; import { coerceComponent } from '../../util/vue';
export default { export default {
@ -29,7 +27,7 @@ export default {
if (this.challenge.goalDescription) { if (this.challenge.goalDescription) {
return coerceComponent(this.challenge.goalDescription); return coerceComponent(this.challenge.goalDescription);
} }
return coerceComponent(`{{ format(${this.challenge.goal}) }} ${this.currencyDisplayName || 'points'}`); return coerceComponent(`{{ format(${this.challenge.goal}) }} ${this.challenge.currencyDisplayName || 'points'}`);
}, },
rewardDescription() { rewardDescription() {
return coerceComponent(this.challenge.rewardDescription); return coerceComponent(this.challenge.rewardDescription);

View file

@ -8,8 +8,8 @@
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { player } from '../../store/proxies'; import player from '../../game/player';
import { format, formatWhole } from '../../util/bignum'; import { format, formatWhole } from '../../util/bignum';
export default { export default {

View file

@ -9,7 +9,7 @@
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { coerceComponent } from '../../util/vue'; import { coerceComponent } from '../../util/vue';
import { formatWhole } from '../../util/bignum'; import { formatWhole } from '../../util/bignum';

View file

@ -9,7 +9,7 @@
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
export default { export default {
name: 'grid', name: 'grid',

View file

@ -9,7 +9,7 @@
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { coerceComponent } from '../../util/vue'; import { coerceComponent } from '../../util/vue';
export default { export default {

View file

@ -4,18 +4,18 @@
<span class="toggle"></span> <span class="toggle"></span>
<component :is="title" /> <component :is="title" />
</button> </button>
<transition-expand> <collapse-transition>
<div v-if="!collapsed" class="body" :style="{ backgroundColor: this.borderColor }"> <div v-if="!collapsed" class="body" :style="{ backgroundColor: borderColor }">
<component :is="body" :style="bodyStyle" /> <component :is="body" :style="bodyStyle" />
</div> </div>
</transition-expand> </collapse-transition>
<branch-node :branches="infobox.branches" :id="id" featureType="infobox" /> <branch-node :branches="infobox.branches" :id="id" featureType="infobox" />
</div> </div>
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { player } from '../../store/proxies'; import player from '../../game/player';
import { coerceComponent } from '../../util/vue'; import { coerceComponent } from '../../util/vue';
import themes from '../../data/themes'; import themes from '../../data/themes';

View file

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<span v-if="showPrefix">You have</span> <span v-if="showPrefix">You have </span>
<resource :amount="amount" :color="color" /> <resource :amount="amount" :color="color" />
{{ resource }}<!-- remove whitespace --> {{ resource }}<!-- remove whitespace -->
<span v-if="effectDisplay">, <component :is="effectDisplay" /></span> <span v-if="effectDisplay">, <component :is="effectDisplay" /></span>
@ -9,8 +9,8 @@
</template> </template>
<script> <script>
import { player } from '../../store/proxies'; import player from '../../game/player';
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { format, formatWhole } from '../../util/bignum'; import { format, formatWhole } from '../../util/bignum';
import { coerceComponent } from '../../util/vue'; import { coerceComponent } from '../../util/vue';

View file

@ -5,8 +5,8 @@
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { player } from '../../store/proxies'; import player from '../../game/player';
import { coerceComponent } from '../../util/vue'; import { coerceComponent } from '../../util/vue';
export default { export default {
@ -16,6 +16,7 @@ export default {
layer: String, layer: String,
display: [ String, Object ] display: [ String, Object ]
}, },
emits: [ 'press' ],
computed: { computed: {
style() { style() {
return [ return [

View file

@ -8,7 +8,7 @@
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { coerceComponent } from '../../util/vue'; import { coerceComponent } from '../../util/vue';
export default { export default {

View file

@ -5,7 +5,7 @@
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { getFiltered } from '../../util/vue'; import { getFiltered } from '../../util/vue';
export default { export default {

View file

@ -7,7 +7,7 @@
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { resetLayer } from '../../util/layers'; import { resetLayer } from '../../util/layers';
import { coerceComponent } from '../../util/vue'; import { coerceComponent } from '../../util/vue';

View file

@ -9,8 +9,8 @@
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { player } from '../../store/proxies'; import player from '../../game/player';
import Decimal, { formatWhole } from '../../util/bignum'; import Decimal, { formatWhole } from '../../util/bignum';
export default { export default {

View file

@ -8,26 +8,28 @@
<component :is="respecButtonDisplay" /> <component :is="respecButtonDisplay" />
</button> </button>
<Modal :show="confirming" @close="cancel"> <Modal :show="confirming" @close="cancel">
<div slot="header"> <template v-slot:header>
<h2>Confirm Respec</h2> <h2>Confirm Respec</h2>
</div> </template>
<slot name="respec-warning" slot="body"> <template v-slot:body>
<slot name="respec-warning">
<div>Are you sure you want to respec? This will force you to do a {{ name }} respec as well!</div> <div>Are you sure you want to respec? This will force you to do a {{ name }} respec as well!</div>
</slot> </slot>
<div slot="footer"> </template>
<template v-slot:footer>
<div class="modal-footer"> <div class="modal-footer">
<div class="modal-flex-grow"></div> <div class="modal-flex-grow"></div>
<danger-button class="button modal-button" @click="confirm" skipConfirm>Yes</danger-button> <danger-button class="button modal-button" @click="confirm" skipConfirm>Yes</danger-button>
<button class="button modal-button" @click="cancel">Cancel</button> <button class="button modal-button" @click="cancel">Cancel</button>
</div> </div>
</div> </template>
</Modal> </Modal>
</div> </div>
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { player } from '../../store/proxies'; import player from '../../game/player';
import { coerceComponent } from '../../util/vue'; import { coerceComponent } from '../../util/vue';
export default { export default {
@ -43,6 +45,7 @@ export default {
confirmRespec: Boolean, confirmRespec: Boolean,
display: [ String, Object ] display: [ String, Object ]
}, },
emits: [ 'set-confirm-respec', 'respec' ],
computed: { computed: {
style() { style() {
return [ return [

View file

@ -15,7 +15,7 @@
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { coerceComponent } from '../../util/vue'; import { coerceComponent } from '../../util/vue';
export default { export default {

View file

@ -1,20 +1,20 @@
<template> <template>
<div v-if="filteredUpgrades" class="table"> <div v-if="filteredUpgrades" class="table">
<div v-frag v-if="filteredUpgrades.rows && filteredUpgrades.cols"> <template v-if="filteredUpgrades.rows && filteredUpgrades.cols">
<div v-for="row in filteredUpgrades.rows" class="row" :key="row"> <div v-for="row in filteredUpgrades.rows" class="row" :key="row">
<div v-for="col in filteredUpgrades.cols" :key="col"> <div v-for="col in filteredUpgrades.cols" :key="col">
<upgrade v-if="filteredUpgrades[row * 10 + col] !== undefined" class="align" :id="row * 10 + col" /> <upgrade v-if="filteredUpgrades[row * 10 + col] !== undefined" class="align" :id="row * 10 + col" />
</div> </div>
</div> </div>
</div> </template>
<div v-frag v-else> <row v-else>
<upgrade v-for="(upgrade, id) in filteredUpgrades" :key="id" class="align" :id="id" /> <upgrade v-for="(upgrade, id) in filteredUpgrades" :key="id" class="align" :id="id" />
</div> </row>
</div> </div>
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { getFiltered } from '../../util/vue'; import { getFiltered } from '../../util/vue';
export default { export default {

View file

@ -1,5 +1,5 @@
<template> <template>
<span class="container"> <span class="container" :class="{ confirming }">
<span v-if="confirming">Are you sure?</span> <span v-if="confirming">Are you sure?</span>
<button @click.stop="click" class="button danger" :disabled="disabled"> <button @click.stop="click" class="button danger" :disabled="disabled">
<span v-if="confirming">Yes</span> <span v-if="confirming">Yes</span>
@ -21,6 +21,7 @@ export default {
disabled: Boolean, disabled: Boolean,
skipConfirm: Boolean skipConfirm: Boolean
}, },
emits: [ 'click', 'confirmingChanged' ],
watch: { watch: {
confirming(newValue) { confirming(newValue) {
this.$emit('confirmingChanged', newValue); this.$emit('confirmingChanged', newValue);
@ -50,6 +51,10 @@ export default {
align-items: center; align-items: center;
} }
.container.confirming button {
font-size: 1em;
}
.container > * { .container > * {
margin: 0 4px; margin: 0 4px;
} }

View file

@ -14,6 +14,7 @@ export default {
props: { props: {
left: Boolean left: Boolean
}, },
emits: [ 'click' ],
methods: { methods: {
click() { click() {
this.$emit('click'); this.$emit('click');

View file

@ -1,64 +1,62 @@
<template> <template>
<div class="field"> <div class="field">
<span class="field-title" v-if="title">{{ title }}</span> <span class="field-title" v-if="title">{{ title }}</span>
<v-select :options="options" :value="value" @input="setSelected" /> <vue-select :options="options" :model-value="value" @update:modelValue="setSelected" label-by="label" :value-by="getValue" :placeholder="placeholder" :close-on-select="closeOnSelect" />
</div> </div>
</template> </template>
<script> <script>
import vSelect from 'vue-select';
import 'vue-select/dist/vue-select.css';
export default { export default {
name: 'Select', name: 'Select',
props: { props: {
title: String, title: String,
options: Array, // https://vue-select.org/guide/options.html#options-prop options: Array, // https://vue-select.org/guide/options.html#options-prop
value: [ String, Object ], value: [ String, Object ],
default: [ String, Object ] default: [ String, Object ],
}, placeholder: String,
components: { closeOnSelect: Boolean
vSelect
}, },
emits: [ 'change' ],
methods: { methods: {
setSelected(option) { setSelected(value) {
const value = option?.value || this.default; value = value || this.default;
this.$emit('change', value); this.$emit('change', value);
},
getValue(item) {
return item?.value;
} }
} }
}; };
</script> </script>
<style> <style>
.v-select { .vue-select {
width: 50%; width: 50%;
} }
.v-select .vs__dropdown-toggle { .field-buttons .vue-select {
border-color: rgba(var(--color), .26); width: unset;
margin: -1px 0;
} }
.v-select .vs__selected { .vue-select,
color: var(--color); .vue-dropdown {
}
.v-select .vs__clear,
.v-select .vs__open-indicator {
fill: var(--color);
opacity: .5;
}
.v-select .vs__dropdown-menu {
background: var(--background);
border-color: rgba(var(--color), .26); border-color: rgba(var(--color), .26);
} }
.v-select .vs__dropdown-option { .vue-dropdown {
background: var(--secondary-background);
}
.vue-dropdown-item {
color: var(--color); color: var(--color);
} }
.v-select .vs__open-indicator { .vue-dropdown-item.highlighted {
cursor: pointer; background-color: var(--background-tooltip);
}
.vue-dropdown-item.selected,
.vue-dropdown-item.highlighted.selected {
background-color: var(--bought);
} }
</style> </style>

View file

@ -15,7 +15,8 @@ export default {
value: Number, value: Number,
min: Number, min: Number,
max: Number max: Number
} },
emits: [ 'change' ]
}; };
</script> </script>

View file

@ -4,7 +4,7 @@
<span class="field-title" v-if="title">{{ title }}</span> <span class="field-title" v-if="title">{{ title }}</span>
<textarea-autosize v-if="textarea" :placeholder="placeholder" :value="value" :maxHeight="maxHeight" <textarea-autosize v-if="textarea" :placeholder="placeholder" :value="value" :maxHeight="maxHeight"
@input="value => $emit('change', value)" ref="field" /> @input="value => $emit('change', value)" ref="field" />
<input v-else type="text" :value="value" @input="e => $emit('input', e.target.value)" :placeholder="placeholder" ref="field" <input v-else type="text" :value="value" @input="e => $emit('change', e.target.value)" :placeholder="placeholder" ref="field"
:class="{ fullWidth: !title }" /> :class="{ fullWidth: !title }" />
</div> </div>
</form> </form>
@ -20,6 +20,7 @@ export default {
placeholder: String, placeholder: String,
maxHeight: Number maxHeight: Number
}, },
emits: [ 'change', 'submit', 'input' ],
mounted() { mounted() {
this.$refs.field.focus(); this.$refs.field.focus();
} }

View file

@ -13,6 +13,7 @@ export default {
title: String, title: String,
value: Boolean value: Boolean
}, },
emits: [ 'change' ],
methods: { methods: {
handleInput(e) { handleInput(e) {
this.$emit('change', e.target.checked); this.$emit('change', e.target.checked);

View file

@ -1,29 +1,28 @@
// Import and register all components, // Import and register all components,
// which will allow us to use them in any template strings anywhere in the project // which will allow us to use them in any template strings anywhere in the project
import Vue from 'vue'; //import TransitionExpand from 'vue-transition-expand';
//import 'vue-transition-expand/dist/vue-transition-expand.css';
/* from files */ import CollapseTransition from '@ivanv/vue-collapse-transition/src/CollapseTransition.vue';
const componentsContext = require.context('./');
componentsContext.keys().forEach(path => {
const component = componentsContext(path).default;
if (component) {
Vue.component(component.name, component);
}
});
/* from packages */
import frag from 'vue-frag';
Vue.directive('frag', frag);
import TransitionExpand from 'vue-transition-expand';
import 'vue-transition-expand/dist/vue-transition-expand.css';
Vue.use(TransitionExpand);
import VueTextareaAutosize from 'vue-textarea-autosize'; import VueTextareaAutosize from 'vue-textarea-autosize';
Vue.use(VueTextareaAutosize);
import PortalVue from 'portal-vue';
Vue.use(PortalVue);
import Sortable from 'vue-sortable'; import Sortable from 'vue-sortable';
Vue.use(Sortable); import VueNextSelect from 'vue-next-select';
import simplebar from 'simplebar-vue'; import 'vue-next-select/dist/index.css';
import 'simplebar/dist/simplebar.min.css';
Vue.component('simplebar', simplebar); export function registerComponents(vue) {
/* from files */
const componentsContext = require.context('./');
componentsContext.keys().forEach(path => {
const component = componentsContext(path).default;
if (component && !(component.name in vue._context.components)) {
vue.component(component.name, component);
}
});
/* from packages */
//vue.use(TransitionExpand);
vue.component('collapse-transition', CollapseTransition);
vue.use(VueTextareaAutosize);
vue.use(Sortable);
vue.component('vue-select', VueNextSelect);
}

View file

@ -1,5 +1,4 @@
<template> <template>
<div v-frag>
<infobox v-if="infobox != undefined" :id="infobox" /> <infobox v-if="infobox != undefined" :id="infobox" />
<main-display /> <main-display />
<sticky v-if="showPrestigeButton"><prestige-button /></sticky> <sticky v-if="showPrestigeButton"><prestige-button /></sticky>
@ -11,11 +10,10 @@
<upgrades /> <upgrades />
<challenges /> <challenges />
<achievements /> <achievements />
</div>
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { coerceComponent } from '../../util/vue'; import { coerceComponent } from '../../util/vue';
export default { export default {

View file

@ -1,12 +1,14 @@
<template> <template>
<Modal :show="show"> <Modal :show="show">
<div slot="header" class="game-over-modal-header"> <template v-slot:header>
<div class="game-over-modal-header">
<img class="game-over-modal-logo" v-if="logo" :src="logo" :alt="modInfo" /> <img class="game-over-modal-logo" v-if="logo" :src="logo" :alt="modInfo" />
<div class="game-over-modal-title"> <div class="game-over-modal-title">
<h2>Congratulations!</h2> <h2>Congratulations!</h2>
<h4>You've beaten {{ title }} v{{ versionNumber }}: {{ versionTitle }}</h4> <h4>You've beaten {{ title }} v{{ versionNumber }}: {{ versionTitle }}</h4>
</div> </div>
</div> </div>
</template>
<template v-slot:body="{ shown }"> <template v-slot:body="{ shown }">
<div v-if="shown"> <div v-if="shown">
<div>It took you {{ timePlayed }} to beat the game.</div> <div>It took you {{ timePlayed }} to beat the game.</div>
@ -21,17 +23,20 @@
</div> </div>
</div> </div>
</template> </template>
<div slot="footer" class="game-over-footer"> <template v-slot:footer>
<div class="game-over-footer">
<button @click="keepGoing" class="button">Keep Going</button> <button @click="keepGoing" class="button">Keep Going</button>
<button @click="playAgain" class="button danger">Play Again</button> <button @click="playAgain" class="button danger">Play Again</button>
</div> </div>
</template>
</Modal> </Modal>
</template> </template>
<script> <script>
import modInfo from '../../data/modInfo.json'; import modInfo from '../../data/modInfo.json';
import { hasWon } from '../../data/mod';
import { formatTime } from '../../util/bignum'; import { formatTime } from '../../util/bignum';
import { player } from '../../store/proxies'; import player from '../../game/player';
export default { export default {
name: 'GameOverScreen', name: 'GameOverScreen',
@ -44,7 +49,7 @@ export default {
return formatTime(player.timePlayed); return formatTime(player.timePlayed);
}, },
show() { show() {
return this.$store.getters.hasWon && !player.keepGoing; return hasWon.value && !player.keepGoing;
} }
}, },
methods: { methods: {

View file

@ -1,12 +1,14 @@
<template> <template>
<Modal :show="show" @close="$emit('closeDialog', 'Info')"> <Modal :show="show" @close="$emit('closeDialog', 'Info')">
<div slot="header" class="info-modal-header"> <template v-slot:header>
<div class="info-modal-header">
<img class="info-modal-logo" v-if="logo" :src="logo" :alt="title" /> <img class="info-modal-logo" v-if="logo" :src="logo" :alt="title" />
<div class="info-modal-title"> <div class="info-modal-title">
<h2>{{ title }}</h2> <h2>{{ title }}</h2>
<h4>v{{ versionNumber}}: {{ versionTitle }}</h4> <h4>v{{ versionNumber}}: {{ versionTitle }}</h4>
</div> </div>
</div> </div>
</template>
<template v-slot:body="{ shown }"> <template v-slot:body="{ shown }">
<div v-if="shown"> <div v-if="shown">
<div v-if="author"> <div v-if="author">
@ -59,7 +61,8 @@
<script> <script>
import modInfo from '../../data/modInfo.json'; import modInfo from '../../data/modInfo.json';
import { formatTime } from '../../util/bignum'; import { formatTime } from '../../util/bignum';
import { hotkeys } from '../../store/layers'; import { hotkeys } from '../../game/layers';
import player from '../../game/player';
export default { export default {
name: 'Info', name: 'Info',
@ -70,9 +73,10 @@ export default {
props: { props: {
show: Boolean show: Boolean
}, },
emits: [ 'closeDialog', 'openDialog' ],
computed: { computed: {
timePlayed() { timePlayed() {
return formatTime(this.$store.state.timePlayed); return formatTime(player.timePlayed);
}, },
hotkeys() { hotkeys() {
return Object.keys(hotkeys).reduce((acc, curr) => { return Object.keys(hotkeys).reduce((acc, curr) => {

View file

@ -1,20 +1,18 @@
<template> <template>
<div v-frag>
<slot /> <slot />
</div>
</template> </template>
<script> <script>
import { ReactiveProvideMixin } from 'vue-reactive-provide';
export default { export default {
name: 'LayerProvider', name: 'LayerProvider',
mixins: [ provide() {
ReactiveProvideMixin({ return {
name: 'tab', 'tab': {
props: true layer: this.layer,
}) index: this.index
], }
};
},
props: { props: {
layer: String, layer: String,
index: Number index: Number

View file

@ -21,8 +21,8 @@
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { player } from '../../store/proxies'; import player from '../../game/player';
import { coerceComponent } from '../../util/vue'; import { coerceComponent } from '../../util/vue';
import { isPlainObject } from '../../util/common'; import { isPlainObject } from '../../util/common';
import modInfo from '../../data/modInfo.json'; import modInfo from '../../data/modInfo.json';
@ -81,7 +81,7 @@ export default {
return null; return null;
}, },
activeSubtab() { activeSubtab() {
return layers[this.layer].activeSubtab.id; return layers[this.layer].activeSubtab?.id;
}, },
firstTab() { firstTab() {
if (this.forceFirstTab != undefined) { if (this.forceFirstTab != undefined) {
@ -101,13 +101,13 @@ export default {
tab.style.flexGrow = 0; tab.style.flexGrow = 0;
tab.style.flexShrink = 0; tab.style.flexShrink = 0;
tab.style.width = "60px"; tab.style.width = "60px";
tab.style.minWidth = null; tab.style.minWidth = tab.style.flexBasis = null;
tab.style.margin = 0; tab.style.margin = 0;
} else { } else {
tab.style.flexGrow = null; tab.style.flexGrow = null;
tab.style.flexShrink = null; tab.style.flexShrink = null;
tab.style.width = null; tab.style.width = null;
tab.style.minWidth = `${layers[this.layer].minWidth}px`; tab.style.minWidth = tab.style.flexBasis = `${layers[this.layer].minWidth}px`;
tab.style.margin = null; tab.style.margin = null;
} }
} }
@ -123,13 +123,13 @@ export default {
tab.style.flexGrow = 0; tab.style.flexGrow = 0;
tab.style.flexShrink = 0; tab.style.flexShrink = 0;
tab.style.width = "60px"; tab.style.width = "60px";
tab.style.minWidth = null; tab.style.minWidth = tab.style.flexBasis = null;
tab.style.margin = 0; tab.style.margin = 0;
} else { } else {
tab.style.flexGrow = null; tab.style.flexGrow = null;
tab.style.flexShrink = null; tab.style.flexShrink = null;
tab.style.width = null; tab.style.width = null;
tab.style.minWidth = `${layers[this.layer].minWidth}px`; tab.style.minWidth = tab.style.flexBasis = `${layers[this.layer].minWidth}px`;
tab.style.margin = null; tab.style.margin = null;
} }
} else { } else {
@ -180,7 +180,7 @@ export default {
bottom: 0; bottom: 0;
display: flex; display: flex;
padding: 0; padding: 0;
padding-top: 50px; padding-top: 55px;
margin: 0; margin: 0;
cursor: pointer; cursor: pointer;
font-size: 40px; font-size: 40px;
@ -299,3 +299,9 @@ export default {
text-shadow: 0 0 7px var(--color); text-shadow: 0 0 7px var(--color);
} }
</style> </style>
<style>
.subtabs-container + * {
margin-top: 20px;
}
</style>

View file

@ -12,8 +12,8 @@
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { player } from '../../store/proxies'; import player from '../../game/player';
import { coerceComponent } from '../../util/vue'; import { coerceComponent } from '../../util/vue';
import themes from '../../data/themes'; import themes from '../../data/themes';

View file

@ -1,7 +1,7 @@
<template> <template>
<portal to="modal-root"> <teleport to="#modal-root">
<transition name="modal" @before-enter="setAnimating(true)" @after-leave="setAnimating(false)"> <transition name="modal" @before-enter="setAnimating(true)" @after-leave="setAnimating(false)">
<div class="modal-mask" v-show="show" v-on:pointerdown.self="$emit('close')"> <div class="modal-mask" v-show="show" v-on:pointerdown.self="$emit('close')" v-bind="$attrs">
<div class="modal-wrapper"> <div class="modal-wrapper">
<div class="modal-container"> <div class="modal-container">
<div class="modal-header"> <div class="modal-header">
@ -9,13 +9,13 @@
default header default header
</slot> </slot>
</div> </div>
<simplebar class="modal-body"> <div class="modal-body">
<branches> <branches>
<slot name="body" :shown="isVisible"> <slot name="body" :shown="isVisible">
default body default body
</slot> </slot>
</branches> </branches>
</simplebar> </div>
<div class="modal-footer"> <div class="modal-footer">
<slot name="footer" :shown="isVisible"> <slot name="footer" :shown="isVisible">
<div class="modal-default-footer"> <div class="modal-default-footer">
@ -30,7 +30,7 @@
</div> </div>
</div> </div>
</transition> </transition>
</portal> </teleport>
</template> </template>
<script> <script>
@ -44,6 +44,7 @@ export default {
props: { props: {
show: Boolean show: Boolean
}, },
emits: [ 'close' ],
computed: { computed: {
isVisible() { isVisible() {
return this.show || this.isAnimating; return this.show || this.isAnimating;
@ -96,7 +97,6 @@ export default {
} }
.modal-body { .modal-body {
display: flex;
margin: 20px 0; margin: 20px 0;
width: 100%; width: 100%;
overflow-y: auto; overflow-y: auto;
@ -115,7 +115,7 @@ export default {
flex-grow: 1; flex-grow: 1;
} }
.modal-enter { .modal-enter-from {
opacity: 0; opacity: 0;
} }
@ -123,7 +123,7 @@ export default {
opacity: 0; opacity: 0;
} }
.modal-enter .modal-container, .modal-enter-from .modal-container,
.modal-leave-active .modal-container { .modal-leave-active .modal-container {
-webkit-transform: scale(1.1); -webkit-transform: scale(1.1);
transform: scale(1.1); transform: scale(1.1);

View file

@ -1,11 +1,12 @@
<template> <template>
<div v-frag> <Modal :show="hasNaN" v-bind="$attrs">
<Modal :show="show"> <template v-slot:header>
<div slot="header" class="nan-modal-header"> <div class="nan-modal-header">
<h2>NaN value detected!</h2> <h2>NaN value detected!</h2>
</div> </div>
<div slot="body"> </template>
<div>Attempted to assign NaN value to "{{ property }}" (previously {{ format(previous) }}). Auto-saving has been {{ autosave ? 'enabled' : 'disabled' }}. Check the console for more details, and consider sharing it with the developers on discord.</div> <template v-slot:body>
<div>Attempted to assign "{{ path }}" to NaN (previously {{ format(previous) }}). Auto-saving has been {{ autosave ? 'enabled' : 'disabled' }}. Check the console for more details, and consider sharing it with the developers on discord.</div>
<br> <br>
<div> <div>
<a :href="discordLink" class="nan-modal-discord-link"> <a :href="discordLink" class="nan-modal-discord-link">
@ -16,23 +17,25 @@
<br> <br>
<Toggle title="Autosave" :value="autosave" @change="setAutosave" /> <Toggle title="Autosave" :value="autosave" @change="setAutosave" />
<Toggle title="Pause game" :value="paused" @change="togglePaused" /> <Toggle title="Pause game" :value="paused" @change="togglePaused" />
</div> </template>
<div slot="footer" class="nan-footer"> <template v-slot:footer>
<div class="nan-footer">
<button @click="toggleSavesManager" class="button">Open Saves Manager</button> <button @click="toggleSavesManager" class="button">Open Saves Manager</button>
<button @click="setZero" class="button">Set to 0</button> <button @click="setZero" class="button">Set to 0</button>
<button @click="setOne" class="button">Set to 1</button> <button @click="setOne" class="button">Set to 1</button>
<button @click="setPrev" class="button" v-if="previous && previous.neq(0) && previous.neq(1)">Set to previous</button> <button @click="setPrev" class="button" v-if="previous && previous.neq(0) && previous.neq(1)">Set to previous</button>
<button @click="ignore" class="button danger">Ignore</button> <button @click="ignore" class="button danger">Ignore</button>
</div> </div>
</template>
</Modal> </Modal>
<SavesManager :show="showSaves" @closeDialog="toggleSavesManager" /> <SavesManager :show="showSaves" @closeDialog="toggleSavesManager" />
</div>
</template> </template>
<script> <script>
import modInfo from '../../data/modInfo.json'; import modInfo from '../../data/modInfo.json';
import Decimal, { format } from '../../util/bignum'; import Decimal, { format } from '../../util/bignum';
import { player } from '../../store/proxies'; import { mapState } from '../../util/vue';
import player from '../../game/player';
export default { export default {
name: 'NaNScreen', name: 'NaNScreen',
@ -41,36 +44,34 @@ export default {
return { discordName, discordLink, format, showSaves: false }; return { discordName, discordLink, format, showSaves: false };
}, },
computed: { computed: {
show() { ...mapState([ 'hasNaN', 'autosave' ]),
return player.hasNaN; path() {
}, return player.NaNPath.join('.');
property() {
return player.NaNProperty;
},
autosave() {
return player.autosave;
}, },
previous() { previous() {
return player.NaNPrevious; return player.NaNReceiver?.[this.property];
}, },
paused() { paused() {
return player.devSpeed === 0; return player.devSpeed === 0;
},
property() {
return player.NaNPath.slice(-1)[0];
} }
}, },
methods: { methods: {
setZero() { setZero() {
player.NaNReceiver[player.NaNProperty] = new Decimal(0); player.NaNReceiver[this.property] = new Decimal(0);
player.hasNaN = false; player.hasNaN = false;
}, },
setOne() { setOne() {
player.NaNReceiver[player.NaNProperty] = new Decimal(1); player.NaNReceiver[this.property] = new Decimal(1);
player.hasNaN = false; player.hasNaN = false;
}, },
setPrev() { setPrev() {
player.NaNReceiver[player.NaNProperty] = player.NaNPrevious;
player.hasNaN = false; player.hasNaN = false;
}, },
ignore() { ignore() {
player.NaNReceiver[this.property] = new Decimal(NaN);
player.hasNaN = false; player.hasNaN = false;
}, },
setAutosave(autosave) { setAutosave(autosave) {

View file

@ -1,6 +1,5 @@
<template> <template>
<div v-frag> <div class="nav" v-if="useHeader" v-bind="$attrs">
<div class="nav" v-if="useHeader">
<img v-if="banner" :src="banner" height="100%" :alt="title" /> <img v-if="banner" :src="banner" height="100%" :alt="title" />
<div v-else class="title">{{ title }}</div> <div v-else class="title">{{ title }}</div>
<div @click="openDialog('Changelog')" class="version-container"> <div @click="openDialog('Changelog')" class="version-container">
@ -32,7 +31,7 @@
</tooltip> </tooltip>
</div> </div>
</div> </div>
<div v-else class="overlay-nav"> <div v-else class="overlay-nav" v-bind="$attrs">
<div @click="openDialog('Changelog')" class="version-container"> <div @click="openDialog('Changelog')" class="version-container">
<tooltip display="Changelog" right xoffset="25%" class="version"><span>v{{ version }}</span></tooltip> <tooltip display="Changelog" right xoffset="25%" class="version"><span>v{{ version }}</span></tooltip>
</div> </div>
@ -60,7 +59,6 @@
<Info :show="showInfo" @openDialog="openDialog" @closeDialog="closeDialog" /> <Info :show="showInfo" @openDialog="openDialog" @closeDialog="closeDialog" />
<SavesManager :show="showSaves" @closeDialog="closeDialog" /> <SavesManager :show="showSaves" @closeDialog="closeDialog" />
<Options :show="showOptions" @closeDialog="closeDialog" /> <Options :show="showOptions" @closeDialog="closeDialog" />
</div>
</template> </template>
<script> <script>
@ -173,7 +171,7 @@ export default {
width: 200px; width: 200px;
transition: right .25s ease; transition: right .25s ease;
background: var(--secondary-background); background: var(--secondary-background);
z-index: 1; z-index: 10;
} }
.overlay-nav .discord-links { .overlay-nav .discord-links {

View file

@ -1,9 +1,11 @@
<template> <template>
<Modal :show="show" @close="$emit('closeDialog', 'Options')"> <Modal :show="show" @close="$emit('closeDialog', 'Options')">
<div slot="header" class="header"> <template v-slot:header>
<div class="header">
<h2>Options</h2> <h2>Options</h2>
</div> </div>
<div slot="body"> </template>
<template v-slot:body>
<Select title="Theme" :options="themes" :value="theme" @change="setTheme" default="classic" /> <Select title="Theme" :options="themes" :value="theme" @change="setTheme" default="classic" />
<Select title="Show Milestones" :options="msDisplayOptions" :value="msDisplay" @change="setMSDisplay" default="all" /> <Select title="Show Milestones" :options="msDisplayOptions" :value="msDisplay" @change="setMSDisplay" default="all" />
<Toggle title="Offline Production" :value="offlineProd" @change="toggleOption('offlineProd')" /> <Toggle title="Offline Production" :value="offlineProd" @change="toggleOption('offlineProd')" />
@ -11,21 +13,22 @@
<Toggle title="Pause game" :value="paused" @change="togglePaused" /> <Toggle title="Pause game" :value="paused" @change="togglePaused" />
<Toggle title="Show TPS" :value="showTPS" @change="toggleOption('showTPS')" /> <Toggle title="Show TPS" :value="showTPS" @change="toggleOption('showTPS')" />
<Toggle title="Hide Maxed Challenges" :value="hideChallenges" @change="toggleOption('hideChallenges')" /> <Toggle title="Hide Maxed Challenges" :value="hideChallenges" @change="toggleOption('hideChallenges')" />
</div> </template>
</Modal> </Modal>
</template> </template>
<script> <script>
import themes from '../../data/themes'; import themes from '../../data/themes';
import { camelToTitle } from '../../util/common'; import { camelToTitle } from '../../util/common';
import { mapState } from 'vuex'; import { mapState } from '../../util/vue';
import { player } from '../../store/proxies'; import player from '../../game/player';
export default { export default {
name: 'Options', name: 'Options',
props: { props: {
show: Boolean show: Boolean
}, },
emits: [ 'closeDialog' ],
data() { data() {
return { return {
themes: Object.keys(themes).map(theme => ({ label: camelToTitle(theme), value: theme })), themes: Object.keys(themes).map(theme => ({ label: camelToTitle(theme), value: theme })),
@ -34,13 +37,7 @@ export default {
} }
}, },
computed: { computed: {
...mapState([ "autosave", "offlineProd", "showTPS", "hideChallenges" ]), ...mapState([ "autosave", "offlineProd", "showTPS", "hideChallenges", "theme", "msDisplay" ]),
theme() {
return { label: camelToTitle(player.theme), value: player.theme };
},
msDisplay() {
return { label: camelToTitle(player.msDisplay), value: player.msDisplay };
},
paused() { paused() {
return player.devSpeed === 0; return player.devSpeed === 0;
} }

View file

@ -40,13 +40,14 @@
</template> </template>
<script> <script>
import { player } from '../../store/proxies'; import player from '../../game/player';
export default { export default {
name: 'save', name: 'save',
props: { props: {
save: Object save: Object
}, },
emits: [ 'export', 'open', 'duplicate', 'delete', 'editSave' ],
data() { data() {
return { return {
dateFormat: new Intl.DateTimeFormat('en-US', { dateFormat: new Intl.DateTimeFormat('en-US', {
@ -161,8 +162,7 @@ export default {
.save .button.danger { .save .button.danger {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 0; padding: 4px;
padding-left: 6px;
} }
.save .field { .save .field {

View file

@ -1,21 +1,22 @@
<template> <template>
<Modal :show="show" @close="$emit('closeDialog', 'Saves')"> <Modal :show="show" @close="$emit('closeDialog', 'Saves')">
<div slot="header"> <template v-slot:header>
<h2>Saves Manager</h2> <h2>Saves Manager</h2>
</div> </template>
<div slot="body" v-sortable="{ update, handle: '.handle' }"> <template v-slot:body v-sortable="{ update, handle: '.handle' }">
<save v-for="(save, index) in saves" :key="index" :save="save" @open="openSave(save.id)" @export="exportSave(save.id)" <save v-for="(save, index) in saves" :key="index" :save="save" @open="openSave(save.id)" @export="exportSave(save.id)"
@editSave="name => editSave(save.id, name)" @duplicate="duplicateSave(save.id)" @delete="deleteSave(save.id)" /> @editSave="name => editSave(save.id, name)" @duplicate="duplicateSave(save.id)" @delete="deleteSave(save.id)" />
</div> </template>
<div slot="footer" class="modal-footer"> <template v-slot:footer>
<div class="modal-footer">
<TextField :value="saveToImport" @submit="importSave" @input="importSave" <TextField :value="saveToImport" @submit="importSave" @input="importSave"
title="Import Save" placeholder="Paste your save here!" :class="{ importingFailed }" /> title="Import Save" placeholder="Paste your save here!" :class="{ importingFailed }" />
<div class="field"> <div class="field">
<span class="field-title">Create Save</span> <span class="field-title">Create Save</span>
<div class="field-buttons"> <div class="field-buttons">
<button class="button" @click="newSave">New Game</button> <button class="button" @click="newSave">New Game</button>
<Select v-if="Object.keys(bank).length > 0" :value="{ label: 'Select preset' }" :options="bank" <Select v-if="Object.keys(bank).length > 0" :options="bank" closeOnSelect
@change="newFromPreset" /> @change="newFromPreset" placeholder="Select preset" class="presets" :value="[]" />
</div> </div>
</div> </div>
<div class="footer"> <div class="footer">
@ -25,13 +26,13 @@
</button> </button>
</div> </div>
</div> </div>
</template>
</Modal> </Modal>
</template> </template>
<script> <script>
import Vue from 'vue';
import { newSave, getUniqueID, loadSave, save } from '../../util/save'; import { newSave, getUniqueID, loadSave, save } from '../../util/save';
import { player } from '../../store/proxies'; import player from '../../game/player';
import modInfo from '../../data/modInfo.json'; import modInfo from '../../data/modInfo.json';
export default { export default {
@ -39,6 +40,7 @@ export default {
props: { props: {
show: Boolean show: Boolean
}, },
emits: [ 'closeDialog' ],
data() { data() {
let bankContext = require.context('raw-loader!../../../saves', true, /\.txt$/); let bankContext = require.context('raw-loader!../../../saves', true, /\.txt$/);
let bank = bankContext.keys().reduce((acc, curr) => { let bank = bankContext.keys().reduce((acc, curr) => {
@ -109,14 +111,14 @@ export default {
const modData = JSON.parse(decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id))))); const modData = JSON.parse(decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id)))));
modData.saves.push(playerData.id); modData.saves.push(playerData.id);
localStorage.setItem(modInfo.id, btoa(unescape(encodeURIComponent(JSON.stringify(modData))))); localStorage.setItem(modInfo.id, btoa(unescape(encodeURIComponent(JSON.stringify(modData)))));
Vue.set(this.saves, playerData.id, playerData); this.saves[playerData.id] = playerData;
}, },
deleteSave(id) { deleteSave(id) {
const modData = JSON.parse(decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id))))); const modData = JSON.parse(decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id)))));
modData.saves = modData.saves.filter(save => save !== id); modData.saves = modData.saves.filter(save => save !== id);
localStorage.removeItem(id); localStorage.removeItem(id);
localStorage.setItem(modInfo.id, btoa(unescape(encodeURIComponent(JSON.stringify(modData))))); localStorage.setItem(modInfo.id, btoa(unescape(encodeURIComponent(JSON.stringify(modData)))));
Vue.delete(this.saves, id); delete this.saves[id];
}, },
openSave(id) { openSave(id) {
this.saves[player.id].time = player.time; this.saves[player.id].time = player.time;
@ -127,7 +129,7 @@ export default {
}, },
async newSave() { async newSave() {
const playerData = await newSave(); const playerData = await newSave();
Vue.set(this.saves, playerData.id, playerData); this.saves[playerData.id] = playerData;
}, },
newFromPreset(preset) { newFromPreset(preset) {
const playerData = JSON.parse(decodeURIComponent(escape(atob(preset)))); const playerData = JSON.parse(decodeURIComponent(escape(atob(preset))));
@ -137,7 +139,7 @@ export default {
const modData = JSON.parse(decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id))))); const modData = JSON.parse(decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id)))));
modData.saves.push(playerData.id); modData.saves.push(playerData.id);
localStorage.setItem(modInfo.id, btoa(unescape(encodeURIComponent(JSON.stringify(modData))))); localStorage.setItem(modInfo.id, btoa(unescape(encodeURIComponent(JSON.stringify(modData)))));
Vue.set(this.saves, playerData.id, playerData); this.saves[playerData.id] = playerData;
}, },
editSave(id, newName) { editSave(id, newName) {
this.saves[id].name = newName; this.saves[id].name = newName;
@ -157,7 +159,7 @@ export default {
const id = getUniqueID(); const id = getUniqueID();
playerData.id = id; playerData.id = id;
localStorage.setItem(id, btoa(unescape(encodeURIComponent(JSON.stringify(playerData))))); localStorage.setItem(id, btoa(unescape(encodeURIComponent(JSON.stringify(playerData)))));
Vue.set(this.saves, id, playerData); this.saves[id] = playerData;
this.saveToImport = ""; this.saveToImport = "";
this.importingFailed = false; this.importingFailed = false;
@ -217,4 +219,12 @@ export default {
.field-buttons .v-select { .field-buttons .v-select {
width: 220px; width: 220px;
} }
.presets .vue-dropdown {
}
.presets .vue-select[aria-expanded='true'] vue-dropdown {
visibility: hidden;
}
</style> </style>

View file

@ -5,7 +5,7 @@
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { coerceComponent } from '../../util/vue'; import { coerceComponent } from '../../util/vue';
export default { export default {

View file

@ -6,7 +6,7 @@
<script> <script>
import Decimal, { formatWhole } from '../../util/bignum'; import Decimal, { formatWhole } from '../../util/bignum';
import { player } from '../../store/proxies'; import player from '../../game/player';
export default { export default {
name: 'TPS', name: 'TPS',

View file

@ -6,8 +6,8 @@
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { player } from '../../store/proxies'; import player from '../../game/player';
import themes from '../../data/themes'; import themes from '../../data/themes';
export default { export default {
@ -18,6 +18,7 @@ export default {
options: Object, options: Object,
activeTab: Boolean activeTab: Boolean
}, },
emits: [ 'selectTab' ],
inject: [ 'tab' ], inject: [ 'tab' ],
computed: { computed: {
floating() { floating() {

View file

@ -1,26 +1,24 @@
<template> <template>
<simplebar class="tabs-container"> <div class="tabs-container">
<div v-for="(tab, index) in tabs" :key="index" class="tab" :ref="`tab-${index}`"> <div v-for="(tab, index) in tabs" :key="index" class="tab" :ref="`tab-${index}`">
<Nav v-if="index === 0 && !useHeader" /> <Nav v-if="index === 0 && !useHeader" />
<simplebar>
<div class="inner-tab"> <div class="inner-tab">
<LayerProvider :layer="tab" :index="index" v-if="tab in components && components[tab]"> <LayerProvider :layer="tab" :index="index" v-if="tab in components && components[tab]">
<component :is="components[tab]" /> <component :is="components[tab]" />
</LayerProvider> </LayerProvider>
<layer-tab :layer="tab" :index="index" v-else-if="tab in components" :minimizable="true" <layer-tab :layer="tab" :index="index" v-else-if="tab in components" :minimizable="true"
:tab="() => $refs[`tab-${index}`] && $refs[`tab-${index}`][0]" /> :tab="() => $refs[`tab-${index}`]" />
<component :is="tab" :index="index" v-else /> <component :is="tab" :index="index" v-else />
</div> </div>
</simplebar>
<div class="separator" v-if="index !== tabs.length - 1"></div> <div class="separator" v-if="index !== tabs.length - 1"></div>
</div> </div>
</simplebar> </div>
</template> </template>
<script> <script>
import modInfo from '../../data/modInfo.json'; import modInfo from '../../data/modInfo.json';
import { mapState } from 'vuex'; import { layers } from '../../game/layers';
import { layers } from '../../store/layers'; import { mapState } from '../../util/vue';
export default { export default {
name: 'Tabs', name: 'Tabs',
@ -45,6 +43,7 @@ export default {
flex-grow: 1; flex-grow: 1;
overflow-x: auto; overflow-x: auto;
overflow-y: hidden; overflow-y: hidden;
display: flex;
} }
.tabs { .tabs {
@ -77,11 +76,6 @@ export default {
background: var(--separator); background: var(--separator);
z-index: 1; z-index: 1;
} }
.tab > [data-simplebar] {
height: 100%;
overflow-x: hidden;
}
</style> </style>
<style> <style>
@ -95,23 +89,4 @@ export default {
.tab .modal-body hr { .tab .modal-body hr {
margin: 7px 0; margin: 7px 0;
} }
.tabs-container > .simplebar-wrapper > .simplebar-mask > .simplebar-offset > .simplebar-content-wrapper > .simplebar-content {
display: flex;
height: 100vh;
}
.useHeader .tabs-container > .simplebar-wrapper > .simplebar-mask > .simplebar-offset > .simplebar-content-wrapper > .simplebar-content {
height: calc(100vh - 50px);
}
.tab > [data-simplebar] > .simplebar-wrapper > .simplebar-mask > .simplebar-offset > .simplebar-content-wrapper {
position: static;
}
.tab > [data-simplebar] > .simplebar-wrapper > .simplebar-mask > .simplebar-offset > .simplebar-content-wrapper > .simplebar-content {
flex-direction: column;
min-height: 100%;
display: flex;
}
</style> </style>

View file

@ -1,10 +1,9 @@
<template> <template>
<div class="tooltip-container" :class="{ shown }" @mouseenter="setHover(true)" @mouseleave="setHover(false)"> <div class="tooltip-container" :class="{ shown }" @mouseenter="setHover(true)" @mouseleave="setHover(false)">
<slot /> <slot />
<!-- Make sure slot is *before* the transition in case the slot uses v-frag, which messes up the tooltip -->
<transition name="fade"> <transition name="fade">
<div v-if="shown" class="tooltip" :class="{ top, left, right, bottom }" <div v-if="shown" class="tooltip" :class="{ top, left, right, bottom }"
:style="{ '--xoffset': xoffset, '--yoffset': yoffset }"> :style="{ '--xoffset': xoffset || '0px', '--yoffset': yoffset || '0px' }">
<component :is="tooltipDisplay" /> <component :is="tooltipDisplay" />
</div> </div>
</transition> </transition>
@ -81,7 +80,7 @@ export default {
} }
.shown { .shown {
z-index: 1; z-index: 10;
} }
.fade-enter, .fade-leave-to { .fade-enter, .fade-leave-to {

View file

@ -20,7 +20,7 @@ export default {
this.branches?.map(this.handleBranch).forEach(branch => this.registerBranch(id, branch)); this.branches?.map(this.handleBranch).forEach(branch => this.registerBranch(id, branch));
} }
}, },
beforeDestroy() { beforeUnmount() {
const id = `${this.featureType}@${this.id}`; const id = `${this.featureType}@${this.id}`;
if (this.unregisterNode) { if (this.unregisterNode) {
this.unregisterNode(id); this.unregisterNode(id);

View file

@ -1,17 +1,13 @@
<template> <template>
<div v-frag>
<slot /> <slot />
<div ref="resizeListener" class="resize-listener" /> <div ref="resizeListener" class="resize-listener" />
<svg> <svg v-bind="$attrs">
<branch-line v-for="(branch, index) in branches" :key="index" <branch-line v-for="(branch, index) in branches" :key="index"
:startNode="nodes[branch.start]" :endNode="nodes[branch.end]" :options="branch.options" /> :startNode="nodes[branch.start]" :endNode="nodes[branch.end]" :options="branch.options" />
</svg> </svg>
</div>
</template> </template>
<script> <script>
import Vue from 'vue';
const observerOptions = { const observerOptions = {
attributes: true, attributes: true,
childList: true, childList: true,
@ -57,12 +53,12 @@ export default {
}, },
updateNode(id, containerRect) { updateNode(id, containerRect) {
const linkStartRect = this.nodes[id].element.getBoundingClientRect(); const linkStartRect = this.nodes[id].element.getBoundingClientRect();
Vue.set(this.nodes[id], 'x', linkStartRect.x + linkStartRect.width / 2 - containerRect.x); this.nodes[id].x = linkStartRect.x + linkStartRect.width / 2 - containerRect.x;
Vue.set(this.nodes[id], 'y', linkStartRect.y + linkStartRect.height / 2 - containerRect.y); this.nodes[id].y = linkStartRect.y + linkStartRect.height / 2 - containerRect.y;
}, },
registerNode(id, component) { registerNode(id, component) {
const element = component.$el.parentElement; const element = component.$el.parentElement;
Vue.set(this.nodes, id, { component, element }); this.nodes[id] = { component, element };
this.observer.observe(element, observerOptions); this.observer.observe(element, observerOptions);
this.$nextTick(() => { this.$nextTick(() => {
if (this.$refs.resizeListener != undefined) { if (this.$refs.resizeListener != undefined) {
@ -71,17 +67,15 @@ export default {
}); });
}, },
unregisterNode(id) { unregisterNode(id) {
Vue.delete(this.nodes, id); delete this.nodes[id];
}, },
registerBranch(start, options) { registerBranch(start, options) {
const end = typeof options === 'string' ? options : options.target; const end = typeof options === 'string' ? options : options.target;
this.links.push({ start, end, options }); this.links.push({ start, end, options });
Vue.set(this, 'links', this.links);
}, },
unregisterBranch(start, options) { unregisterBranch(start, options) {
const index = this.links.findIndex(l => l.start === start && l.options === options); const index = this.links.findIndex(l => l.start === start && l.options === options);
this.links.splice(index, 1); this.links.splice(index, 1);
Vue.set(this, 'links', this.links);
} }
} }
}; };

View file

@ -1,5 +1,4 @@
<template> <template>
<div v-frag>
<span class="row" v-for="(row, index) in rows" :key="index"> <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" :append="append" /> <tree-node v-for="(node, nodeIndex) in row" :key="nodeIndex" :id="node" @show-modal="openModal" :append="append" />
</span> </span>
@ -7,14 +6,13 @@
<tree-node v-for="(node, nodeIndex) in rows.side" :key="nodeIndex" :id="node" @show-modal="openModal" :append="append" small /> <tree-node v-for="(node, nodeIndex) in rows.side" :key="nodeIndex" :id="node" @show-modal="openModal" :append="append" small />
</span> </span>
<modal :show="showModal" @close="closeModal"> <modal :show="showModal" @close="closeModal">
<div slot="header"><h2 v-if="modalHeader">{{ modalHeader }}</h2></div> <template v-slot:header><h2 v-if="modalHeader">{{ modalHeader }}</h2></template>
<layer-tab slot="body" v-if="modal" :layer="modal" :index="tab.index" :forceFirstTab="true" /> <template v-slot:body><layer-tab v-if="modal" :layer="modal" :index="tab.index" :forceFirstTab="true" /></template>
</modal> </modal>
</div>
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
export default { export default {
name: 'tree', name: 'tree',

View file

@ -21,8 +21,8 @@
</template> </template>
<script> <script>
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { player } from '../../store/proxies'; import player from '../../game/player';
import { coerceComponent } from '../../util/vue'; import { coerceComponent } from '../../util/vue';
export default { export default {
@ -32,6 +32,7 @@ export default {
small: Boolean, small: Boolean,
append: Boolean append: Boolean
}, },
emits: [ 'show-modal' ],
inject: [ 'tab' ], inject: [ 'tab' ],
computed: { computed: {
layer() { layer() {

View file

@ -1,5 +1,5 @@
import Decimal from '../../../util/bignum'; import Decimal from '../../../util/bignum';
import { player } from '../../../store/proxies'; import player from '../../../game/player';
export default { export default {
id: "a", id: "a",

View file

@ -1,10 +1,12 @@
import Decimal, { format, formatWhole } from '../../../util/bignum'; import Decimal, { format, formatWhole } from '../../../util/bignum';
import { player, tmp } from '../../../store/proxies'; import player from '../../../game/player';
import { layers } from '../../../store/layers'; import { layers } from '../../../game/layers';
import { hasUpgrade, hasMilestone, getBuyableAmount, setBuyableAmount, upgradeEffect, buyableEffect, challengeCompletions } from '../../../util/features'; import { hasUpgrade, hasMilestone, getBuyableAmount, setBuyableAmount, upgradeEffect, buyableEffect, challengeCompletions } from '../../../util/features';
import { resetLayer, resetLayerData } from '../../../util/layers'; import { resetLayer, resetLayerData } from '../../../util/layers';
import { UP, RIGHT } from '../../../util/vue'; import { UP, RIGHT } from '../../../util/vue';
const tmp = layers;
export default { export default {
id: "c", // This is assigned automatically, both to the layer and all upgrades, etc. Shown here so you know about it 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. name: "Candies", // This is optional, only used in a few places, If absent it just uses the layer id.
@ -230,10 +232,9 @@ export default {
microtabs: { microtabs: {
stuff: { stuff: {
first: { first: {
display: `<div v-frag> display: `
<upgrades /> <upgrades />
<div>confirmed</div> <div>confirmed</div>`
</div>`
}, },
second: { second: {
embedLayer: "f" embedLayer: "f"
@ -304,7 +305,6 @@ export default {
buttonStyle() {return {'color': 'orange'}}, buttonStyle() {return {'color': 'orange'}},
notify: true, notify: true,
display: ` display: `
<div v-frag>
<main-display /> <main-display />
<sticky><prestige-button /></sticky> <sticky><prestige-button /></sticky>
<resource-display /> <resource-display />
@ -317,9 +317,7 @@ export default {
<milestones /> <milestones />
<spacer /> <spacer />
<upgrades /> <upgrades />
<challenges /> <challenges />`,
</div>
`,
glowColor: "blue", glowColor: "blue",
}, },
@ -328,7 +326,6 @@ export default {
style() {return {'background-color': '#222222', '--background': '#222222'}}, style() {return {'background-color': '#222222', '--background': '#222222'}},
buttonStyle() {return {'border-color': 'orange'}}, buttonStyle() {return {'border-color': 'orange'}},
display: ` display: `
<div v-frag>
<buyables /> <buyables />
<spacer /> <spacer />
<row style="width: 600px; height: 350px; background-color: green; border-style: solid;"> <row style="width: 600px; height: 350px; background-color: green; border-style: solid;">
@ -343,13 +340,10 @@ export default {
</column> </column>
</row> </row>
<spacer /> <spacer />
<img src="https://unsoftcapped2.github.io/The-Modding-Tree-2/discord.png" /> <img src="https://unsoftcapped2.github.io/The-Modding-Tree-2/discord.png" />`
</div>
`
}, },
jail: { jail: {
display: ` display: `
<div v-frag>
<infobox id="coolInfo" /> <infobox id="coolInfo" />
<bar id="longBoi" /> <bar id="longBoi" />
<spacer /> <spacer />
@ -366,21 +360,16 @@ export default {
</row> </row>
<spacer /> <spacer />
<div>It's jail because "bars"! So funny! Ha ha!</div> <div>It's jail because "bars"! So funny! Ha ha!</div>
<tree :nodes="[['f', 'c'], ['g', 'spook', 'h']]" /> <tree :nodes="[['f', 'c'], ['g', 'spook', 'h']]" />`
</div>
`
}, },
illuminati: { illuminati: {
unlocked() {return (hasUpgrade("c", 13))}, unlocked() {return (hasUpgrade("c", 13))},
display: ` display: `
<div v-frag>
<h1> C O N F I R M E D </h1> <h1> C O N F I R M E D </h1>
<spacer /> <spacer />
<microtab family="stuff" style="width: 660px; height: 370px; background-color: brown; --background: brown; border: solid white; margin: auto" /> <microtab family="stuff" style="width: 660px; height: 370px; background-color: brown; --background: brown; border: solid white; margin: auto" />
<div>Adjust how many points H gives you!</div> <div>Adjust how many points H gives you!</div>
<Slider :value="player.c.otherThingy" @change="value => player.c.otherThingy = value" :min="1" :max="30" /> <Slider :value="player.c.otherThingy" @change="value => player.c.otherThingy = value" :min="1" :max="30" />`
</div>
`
} }
}, },

View file

@ -1,5 +1,6 @@
import Decimal, { formatWhole } from '../../../util/bignum'; import Decimal, { formatWhole } from '../../../util/bignum';
import { player, tmp } from '../../../store/proxies'; import player from '../../../game/player';
import { layers as tmp } from '../../../game/layers';
import { getClickableState } from '../../../util/features'; import { getClickableState } from '../../../util/features';
export default { export default {

View file

@ -1,6 +1,6 @@
import Decimal, { format } from '../../util/bignum'; import Decimal, { format } from '../../util/bignum';
import { player } from '../../store/proxies'; import player from '../../game/player';
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { hasUpgrade, hasMilestone, getBuyableAmount, setBuyableAmount, hasChallenge } from '../../util/features'; import { hasUpgrade, hasMilestone, getBuyableAmount, setBuyableAmount, hasChallenge } from '../../util/features';
import { resetLayer } from '../../util/layers'; import { resetLayer } from '../../util/layers';

View file

@ -1,6 +1,6 @@
import Decimal, { format } from '../../util/bignum'; import Decimal, { format } from '../../util/bignum';
import { player } from '../../store/proxies'; import player from '../../game/player';
import { layers } from '../../store/layers'; import { layers } from '../../game/layers';
import { hasUpgrade, hasMilestone, getBuyableAmount, setBuyableAmount, hasChallenge } from '../../util/features'; import { hasUpgrade, hasMilestone, getBuyableAmount, setBuyableAmount, hasChallenge } from '../../util/features';
import { resetLayer } from '../../util/layers'; import { resetLayer } from '../../util/layers';
@ -871,38 +871,35 @@ challenges:{
}, },
subtabs: { subtabs: {
"Upgrades": { "Upgrades": {
display: `<div v-frag> display: `
<main-display /> <main-display />
<spacer /> <spacer />
<prestige-button display="" /> <prestige-button display="" />
<spacer /> <spacer />
<spacer /> <spacer />
<upgrades /> <upgrades />`
</div>`
}, },
"Challenges": { "Challenges": {
unlocked() { return hasUpgrade("p", 51) || hasMilestone("p", 0); }, unlocked() { return hasUpgrade("p", 51) || hasMilestone("p", 0); },
display: `<div v-frag> display: `
<spacer /> <spacer />
<spacer /> <spacer />
<challenges /> <challenges />`
</div>`
}, },
"Buyables and Milestones": { "Buyables and Milestones": {
unlocked(){return hasUpgrade("p",74)||hasMilestone("p",0)}, unlocked(){return hasUpgrade("p",74)||hasMilestone("p",0)},
display: `<div v-frag> display: `
<spacer /> <spacer />
<spacer /> <spacer />
<row><buyable id="11" /></row> <row><buyable id="11" /></row>
<spacer /> <spacer />
<div v-if="hasMilestone('p', 0)">Your boosts are making the point challenge {{ getBuyableAmount('p', 11).plus(1) }}x less pointy</div> <div v-if="hasMilestone('p', 0)">Your boosts are making the point challenge {{ getBuyableAmount('p', 11).plus(1) }}x less pointy</div>
<spacer /> <spacer />
<milestones /> <milestones />`
</div>`
}, },
"Generators": { "Generators": {
unlocked(){return hasMilestone("p",5)||player.i.points.gte(1)}, unlocked(){return hasMilestone("p",5)||player.i.points.gte(1)},
display: `<div v-frag> display: `
<spacer /> <spacer />
<div>You have {{ format(player.p.gp) }} generator points, adding {{ format(hasUpgrade("p",132)?player.p.gp.plus(1).pow(new Decimal(1).div(2)):hasUpgrade("p",101)?player.p.gp.plus(1).pow(new Decimal(1).div(3)):hasUpgrade("p",93)?player.p.gp.plus(1).pow(0.2):player.p.gp.plus(1).log10()) }} to point gain</div> <div>You have {{ format(player.p.gp) }} generator points, adding {{ format(hasUpgrade("p",132)?player.p.gp.plus(1).pow(new Decimal(1).div(2)):hasUpgrade("p",101)?player.p.gp.plus(1).pow(new Decimal(1).div(3)):hasUpgrade("p",93)?player.p.gp.plus(1).pow(0.2):player.p.gp.plus(1).log10()) }} to point gain</div>
<div>You have {{ format(player.p.g) }} generators, generating {{ format(player.p.g.times(player.p.geff)) }} generator points per second</div> <div>You have {{ format(player.p.g) }} generators, generating {{ format(player.p.g.times(player.p.geff)) }} generator points per second</div>
@ -910,12 +907,11 @@ challenges:{
<spacer /> <spacer />
<spacer /> <spacer />
<buyables :buyables="[12, 13, 14]" /> <buyables :buyables="[12, 13, 14]" />
<row><clickable id="11" /></row> <row><clickable id="11" /></row>`
</div>`
}, },
"Pointy Points": { "Pointy Points": {
unlocked(){return hasUpgrade("p",104)||player.i.points.gte(1)}, unlocked(){return hasUpgrade("p",104)||player.i.points.gte(1)},
display: `<div v-frag> display: `
<div style="color: red; font-size: 32px; font-family: Comic Sans MS">{{ format(player.p.buyables[21]) }} pointy points</div> <div style="color: red; font-size: 32px; font-family: Comic Sans MS">{{ format(player.p.buyables[21]) }} pointy points</div>
<div style="color: red; font-size: 32px; font-family: Comic Sans MS">My pointy points are multiplying generator efficiency by {{ format(new Decimal(player.p.buyables[21]).plus(1)) }}</div> <div style="color: red; font-size: 32px; font-family: Comic Sans MS">My pointy points are multiplying generator efficiency by {{ format(new Decimal(player.p.buyables[21]).plus(1)) }}</div>
<spacer /> <spacer />
@ -932,15 +928,13 @@ challenges:{
<spacer /> <spacer />
<spacer /> <spacer />
<div v-if="hasMilestone('p', 11)" style="font-size: 24px">Booster upgrades</div> <div v-if="hasMilestone('p', 11)" style="font-size: 24px">Booster upgrades</div>
<upgrades :upgrades="[231, 232, 233, 234, 235]" /> <upgrades :upgrades="[231, 232, 233, 234, 235]" />`
</div>`
}, },
"Buyables": { "Buyables": {
unlocked(){return hasMilestone("p",13)}, unlocked(){return hasMilestone("p",13)},
display: `<div v-frag> display: `
<buyables :buyables="[31, 32, 33]" /> <buyables :buyables="[31, 32, 33]" />
<buyables :buyables="[41, 42, 43]" /> <buyables :buyables="[41, 42, 43]" />`
</div>`
} }
} }
} }

View file

@ -1,8 +1,8 @@
import { computed } from 'vue';
import { hasUpgrade, upgradeEffect, hasMilestone, inChallenge, getBuyableAmount } from '../util/features'; import { hasUpgrade, upgradeEffect, hasMilestone, inChallenge, getBuyableAmount } from '../util/features';
import { layers } from '../store/layers'; import { layers } from '../game/layers';
import { player } from '../store/proxies'; import player from '../game/player';
import Decimal from '../util/bignum'; import Decimal from '../util/bignum';
import modInfo from './modInfo';
// Import initial layers // Import initial layers
import f from './layers/aca/f.js'; import f from './layers/aca/f.js';
@ -40,7 +40,7 @@ const spook = {
const main = { const main = {
id: 'main', id: 'main',
display: `<div v-frag> display: `
<div v-if="player.devSpeed === 0">Game Paused</div> <div v-if="player.devSpeed === 0">Game Paused</div>
<div v-else-if="player.devSpeed && player.devSpeed !== 1">Dev Speed: {{ format(player.devSpeed) }}x</div> <div v-else-if="player.devSpeed && player.devSpeed !== 1">Dev Speed: {{ format(player.devSpeed) }}x</div>
<div v-if="player.offTime != undefined">Offline Time: {{ formatTime(player.offTime.remain) }}</div> <div v-if="player.offTime != undefined">Offline Time: {{ formatTime(player.offTime.remain) }}</div>
@ -49,12 +49,11 @@ const main = {
<h2>{{ format(player.points) }}</h2> <h2>{{ format(player.points) }}</h2>
<span v-if="player.points.lt('1e1e6')"> points</span> <span v-if="player.points.lt('1e1e6')"> points</span>
</div> </div>
<div v-if="Decimal.gt($store.getters.pointGain, 0)"> <div v-if="Decimal.gt(pointGain, 0)">
({{ player.oompsMag != 0 ? format(player.oomps) + " OOM" + (player.oompsMag < 0 ? "^OOM" : player.oompsMag > 1 ? "^" + player.oompsMag : "") + "s" : formatSmall($store.getters.pointGain) }}/sec) ({{ player.oompsMag != 0 ? format(player.oomps) + " OOM" + (player.oompsMag < 0 ? "^OOM" : player.oompsMag > 1 ? "^" + player.oompsMag : "") + "s" : formatSmall(pointGain) }}/sec)
</div> </div>
<spacer /> <spacer />
<tree :append="true" /> <tree :append="true" />`,
</div>`,
name: "Tree" name: "Tree"
}; };
@ -66,11 +65,11 @@ export function getStartingData() {
} }
} }
export const getters = { export const hasWon = computed(() => {
hasWon() {
return false; return false;
}, });
pointGain() {
export const pointGain = computed(() => {
if(!hasUpgrade("c", 11)) if(!hasUpgrade("c", 11))
return new Decimal(0); return new Decimal(0);
let gain = new Decimal(3.19) let gain = new Decimal(3.19)
@ -110,8 +109,7 @@ export const getters = {
if (hasMilestone("p",13))gain=gain.times(layers.p.buyables[31].effect) if (hasMilestone("p",13))gain=gain.times(layers.p.buyables[31].effect)
if (hasMilestone("p",13))gain=gain.pow(layers.p.buyables[42].effect) if (hasMilestone("p",13))gain=gain.pow(layers.p.buyables[42].effect)
return gain; return gain;
} });
};
/* eslint-disable-next-line no-unused-vars */ /* eslint-disable-next-line no-unused-vars */
export function update(delta) { export function update(delta) {
@ -120,5 +118,3 @@ export function update(delta) {
/* eslint-disable-next-line no-unused-vars */ /* eslint-disable-next-line no-unused-vars */
export function fixOldSave(oldVersion, playerData) { export function fixOldSave(oldVersion, playerData) {
} }
document.title = modInfo.title;

View file

@ -1,9 +1,8 @@
import { update as modUpdate } from '../data/mod'; import { update as modUpdate, hasWon, pointGain } from '../data/mod';
import Decimal from '../util/bignum'; import Decimal from '../util/bignum';
import modInfo from '../data/modInfo.json'; import modInfo from '../data/modInfo.json';
import store from './index';
import { layers } from './layers'; import { layers } from './layers';
import { player } from './proxies'; import player from './player';
function updatePopups(/* diff */) { function updatePopups(/* diff */) {
// TODO // TODO
@ -104,7 +103,7 @@ function update() {
} }
// Stop here if the game is paused on the win screen // Stop here if the game is paused on the win screen
if (store.getters.hasWon && !player.keepGoing) { if (hasWon.value && !player.keepGoing) {
return; return;
} }
// Stop here if the player had a NaN value // Stop here if the player had a NaN value
@ -145,13 +144,13 @@ function update() {
} }
player.timePlayed = player.timePlayed.add(diff); player.timePlayed = player.timePlayed.add(diff);
if (player.points != undefined) { if (player.points != undefined) {
player.points = player.points.add(Decimal.times(store.getters.pointGain, diff)); player.points = player.points.add(Decimal.times(pointGain.value, diff));
} }
modUpdate(diff); modUpdate(diff);
updateOOMPS(trueDiff); updateOOMPS(trueDiff);
updateLayers(diff); updateLayers(diff);
} }
export function startGameLoop() { export default function startGameLoop() {
setInterval(update, 50); setInterval(update, 50);
} }

View file

@ -1,11 +1,11 @@
import Vue from 'vue';
import clone from 'lodash.clonedeep'; import clone from 'lodash.clonedeep';
import { isFunction, isPlainObject } from '../util/common'; import { isFunction, isPlainObject } from '../util/common';
import { createProxy, createGridProxy, player as playerProxy } from './proxies'; import { createProxy, createGridProxy } from '../util/proxies';
import playerProxy from './player';
import Decimal from '../util/bignum'; import Decimal from '../util/bignum';
import store from './index';
import { noCache, getStartingBuyables, getStartingClickables, getStartingChallenges, defaultLayerProperties } from '../util/layers'; import { noCache, getStartingBuyables, getStartingClickables, getStartingChallenges, defaultLayerProperties } from '../util/layers';
import { applyPlayerData } from '../util/save'; import { applyPlayerData } from '../util/save';
import { isRef } from 'vue';
export const layers = {}; export const layers = {};
export const hotkeys = []; export const hotkeys = [];
@ -49,11 +49,9 @@ export function addLayer(layer, player = null) {
layer.base = 2; layer.base = 2;
} }
const getters = {};
// Process each feature // Process each feature
for (let property of uncachedProperties) { for (let property of uncachedProperties) {
if (layer[property]) { if (layer[property] && !isRef(layer.property)) {
layer[property].forceCached = false; layer[property].forceCached = false;
} }
} }
@ -312,21 +310,20 @@ export function addLayer(layer, player = null) {
setDefault(layer.grids[id], 'hold', null, false); setDefault(layer.grids[id], 'hold', null, false);
setDefault(layer.grids[id], 'getTitle', null, false); setDefault(layer.grids[id], 'getTitle', null, false);
setDefault(layer.grids[id], 'getDisplay', null, false); setDefault(layer.grids[id], 'getDisplay', null, false);
layer.grids[id] = createGridProxy(layer.grids[id], getters, `${layer.id}/grids-${id}-`); layer.grids[id] = createGridProxy(layer.grids[id]);
} }
} }
} }
if (layer.subtabs) { if (layer.subtabs) {
layer.activeSubtab = function() { layer.activeSubtab = function() {
if (this.subtabs != undefined) { if (layer.subtabs[playerProxy.subtabs[layer.id].mainTabs] &&
if (this.subtabs[player.subtabs[layer.id].mainTabs] && layer.subtabs[playerProxy.subtabs[layer.id].mainTabs].unlocked !== false) {
this.subtabs[player.subtabs[layer.id].mainTabs].unlocked !== false) { return layer.subtabs[playerProxy.subtabs[layer.id].mainTabs];
return this.subtabs[player.subtabs[layer.id].mainTabs];
} }
// Default to first unlocked tab // Default to first unlocked tab
return Object.values(this.subtabs).find(subtab => subtab.unlocked !== false); return Object.values(layer.subtabs).find(subtab => subtab.unlocked !== false);
}
} }
setDefault(player, 'subtabs', {});
setDefault(player.subtabs, layer.id, {}); setDefault(player.subtabs, layer.id, {});
setDefault(player.subtabs[layer.id], 'mainTabs', Object.keys(layer.subtabs)[0]); setDefault(player.subtabs[layer.id], 'mainTabs', Object.keys(layer.subtabs)[0]);
for (let id in layer.subtabs) { for (let id in layer.subtabs) {
@ -338,16 +335,17 @@ export function addLayer(layer, player = null) {
} }
} }
if (layer.microtabs) { if (layer.microtabs) {
setDefault(player, 'subtabs', {});
setDefault(player.subtabs, layer.id, {}); setDefault(player.subtabs, layer.id, {});
for (let family in layer.microtabs) { for (let family in layer.microtabs) {
layer.microtabs[family].activeMicrotab = function() { layer.microtabs[family].activeMicrotab = function() {
if (this[player.subtabs[this.layer]?.[family]] && this[player.subtabs[this.layer][family]].unlocked !== false) { if (this[playerProxy.subtabs[this.layer][family]] && this[playerProxy.subtabs[this.layer][family]].unlocked !== false) {
return this[player.subtabs[this.layer][family]]; return this[playerProxy.subtabs[this.layer][family]];
} }
// Default to first unlocked tab // Default to first unlocked tab
return this[Object.keys(this).find(microtab => microtab !== 'activeMicrotab' && this[microtab].unlocked !== false)]; return this[Object.keys(this).find(microtab => microtab !== 'activeMicrotab' && this[microtab].unlocked !== false)];
} }
setDefault(player.subtabs[layer.id], family, Object.keys(layer.microtabs[family])[0]); setDefault(player.subtabs[layer.id], family, Object.keys(layer.microtabs[family]).find(tab => tab !== 'activeMicrotab'));
layer.microtabs[family].layer = layer.id; layer.microtabs[family].layer = layer.id;
layer.microtabs[family].family = family; layer.microtabs[family].family = family;
for (let id in layer.microtabs[family]) { for (let id in layer.microtabs[family]) {
@ -356,7 +354,7 @@ export function addLayer(layer, player = null) {
layer.microtabs[family][id].family = family; layer.microtabs[family][id].family = family;
layer.microtabs[family][id].id = id; layer.microtabs[family][id].id = id;
layer.microtabs[family][id].active = function() { layer.microtabs[family][id].active = function() {
return player.subtabs[this.layer]?.[this.family] === this.id; return playerProxy.subtabs[this.layer][this.family] === this.id;
} }
} }
} }
@ -374,11 +372,10 @@ export function addLayer(layer, player = null) {
} }
// Create layer proxy // Create layer proxy
layer = createProxy(layer, getters, `${layer.id}/`); layer = createProxy(layer);
// Register layer // Register layer
layers[layer.id] = layer; layers[layer.id] = layer;
store.registerModule(`layer-${layer.id}`, { getters });
// Register hotkeys // Register hotkeys
if (layer.hotkeys) { if (layer.hotkeys) {
@ -392,12 +389,11 @@ export function removeLayer(layer) {
// Un-set hotkeys // Un-set hotkeys
if (layers[layer].hotkeys) { if (layers[layer].hotkeys) {
for (let id in layers[layer].hotkeys) { for (let id in layers[layer].hotkeys) {
Vue.delete(hotkeys, id); delete hotkeys[id];
} }
} }
// Un-register layer delete layers[layer];
store.unregisterModule(`layer-${layer}`);
} }
export function reloadLayer(layer) { export function reloadLayer(layer) {

52
src/game/player.js Normal file
View file

@ -0,0 +1,52 @@
import { reactive } from 'vue';
import { isPlainObject } from '../util/common';
import Decimal from '../util/bignum';
const state = reactive({});
const playerHandler = {
get(target, key) {
if (key === '__state' || key === '__path') {
return target[key];
}
if (target.__state[key] == undefined) {
return;
}
if (isPlainObject(target.__state[key]) && !(target.__state[key] instanceof Decimal)) {
if (target.__state[key] !== target[key]?.__state) {
const path = [ ...target.__path, key ];
target[key] = new Proxy({ __state: target.__state[key], __path: path }, playerHandler);
}
return target[key];
}
return target.__state[key];
},
set(target, property, value, receiver) {
if (!state.hasNaN && ((typeof value === 'number' && isNaN(value)) || (value instanceof Decimal && (isNaN(value.sign) || isNaN(value.layer) || isNaN(value.mag))))) {
const currentValue = target.__state[property];
if (!((typeof currentValue === 'number' && isNaN(currentValue)) || (currentValue instanceof Decimal && (isNaN(currentValue.sign) || isNaN(currentValue.layer) || isNaN(currentValue.mag))))) {
state.autosave = false;
state.hasNaN = true;
state.NaNPath = [ ...target.__path, property ];
state.NaNReceiver = receiver;
console.error(`Attempted to set NaN value`, [ ...target.__path, property ], target.__state);
throw 'Attempted to set NaN value. See above for details';
}
}
target.__state[property] = value;
if (property === 'points') {
if (target.__state.best != undefined) {
target.__state.best = Decimal.max(target.__state.best, value);
}
if (target.__state.total != undefined) {
const diff = Decimal.sub(value, target.__state.points);
if (diff.gt(0)) {
target.__state.total = target.__state.total.add(diff);
}
}
}
return true;
}
};
export default window.player = new Proxy({ __state: state, __path: [ 'player' ] }, playerHandler);

View file

@ -1,3 +1,7 @@
:root {
color-scheme: dark;
}
* { * {
transition-duration: 0.5s; transition-duration: 0.5s;
font-family: "Roboto Mono", monospace; font-family: "Roboto Mono", monospace;

View file

@ -1,24 +1,22 @@
import Vue from 'vue'; import { createApp } from 'vue';
import App from './App'; import App from './App';
import store from './store';
import { load } from './util/save'; import { load } from './util/save';
import { setVue } from './util/vue'; import { setVue } from './util/vue';
import { startGameLoop } from './store/game'; import gameLoop from './game/gameLoop';
import './components/index'; import { registerComponents } from './components/index';
import modInfo from './data/modInfo.json';
// Setup
Vue.config.productionTip = false;
requestAnimationFrame(async () => { requestAnimationFrame(async () => {
await load(); await load();
// Create Vue // Create Vue
const vue = window.vue = new Vue({ const vue = window.vue = createApp({
store, ...App
render: h => h(App)
}); });
setVue(vue); setVue(vue);
vue.$mount('#app'); registerComponents(vue);
vue.mount('#app');
document.title = modInfo.title;
startGameLoop(); gameLoop();
}); });

View file

@ -1,11 +0,0 @@
import Vue from 'vue'
import Vuex from 'vuex'
import { getInitialStore } from '../util/save';
import { getters } from '../data/mod';
Vue.use(Vuex);
export default new Vuex.Store({
state: getInitialStore(),
getters
});

View file

@ -1,223 +0,0 @@
import { layers } from './layers';
import { isFunction, isPlainObject } from '../util/common';
import Decimal from '../util/bignum';
import store from './index';
import Vue from 'vue';
export const tmp = new Proxy({}, {
get(target, property) {
if (property in layers) {
return layers[property];
}
// TODO implement other tmp values for backwards compatibility
console.error(`No getter for "${property}"`, target);
}
});
const playerHandler = {
get(target, key) {
if (key === 'isProxy') {
return true;
}
if (target[key] == undefined) {
return;
}
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);
return target[key];
}
return target[key];
},
set(target, property, value, receiver) {
if (!player.hasNaN && value instanceof Decimal && (isNaN(value.sign) || isNaN(value.layer) || isNaN(value.mag))) {
player.autosave = false;
player.hasNaN = true;
player.NaNProperty = property;
player.NaNReceiver = receiver;
player.NaNPrevious = target[property];
Vue.set(target, property, value);
console.error(`Attempted to set NaN value`, target, property);
throw 'Attempted to set NaN value. See above for details';
}
Vue.set(target, property, value);
if (property === 'points') {
if (target.best != undefined) {
target.best = Decimal.max(target.best, value);
}
if (target.total != undefined) {
const diff = Decimal.sub(value, target.points);
if (diff.gt(0)) {
target.total = target.total.add(diff);
}
}
}
return true;
},
deleteProperty(target, prop) {
Vue.delete(target, prop);
return true;
}
};
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;
}
// 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;
}
function travel(callback, object, objectProxy, getters, prefix) {
for (let key in object) {
if (object[key].isProxy) {
continue;
}
if (isFunction(object[key])) {
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]) || Array.isArray(object[key])) && !(object[key] instanceof Decimal)) {
object[key] = callback(object[key], getters, `${prefix}${key}-`);
}
}
}
function getHandler(prefix) {
return {
get(target, key, receiver) {
if (key === 'isProxy') {
return true;
}
if (target[key] == undefined) {
return;
}
if (target[key].isProxy || target[key] instanceof Decimal) {
return 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}-`));
return target[key];
} else if (isFunction(target[key])) {
const getterID = `${prefix}${key}`;
if (getterID in store.getters) {
return store.getters[getterID];
} else {
return target[key].bind(receiver);
}
}
return target[key];
},
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);
}
}
};
}
function getGridHandler(prefix) {
return {
get(target, key) {
if (key === 'isProxy') {
return true;
}
if (target[key] && (target[key].isProxy || target[key] instanceof Decimal)) {
return 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}-`));
return target[key];
} else if (isFunction(target[key])) {
const getterID = `${prefix}${key}`;
if (getterID in store.getters) {
return store.getters[getterID];
}
// Non-cached functions are going to be cell-specific, so don't call them with the grid handler
}
if (typeof key !== 'symbol' && !isNaN(key)) {
target[key] = new Proxy(target, getCellHandler(key));
}
return target[key];
},
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);
}
}
};
}
function getCellHandler(id) {
return {
get(target, key, receiver) {
if (key === 'isProxy') {
return true;
}
let prop = target[key];
if (isFunction(prop) && prop.forceCached === false) {
return () => prop.call(receiver, id, target.getData(id));
}
if (prop != undefined || 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.getData(id));
} else if (prop != undefined) {
return prop;
}
prop = target[`on${key}`];
if (isFunction(prop)) {
return () => prop.call(receiver, id, target.getData(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);
}
}
};
}

View file

@ -1,4 +1,4 @@
import { layers } from '../store/layers'; import { layers } from '../game/layers';
export function hasUpgrade(layer, id) { export function hasUpgrade(layer, id) {
return layers[layer]?.upgrades?.[id]?.bought; return layers[layer]?.upgrades?.[id]?.bought;

View file

@ -1,7 +1,7 @@
import Decimal from './bignum'; import Decimal from './bignum';
import { isPlainObject } from './common'; import { isPlainObject } from './common';
import { layers, hotkeys } from '../store/layers'; import { layers, hotkeys } from '../game/layers';
import { player } from '../store/proxies'; import player from '../game/player';
export function resetLayer(layer, force = false) { export function resetLayer(layer, force = false) {
layers[layer].reset(force); layers[layer].reset(force);
@ -72,7 +72,6 @@ export function resetLayerData(layer, keep = []) {
} }
export function resetRow(row, ignore) { export function resetRow(row, ignore) {
console.log(row, ignore);
Object.values(layers).filter(layer => layer.row === row && layer.layer !== ignore).forEach(layer => layer.hardReset()); Object.values(layers).filter(layer => layer.row === row && layer.layer !== ignore).forEach(layer => layer.hardReset());
} }

149
src/util/proxies.js Normal file
View file

@ -0,0 +1,149 @@
import { isFunction, isPlainObject } from './common';
import Decimal from './bignum';
import { isRef, computed } from 'vue';
export function createProxy(object) {
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, mainHandler);
travel(createProxy, object, objectProxy);
return objectProxy;
}
// TODO cache grid values? Currently they'll be calculated every render they're visible
export function createGridProxy(object) {
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, gridHandler);
travel(createGridProxy, object, objectProxy);
return objectProxy;
}
function travel(callback, object, objectProxy) {
for (let key in object) {
if (object[key].isProxy) {
continue;
}
if (isFunction(object[key])) {
if ((object[key].length !== 0 && object[key].forceCached !== true) || object[key].forceCached === false) {
continue;
}
object[key] = computed(object[key].bind(objectProxy));
} else if ((isPlainObject(object[key]) || Array.isArray(object[key])) && !(object[key] instanceof Decimal)) {
object[key] = callback(object[key]);
}
}
}
const mainHandler = {
get(target, key, receiver) {
if (key === 'isProxy') {
return true;
}
if (target[key] == undefined) {
return;
}
if (isRef(target[key])) {
return target[key].value;
} else if (target[key].isProxy || target[key] instanceof Decimal) {
return 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], mainHandler);
return target[key];
} else if (isFunction(target[key])) {
return target[key].bind(receiver);
}
return target[key];
},
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);
}
}
};
const gridHandler = {
get(target, key, receiver) {
if (key === 'isProxy') {
return true;
}
if (isRef(target[key])) {
return target[key].value;
} else if (target[key] && (target[key].isProxy || target[key] instanceof Decimal)) {
return 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], mainHandler);
return target[key];
} else if (isFunction(target[key])) {
return target[key].bind(receiver);
}
if (typeof key !== 'symbol' && !isNaN(key)) {
target[key] = new Proxy(target, getCellHandler(key));
}
return target[key];
},
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);
}
}
};
function getCellHandler(id) {
return {
get(target, key, receiver) {
if (key === 'isProxy') {
return true;
}
let prop = target[key];
if (isFunction(prop) && prop.forceCached === false) {
return () => prop.call(receiver, id, target.getData(id));
}
if (prop != undefined || 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.getData(id));
} else if (prop != undefined) {
return prop;
}
prop = target[`on${key}`];
if (isFunction(prop)) {
return () => prop.call(receiver, id, target.getData(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);
}
}
};
}

View file

@ -1,6 +1,6 @@
import modInfo from '../data/modInfo'; import modInfo from '../data/modInfo';
import { getStartingData, getInitialLayers, fixOldSave } from '../data/mod'; import { getStartingData, getInitialLayers, fixOldSave } from '../data/mod';
import { player } from '../store/proxies'; import player from '../game/player';
import Decimal from './bignum'; import Decimal from './bignum';
export const NOT_IMPORTING = false; export const NOT_IMPORTING = false;
@ -32,9 +32,8 @@ export function getInitialStore(playerData = {}) {
// Values that don't get loaded/saved // Values that don't get loaded/saved
hasNaN: false, hasNaN: false,
NaNProperty: "", NaNPath: [],
NaNReceiver: null, NaNReceiver: null,
NaNPrevious: null,
importing: NOT_IMPORTING, importing: NOT_IMPORTING,
saveToImport: "", saveToImport: "",
saveToExport: "" saveToExport: ""
@ -43,7 +42,7 @@ export function getInitialStore(playerData = {}) {
export function save() { export function save() {
/* eslint-disable-next-line no-unused-vars */ /* eslint-disable-next-line no-unused-vars */
let { hasNaN, NaNProperty, NaNReceiver, NaNPrevious, importing, saveToImport, saveToExport, ...playerData } = player; let { hasNaN, NaNPath, NaNReceiver, importing, saveToImport, saveToExport, ...playerData } = player.__state;
player.saveToExport = btoa(unescape(encodeURIComponent(JSON.stringify(playerData)))); player.saveToExport = btoa(unescape(encodeURIComponent(JSON.stringify(playerData))));
localStorage.setItem(player.id, player.saveToExport); localStorage.setItem(player.id, player.saveToExport);
@ -67,6 +66,7 @@ export async function load() {
await loadSave(newSave()); await loadSave(newSave());
return; return;
} }
playerData.id = modData.active;
await loadSave(playerData); await loadSave(playerData);
} catch (e) { } catch (e) {
await loadSave(newSave()); await loadSave(newSave());
@ -99,7 +99,7 @@ export function getUniqueID() {
} }
export async function loadSave(playerData) { export async function loadSave(playerData) {
const { layers, removeLayer, addLayer } = await import('../store/layers'); const { layers, removeLayer, addLayer } = await import('../game/layers');
for (let layer in layers) { for (let layer in layers) {
removeLayer(layer); removeLayer(layer);
@ -119,7 +119,7 @@ export async function loadSave(playerData) {
Object.assign(player, playerData); Object.assign(player, playerData);
for (let prop in player) { for (let prop in player) {
if (!(prop in playerData) && !(prop in layers)) { if (!(prop in playerData) && !(prop in layers) && prop !== '__state' && prop !== '__path') {
delete player[prop]; delete player[prop];
} }
} }
@ -157,3 +157,4 @@ window.onbeforeunload = () => {
save(); save();
} }
}; };
window.save = save;

View file

@ -1,5 +1,6 @@
import { player } from '../store/proxies'; import player from '../game/player';
import { layers } from '../store/layers'; import { layers } from '../game/layers';
import { hasWon, pointGain } from '../data/mod';
import { hasUpgrade, hasMilestone, hasAchievement, hasChallenge, maxedChallenge, challengeCompletions, inChallenge, getBuyableAmount, setBuyableAmount, getClickableState, setClickableState, getGridData, setGridData, upgradeEffect, challengeEffect, buyableEffect, clickableEffect, achievementEffect, gridEffect } from './features'; import { hasUpgrade, hasMilestone, hasAchievement, hasChallenge, maxedChallenge, challengeCompletions, inChallenge, getBuyableAmount, setBuyableAmount, getClickableState, setClickableState, getGridData, setGridData, upgradeEffect, challengeEffect, buyableEffect, clickableEffect, achievementEffect, gridEffect } from './features';
import Decimal, * as numberUtils from './bignum'; import Decimal, * as numberUtils from './bignum';
@ -9,12 +10,8 @@ export function setVue(vm) {
} }
// Pass in various data that the template could potentially use // Pass in various data that the template could potentially use
const computed = {
player() { return player; },
layers() { return layers; }
};
const data = function() { const data = function() {
return { Decimal, ...numberUtils }; return { Decimal, player, layers, hasWon, pointGain, ...numberUtils };
} }
export function coerceComponent(component, defaultWrapper = 'span') { export function coerceComponent(component, defaultWrapper = 'span') {
if (typeof component === 'number') { if (typeof component === 'number') {
@ -22,12 +19,12 @@ export function coerceComponent(component, defaultWrapper = 'span') {
} }
if (typeof component === 'string') { if (typeof component === 'string') {
component = component.trim(); component = component.trim();
if (!(component in vue.$options.components)) { if (!(component in vue._context.components)) {
if (component.charAt(0) !== '<') { if (component.charAt(0) !== '<') {
component = `<${defaultWrapper}>${component}</${defaultWrapper}>`; component = `<${defaultWrapper}>${component}</${defaultWrapper}>`;
} }
return { template: component, computed, data, inject: [ 'tab' ], methods: { hasUpgrade, hasMilestone, hasAchievement, hasChallenge, maxedChallenge, challengeCompletions, inChallenge, getBuyableAmount, setBuyableAmount, getClickableState, setClickableState, getGridData, setGridData, upgradeEffect, challengeEffect, buyableEffect, clickableEffect, achievementEffect, gridEffect } }; return { template: component, data, inject: [ 'tab' ], methods: { hasUpgrade, hasMilestone, hasAchievement, hasChallenge, maxedChallenge, challengeCompletions, inChallenge, getBuyableAmount, setBuyableAmount, getClickableState, setClickableState, getGridData, setGridData, upgradeEffect, challengeEffect, buyableEffect, clickableEffect, achievementEffect, gridEffect } };
} }
} }
return component; return component;
@ -46,6 +43,13 @@ export function getFiltered(objects, filter = null) {
return objects; return objects;
} }
export function mapState(properties = []) {
return properties.reduce((acc, curr) => {
acc[curr] = () => player[curr];
return acc;
}, {});
}
export const UP = 'UP'; export const UP = 'UP';
export const DOWN = 'DOWN'; export const DOWN = 'DOWN';
export const LEFT = 'LEFT'; export const LEFT = 'LEFT';