Moved properties from player data to new settings and state objects

This commit is contained in:
thepaperpilot 2021-09-05 18:53:04 -05:00
parent bc808098ff
commit eaf47bb946
25 changed files with 367 additions and 285 deletions

View file

@ -11,11 +11,11 @@
<script lang="ts">
import { defineComponent } from "vue";
import themes from "./data/themes";
import player from "./game/player";
import modInfo from "./data/modInfo.json";
import { mapState } from "./util/vue";
import themes from "./data/themes";
import settings from "./game/settings";
import "./main.css";
import { mapSettings } from "./util/vue";
export default defineComponent({
name: "App",
@ -23,9 +23,9 @@ export default defineComponent({
return { useHeader: modInfo.useHeader };
},
computed: {
...mapState(["showTPS"]),
...mapSettings(["showTPS"]),
theme() {
return themes[player.theme].variables;
return themes[settings.theme].variables;
}
},
methods: {

View file

@ -178,6 +178,7 @@ import themes from "@/data/themes";
import { ProgressDisplay, Shape } from "@/game/enums";
import { layers } from "@/game/layers";
import player from "@/game/player";
import settings from "@/game/settings";
import { BoardNode, BoardNodeAction, NodeLabel, NodeType } from "@/typings/features/board";
import { getNodeTypeProperty } from "@/util/features";
import { defineComponent, PropType } from "vue";
@ -279,18 +280,18 @@ export default defineComponent({
return getNodeTypeProperty(this.nodeType, this.node, "progress") || 0;
},
backgroundColor(): string {
return themes[player.theme].variables["--background"];
return themes[settings.theme].variables["--background"];
},
outlineColor(): string {
return (
getNodeTypeProperty(this.nodeType, this.node, "outlineColor") ||
themes[player.theme].variables["--outline"]
themes[settings.theme].variables["--outline"]
);
},
fillColor(): string {
return (
getNodeTypeProperty(this.nodeType, this.node, "fillColor") ||
themes[player.theme].variables["--raised-background"]
themes[settings.theme].variables["--raised-background"]
);
},
progressColor(): string {
@ -299,7 +300,7 @@ export default defineComponent({
titleColor(): string {
return (
getNodeTypeProperty(this.nodeType, this.node, "titleColor") ||
themes[player.theme].variables["--foreground"]
themes[settings.theme].variables["--foreground"]
);
},
progressDisplay(): ProgressDisplay {

View file

@ -17,6 +17,7 @@
import themes from "@/data/themes";
import { layers } from "@/game/layers";
import player from "@/game/player";
import settings from "@/game/settings";
import { Infobox } from "@/typings/features/infobox";
import { coerceComponent, InjectLayerMixin } from "@/util/vue";
import { Component, defineComponent } from "vue";
@ -67,7 +68,7 @@ export default defineComponent({
return player.layers[this.layer].infoboxes[this.id];
},
stacked(): boolean {
return themes[player.theme].stackedInfoboxes;
return themes[settings.theme].stackedInfoboxes;
}
},
methods: {

View file

@ -1,11 +1,12 @@
<template>
<label class="field">
<input type="checkbox" class="toggle" :checked="value" @input="handleInput" />
<span>{{ title }}</span>
<component :is="display" />
</label>
</template>
<script lang="ts">
import { coerceComponent } from "@/util/vue";
import { defineComponent } from "vue";
// Reference: https://codepen.io/finnhvman/pen/pOeyjE
@ -16,6 +17,11 @@ export default defineComponent({
value: Boolean
},
emits: ["change"],
computed: {
display() {
return coerceComponent(this.title || "", "span");
}
},
methods: {
handleInput(e: InputEvent) {
this.$emit("change", (e.target as HTMLInputElement).checked);

View file

@ -49,6 +49,7 @@ import modInfo from "@/data/modInfo.json";
import themes from "@/data/themes";
import { layers } from "@/game/layers";
import player from "@/game/player";
import settings from "@/game/settings";
import { Subtab } from "@/typings/features/subtab";
import { coerceComponent } from "@/util/vue";
import { Component, defineComponent } from "vue";
@ -76,7 +77,7 @@ export default defineComponent({
return layers[this.layer].name || this.layer;
},
floating(): boolean {
return themes[player.theme].floatingTabs;
return themes[settings.theme].floatingTabs;
},
style(): Array<Partial<CSSStyleDeclaration> | undefined> {
const style = [];

View file

@ -23,7 +23,8 @@
import themes from "@/data/themes";
import { layers } from "@/game/layers";
import player from "@/game/player";
import { Microtab, MicrotabFamily } from "@/typings/features/subtab";
import settings from "@/game/settings";
import { Microtab } from "@/typings/features/subtab";
import { coerceComponent, InjectLayerMixin } from "@/util/vue";
import { defineComponent } from "vue";
@ -40,9 +41,9 @@ export default defineComponent({
inject: ["tab"],
computed: {
floating() {
return themes[player.theme].floatingTabs;
return themes[settings.theme].floatingTabs;
},
tabFamily(): MicrotabFamily {
tabFamily() {
return layers[this.layer].microtabs![this.family];
},
microtabs() {
@ -61,13 +62,13 @@ export default defineComponent({
activeMicrotab() {
return this.id != undefined
? this.tabFamily.data[this.id]
: this.tabFamily.activeMicrotab;
: this.tabFamily.activeMicrotab!;
},
embed() {
return this.activeMicrotab!.embedLayer;
},
display() {
return this.activeMicrotab!.display && coerceComponent(this.activeMicrotab!.display!);
return this.activeMicrotab!.display && coerceComponent(this.activeMicrotab!.display);
}
},
methods: {

View file

@ -46,8 +46,9 @@
<script lang="ts">
import modInfo from "@/data/modInfo.json";
import player from "@/game/player";
import state from "@/game/state";
import Decimal, { format } from "@/util/bignum";
import { mapState } from "@/util/vue";
import { mapPlayer, mapState } from "@/util/vue";
import { defineComponent } from "vue";
export default defineComponent({
@ -57,13 +58,14 @@ export default defineComponent({
return { discordName, discordLink, format, showSaves: false };
},
computed: {
...mapState(["hasNaN", "autosave"]),
...mapPlayer(["autosave"]),
...mapState(["hasNaN"]),
path(): string | undefined {
return player.NaNPath?.join(".");
return state.NaNPath?.join(".");
},
previous(): any {
if (player.NaNReceiver && this.property) {
return player.NaNReceiver[this.property];
previous(): unknown {
if (state.NaNReceiver && this.property) {
return state.NaNReceiver[this.property];
}
return null;
},
@ -71,29 +73,29 @@ export default defineComponent({
return player.devSpeed === 0;
},
property(): string | undefined {
return player.NaNPath?.slice(-1)[0];
return state.NaNPath?.slice(-1)[0];
}
},
methods: {
setZero() {
if (player.NaNReceiver && this.property) {
player.NaNReceiver[this.property] = new Decimal(0);
player.hasNaN = false;
if (state.NaNReceiver && this.property) {
state.NaNReceiver[this.property] = new Decimal(0);
state.hasNaN = false;
}
},
setOne() {
if (player.NaNReceiver && this.property) {
player.NaNReceiver[this.property] = new Decimal(1);
player.hasNaN = false;
if (state.NaNReceiver && this.property) {
state.NaNReceiver[this.property] = new Decimal(1);
state.hasNaN = false;
}
},
setPrev() {
player.hasNaN = false;
state.hasNaN = false;
},
ignore() {
if (player.NaNReceiver && this.property) {
player.NaNReceiver[this.property] = new Decimal(NaN);
player.hasNaN = false;
if (state.NaNReceiver && this.property) {
state.NaNReceiver[this.property] = new Decimal(NaN);
state.hasNaN = false;
}
},
setAutosave(autosave: boolean) {

View file

@ -20,18 +20,26 @@
@change="setMSDisplay"
default="all"
/>
<Toggle
title="Offline Production"
:value="offlineProd"
@change="toggleOption('offlineProd')"
/>
<Toggle title="Autosave" :value="autosave" @change="toggleOption('autosave')" />
<Toggle title="Pause game" :value="paused" @change="togglePaused" />
<Toggle title="Show TPS" :value="showTPS" @change="toggleOption('showTPS')" />
<Toggle title="Show TPS" :value="showTPS" @change="toggleSettingsOption('showTPS')" />
<Toggle
title="Hide Maxed Challenges"
:value="hideChallenges"
@change="toggleOption('hideChallenges')"
@change="toggleSettingsOption('hideChallenges')"
/>
<Toggle
title="Offline Production<tooltip display='Save-specific'>*</tooltip>"
:value="offlineProd"
@change="togglePlayerOption('offlineProd')"
/>
<Toggle
title="Autosave<tooltip display='Save-specific'>*</tooltip>"
:value="autosave"
@change="togglePlayerOption('autosave')"
/>
<Toggle
title="Pause game<tooltip display='Save-specific'>*</tooltip>"
:value="paused"
@change="togglePaused"
/>
</template>
</Modal>
@ -41,9 +49,12 @@
import { defineComponent } from "vue";
import themes, { Themes } from "@/data/themes";
import { camelToTitle } from "@/util/common";
import { mapState } from "@/util/vue";
import { mapPlayer, mapSettings } from "@/util/vue";
import player from "@/game/player";
import { MilestoneDisplay } from "@/game/enums";
import { PlayerData } from "@/typings/player";
import settings from "@/game/settings";
import { Settings } from "@/typings/settings";
export default defineComponent({
name: "Options",
@ -57,27 +68,31 @@ export default defineComponent({
label: camelToTitle(theme),
value: theme
})),
msDisplayOptions: ["all", "last", "configurable", "incomplete", "none"].map(option => ({
msDisplayOptions: Object.values(MilestoneDisplay).map(option => ({
label: camelToTitle(option),
value: option
}))
};
},
computed: {
...mapState(["autosave", "offlineProd", "showTPS", "hideChallenges", "theme", "msDisplay"]),
...mapSettings(["showTPS", "hideChallenges", "theme", "msDisplay"]),
...mapPlayer(["autosave", "offlineProd"]),
paused() {
return player.devSpeed === 0;
}
},
methods: {
toggleOption(option: string) {
togglePlayerOption(option: keyof PlayerData) {
player[option] = !player[option];
},
toggleSettingsOption(option: keyof Settings) {
settings[option] = !settings[option];
},
setTheme(theme: Themes) {
player.theme = theme;
settings.theme = theme;
},
setMSDisplay(msDisplay: MilestoneDisplay) {
player.msDisplay = msDisplay;
settings.msDisplay = msDisplay;
},
togglePaused() {
player.devSpeed = this.paused ? 1 : 0;
@ -90,4 +105,9 @@ export default defineComponent({
.header {
margin-bottom: -10px;
}
* >>> .tooltip-container {
display: inline;
margin-left: 5px;
}
</style>

View file

@ -3,17 +3,19 @@
<template v-slot:header>
<h2>Saves Manager</h2>
</template>
<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)"
@editSave="name => editSave(save.id, name)"
@duplicate="duplicateSave(save.id)"
@delete="deleteSave(save.id)"
/>
<template v-slot:body>
<div v-sortable="{ update, handle: '.handle' }">
<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)"
/>
</div>
</template>
<template v-slot:footer>
<div class="modal-footer">
@ -55,8 +57,9 @@
</template>
<script lang="ts">
import modInfo from "@/data/modInfo.json";
import player from "@/game/player";
import settings from "@/game/settings";
import state from "@/game/state";
import { PlayerData } from "@/typings/player";
import { getUniqueID, loadSave, newSave, save } from "@/util/save";
import { defineComponent } from "vue";
@ -86,7 +89,10 @@ export default defineComponent({
bank
} as {
importingFailed: boolean;
saves: Record<string, Partial<PlayerData>>;
saves: Record<
string,
Omit<Partial<PlayerData>, "id"> & { id: string; error?: unknown }
>;
saveToImport: string;
bank: Array<{ label: string; value: string }>;
};
@ -100,39 +106,36 @@ export default defineComponent({
},
methods: {
loadSaveData() {
try {
const { saves } = JSON.parse(
decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id)!)))
);
this.saves = saves.reduce(
(acc: Record<string, Partial<PlayerData>>, curr: string) => {
try {
acc[curr] = JSON.parse(
decodeURIComponent(escape(atob(localStorage.getItem(curr)!)))
);
this.saves = settings.saves.reduce(
(
acc: Record<
string,
Omit<Partial<PlayerData>, "id"> & { id: string; error?: unknown }
>,
curr: string
) => {
try {
const save = localStorage.getItem(curr);
if (save == null) {
acc[curr] = { error: `Save with id "${curr}" doesn't exist`, id: curr };
} else {
acc[curr] = JSON.parse(decodeURIComponent(escape(atob(save))));
acc[curr].id = curr;
} catch (error) {
console.warn(`Can't load save with id "${curr}"`, error);
acc[curr] = { error, id: curr };
}
return acc;
},
{}
);
} catch (e) {
this.saves = { [player.id]: player };
const modData = { active: player.id, saves: [player.id] };
localStorage.setItem(
modInfo.id,
btoa(unescape(encodeURIComponent(JSON.stringify(modData))))
);
}
} catch (error) {
console.warn(`Can't load save with id "${curr}"`, error);
acc[curr] = { error, id: curr };
}
return acc;
},
{}
);
},
exportSave(id: string) {
let saveToExport;
if (player.id === id) {
save();
saveToExport = player.saveToExport;
saveToExport = state.saveToExport;
} else {
saveToExport = btoa(unescape(encodeURIComponent(JSON.stringify(this.saves[id]))));
}
@ -157,40 +160,18 @@ export default defineComponent({
btoa(unescape(encodeURIComponent(JSON.stringify(playerData))))
);
const modData = JSON.parse(
decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id)!)))
);
modData.saves.push(playerData.id);
localStorage.setItem(
modInfo.id,
btoa(unescape(encodeURIComponent(JSON.stringify(modData))))
);
settings.saves.push(playerData.id);
this.saves[playerData.id] = playerData;
},
deleteSave(id: string) {
const modData = JSON.parse(
decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id)!)))
);
modData.saves = modData.saves.filter((save: string) => save !== id);
settings.saves = settings.saves.filter((save: string) => save !== id);
localStorage.removeItem(id);
localStorage.setItem(
modInfo.id,
btoa(unescape(encodeURIComponent(JSON.stringify(modData))))
);
delete this.saves[id];
},
openSave(id: string) {
this.saves[player.id].time = player.time;
save();
loadSave(this.saves[id]);
const modData = JSON.parse(
decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id)!)))
);
modData.active = id;
localStorage.setItem(
modInfo.id,
btoa(unescape(encodeURIComponent(JSON.stringify(modData))))
);
},
newSave() {
const playerData = newSave();
@ -204,14 +185,7 @@ export default defineComponent({
btoa(unescape(encodeURIComponent(JSON.stringify(playerData))))
);
const modData = JSON.parse(
decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id)!)))
);
modData.saves.push(playerData.id);
localStorage.setItem(
modInfo.id,
btoa(unescape(encodeURIComponent(JSON.stringify(modData))))
);
settings.saves.push(playerData.id);
this.saves[playerData.id] = playerData;
},
editSave(id: string, newName: string) {
@ -227,7 +201,6 @@ export default defineComponent({
}
},
importSave(text: string) {
console.log(text);
this.saveToImport = text;
if (text) {
this.$nextTick(() => {
@ -247,14 +220,7 @@ export default defineComponent({
this.saveToImport = "";
this.importingFailed = false;
const modData = JSON.parse(
decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id)!)))
);
modData.saves.push(id);
localStorage.setItem(
modInfo.id,
btoa(unescape(encodeURIComponent(JSON.stringify(modData))))
);
settings.saves.push(id);
} catch (e) {
this.importingFailed = true;
}
@ -264,14 +230,7 @@ export default defineComponent({
}
},
update(e: { newIndex: number; oldIndex: number }) {
const modData = JSON.parse(
decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id)!)))
);
modData.saves.splice(e.newIndex, 0, modData.saves.splice(e.oldIndex, 1)[0]);
localStorage.setItem(
modInfo.id,
btoa(unescape(encodeURIComponent(JSON.stringify(modData))))
);
settings.saves.splice(e.newIndex, 0, settings.saves.splice(e.oldIndex, 1)[0]);
}
}
});

View file

@ -3,7 +3,7 @@
</template>
<script lang="ts">
import player from "@/game/player";
import state from "@/game/state";
import Decimal, { formatWhole } from "@/util/bignum";
import { defineComponent } from "vue";
@ -13,8 +13,8 @@ export default defineComponent({
tps() {
return formatWhole(
Decimal.div(
player.lastTenTicks.length,
player.lastTenTicks.reduce((acc, curr) => acc + curr, 0)
state.lastTenTicks.length,
state.lastTenTicks.reduce((acc, curr) => acc + curr, 0)
)
);
}

View file

@ -18,6 +18,7 @@
import themes from "@/data/themes";
import { layers } from "@/game/layers";
import player from "@/game/player";
import settings from "@/game/settings";
import { Subtab } from "@/typings/features/subtab";
import { InjectLayerMixin } from "@/util/vue";
import { defineComponent, PropType } from "vue";
@ -36,7 +37,7 @@ export default defineComponent({
emits: ["selectTab"],
computed: {
floating(): boolean {
return themes[player.theme].floatingTabs;
return themes[settings.theme].floatingTabs;
},
style(): Array<Partial<CSSStyleDeclaration> | undefined> {
return [

View file

@ -27,7 +27,7 @@
<script lang="ts">
import modInfo from "@/data/modInfo.json";
import { layers } from "@/game/layers";
import { coerceComponent, mapState } from "@/util/vue";
import { coerceComponent, mapPlayer } from "@/util/vue";
import { Component, defineComponent } from "vue";
export default defineComponent({
@ -36,7 +36,7 @@ export default defineComponent({
return { useHeader: modInfo.useHeader };
},
computed: {
...mapState(["tabs"]),
...mapPlayer(["tabs"]),
components() {
return Object.keys(layers).reduce(
(acc: Record<string, Component | string | false>, curr) => {

View file

@ -1230,11 +1230,11 @@ export default {
},
effect() {
if (!hasChallenge(this.layer, this.id)) return 0;
if (player.layers[this.layer].challenges![this.id] == new Decimal(1))
if (Decimal.eq(player.layers[this.layer].challenges![this.id], 1))
return 50;
if (player.layers[this.layer].challenges![this.id] == new Decimal(2))
if (Decimal.eq(player.layers[this.layer].challenges![this.id], 2))
return 60;
if (player.layers[this.layer].challenges![this.id] == new Decimal(3))
if (Decimal.eq(player.layers[this.layer].challenges![this.id], 3))
return 70;
},
completionLimit() {

View file

@ -81,7 +81,7 @@ const main = {
<span v-if="player.points.lt('1e1e6')"> points</span>
</div>
<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(pointGain) }}/sec)
({{ state.oompsMag != 0 ? format(state.oomps) + " OOM" + (state.oompsMag < 0 ? "^OOM" : state.oompsMag > 1 ? "^" + state.oompsMag : "") + "s" : formatSmall(pointGain) }}/sec)
</div>
<spacer />
<modal :show="false">

View file

@ -3,11 +3,7 @@ import modInfo from "@/data/modInfo.json";
import Decimal, { DecimalSource } from "@/util/bignum";
import { layers } from "./layers";
import player from "./player";
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
function updatePopups(diff: number) {
// TODO
}
import state from "./state";
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
function updateParticles(diff: number) {
@ -16,21 +12,21 @@ function updateParticles(diff: number) {
function updateOOMPS(diff: DecimalSource) {
if (player.points != undefined) {
player.oompsMag = 0;
state.oompsMag = 0;
if (player.points.lte(new Decimal(1e100))) {
player.lastPoints = player.points;
state.lastPoints = player.points;
return;
}
let curr = player.points;
let prev = (player.lastPoints as Decimal) || new Decimal(0);
player.lastPoints = curr;
let prev = (state.lastPoints as Decimal) || new Decimal(0);
state.lastPoints = curr;
if (curr.gt(prev)) {
if (curr.gte("10^^8")) {
curr = curr.slog(1e10);
prev = prev.slog(1e10);
player.oomps = curr.sub(prev).div(diff);
player.oompsMag = -1;
state.oomps = curr.sub(prev).div(diff);
state.oompsMag = -1;
} else {
while (
curr
@ -38,13 +34,13 @@ function updateOOMPS(diff: DecimalSource) {
.log(10)
.div(diff)
.gte("100") &&
player.oompsMag <= 5 &&
state.oompsMag <= 5 &&
prev.gt(0)
) {
curr = curr.log(10);
prev = prev.log(10);
player.oomps = curr.sub(prev).div(diff);
player.oompsMag++;
state.oomps = curr.sub(prev).div(diff);
state.oompsMag++;
}
}
}
@ -118,11 +114,10 @@ function update() {
const trueDiff = diff;
// Always update UI
updatePopups(trueDiff);
updateParticles(trueDiff);
player.lastTenTicks.push(trueDiff);
if (player.lastTenTicks.length > 10) {
player.lastTenTicks = player.lastTenTicks.slice(1);
state.lastTenTicks.push(trueDiff);
if (state.lastTenTicks.length > 10) {
state.lastTenTicks = state.lastTenTicks.slice(1);
}
// Stop here if the game is paused on the win screen
@ -130,7 +125,7 @@ function update() {
return;
}
// Stop here if the player had a NaN value
if (player.hasNaN) {
if (state.hasNaN) {
return;
}

View file

@ -36,6 +36,7 @@ import clone from "lodash.clonedeep";
import { isRef } from "vue";
import { ProgressDisplay, Shape } from "./enums";
import { default as playerProxy } from "./player";
import settings from "./settings";
export const layers: Record<string, Readonly<Layer>> = {};
export const hotkeys: Hotkey[] = [];
@ -217,7 +218,7 @@ export function addLayer(layer: RawLayer, player?: Partial<PlayerData>): void {
for (const id in layer.challenges.data) {
layer.challenges.data[id].shown = function() {
return (
this.unlocked !== false && (playerProxy.hideChallenges === false || !this.maxed)
this.unlocked !== false && (settings.hideChallenges === false || !this.maxed)
);
};
layer.challenges.data[id].completed = function() {
@ -376,7 +377,7 @@ export function addLayer(layer: RawLayer, player?: Partial<PlayerData>): void {
if (!this.unlocked) {
return false;
}
switch (playerProxy.msDisplay) {
switch (settings.msDisplay) {
default:
case "all":
return true;

View file

@ -1,15 +1,12 @@
import { Themes } from "@/data/themes";
import { PlayerData } from "@/typings/player";
import Decimal from "@/util/bignum";
import { isPlainObject } from "@/util/common";
import { reactive } from "vue";
import { ImportingStatus, MilestoneDisplay } from "./enums";
import transientState from "./state";
const state = reactive<PlayerData>({
id: "",
points: new Decimal(0),
oomps: new Decimal(0),
oompsMag: 0,
name: "",
tabs: [],
time: -1,
@ -18,22 +15,11 @@ const state = reactive<PlayerData>({
offlineTime: null,
timePlayed: new Decimal(0),
keepGoing: false,
lastTenTicks: [],
showTPS: true,
msDisplay: MilestoneDisplay.All,
hideChallenges: false,
theme: Themes.Nordic,
subtabs: {},
minimized: {},
modID: "",
modVersion: "",
justLoaded: false,
hasNaN: false,
NaNPath: [],
NaNReceiver: null,
importing: ImportingStatus.NotImporting,
saveToImport: "",
saveToExport: "",
layers: {}
});
@ -65,7 +51,7 @@ const playerHandler: ProxyHandler<Record<string, any>> = {
receiver: ProxyConstructor
): boolean {
if (
!state.hasNaN &&
!transientState.hasNaN &&
((typeof value === "number" && isNaN(value)) ||
(value instanceof Decimal &&
(isNaN(value.sign) || isNaN(value.layer) || isNaN(value.mag))))
@ -81,9 +67,9 @@ const playerHandler: ProxyHandler<Record<string, any>> = {
)
) {
state.autosave = false;
state.hasNaN = true;
state.NaNPath = [...target.__path, property];
state.NaNReceiver = (receiver as unknown) as Record<string, unknown>;
transientState.hasNaN = true;
transientState.NaNPath = [...target.__path, property];
transientState.NaNReceiver = (receiver as unknown) as Record<string, unknown>;
console.error(
`Attempted to set NaN value`,
[...target.__path, property],

72
src/game/settings.ts Normal file
View file

@ -0,0 +1,72 @@
import modInfo from "@/data/modInfo.json";
import { Themes } from "@/data/themes";
import { Settings } from "@/typings/settings";
import { isPlainObject } from "@/util/common";
import { hardReset } from "@/util/save";
import { reactive } from "vue";
import { MilestoneDisplay } from "./enums";
const state = reactive<Settings>({
active: "",
saves: [],
showTPS: true,
msDisplay: MilestoneDisplay.All,
hideChallenges: false,
theme: Themes.Nordic
});
const settingsHandler: ProxyHandler<Record<string, any>> = {
get(target: Record<string, any>, key: string): any {
if (key === "__state") {
return target[key];
}
if (target.__state[key] == undefined) {
return;
}
if (isPlainObject(target.__state[key])) {
if (target.__state[key] !== target[key]?.__state) {
target[key] = new Proxy({ __state: target.__state[key] }, settingsHandler);
}
return target[key];
}
return target.__state[key];
},
set(target: Record<string, any>, property: string, value: any): boolean {
target.__state[property] = value;
localStorage.setItem(modInfo.id, btoa(unescape(encodeURIComponent(JSON.stringify(state)))));
return true;
},
ownKeys(target: Record<string, any>) {
return Reflect.ownKeys(target.__state);
},
has(target: Record<string, any>, key: string) {
return Reflect.has(target.__state, key);
}
};
export default window.settings = new Proxy({ __state: state }, settingsHandler) as Settings;
export function loadSettings(): void {
try {
const item: string | null = localStorage.getItem(modInfo.id);
if (item != null && item !== "") {
const settings = JSON.parse(decodeURIComponent(escape(atob(item))));
if (typeof settings === "object") {
Object.assign(state, settings);
}
}
// eslint-disable-next-line no-empty
} catch {}
}
export const hardResetSettings = (window.hardResetSettings = () => {
Object.assign(state, {
active: "",
saves: [],
showTPS: true,
msDisplay: MilestoneDisplay.All,
hideChallenges: false,
theme: Themes.Nordic
});
hardReset();
});

44
src/game/state.ts Normal file
View file

@ -0,0 +1,44 @@
import { Transient } from "@/typings/transient";
import Decimal from "@/util/bignum";
import { isPlainObject } from "@/util/common";
import { reactive } from "vue";
const state = reactive<Transient>({
lastTenTicks: [],
hasNaN: false,
NaNPath: [],
lastPoints: new Decimal(0),
saveToExport: "",
oomps: new Decimal(0),
oompsMag: 0
});
const stateHandler: ProxyHandler<Record<string, any>> = {
get(target: Record<string, any>, key: string): any {
if (key === "__state") {
return target[key];
}
if (target.__state[key] == undefined) {
return;
}
if (isPlainObject(target.__state[key])) {
if (target.__state[key] !== target[key]?.__state) {
target[key] = new Proxy({ __state: target.__state[key] }, stateHandler);
}
return target[key];
}
return target.__state[key];
},
set(target: Record<string, any>, property: string, value: any): boolean {
target.__state[property] = value;
return true;
},
ownKeys(target: Record<string, any>) {
return Reflect.ownKeys(target.__state);
},
has(target: Record<string, any>, key: string) {
return Reflect.has(target.__state, key);
}
};
export default window.state = new Proxy({ __state: state }, stateHandler) as Transient;

View file

@ -1,14 +1,19 @@
import Decimal, { DecimalSource } from "@/util/bignum";
import { App } from "vue";
import { PlayerData } from "./player";
import { Settings } from "./settings";
import { Transient } from "./transient";
declare global {
interface Window {
vue: App;
save: () => void;
hardReset: () => void;
hardResetSettings: () => void;
layers: Dictionary<typeof Proxy>;
player: PlayerData;
state: Transient;
settings: Settings;
Decimal: typeof Decimal;
exponentialFormat: (
num: DecimalSource,

View file

@ -1,21 +1,11 @@
import { Themes } from "@/data/themes";
import { DecimalSource } from "@/lib/break_eternity";
import Decimal from "@/util/bignum";
import Decimal, { DecimalSource } from "@/util/bignum";
import { BoardData } from "./features/board";
import { MilestoneDisplay } from "./features/milestone";
import { State } from "./state";
export interface ModSaveData {
active?: string;
saves?: string[];
}
export interface PlayerData {
id: string;
devSpeed?: DecimalSource;
points: Decimal;
oomps: Decimal;
oompsMag: number;
name: string;
tabs: Array<string>;
time: number;
@ -24,11 +14,6 @@ export interface PlayerData {
offlineTime: Decimal | null;
timePlayed: Decimal;
keepGoing: boolean;
lastTenTicks: Array<number>;
showTPS: boolean;
msDisplay: MilestoneDisplay;
hideChallenges: boolean;
theme: Themes;
subtabs: {
[index: string]: {
mainTabs?: string;
@ -39,12 +24,6 @@ export interface PlayerData {
modID: string;
modVersion: string;
justLoaded: boolean;
hasNaN: boolean;
NaNPath?: Array<string>;
NaNReceiver?: Record<string, unknown> | null;
importing: ImportingStatus;
saveToImport: string;
saveToExport: string;
layers: Record<string, LayerSaveData>;
[index: string]: unknown;
}

13
src/typings/settings.d.ts vendored Normal file
View file

@ -0,0 +1,13 @@
import { Themes } from "@/data/themes";
import { MilestoneDisplay } from "@/game/enums";
// This is the global save data, persists between individual saves
export interface Settings {
active: string;
saves: string[];
showTPS: boolean;
msDisplay: MilestoneDisplay;
hideChallenges: boolean;
theme: Themes;
[index: string]: unknown;
}

13
src/typings/transient.d.ts vendored Normal file
View file

@ -0,0 +1,13 @@
import Decimal from "@/lib/break_eternity";
// Save data that doesn't persist between reloads
export interface Transient {
lastTenTicks: number[];
hasNaN: bool;
NaNPath?: string[];
NaNReceiver?: Record<string, unknown>;
saveToExport: string;
lastPoints: Decimal;
oomps: Decimal;
oompsMag: number;
}

View file

@ -1,9 +1,9 @@
import { fixOldSave, getInitialLayers, getStartingData } from "@/data/mod";
import modInfo from "@/data/modInfo.json";
import { Themes } from "@/data/themes";
import { ImportingStatus, MilestoneDisplay } from "@/game/enums";
import player from "@/game/player";
import { ModSaveData, PlayerData } from "@/typings/player";
import settings, { loadSettings } from "@/game/settings";
import state from "@/game/state";
import { PlayerData } from "@/typings/player";
import Decimal from "./bignum";
export function getInitialStore(playerData: Partial<PlayerData> = {}): PlayerData {
@ -11,8 +11,6 @@ export function getInitialStore(playerData: Partial<PlayerData> = {}): PlayerDat
{
id: `${modInfo.id}-0`,
points: new Decimal(0),
oomps: new Decimal(0),
oompsMag: 0,
name: "Default Save",
tabs: modInfo.initialTabs.slice(),
time: Date.now(),
@ -21,61 +19,29 @@ export function getInitialStore(playerData: Partial<PlayerData> = {}): PlayerDat
offlineTime: new Decimal(0),
timePlayed: new Decimal(0),
keepGoing: false,
lastTenTicks: [],
showTPS: true,
msDisplay: MilestoneDisplay.All,
hideChallenges: false,
theme: Themes.Nordic,
subtabs: {},
minimized: {},
modID: modInfo.id,
modVersion: modInfo.versionNumber,
layers: {},
justLoaded: false,
...getStartingData(),
// Values that don't get loaded/saved
hasNaN: false,
NaNPath: [],
NaNReceiver: null,
importing: ImportingStatus.NotImporting,
saveToImport: "",
saveToExport: ""
...getStartingData()
},
playerData
) as PlayerData;
}
export function save(): void {
/* eslint-disable @typescript-eslint/no-unused-vars */
const {
hasNaN,
NaNPath,
NaNReceiver,
importing,
saveToImport,
saveToExport,
...playerData
} = player.__state as PlayerData;
/* eslint-enable @typescript-eslint/no-unused-vars */
player.saveToExport = btoa(unescape(encodeURIComponent(JSON.stringify(playerData))));
localStorage.setItem(player.id, player.saveToExport);
state.saveToExport = btoa(unescape(encodeURIComponent(JSON.stringify(player.__state))));
localStorage.setItem(player.id, state.saveToExport);
}
export async function load(): Promise<void> {
// Load global settings
loadSettings();
try {
let modData: string | ModSaveData | null = localStorage.getItem(modInfo.id);
if (modData == null) {
await loadSave(newSave());
return;
}
modData = JSON.parse(decodeURIComponent(escape(atob(modData)))) as ModSaveData;
if (modData?.active == null) {
await loadSave(newSave());
return;
}
const save = localStorage.getItem(modData.active);
const save = localStorage.getItem(settings.active);
if (save == null) {
await loadSave(newSave());
return;
@ -85,7 +51,7 @@ export async function load(): Promise<void> {
await loadSave(newSave());
return;
}
playerData.id = modData.active;
playerData.id = settings.active;
await loadSave(playerData);
} catch (e) {
await loadSave(newSave());
@ -97,21 +63,7 @@ export function newSave(): PlayerData {
const playerData = getInitialStore({ id });
localStorage.setItem(id, btoa(unescape(encodeURIComponent(JSON.stringify(playerData)))));
const rawModData = localStorage.getItem(modInfo.id);
if (rawModData == null) {
const modData = { active: id, saves: [id] };
localStorage.setItem(
modInfo.id,
btoa(unescape(encodeURIComponent(JSON.stringify(modData))))
);
} else {
const modData = JSON.parse(decodeURIComponent(escape(atob(rawModData))));
modData.saves.push(id);
localStorage.setItem(
modInfo.id,
btoa(unescape(encodeURIComponent(JSON.stringify(modData))))
);
}
settings.saves.push(id);
return playerData;
}
@ -150,6 +102,7 @@ export async function loadSave(playerData: Partial<PlayerData>): Promise<void> {
}
}
player.justLoaded = true;
settings.active = player.id;
}
export function applyPlayerData<T extends Record<string, any>>(
@ -191,7 +144,4 @@ window.onbeforeunload = () => {
window.save = save;
export const hardReset = (window.hardReset = async () => {
await loadSave(newSave());
const modData = JSON.parse(decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id)!))));
modData.active = player.id;
localStorage.setItem(modInfo.id, btoa(unescape(encodeURIComponent(JSON.stringify(modData)))));
});

View file

@ -1,8 +1,13 @@
import { hasWon, pointGain } from "@/data/mod";
import { layers } from "@/game/layers";
import player from "@/game/player";
import settings from "@/game/settings";
import state from "@/game/state";
import { Feature, Features, GridFeatures } from "@/typings/features/feature";
import { Layer } from "@/typings/layer";
import { PlayerData } from "@/typings/player";
import { Settings } from "@/typings/settings";
import { Transient } from "@/typings/transient";
import { App, Component, ComponentOptions, defineComponent, inject, PropType } from "vue";
import Decimal, * as numberUtils from "./bignum";
import {
@ -34,7 +39,7 @@ export function setVue(vm: App): void {
// Pass in various data that the template could potentially use
const data = function(): Record<string, unknown> {
return { Decimal, player, layers, hasWon, pointGain, ...numberUtils };
return { Decimal, player, state, settings, layers, hasWon, pointGain, ...numberUtils };
};
export function coerceComponent(
component: string | ComponentOptions | Component,
@ -95,14 +100,41 @@ export function getFiltered<T>(
return objects;
}
export function mapState(properties: Array<string> = []): Record<string, unknown> {
return properties.reduce((acc: Record<string, unknown>, curr: string): Record<
string,
unknown
> => {
type OmitIndex<T> = {
[K in keyof T as unknown extends Record<K, 1> ? never : K]: T[K];
};
export function mapPlayer<K extends keyof OmitIndex<PlayerData>>(
properties: K[] = []
): {
[P in K]: () => PlayerData[P];
} {
return properties.reduce((acc, curr: keyof PlayerData) => {
acc[curr] = () => player[curr];
return acc;
}, {});
}, {} as any);
}
export function mapSettings<K extends keyof OmitIndex<Settings>>(
properties: K[] = []
): {
[P in K]: () => Settings[P];
} {
return properties.reduce((acc, curr: keyof Settings) => {
acc[curr] = () => settings[curr];
return acc;
}, {} as any);
}
export function mapState<K extends keyof OmitIndex<Transient>>(
properties: K[] = []
): {
[P in K]: () => Transient[P];
} {
return properties.reduce((acc, curr: keyof Transient) => {
acc[curr] = () => state[curr];
return acc;
}, {} as any);
}
export const InjectLayerMixin = {
@ -126,9 +158,9 @@ export function FilteredFeaturesMixin<T extends Feature>(
};
};
computed: {
filtered: () => Record<string, T> | undefined;
rows: () => number | undefined;
cols: () => number | undefined;
filtered: (this: { layer: string; [feature]: string[] }) => Record<string, T> | undefined;
rows: (this: { layer: string }) => number | undefined;
cols: (this: { layer: string }) => number | undefined;
};
} {
return {
@ -139,19 +171,19 @@ export function FilteredFeaturesMixin<T extends Feature>(
}
},
computed: {
filtered(this: { layer: string; [feature]: string[] }) {
filtered() {
return (
(layers[this.layer][feature] as Features<T> | undefined) &&
getFiltered((layers[this.layer][feature] as Features<T>).data, this[feature])
);
},
rows(this: { layer: string }) {
rows() {
return (
(layers[this.layer][feature] as Features<T> | undefined) &&
(layers[this.layer][feature] as Features<T> | GridFeatures<T>).rows
);
},
cols(this: { layer: string }) {
cols() {
return (
(layers[this.layer][feature] as Features<T> | undefined) &&
(layers[this.layer][feature] as Features<T> | GridFeatures<T>).cols