Merge remote-tracking branch 'template/main'
This commit is contained in:
commit
44901754f4
28 changed files with 339 additions and 145 deletions
29
CHANGELOG.md
29
CHANGELOG.md
|
@ -6,6 +6,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.3.3] - 2022-04-24
|
||||||
|
### Fixed
|
||||||
|
- Spacing between rows in Tree components
|
||||||
|
- Computed style attributes on tooltips were ignored
|
||||||
|
- Tooltips could cause infinite loops due to cyclical dependencies
|
||||||
|
|
||||||
|
## [0.3.2] - 2022-04-23
|
||||||
|
### Fixed
|
||||||
|
- Clickables and several other elements would not register clicks sometimes, if the display is updating rapidly
|
||||||
|
- createLayerTreeNode wasn't using display option correctly
|
||||||
|
|
||||||
|
## [0.3.1] - 2022-04-23
|
||||||
|
### Added
|
||||||
|
- Render utility methods that always return JSX Elements
|
||||||
|
### Changed
|
||||||
|
- **BREAKING** Tooltips overhaul
|
||||||
|
- Tree Nodes no longer have tooltips related properties
|
||||||
|
- Tooltips can now be added to any feature with a Vue component using the `addTooltip` function
|
||||||
|
- Any tooltip can be made pinnable by setting pinnable to true in the addTooltip options, or by passing a `Ref<boolean>` to a Tooltip component
|
||||||
|
- Pinned tooltips have an icon to represent that. It can be disabled by setting the theme's `showPin` property to false
|
||||||
|
- Modifiers are now their own features rather than a part of conversions
|
||||||
|
- Including utilities to display the current state of all the modifiers
|
||||||
|
- TabFamilies' options function is now optional
|
||||||
|
- Layer.minWidth can take string values
|
||||||
|
- If parseable into a number, it'll have "px" appended. Otherwise it'll be un-processed
|
||||||
|
- TreeNodes now have Vue components attached to them
|
||||||
|
- `createResourceTooltip` now shows the resource name
|
||||||
|
- Made classic and aqua theme's `feature-foreground` color dark rather than light
|
||||||
|
|
||||||
## [0.3.0] - 2022-04-10
|
## [0.3.0] - 2022-04-10
|
||||||
### Added
|
### Added
|
||||||
- conversion.currentAt [#4](https://github.com/profectus-engine/Profectus/pull/4)
|
- conversion.currentAt [#4](https://github.com/profectus-engine/Profectus/pull/4)
|
||||||
|
|
31
package-lock.json
generated
31
package-lock.json
generated
|
@ -1,16 +1,17 @@
|
||||||
{
|
{
|
||||||
"name": "profectus",
|
"name": "profectus",
|
||||||
"version": "0.3.0",
|
"version": "0.3.3",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "profectus",
|
"name": "profectus",
|
||||||
"version": "0.3.0",
|
"version": "0.3.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@pixi/particle-emitter": "^5.0.4",
|
"@pixi/particle-emitter": "^5.0.4",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"lodash.clonedeep": "^4.5.0",
|
"lodash.clonedeep": "^4.5.0",
|
||||||
|
"lz-string": "^1.4.4",
|
||||||
"nanoevents": "^6.0.2",
|
"nanoevents": "^6.0.2",
|
||||||
"pixi.js": "^6.3.0",
|
"pixi.js": "^6.3.0",
|
||||||
"vue": "^3.2.26",
|
"vue": "^3.2.26",
|
||||||
|
@ -26,6 +27,7 @@
|
||||||
"@jetblack/operator-overloading": "^0.2.0",
|
"@jetblack/operator-overloading": "^0.2.0",
|
||||||
"@rushstack/eslint-patch": "^1.1.0",
|
"@rushstack/eslint-patch": "^1.1.0",
|
||||||
"@types/lodash.clonedeep": "^4.5.6",
|
"@types/lodash.clonedeep": "^4.5.6",
|
||||||
|
"@types/lz-string": "^1.3.34",
|
||||||
"@vue/babel-plugin-jsx": "^1.1.1",
|
"@vue/babel-plugin-jsx": "^1.1.1",
|
||||||
"@vue/cli-plugin-babel": "^5.0.3",
|
"@vue/cli-plugin-babel": "^5.0.3",
|
||||||
"@vue/cli-plugin-eslint": "^5.0.3",
|
"@vue/cli-plugin-eslint": "^5.0.3",
|
||||||
|
@ -2461,6 +2463,12 @@
|
||||||
"@types/lodash": "*"
|
"@types/lodash": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/lz-string": {
|
||||||
|
"version": "1.3.34",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/lz-string/-/lz-string-1.3.34.tgz",
|
||||||
|
"integrity": "sha512-j6G1e8DULJx3ONf6NdR5JiR2ZY3K3PaaqiEuKYkLQO0Czfi1AzrtjfnfCROyWGeDd5IVMKCwsgSmMip9OWijow==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/mime": {
|
"node_modules/@types/mime": {
|
||||||
"version": "1.3.2",
|
"version": "1.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
|
||||||
|
@ -8592,6 +8600,14 @@
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lz-string": {
|
||||||
|
"version": "1.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz",
|
||||||
|
"integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=",
|
||||||
|
"bin": {
|
||||||
|
"lz-string": "bin/bin.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/magic-string": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.25.7",
|
"version": "0.25.7",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
|
||||||
|
@ -15010,6 +15026,12 @@
|
||||||
"@types/lodash": "*"
|
"@types/lodash": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/lz-string": {
|
||||||
|
"version": "1.3.34",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/lz-string/-/lz-string-1.3.34.tgz",
|
||||||
|
"integrity": "sha512-j6G1e8DULJx3ONf6NdR5JiR2ZY3K3PaaqiEuKYkLQO0Czfi1AzrtjfnfCROyWGeDd5IVMKCwsgSmMip9OWijow==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@types/mime": {
|
"@types/mime": {
|
||||||
"version": "1.3.2",
|
"version": "1.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
|
||||||
|
@ -19594,6 +19616,11 @@
|
||||||
"yallist": "^4.0.0"
|
"yallist": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"lz-string": {
|
||||||
|
"version": "1.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz",
|
||||||
|
"integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY="
|
||||||
|
},
|
||||||
"magic-string": {
|
"magic-string": {
|
||||||
"version": "0.25.7",
|
"version": "0.25.7",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "profectus",
|
"name": "profectus",
|
||||||
"version": "0.3.0",
|
"version": "0.3.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vue-cli-service serve",
|
"start": "vue-cli-service serve",
|
||||||
|
@ -12,6 +12,7 @@
|
||||||
"@pixi/particle-emitter": "^5.0.4",
|
"@pixi/particle-emitter": "^5.0.4",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"lodash.clonedeep": "^4.5.0",
|
"lodash.clonedeep": "^4.5.0",
|
||||||
|
"lz-string": "^1.4.4",
|
||||||
"nanoevents": "^6.0.2",
|
"nanoevents": "^6.0.2",
|
||||||
"pixi.js": "^6.3.0",
|
"pixi.js": "^6.3.0",
|
||||||
"vue": "^3.2.26",
|
"vue": "^3.2.26",
|
||||||
|
@ -27,6 +28,7 @@
|
||||||
"@jetblack/operator-overloading": "^0.2.0",
|
"@jetblack/operator-overloading": "^0.2.0",
|
||||||
"@rushstack/eslint-patch": "^1.1.0",
|
"@rushstack/eslint-patch": "^1.1.0",
|
||||||
"@types/lodash.clonedeep": "^4.5.6",
|
"@types/lodash.clonedeep": "^4.5.6",
|
||||||
|
"@types/lz-string": "^1.3.34",
|
||||||
"@vue/babel-plugin-jsx": "^1.1.1",
|
"@vue/babel-plugin-jsx": "^1.1.1",
|
||||||
"@vue/cli-plugin-babel": "^5.0.3",
|
"@vue/cli-plugin-babel": "^5.0.3",
|
||||||
"@vue/cli-plugin-eslint": "^5.0.3",
|
"@vue/cli-plugin-eslint": "^5.0.3",
|
||||||
|
|
|
@ -57,6 +57,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import projInfo from "data/projInfo.json";
|
||||||
import Modal from "components/Modal.vue";
|
import Modal from "components/Modal.vue";
|
||||||
import player, { PlayerData } from "game/player";
|
import player, { PlayerData } from "game/player";
|
||||||
import settings from "game/settings";
|
import settings from "game/settings";
|
||||||
|
@ -66,6 +67,7 @@ import Select from "./fields/Select.vue";
|
||||||
import Text from "./fields/Text.vue";
|
import Text from "./fields/Text.vue";
|
||||||
import Save from "./Save.vue";
|
import Save from "./Save.vue";
|
||||||
import Draggable from "vuedraggable";
|
import Draggable from "vuedraggable";
|
||||||
|
import LZString from "lz-string";
|
||||||
|
|
||||||
export type LoadablePlayerData = Omit<Partial<PlayerData>, "id"> & { id: string; error?: unknown };
|
export type LoadablePlayerData = Omit<Partial<PlayerData>, "id"> & { id: string; error?: unknown };
|
||||||
|
|
||||||
|
@ -81,21 +83,32 @@ defineExpose({
|
||||||
const importingFailed = ref(false);
|
const importingFailed = ref(false);
|
||||||
const saveToImport = ref("");
|
const saveToImport = ref("");
|
||||||
|
|
||||||
watch(saveToImport, save => {
|
watch(saveToImport, importedSave => {
|
||||||
if (save) {
|
if (importedSave) {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
try {
|
try {
|
||||||
const playerData = JSON.parse(decodeURIComponent(escape(atob(save))));
|
if (importedSave[0] === "{") {
|
||||||
|
// plaintext. No processing needed
|
||||||
|
} else if (importedSave[0] === "e") {
|
||||||
|
// Assumed to be base64, which starts with e
|
||||||
|
importedSave = decodeURIComponent(escape(atob(importedSave)));
|
||||||
|
} else if (importedSave[0] === "ᯡ") {
|
||||||
|
// Assumed to be lz, which starts with ᯡ
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
importedSave = LZString.decompressFromUTF16(importedSave)!;
|
||||||
|
} else {
|
||||||
|
console.warn("Unable to determine preset encoding", importedSave);
|
||||||
|
importingFailed.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const playerData = JSON.parse(importedSave);
|
||||||
if (typeof playerData !== "object") {
|
if (typeof playerData !== "object") {
|
||||||
importingFailed.value = true;
|
importingFailed.value = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const id = getUniqueID();
|
const id = getUniqueID();
|
||||||
playerData.id = id;
|
playerData.id = id;
|
||||||
localStorage.setItem(
|
save(playerData);
|
||||||
id,
|
|
||||||
btoa(unescape(encodeURIComponent(JSON.stringify(playerData))))
|
|
||||||
);
|
|
||||||
saveToImport.value = "";
|
saveToImport.value = "";
|
||||||
importingFailed.value = false;
|
importingFailed.value = false;
|
||||||
|
|
||||||
|
@ -124,14 +137,30 @@ let bank = ref(
|
||||||
const cachedSaves = shallowReactive<Record<string, LoadablePlayerData | undefined>>({});
|
const cachedSaves = shallowReactive<Record<string, LoadablePlayerData | undefined>>({});
|
||||||
function getCachedSave(id: string) {
|
function getCachedSave(id: string) {
|
||||||
if (cachedSaves[id] == null) {
|
if (cachedSaves[id] == null) {
|
||||||
const save = localStorage.getItem(id);
|
let save = localStorage.getItem(id);
|
||||||
if (save == null) {
|
if (save == null) {
|
||||||
cachedSaves[id] = { error: `Save doesn't exist in localStorage`, id };
|
cachedSaves[id] = { error: `Save doesn't exist in localStorage`, id };
|
||||||
} else if (save === "dW5kZWZpbmVk") {
|
} else if (save === "dW5kZWZpbmVk") {
|
||||||
cachedSaves[id] = { error: `Save is undefined`, id };
|
cachedSaves[id] = { error: `Save is undefined`, id };
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
cachedSaves[id] = { ...JSON.parse(decodeURIComponent(escape(atob(save)))), id };
|
if (save[0] === "{") {
|
||||||
|
// plaintext. No processing needed
|
||||||
|
} else if (save[0] === "e") {
|
||||||
|
// Assumed to be base64, which starts with e
|
||||||
|
save = decodeURIComponent(escape(atob(save)));
|
||||||
|
} else if (save[0] === "ᯡ") {
|
||||||
|
// Assumed to be lz, which starts with ᯡ
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
save = LZString.decompressFromUTF16(save)!;
|
||||||
|
} else {
|
||||||
|
console.warn("Unable to determine preset encoding", save);
|
||||||
|
importingFailed.value = true;
|
||||||
|
cachedSaves[id] = { error: "Unable to determine preset encoding", id };
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
return cachedSaves[id]!;
|
||||||
|
}
|
||||||
|
cachedSaves[id] = { ...JSON.parse(save), id };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
cachedSaves[id] = { error, id };
|
cachedSaves[id] = { error, id };
|
||||||
console.warn(
|
console.warn(
|
||||||
|
@ -162,7 +191,19 @@ function exportSave(id: string) {
|
||||||
if (player.id === id) {
|
if (player.id === id) {
|
||||||
saveToExport = save();
|
saveToExport = save();
|
||||||
} else {
|
} else {
|
||||||
saveToExport = btoa(unescape(encodeURIComponent(JSON.stringify(saves.value[id]))));
|
saveToExport = JSON.stringify(saves.value[id]);
|
||||||
|
switch (projInfo.saveEncoding) {
|
||||||
|
default:
|
||||||
|
console.warn(`Unknown save encoding: ${projInfo.saveEncoding}. Defaulting to lz`);
|
||||||
|
case "lz":
|
||||||
|
saveToExport = LZString.compressToUTF16(saveToExport);
|
||||||
|
break;
|
||||||
|
case "base64":
|
||||||
|
saveToExport = btoa(unescape(encodeURIComponent(saveToExport)));
|
||||||
|
break;
|
||||||
|
case "plain":
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put on clipboard. Using the clipboard API asks for permissions and stuff
|
// Put on clipboard. Using the clipboard API asks for permissions and stuff
|
||||||
|
@ -181,10 +222,7 @@ function duplicateSave(id: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const playerData = { ...saves.value[id], id: getUniqueID() };
|
const playerData = { ...saves.value[id], id: getUniqueID() };
|
||||||
localStorage.setItem(
|
save(playerData as PlayerData);
|
||||||
playerData.id,
|
|
||||||
btoa(unescape(encodeURIComponent(JSON.stringify(playerData))))
|
|
||||||
);
|
|
||||||
|
|
||||||
settings.saves.push(playerData.id);
|
settings.saves.push(playerData.id);
|
||||||
}
|
}
|
||||||
|
@ -207,12 +245,22 @@ function openSave(id: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function newFromPreset(preset: string) {
|
function newFromPreset(preset: string) {
|
||||||
const playerData = JSON.parse(decodeURIComponent(escape(atob(preset))));
|
if (preset[0] === "{") {
|
||||||
|
// plaintext. No processing needed
|
||||||
|
} else if (preset[0] === "e") {
|
||||||
|
// Assumed to be base64, which starts with e
|
||||||
|
preset = decodeURIComponent(escape(atob(preset)));
|
||||||
|
} else if (preset[0] === "ᯡ") {
|
||||||
|
// Assumed to be lz, which starts with ᯡ
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
preset = LZString.decompressFromUTF16(preset)!;
|
||||||
|
} else {
|
||||||
|
console.warn("Unable to determine preset encoding", preset);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const playerData = JSON.parse(preset);
|
||||||
playerData.id = getUniqueID();
|
playerData.id = getUniqueID();
|
||||||
localStorage.setItem(
|
save(playerData as PlayerData);
|
||||||
playerData.id,
|
|
||||||
btoa(unescape(encodeURIComponent(JSON.stringify(playerData))))
|
|
||||||
);
|
|
||||||
|
|
||||||
settings.saves.push(playerData.id);
|
settings.saves.push(playerData.id);
|
||||||
}
|
}
|
||||||
|
@ -225,7 +273,7 @@ function editSave(id: string, newName: string) {
|
||||||
player.name = newName;
|
player.name = newName;
|
||||||
save();
|
save();
|
||||||
} else {
|
} else {
|
||||||
localStorage.setItem(id, btoa(unescape(encodeURIComponent(JSON.stringify(currSave)))));
|
save(currSave as PlayerData);
|
||||||
cachedSaves[id] = undefined;
|
cachedSaves[id] = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@ function onUpdate(value: SelectOption) {
|
||||||
}
|
}
|
||||||
|
|
||||||
.vue-dropdown-item {
|
.vue-dropdown-item {
|
||||||
color: var(--feature-foreground);
|
color: var(--foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vue-dropdown-item,
|
.vue-dropdown-item,
|
||||||
|
|
|
@ -133,7 +133,7 @@ export function createResetButton<T extends ClickableOptions & ResetButtonOption
|
||||||
export interface LayerTreeNodeOptions extends TreeNodeOptions {
|
export interface LayerTreeNodeOptions extends TreeNodeOptions {
|
||||||
layerID: string;
|
layerID: string;
|
||||||
color: Computable<string>; // marking as required
|
color: Computable<string>; // marking as required
|
||||||
display?: Computable<string>;
|
display?: Computable<CoercableComponent>;
|
||||||
append?: Computable<boolean>;
|
append?: Computable<boolean>;
|
||||||
}
|
}
|
||||||
export type LayerTreeNode<T extends LayerTreeNodeOptions> = Replace<
|
export type LayerTreeNode<T extends LayerTreeNodeOptions> = Replace<
|
||||||
|
@ -146,7 +146,7 @@ export type LayerTreeNode<T extends LayerTreeNodeOptions> = Replace<
|
||||||
export type GenericLayerTreeNode = Replace<
|
export type GenericLayerTreeNode = Replace<
|
||||||
LayerTreeNode<LayerTreeNodeOptions>,
|
LayerTreeNode<LayerTreeNodeOptions>,
|
||||||
{
|
{
|
||||||
display: ProcessedComputable<string>;
|
display: ProcessedComputable<CoercableComponent>;
|
||||||
append?: ProcessedComputable<boolean>;
|
append?: ProcessedComputable<boolean>;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
@ -161,7 +161,7 @@ export function createLayerTreeNode<T extends LayerTreeNodeOptions>(
|
||||||
processComputable(options as T, "append");
|
processComputable(options as T, "append");
|
||||||
return {
|
return {
|
||||||
...options,
|
...options,
|
||||||
display: options.layerID,
|
display: options.display,
|
||||||
onClick: unref((options as unknown as GenericLayerTreeNode).append)
|
onClick: unref((options as unknown as GenericLayerTreeNode).append)
|
||||||
? function () {
|
? function () {
|
||||||
if (player.tabs.includes(options.layerID)) {
|
if (player.tabs.includes(options.layerID)) {
|
||||||
|
|
|
@ -8,6 +8,8 @@ import { jsx } from "features/feature";
|
||||||
import { createReset } from "features/reset";
|
import { createReset } from "features/reset";
|
||||||
import MainDisplay from "features/resources/MainDisplay.vue";
|
import MainDisplay from "features/resources/MainDisplay.vue";
|
||||||
import { createResource } from "features/resources/resource";
|
import { createResource } from "features/resources/resource";
|
||||||
|
import { addTooltip } from "features/tooltips/tooltip";
|
||||||
|
import { createResourceTooltip } from "features/trees/tree";
|
||||||
import { createLayer } from "game/layers";
|
import { createLayer } from "game/layers";
|
||||||
import { DecimalSource } from "util/bignum";
|
import { DecimalSource } from "util/bignum";
|
||||||
import { render } from "util/vue";
|
import { render } from "util/vue";
|
||||||
|
@ -35,6 +37,10 @@ const layer = createLayer(id, () => {
|
||||||
color,
|
color,
|
||||||
reset
|
reset
|
||||||
}));
|
}));
|
||||||
|
addTooltip(treeNode, {
|
||||||
|
display: createResourceTooltip(points),
|
||||||
|
pinnable: true
|
||||||
|
});
|
||||||
|
|
||||||
const resetButton = createResetButton(() => ({
|
const resetButton = createResetButton(() => ({
|
||||||
conversion,
|
conversion,
|
||||||
|
|
|
@ -18,5 +18,6 @@
|
||||||
|
|
||||||
"maxTickLength": 3600,
|
"maxTickLength": 3600,
|
||||||
"offlineLimit": 1,
|
"offlineLimit": 1,
|
||||||
"enablePausing": true
|
"enablePausing": true,
|
||||||
|
"saveEncoding": "lz"
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ const defaultTheme: Theme = {
|
||||||
variables: {
|
variables: {
|
||||||
"--foreground": "#dfdfdf",
|
"--foreground": "#dfdfdf",
|
||||||
"--background": "#0f0f0f",
|
"--background": "#0f0f0f",
|
||||||
"--feature-foreground": "#eee",
|
"--feature-foreground": "#0f0f0f",
|
||||||
"--tooltip-background": "rgba(0, 0, 0, 0.75)",
|
"--tooltip-background": "rgba(0, 0, 0, 0.75)",
|
||||||
"--raised-background": "#0f0f0f",
|
"--raised-background": "#0f0f0f",
|
||||||
"--points": "#ffffff",
|
"--points": "#ffffff",
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
Visibility
|
Visibility
|
||||||
} from "features/feature";
|
} from "features/feature";
|
||||||
import "game/notifications";
|
import "game/notifications";
|
||||||
import { Persistent, PersistentState, persistent } from "game/persistence";
|
import { Persistent, persistent } from "game/persistence";
|
||||||
import {
|
import {
|
||||||
Computable,
|
Computable,
|
||||||
GetComputableType,
|
GetComputableType,
|
||||||
|
@ -21,7 +21,7 @@ import {
|
||||||
} from "util/computed";
|
} from "util/computed";
|
||||||
import { createLazyProxy } from "util/proxies";
|
import { createLazyProxy } from "util/proxies";
|
||||||
import { coerceComponent } from "util/vue";
|
import { coerceComponent } from "util/vue";
|
||||||
import { Ref, unref, watchEffect } from "vue";
|
import { unref, watchEffect } from "vue";
|
||||||
import { useToast } from "vue-toastification";
|
import { useToast } from "vue-toastification";
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
@ -39,9 +39,9 @@ export interface AchievementOptions {
|
||||||
onComplete?: VoidFunction;
|
onComplete?: VoidFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseAchievement extends Persistent<boolean> {
|
export interface BaseAchievement {
|
||||||
id: string;
|
id: string;
|
||||||
earned: Ref<boolean>;
|
earned: Persistent<boolean>;
|
||||||
complete: VoidFunction;
|
complete: VoidFunction;
|
||||||
type: typeof AchievementType;
|
type: typeof AchievementType;
|
||||||
[Component]: typeof AchievementComponent;
|
[Component]: typeof AchievementComponent;
|
||||||
|
@ -68,17 +68,18 @@ export type GenericAchievement = Replace<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createAchievement<T extends AchievementOptions>(
|
export function createAchievement<T extends AchievementOptions>(
|
||||||
optionsFunc: OptionsFunc<T, Achievement<T>, BaseAchievement>
|
optionsFunc?: OptionsFunc<T, Achievement<T>, BaseAchievement>
|
||||||
): Achievement<T> {
|
): Achievement<T> {
|
||||||
return createLazyProxy(persistent => {
|
const earned = persistent<boolean>(false);
|
||||||
const achievement = Object.assign(persistent, optionsFunc());
|
return createLazyProxy(() => {
|
||||||
|
const achievement = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>);
|
||||||
achievement.id = getUniqueID("achievement-");
|
achievement.id = getUniqueID("achievement-");
|
||||||
achievement.type = AchievementType;
|
achievement.type = AchievementType;
|
||||||
achievement[Component] = AchievementComponent;
|
achievement[Component] = AchievementComponent;
|
||||||
|
|
||||||
achievement.earned = achievement[PersistentState];
|
achievement.earned = earned;
|
||||||
achievement.complete = function () {
|
achievement.complete = function () {
|
||||||
achievement[PersistentState].value = true;
|
earned.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
processComputable(achievement as T, "visibility");
|
processComputable(achievement as T, "visibility");
|
||||||
|
@ -122,5 +123,5 @@ export function createAchievement<T extends AchievementOptions>(
|
||||||
}
|
}
|
||||||
|
|
||||||
return achievement as unknown as Achievement<T>;
|
return achievement as unknown as Achievement<T>;
|
||||||
}, persistent<boolean>(false));
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import ClickableComponent from "features/clickables/Clickable.vue";
|
import ClickableComponent from "features/clickables/Clickable.vue";
|
||||||
import { Resource } from "features/resources/resource";
|
import { Resource } from "features/resources/resource";
|
||||||
import { Persistent, PersistentState, persistent } from "game/persistence";
|
import { Persistent, persistent } from "game/persistence";
|
||||||
import Decimal, { DecimalSource, format, formatWhole } from "util/bignum";
|
import Decimal, { DecimalSource, format, formatWhole } from "util/bignum";
|
||||||
import {
|
import {
|
||||||
Computable,
|
Computable,
|
||||||
|
@ -49,9 +49,9 @@ export interface BuyableOptions {
|
||||||
onPurchase?: (cost: DecimalSource) => void;
|
onPurchase?: (cost: DecimalSource) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseBuyable extends Persistent<DecimalSource> {
|
export interface BaseBuyable {
|
||||||
id: string;
|
id: string;
|
||||||
amount: Ref<DecimalSource>;
|
amount: Persistent<DecimalSource>;
|
||||||
maxed: Ref<boolean>;
|
maxed: Ref<boolean>;
|
||||||
canAfford: Ref<boolean>;
|
canAfford: Ref<boolean>;
|
||||||
canClick: ProcessedComputable<boolean>;
|
canClick: ProcessedComputable<boolean>;
|
||||||
|
@ -90,8 +90,9 @@ export type GenericBuyable = Replace<
|
||||||
export function createBuyable<T extends BuyableOptions>(
|
export function createBuyable<T extends BuyableOptions>(
|
||||||
optionsFunc: OptionsFunc<T, Buyable<T>, BaseBuyable>
|
optionsFunc: OptionsFunc<T, Buyable<T>, BaseBuyable>
|
||||||
): Buyable<T> {
|
): Buyable<T> {
|
||||||
return createLazyProxy(persistent => {
|
const amount = persistent<DecimalSource>(0);
|
||||||
const buyable = Object.assign(persistent, optionsFunc());
|
return createLazyProxy(() => {
|
||||||
|
const buyable = optionsFunc();
|
||||||
|
|
||||||
if (buyable.canPurchase == null && (buyable.resource == null || buyable.cost == null)) {
|
if (buyable.canPurchase == null && (buyable.resource == null || buyable.cost == null)) {
|
||||||
console.warn(
|
console.warn(
|
||||||
|
@ -105,7 +106,7 @@ export function createBuyable<T extends BuyableOptions>(
|
||||||
buyable.type = BuyableType;
|
buyable.type = BuyableType;
|
||||||
buyable[Component] = ClickableComponent;
|
buyable[Component] = ClickableComponent;
|
||||||
|
|
||||||
buyable.amount = buyable[PersistentState];
|
buyable.amount = amount;
|
||||||
buyable.canAfford = computed(() => {
|
buyable.canAfford = computed(() => {
|
||||||
const genericBuyable = buyable as GenericBuyable;
|
const genericBuyable = buyable as GenericBuyable;
|
||||||
const cost = unref(genericBuyable.cost);
|
const cost = unref(genericBuyable.cost);
|
||||||
|
@ -239,5 +240,5 @@ export function createBuyable<T extends BuyableOptions>(
|
||||||
};
|
};
|
||||||
|
|
||||||
return buyable as unknown as Buyable<T>;
|
return buyable as unknown as Buyable<T>;
|
||||||
}, persistent<DecimalSource>(0));
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,4 +138,8 @@ export default defineComponent({
|
||||||
.clickable.small {
|
.clickable.small {
|
||||||
min-height: unset;
|
min-height: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.clickable > * {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -70,10 +70,10 @@ export type GenericClickable = Replace<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createClickable<T extends ClickableOptions>(
|
export function createClickable<T extends ClickableOptions>(
|
||||||
optionsFunc: OptionsFunc<T, Clickable<T>, BaseClickable>
|
optionsFunc?: OptionsFunc<T, Clickable<T>, BaseClickable>
|
||||||
): Clickable<T> {
|
): Clickable<T> {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const clickable = optionsFunc();
|
const clickable = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>);
|
||||||
clickable.id = getUniqueID("clickable-");
|
clickable.id = getUniqueID("clickable-");
|
||||||
clickable.type = ClickableType;
|
clickable.type = ClickableType;
|
||||||
clickable[Component] = ClickableComponent;
|
clickable[Component] = ClickableComponent;
|
||||||
|
|
|
@ -87,4 +87,8 @@ export default defineComponent({
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
background-color: var(--layer-color);
|
background-color: var(--layer-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tile > * {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -20,7 +20,7 @@ import {
|
||||||
} from "util/computed";
|
} from "util/computed";
|
||||||
import { createLazyProxy } from "util/proxies";
|
import { createLazyProxy } from "util/proxies";
|
||||||
import { computed, Ref, unref } from "vue";
|
import { computed, Ref, unref } from "vue";
|
||||||
import { State, Persistent, PersistentState, persistent } from "game/persistence";
|
import { State, Persistent, persistent } from "game/persistence";
|
||||||
|
|
||||||
export const GridType = Symbol("Grid");
|
export const GridType = Symbol("Grid");
|
||||||
|
|
||||||
|
@ -205,12 +205,13 @@ export interface GridOptions {
|
||||||
onHold?: (id: string | number, state: State) => void;
|
onHold?: (id: string | number, state: State) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseGrid extends Persistent<Record<string | number, State>> {
|
export interface BaseGrid {
|
||||||
id: string;
|
id: string;
|
||||||
getID: (id: string | number, state: State) => string;
|
getID: (id: string | number, state: State) => string;
|
||||||
getState: (id: string | number) => State;
|
getState: (id: string | number) => State;
|
||||||
setState: (id: string | number, state: State) => void;
|
setState: (id: string | number, state: State) => void;
|
||||||
cells: Record<string | number, GridCell>;
|
cells: Record<string | number, GridCell>;
|
||||||
|
cellState: Persistent<Record<string | number, State>>;
|
||||||
type: typeof GridType;
|
type: typeof GridType;
|
||||||
[Component]: typeof GridComponent;
|
[Component]: typeof GridComponent;
|
||||||
[GatherProps]: () => Record<string, unknown>;
|
[GatherProps]: () => Record<string, unknown>;
|
||||||
|
@ -244,22 +245,25 @@ export type GenericGrid = Replace<
|
||||||
export function createGrid<T extends GridOptions>(
|
export function createGrid<T extends GridOptions>(
|
||||||
optionsFunc: OptionsFunc<T, Grid<T>, BaseGrid>
|
optionsFunc: OptionsFunc<T, Grid<T>, BaseGrid>
|
||||||
): Grid<T> {
|
): Grid<T> {
|
||||||
return createLazyProxy(persistent => {
|
const cellState = persistent<Record<string | number, State>>({});
|
||||||
const grid = Object.assign(persistent, optionsFunc());
|
return createLazyProxy(() => {
|
||||||
|
const grid = optionsFunc();
|
||||||
grid.id = getUniqueID("grid-");
|
grid.id = getUniqueID("grid-");
|
||||||
grid[Component] = GridComponent;
|
grid[Component] = GridComponent;
|
||||||
|
|
||||||
|
grid.cellState = cellState;
|
||||||
|
|
||||||
grid.getID = function (this: GenericGrid, cell: string | number) {
|
grid.getID = function (this: GenericGrid, cell: string | number) {
|
||||||
return grid.id + "-" + cell;
|
return grid.id + "-" + cell;
|
||||||
};
|
};
|
||||||
grid.getState = function (this: GenericGrid, cell: string | number) {
|
grid.getState = function (this: GenericGrid, cell: string | number) {
|
||||||
if (this[PersistentState].value[cell] != undefined) {
|
if (this.cellState.value[cell] != undefined) {
|
||||||
return this[PersistentState].value[cell];
|
return cellState.value[cell];
|
||||||
}
|
}
|
||||||
return this.cells[cell].startState;
|
return this.cells[cell].startState;
|
||||||
};
|
};
|
||||||
grid.setState = function (this: GenericGrid, cell: string | number, state: State) {
|
grid.setState = function (this: GenericGrid, cell: string | number, state: State) {
|
||||||
this[PersistentState].value[cell] = state;
|
cellState.value[cell] = state;
|
||||||
};
|
};
|
||||||
|
|
||||||
grid.cells = createGridProxy(grid as GenericGrid);
|
grid.cells = createGridProxy(grid as GenericGrid);
|
||||||
|
@ -301,5 +305,5 @@ export function createGrid<T extends GridOptions>(
|
||||||
};
|
};
|
||||||
|
|
||||||
return grid as unknown as Grid<T>;
|
return grid as unknown as Grid<T>;
|
||||||
}, persistent({}));
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
import InfoboxComponent from "features/infoboxes/Infobox.vue";
|
|
||||||
import {
|
import {
|
||||||
CoercableComponent,
|
CoercableComponent,
|
||||||
Component,
|
Component,
|
||||||
OptionsFunc,
|
|
||||||
GatherProps,
|
GatherProps,
|
||||||
getUniqueID,
|
getUniqueID,
|
||||||
|
OptionsFunc,
|
||||||
Replace,
|
Replace,
|
||||||
setDefault,
|
setDefault,
|
||||||
StyleValue,
|
StyleValue,
|
||||||
Visibility
|
Visibility
|
||||||
} from "features/feature";
|
} from "features/feature";
|
||||||
|
import InfoboxComponent from "features/infoboxes/Infobox.vue";
|
||||||
|
import { Persistent, persistent } from "game/persistence";
|
||||||
import {
|
import {
|
||||||
Computable,
|
Computable,
|
||||||
GetComputableType,
|
GetComputableType,
|
||||||
|
@ -18,8 +19,7 @@ import {
|
||||||
ProcessedComputable
|
ProcessedComputable
|
||||||
} from "util/computed";
|
} from "util/computed";
|
||||||
import { createLazyProxy } from "util/proxies";
|
import { createLazyProxy } from "util/proxies";
|
||||||
import { Ref, unref } from "vue";
|
import { unref } from "vue";
|
||||||
import { Persistent, PersistentState, persistent } from "game/persistence";
|
|
||||||
|
|
||||||
export const InfoboxType = Symbol("Infobox");
|
export const InfoboxType = Symbol("Infobox");
|
||||||
|
|
||||||
|
@ -34,9 +34,9 @@ export interface InfoboxOptions {
|
||||||
display: Computable<CoercableComponent>;
|
display: Computable<CoercableComponent>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseInfobox extends Persistent<boolean> {
|
export interface BaseInfobox {
|
||||||
id: string;
|
id: string;
|
||||||
collapsed: Ref<boolean>;
|
collapsed: Persistent<boolean>;
|
||||||
type: typeof InfoboxType;
|
type: typeof InfoboxType;
|
||||||
[Component]: typeof InfoboxComponent;
|
[Component]: typeof InfoboxComponent;
|
||||||
[GatherProps]: () => Record<string, unknown>;
|
[GatherProps]: () => Record<string, unknown>;
|
||||||
|
@ -66,13 +66,14 @@ export type GenericInfobox = Replace<
|
||||||
export function createInfobox<T extends InfoboxOptions>(
|
export function createInfobox<T extends InfoboxOptions>(
|
||||||
optionsFunc: OptionsFunc<T, Infobox<T>, BaseInfobox>
|
optionsFunc: OptionsFunc<T, Infobox<T>, BaseInfobox>
|
||||||
): Infobox<T> {
|
): Infobox<T> {
|
||||||
return createLazyProxy(persistent => {
|
const collapsed = persistent<boolean>(false);
|
||||||
const infobox = Object.assign(persistent, optionsFunc());
|
return createLazyProxy(() => {
|
||||||
|
const infobox = optionsFunc();
|
||||||
infobox.id = getUniqueID("infobox-");
|
infobox.id = getUniqueID("infobox-");
|
||||||
infobox.type = InfoboxType;
|
infobox.type = InfoboxType;
|
||||||
infobox[Component] = InfoboxComponent;
|
infobox[Component] = InfoboxComponent;
|
||||||
|
|
||||||
infobox.collapsed = infobox[PersistentState];
|
infobox.collapsed = collapsed;
|
||||||
|
|
||||||
processComputable(infobox as T, "visibility");
|
processComputable(infobox as T, "visibility");
|
||||||
setDefault(infobox, "visibility", Visibility.Visible);
|
setDefault(infobox, "visibility", Visibility.Visible);
|
||||||
|
@ -112,5 +113,5 @@ export function createInfobox<T extends InfoboxOptions>(
|
||||||
};
|
};
|
||||||
|
|
||||||
return infobox as unknown as Infobox<T>;
|
return infobox as unknown as Infobox<T>;
|
||||||
}, persistent<boolean>(false));
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ export interface Link extends SVGAttributes {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LinksOptions {
|
export interface LinksOptions {
|
||||||
links?: Computable<Link[]>;
|
links: Computable<Link[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseLinks {
|
export interface BaseLinks {
|
||||||
|
|
|
@ -14,7 +14,7 @@ import {
|
||||||
import MilestoneComponent from "features/milestones/Milestone.vue";
|
import MilestoneComponent from "features/milestones/Milestone.vue";
|
||||||
import { globalBus } from "game/events";
|
import { globalBus } from "game/events";
|
||||||
import "game/notifications";
|
import "game/notifications";
|
||||||
import { persistent, Persistent, PersistentState } from "game/persistence";
|
import { persistent, Persistent } from "game/persistence";
|
||||||
import settings, { registerSettingField } from "game/settings";
|
import settings, { registerSettingField } from "game/settings";
|
||||||
import { camelToTitle } from "util/common";
|
import { camelToTitle } from "util/common";
|
||||||
import {
|
import {
|
||||||
|
@ -26,7 +26,7 @@ import {
|
||||||
} from "util/computed";
|
} from "util/computed";
|
||||||
import { createLazyProxy } from "util/proxies";
|
import { createLazyProxy } from "util/proxies";
|
||||||
import { coerceComponent, isCoercableComponent } from "util/vue";
|
import { coerceComponent, isCoercableComponent } from "util/vue";
|
||||||
import { computed, Ref, unref, watchEffect } from "vue";
|
import { computed, unref, watchEffect } from "vue";
|
||||||
import { useToast } from "vue-toastification";
|
import { useToast } from "vue-toastification";
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
@ -57,9 +57,9 @@ export interface MilestoneOptions {
|
||||||
onComplete?: VoidFunction;
|
onComplete?: VoidFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseMilestone extends Persistent<boolean> {
|
export interface BaseMilestone {
|
||||||
id: string;
|
id: string;
|
||||||
earned: Ref<boolean>;
|
earned: Persistent<boolean>;
|
||||||
complete: VoidFunction;
|
complete: VoidFunction;
|
||||||
type: typeof MilestoneType;
|
type: typeof MilestoneType;
|
||||||
[Component]: typeof MilestoneComponent;
|
[Component]: typeof MilestoneComponent;
|
||||||
|
@ -84,17 +84,18 @@ export type GenericMilestone = Replace<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createMilestone<T extends MilestoneOptions>(
|
export function createMilestone<T extends MilestoneOptions>(
|
||||||
optionsFunc: OptionsFunc<T, Milestone<T>, BaseMilestone>
|
optionsFunc?: OptionsFunc<T, Milestone<T>, BaseMilestone>
|
||||||
): Milestone<T> {
|
): Milestone<T> {
|
||||||
return createLazyProxy(persistent => {
|
const earned = persistent<boolean>(false);
|
||||||
const milestone = Object.assign(persistent, optionsFunc());
|
return createLazyProxy(() => {
|
||||||
|
const milestone = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>);
|
||||||
milestone.id = getUniqueID("milestone-");
|
milestone.id = getUniqueID("milestone-");
|
||||||
milestone.type = MilestoneType;
|
milestone.type = MilestoneType;
|
||||||
milestone[Component] = MilestoneComponent;
|
milestone[Component] = MilestoneComponent;
|
||||||
|
|
||||||
milestone.earned = milestone[PersistentState];
|
milestone.earned = earned;
|
||||||
milestone.complete = function () {
|
milestone.complete = function () {
|
||||||
milestone[PersistentState].value = true;
|
earned.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
processComputable(milestone as T, "visibility");
|
processComputable(milestone as T, "visibility");
|
||||||
|
@ -168,7 +169,7 @@ export function createMilestone<T extends MilestoneOptions>(
|
||||||
}
|
}
|
||||||
|
|
||||||
return milestone as unknown as Milestone<T>;
|
return milestone as unknown as Milestone<T>;
|
||||||
}, persistent<boolean>(false));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module "game/settings" {
|
declare module "game/settings" {
|
||||||
|
|
|
@ -42,10 +42,10 @@ export type Particles<T extends ParticlesOptions> = Replace<
|
||||||
export type GenericParticles = Particles<ParticlesOptions>;
|
export type GenericParticles = Particles<ParticlesOptions>;
|
||||||
|
|
||||||
export function createParticles<T extends ParticlesOptions>(
|
export function createParticles<T extends ParticlesOptions>(
|
||||||
optionsFunc: OptionsFunc<T, Particles<T>, BaseParticles>
|
optionsFunc?: OptionsFunc<T, Particles<T>, BaseParticles>
|
||||||
): Particles<T> {
|
): Particles<T> {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const particles = optionsFunc();
|
const particles = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>);
|
||||||
particles.id = getUniqueID("particles-");
|
particles.id = getUniqueID("particles-");
|
||||||
particles.type = ParticlesType;
|
particles.type = ParticlesType;
|
||||||
particles[Component] = ParticlesComponent;
|
particles[Component] = ParticlesComponent;
|
||||||
|
|
|
@ -106,4 +106,8 @@ export default defineComponent({
|
||||||
:not(.floating) .tabButton:not(.active) {
|
:not(.floating) .tabButton:not(.active) {
|
||||||
border-bottom-color: transparent;
|
border-bottom-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tabButton > * {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
} from "features/feature";
|
} from "features/feature";
|
||||||
import TabButtonComponent from "features/tabs/TabButton.vue";
|
import TabButtonComponent from "features/tabs/TabButton.vue";
|
||||||
import TabFamilyComponent from "features/tabs/TabFamily.vue";
|
import TabFamilyComponent from "features/tabs/TabFamily.vue";
|
||||||
import { Persistent, PersistentState, persistent } from "game/persistence";
|
import { Persistent, persistent } from "game/persistence";
|
||||||
import {
|
import {
|
||||||
Computable,
|
Computable,
|
||||||
GetComputableType,
|
GetComputableType,
|
||||||
|
@ -65,11 +65,11 @@ export interface TabFamilyOptions {
|
||||||
style?: Computable<StyleValue>;
|
style?: Computable<StyleValue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseTabFamily extends Persistent<string> {
|
export interface BaseTabFamily {
|
||||||
id: string;
|
id: string;
|
||||||
tabs: Record<string, TabButtonOptions>;
|
tabs: Record<string, TabButtonOptions>;
|
||||||
activeTab: Ref<GenericTab | CoercableComponent | null>;
|
activeTab: Ref<GenericTab | CoercableComponent | null>;
|
||||||
selected: Ref<string>;
|
selected: Persistent<string>;
|
||||||
type: typeof TabFamilyType;
|
type: typeof TabFamilyType;
|
||||||
[Component]: typeof TabFamilyComponent;
|
[Component]: typeof TabFamilyComponent;
|
||||||
[GatherProps]: () => Record<string, unknown>;
|
[GatherProps]: () => Record<string, unknown>;
|
||||||
|
@ -99,8 +99,9 @@ export function createTabFamily<T extends TabFamilyOptions>(
|
||||||
throw "Cannot create tab family with 0 tabs";
|
throw "Cannot create tab family with 0 tabs";
|
||||||
}
|
}
|
||||||
|
|
||||||
return createLazyProxy(persistent => {
|
const selected = persistent(Object.keys(tabs)[0]);
|
||||||
const tabFamily = Object.assign(persistent, optionsFunc?.());
|
return createLazyProxy(() => {
|
||||||
|
const tabFamily = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>);
|
||||||
|
|
||||||
tabFamily.id = getUniqueID("tabFamily-");
|
tabFamily.id = getUniqueID("tabFamily-");
|
||||||
tabFamily.type = TabFamilyType;
|
tabFamily.type = TabFamilyType;
|
||||||
|
@ -124,15 +125,14 @@ export function createTabFamily<T extends TabFamilyOptions>(
|
||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
tabFamily.selected = tabFamily[PersistentState];
|
tabFamily.selected = selected;
|
||||||
tabFamily.activeTab = computed(() => {
|
tabFamily.activeTab = computed(() => {
|
||||||
const tabs = unref(processedTabFamily.tabs);
|
const tabs = unref(processedTabFamily.tabs);
|
||||||
if (
|
if (
|
||||||
tabFamily[PersistentState].value in tabs &&
|
selected.value in tabs &&
|
||||||
unref(tabs[processedTabFamily[PersistentState].value].visibility) ===
|
unref(tabs[selected.value].visibility) === Visibility.Visible
|
||||||
Visibility.Visible
|
|
||||||
) {
|
) {
|
||||||
return unref(tabs[processedTabFamily[PersistentState].value].tab);
|
return unref(tabs[selected.value].tab);
|
||||||
}
|
}
|
||||||
const firstTab = Object.values(tabs).find(
|
const firstTab = Object.values(tabs).find(
|
||||||
tab => unref(tab.visibility) === Visibility.Visible
|
tab => unref(tab.visibility) === Visibility.Visible
|
||||||
|
@ -156,5 +156,5 @@ export function createTabFamily<T extends TabFamilyOptions>(
|
||||||
// This is necessary because board.types is different from T and TabFamily
|
// This is necessary because board.types is different from T and TabFamily
|
||||||
const processedTabFamily = tabFamily as unknown as TabFamily<T>;
|
const processedTabFamily = tabFamily as unknown as TabFamily<T>;
|
||||||
return processedTabFamily;
|
return processedTabFamily;
|
||||||
}, persistent(Object.keys(tabs)[0]));
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import {
|
||||||
ProcessedComputable
|
ProcessedComputable
|
||||||
} from "util/computed";
|
} from "util/computed";
|
||||||
import { VueFeature } from "util/vue";
|
import { VueFeature } from "util/vue";
|
||||||
import { Ref } from "vue";
|
import { nextTick, Ref, unref } from "vue";
|
||||||
import { persistent } from "game/persistence";
|
import { persistent } from "game/persistence";
|
||||||
|
|
||||||
declare module "@vue/runtime-dom" {
|
declare module "@vue/runtime-dom" {
|
||||||
|
@ -73,6 +73,15 @@ export function addTooltip<T extends TooltipOptions>(
|
||||||
element: VueFeature,
|
element: VueFeature,
|
||||||
options: T & ThisType<Tooltip<T>> & Partial<BaseTooltip>
|
options: T & ThisType<Tooltip<T>> & Partial<BaseTooltip>
|
||||||
): Tooltip<T> {
|
): Tooltip<T> {
|
||||||
|
processComputable(options as T, "display");
|
||||||
|
processComputable(options as T, "classes");
|
||||||
|
processComputable(options as T, "style");
|
||||||
|
processComputable(options as T, "direction");
|
||||||
|
setDefault(options, "direction", TooltipDirection.UP);
|
||||||
|
processComputable(options as T, "xoffset");
|
||||||
|
processComputable(options as T, "yoffset");
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
if (options.pinnable) {
|
if (options.pinnable) {
|
||||||
if ("pinned" in element) {
|
if ("pinned" in element) {
|
||||||
console.error(
|
console.error(
|
||||||
|
@ -85,14 +94,6 @@ export function addTooltip<T extends TooltipOptions>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
processComputable(options as T, "display");
|
|
||||||
processComputable(options as T, "classes");
|
|
||||||
processComputable(options as T, "style");
|
|
||||||
processComputable(options as T, "direction");
|
|
||||||
setDefault(options, "direction", TooltipDirection.UP);
|
|
||||||
processComputable(options as T, "xoffset");
|
|
||||||
processComputable(options as T, "yoffset");
|
|
||||||
|
|
||||||
const elementComponent = element[Component];
|
const elementComponent = element[Component];
|
||||||
element[Component] = TooltipComponent;
|
element[Component] = TooltipComponent;
|
||||||
const elementGratherProps = element[GatherProps].bind(element);
|
const elementGratherProps = element[GatherProps].bind(element);
|
||||||
|
@ -105,13 +106,14 @@ export function addTooltip<T extends TooltipOptions>(
|
||||||
},
|
},
|
||||||
display,
|
display,
|
||||||
classes,
|
classes,
|
||||||
style,
|
style: unref(style),
|
||||||
direction,
|
direction,
|
||||||
xoffset,
|
xoffset,
|
||||||
yoffset,
|
yoffset,
|
||||||
pinned
|
pinned
|
||||||
};
|
};
|
||||||
}.bind(options as GenericTooltip);
|
}.bind(options as GenericTooltip);
|
||||||
|
});
|
||||||
|
|
||||||
return options as unknown as Tooltip<T>;
|
return options as unknown as Tooltip<T>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,4 +112,8 @@ export default defineComponent({
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.treeNode > *:first-child > * {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -73,10 +73,10 @@ export type GenericTreeNode = Replace<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function createTreeNode<T extends TreeNodeOptions>(
|
export function createTreeNode<T extends TreeNodeOptions>(
|
||||||
optionsFunc: OptionsFunc<T, TreeNode<T>, BaseTreeNode>
|
optionsFunc?: OptionsFunc<T, TreeNode<T>, BaseTreeNode>
|
||||||
): TreeNode<T> {
|
): TreeNode<T> {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const treeNode = optionsFunc();
|
const treeNode = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>);
|
||||||
treeNode.id = getUniqueID("treeNode-");
|
treeNode.id = getUniqueID("treeNode-");
|
||||||
treeNode.type = TreeNodeType;
|
treeNode.type = TreeNodeType;
|
||||||
treeNode[Component] = TreeNodeComponent;
|
treeNode[Component] = TreeNodeComponent;
|
||||||
|
|
|
@ -141,4 +141,8 @@ export default defineComponent({
|
||||||
width: 120px;
|
width: 120px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.upgrade > * {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -24,7 +24,7 @@ import {
|
||||||
} from "util/computed";
|
} from "util/computed";
|
||||||
import { createLazyProxy } from "util/proxies";
|
import { createLazyProxy } from "util/proxies";
|
||||||
import { computed, Ref, unref } from "vue";
|
import { computed, Ref, unref } from "vue";
|
||||||
import { persistent, Persistent, PersistentState } from "game/persistence";
|
import { persistent, Persistent } from "game/persistence";
|
||||||
|
|
||||||
export const UpgradeType = Symbol("Upgrade");
|
export const UpgradeType = Symbol("Upgrade");
|
||||||
|
|
||||||
|
@ -47,9 +47,9 @@ export interface UpgradeOptions {
|
||||||
onPurchase?: VoidFunction;
|
onPurchase?: VoidFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseUpgrade extends Persistent<boolean> {
|
export interface BaseUpgrade {
|
||||||
id: string;
|
id: string;
|
||||||
bought: Ref<boolean>;
|
bought: Persistent<boolean>;
|
||||||
canPurchase: Ref<boolean>;
|
canPurchase: Ref<boolean>;
|
||||||
purchase: VoidFunction;
|
purchase: VoidFunction;
|
||||||
type: typeof UpgradeType;
|
type: typeof UpgradeType;
|
||||||
|
@ -81,8 +81,9 @@ export type GenericUpgrade = Replace<
|
||||||
export function createUpgrade<T extends UpgradeOptions>(
|
export function createUpgrade<T extends UpgradeOptions>(
|
||||||
optionsFunc: OptionsFunc<T, Upgrade<T>, BaseUpgrade>
|
optionsFunc: OptionsFunc<T, Upgrade<T>, BaseUpgrade>
|
||||||
): Upgrade<T> {
|
): Upgrade<T> {
|
||||||
return createLazyProxy(persistent => {
|
const bought = persistent<boolean>(false);
|
||||||
const upgrade = Object.assign(persistent, optionsFunc());
|
return createLazyProxy(() => {
|
||||||
|
const upgrade = optionsFunc();
|
||||||
upgrade.id = getUniqueID("upgrade-");
|
upgrade.id = getUniqueID("upgrade-");
|
||||||
upgrade.type = UpgradeType;
|
upgrade.type = UpgradeType;
|
||||||
upgrade[Component] = UpgradeComponent;
|
upgrade[Component] = UpgradeComponent;
|
||||||
|
@ -94,7 +95,7 @@ export function createUpgrade<T extends UpgradeOptions>(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
upgrade.bought = upgrade[PersistentState];
|
upgrade.bought = bought;
|
||||||
if (upgrade.canAfford == null) {
|
if (upgrade.canAfford == null) {
|
||||||
upgrade.canAfford = computed(() => {
|
upgrade.canAfford = computed(() => {
|
||||||
const genericUpgrade = upgrade as GenericUpgrade;
|
const genericUpgrade = upgrade as GenericUpgrade;
|
||||||
|
@ -124,7 +125,7 @@ export function createUpgrade<T extends UpgradeOptions>(
|
||||||
unref(genericUpgrade.cost)
|
unref(genericUpgrade.cost)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
genericUpgrade[PersistentState].value = true;
|
bought.value = true;
|
||||||
genericUpgrade.onPurchase?.();
|
genericUpgrade.onPurchase?.();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -167,7 +168,7 @@ export function createUpgrade<T extends UpgradeOptions>(
|
||||||
};
|
};
|
||||||
|
|
||||||
return upgrade as unknown as Upgrade<T>;
|
return upgrade as unknown as Upgrade<T>;
|
||||||
}, persistent<boolean>(false));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setupAutoPurchase(
|
export function setupAutoPurchase(
|
||||||
|
|
|
@ -2,6 +2,7 @@ import projInfo from "data/projInfo.json";
|
||||||
import { Themes } from "data/themes";
|
import { Themes } from "data/themes";
|
||||||
import { CoercableComponent } from "features/feature";
|
import { CoercableComponent } from "features/feature";
|
||||||
import { globalBus } from "game/events";
|
import { globalBus } from "game/events";
|
||||||
|
import LZString from "lz-string";
|
||||||
import { hardReset } from "util/save";
|
import { hardReset } from "util/save";
|
||||||
import { reactive, watch } from "vue";
|
import { reactive, watch } from "vue";
|
||||||
|
|
||||||
|
@ -23,20 +24,44 @@ const state = reactive<Partial<Settings>>({
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
state,
|
state,
|
||||||
state =>
|
state => {
|
||||||
localStorage.setItem(
|
let stringifiedSettings = JSON.stringify(state);
|
||||||
projInfo.id,
|
switch (projInfo.saveEncoding) {
|
||||||
btoa(unescape(encodeURIComponent(JSON.stringify(state))))
|
default:
|
||||||
),
|
console.warn(`Unknown save encoding: ${projInfo.saveEncoding}. Defaulting to lz`);
|
||||||
|
case "lz":
|
||||||
|
stringifiedSettings = LZString.compressToUTF16(stringifiedSettings);
|
||||||
|
break;
|
||||||
|
case "base64":
|
||||||
|
stringifiedSettings = btoa(unescape(encodeURIComponent(stringifiedSettings)));
|
||||||
|
break;
|
||||||
|
case "plain":
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
localStorage.setItem(projInfo.id, stringifiedSettings);
|
||||||
|
},
|
||||||
{ deep: true }
|
{ deep: true }
|
||||||
);
|
);
|
||||||
export default window.settings = state as Settings;
|
export default window.settings = state as Settings;
|
||||||
|
|
||||||
export function loadSettings(): void {
|
export function loadSettings(): void {
|
||||||
try {
|
try {
|
||||||
const item: string | null = localStorage.getItem(projInfo.id);
|
let item: string | null = localStorage.getItem(projInfo.id);
|
||||||
if (item != null && item !== "") {
|
if (item != null && item !== "") {
|
||||||
const settings = JSON.parse(decodeURIComponent(escape(atob(item))));
|
if (item[0] === "{") {
|
||||||
|
// plaintext. No processing needed
|
||||||
|
} else if (item[0] === "e") {
|
||||||
|
// Assumed to be base64, which starts with e
|
||||||
|
item = decodeURIComponent(escape(atob(item)));
|
||||||
|
} else if (item[0] === "ᯡ") {
|
||||||
|
// Assumed to be lz, which starts with ᯡ
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
item = LZString.decompressFromUTF16(item)!;
|
||||||
|
} else {
|
||||||
|
console.warn("Unable to determine settings encoding", item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const settings = JSON.parse(item);
|
||||||
if (typeof settings === "object") {
|
if (typeof settings === "object") {
|
||||||
Object.assign(state, settings);
|
Object.assign(state, settings);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import projInfo from "data/projInfo.json";
|
||||||
import player, { Player, PlayerData, stringifySave } from "game/player";
|
import player, { Player, PlayerData, stringifySave } from "game/player";
|
||||||
import settings, { loadSettings } from "game/settings";
|
import settings, { loadSettings } from "game/settings";
|
||||||
import { ProxyState } from "./proxies";
|
import { ProxyState } from "./proxies";
|
||||||
|
import LZString from "lz-string";
|
||||||
|
|
||||||
export function setupInitialStore(player: Partial<PlayerData> = {}): Player {
|
export function setupInitialStore(player: Partial<PlayerData> = {}): Player {
|
||||||
return Object.assign(
|
return Object.assign(
|
||||||
|
@ -23,9 +24,21 @@ export function setupInitialStore(player: Partial<PlayerData> = {}): Player {
|
||||||
) as Player;
|
) as Player;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function save(): string {
|
export function save(playerData?: PlayerData): string {
|
||||||
const stringifiedSave = btoa(unescape(encodeURIComponent(stringifySave(player[ProxyState]))));
|
let stringifiedSave = stringifySave(playerData ?? player[ProxyState]);
|
||||||
localStorage.setItem(player.id, stringifiedSave);
|
switch (projInfo.saveEncoding) {
|
||||||
|
default:
|
||||||
|
console.warn(`Unknown save encoding: ${projInfo.saveEncoding}. Defaulting to lz`);
|
||||||
|
case "lz":
|
||||||
|
stringifiedSave = LZString.compressToUTF16(stringifiedSave);
|
||||||
|
break;
|
||||||
|
case "base64":
|
||||||
|
stringifiedSave = btoa(unescape(encodeURIComponent(stringifiedSave)));
|
||||||
|
break;
|
||||||
|
case "plain":
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
localStorage.setItem((playerData ?? player[ProxyState]).id, stringifiedSave);
|
||||||
return stringifiedSave;
|
return stringifiedSave;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,12 +47,24 @@ export async function load(): Promise<void> {
|
||||||
loadSettings();
|
loadSettings();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const save = localStorage.getItem(settings.active);
|
let save = localStorage.getItem(settings.active);
|
||||||
if (save == null) {
|
if (save == null) {
|
||||||
await loadSave(newSave());
|
await loadSave(newSave());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const player = JSON.parse(decodeURIComponent(escape(atob(save))));
|
if (save[0] === "{") {
|
||||||
|
// plaintext. No processing needed
|
||||||
|
} else if (save[0] === "e") {
|
||||||
|
// Assumed to be base64, which starts with e
|
||||||
|
save = decodeURIComponent(escape(atob(save)));
|
||||||
|
} else if (save[0] === "ᯡ") {
|
||||||
|
// Assumed to be lz, which starts with ᯡ
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
save = LZString.decompressFromUTF16(save)!;
|
||||||
|
} else {
|
||||||
|
throw `Unable to determine save encoding`;
|
||||||
|
}
|
||||||
|
const player = JSON.parse(save);
|
||||||
if (player.modID !== projInfo.id) {
|
if (player.modID !== projInfo.id) {
|
||||||
await loadSave(newSave());
|
await loadSave(newSave());
|
||||||
return;
|
return;
|
||||||
|
@ -55,7 +80,7 @@ export async function load(): Promise<void> {
|
||||||
export function newSave(): PlayerData {
|
export function newSave(): PlayerData {
|
||||||
const id = getUniqueID();
|
const id = getUniqueID();
|
||||||
const player = setupInitialStore({ id });
|
const player = setupInitialStore({ id });
|
||||||
localStorage.setItem(id, btoa(unescape(encodeURIComponent(stringifySave(player)))));
|
save(player);
|
||||||
|
|
||||||
settings.saves.push(id);
|
settings.saves.push(id);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue