From 69b1fff796e252f5a1befe21b8d6b47e93723c17 Mon Sep 17 00:00:00 2001
From: thepaperpilot <thepaperpilot@gmail.com>
Date: Wed, 16 Jun 2021 00:36:13 -0500
Subject: [PATCH] Implemented NaN screen

---
 src/App.vue                              |   1 +
 src/components/index.js                  |   2 +
 src/components/system/GameOverScreen.vue |  12 +--
 src/components/system/Info.vue           |   6 +-
 src/components/system/NaNScreen.vue      | 114 +++++++++++++++++++++++
 src/store/game.js                        |   4 +
 src/store/proxies.js                     |  12 ++-
 src/util/load.js                         |   9 +-
 8 files changed, 145 insertions(+), 15 deletions(-)
 create mode 100644 src/components/system/NaNScreen.vue

diff --git a/src/App.vue b/src/App.vue
index d437be0..69a0f29 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -4,6 +4,7 @@
 		<Tabs />
 		<TPS v-if="showTPS" />
 		<GameOverScreen />
+		<NaNScreen />
 		<portal-target name="modal-root" multiple />
 	</div>
 </template>
diff --git a/src/components/index.js b/src/components/index.js
index cff837e..b640729 100644
--- a/src/components/index.js
+++ b/src/components/index.js
@@ -43,6 +43,7 @@ import LayerProvider from './system/LayerProvider';
 import LayerTab from './system/LayerTab';
 import Microtab from './system/Microtab';
 import Modal from './system/Modal';
+import NaNScreen from './system/NaNScreen';
 import Nav from './system/Nav';
 import Options from './system/Options';
 import Resource from './system/Resource';
@@ -110,6 +111,7 @@ Vue.component(LayerProvider.name, LayerProvider);
 Vue.component(LayerTab.name, LayerTab);
 Vue.component(Microtab.name, Microtab);
 Vue.component(Modal.name, Modal);
+Vue.component(NaNScreen.name, NaNScreen);
 Vue.component(Nav.name, Nav);
 Vue.component(Options.name, Options);
 Vue.component(Resource.name, Resource);
diff --git a/src/components/system/GameOverScreen.vue b/src/components/system/GameOverScreen.vue
index 6cadb5b..13446c0 100644
--- a/src/components/system/GameOverScreen.vue
+++ b/src/components/system/GameOverScreen.vue
@@ -13,15 +13,9 @@
 			<div>Please check the Discord to discuss the game or to check for new content updates!</div>
 			<br>
 			<div>
-				<a :href="discordLink" v-if="discordLink !== 'https://discord.gg/WzejVAx'">
+				<a :href="discordLink">
 					<img src="images/discord.png" class="game-over-modal-discord" />
-					{{ discordLink }}
-				</a>
-			</div>
-			<div>
-				<a href="https://discord.gg/WzejVAx" class="game-over-modal-discord-link">
-					<img src="images/discord.png" class="game-over-modal-discord" />
-					The Paper Pilot Community
+					{{ discordName }}
 				</a>
 			</div>
 		</div>
@@ -33,7 +27,7 @@
 </template>
 
 <script>
-import modInfo from '../../data/modInfo';
+import modInfo from '../../data/modInfo.json';
 import { formatTime } from '../../util/bignum';
 import { player } from '../../store/proxies';
 
diff --git a/src/components/system/Info.vue b/src/components/system/Info.vue
index c5c8d81..cb48bad 100644
--- a/src/components/system/Info.vue
+++ b/src/components/system/Info.vue
@@ -18,9 +18,9 @@
 			<div class="link" @click="$emit('openDialog', 'Changelog')">Changelog</div>
 			<br>
 			<div>
-				<a :href="discordLink" v-if="discordLink !== 'https://discord.gg/WzejVAx'">
+				<a :href="discordLink" v-if="discordLink !== 'https://discord.gg/WzejVAx'" class="info-modal-discord-link">
 					<img src="images/discord.png" class="info-modal-discord" />
-					{{ discordLink }}
+					{{ discordName }}
 				</a>
 			</div>
 			<div>
@@ -55,7 +55,7 @@
 </template>
 
 <script>
-import modInfo from '../../data/modInfo';
+import modInfo from '../../data/modInfo.json';
 import { formatTime } from '../../util/bignum';
 import { hotkeys } from '../../store/layers';
 
diff --git a/src/components/system/NaNScreen.vue b/src/components/system/NaNScreen.vue
new file mode 100644
index 0000000..6d432b7
--- /dev/null
+++ b/src/components/system/NaNScreen.vue
@@ -0,0 +1,114 @@
+<template>
+	<Modal :show="show">
+		<div slot="header" class="nan-modal-header">
+			<h2>NaN value detected!</h2>
+		</div>
+		<div slot="body">
+			<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>
+			<br>
+			<div>
+				<a :href="discordLink" class="nan-modal-discord-link">
+					<img src="images/discord.png" class="nan-modal-discord" />
+					{{ discordName }}
+				</a>
+			</div>
+			<br>
+			<Toggle title="Autosave" :value="autosave" @change="setAutosave" />
+		</div>
+		<div slot="footer" class="nan-footer">
+			<button @click="setZero" class="button">Set to 0</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="ignore" class="button danger">Ignore</button>
+		</div>
+	</Modal>
+</template>
+
+<script>
+import modInfo from '../../data/modInfo.json';
+import Decimal, { format } from '../../util/bignum';
+import { player } from '../../store/proxies';
+
+export default {
+	name: 'NaNScreen',
+	data() {
+		const { discordName, discordLink } = modInfo;
+		return { discordName, discordLink, format };
+	},
+	computed: {
+		show() {
+			return player.hasNaN;
+		},
+		property() {
+			return player.NaNProperty;
+		},
+		autosave() {
+			return player.autosave;
+		},
+		previous() {
+			return player.NaNPrevious;
+		}
+	},
+	methods: {
+		setZero() {
+			player.NaNReceiver[player.NaNProperty] = new Decimal(0);
+			player.hasNaN = false;
+		},
+		setOne() {
+			player.NaNReceiver[player.NaNProperty] = new Decimal(1);
+			player.hasNaN = false;
+		},
+		setPrev() {
+			player.NaNReceiver[player.NaNProperty] = player.NaNPrevious;
+			player.hasNaN = false;
+		},
+		ignore() {
+			player.hasNaN = false;
+		},
+		setAutosave(autosave) {
+			player.autosave = autosave;
+		}
+	}
+};
+</script>
+
+<style scoped>
+.nan-modal-header {
+	padding: 10px 0;
+    margin-left: 10px;
+}
+
+.nan-footer {
+	display: flex;
+    justify-content: flex-end;
+}
+
+.nan-footer button {
+	margin: 0 10px;
+}
+
+.nan-modal-discord-link {
+	display: flex;
+	align-items: center;
+}
+
+.nan-modal-discord {
+	height: 2em;
+	margin: 0;
+	margin-right: 4px;
+}
+
+.danger {
+	border: solid 2px var(--danger);
+	padding-right: 0;
+}
+
+.danger::after {
+	content:  "!";
+	color: white;
+	background: var(--danger);
+	padding: 2px;
+	margin-left: 6px;
+	height: 100%;
+}
+</style>
diff --git a/src/store/game.js b/src/store/game.js
index 3c1b09a..5a70013 100644
--- a/src/store/game.js
+++ b/src/store/game.js
@@ -107,6 +107,10 @@ function update() {
 	if (store.getters.hasWon && !player.keepGoing) {
 		return;
 	}
+	// Stop here if the player had a NaN value
+	if (player.hasNaN) {
+		return;
+	}
 
 	diff = new Decimal(diff).max(0);
 
diff --git a/src/store/proxies.js b/src/store/proxies.js
index f2b1356..2261101 100644
--- a/src/store/proxies.js
+++ b/src/store/proxies.js
@@ -33,7 +33,17 @@ const playerHandler = {
 
 		return target[key];
 	},
-	set(target, property, value) {
+	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) {
diff --git a/src/util/load.js b/src/util/load.js
index 9faf04d..115d477 100644
--- a/src/util/load.js
+++ b/src/util/load.js
@@ -11,7 +11,6 @@ export function getInitialStore() {
 		offlineProd: true,
 		timePlayed: new Decimal(0),
 		keepGoing: false,
-		hasNaN: false,
 		lastTenTicks: [],
 		showTPS: true,
 		msDisplay: "all",
@@ -32,6 +31,12 @@ export function getInitialStore() {
 				...layer.startData?.()
 			};
 			return acc;
-		}, {})
+		}, {}),
+
+		// Values that don't get saved
+		hasNaN: false,
+		NaNProperty: "",
+		NaNReceiver: null,
+		NaNPrevious: null
 	}
 }