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]
|
||||
|
||||
## [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
|
||||
### Added
|
||||
- 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",
|
||||
"version": "0.3.0",
|
||||
"version": "0.3.3",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "profectus",
|
||||
"version": "0.3.0",
|
||||
"version": "0.3.3",
|
||||
"dependencies": {
|
||||
"@pixi/particle-emitter": "^5.0.4",
|
||||
"core-js": "^3.6.5",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lz-string": "^1.4.4",
|
||||
"nanoevents": "^6.0.2",
|
||||
"pixi.js": "^6.3.0",
|
||||
"vue": "^3.2.26",
|
||||
|
@ -26,6 +27,7 @@
|
|||
"@jetblack/operator-overloading": "^0.2.0",
|
||||
"@rushstack/eslint-patch": "^1.1.0",
|
||||
"@types/lodash.clonedeep": "^4.5.6",
|
||||
"@types/lz-string": "^1.3.34",
|
||||
"@vue/babel-plugin-jsx": "^1.1.1",
|
||||
"@vue/cli-plugin-babel": "^5.0.3",
|
||||
"@vue/cli-plugin-eslint": "^5.0.3",
|
||||
|
@ -2461,6 +2463,12 @@
|
|||
"@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": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
|
||||
|
@ -8592,6 +8600,14 @@
|
|||
"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": {
|
||||
"version": "0.25.7",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
|
||||
|
@ -15010,6 +15026,12 @@
|
|||
"@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": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
|
||||
|
@ -19594,6 +19616,11 @@
|
|||
"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": {
|
||||
"version": "0.25.7",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "profectus",
|
||||
"version": "0.3.0",
|
||||
"version": "0.3.3",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "vue-cli-service serve",
|
||||
|
@ -12,6 +12,7 @@
|
|||
"@pixi/particle-emitter": "^5.0.4",
|
||||
"core-js": "^3.6.5",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lz-string": "^1.4.4",
|
||||
"nanoevents": "^6.0.2",
|
||||
"pixi.js": "^6.3.0",
|
||||
"vue": "^3.2.26",
|
||||
|
@ -27,6 +28,7 @@
|
|||
"@jetblack/operator-overloading": "^0.2.0",
|
||||
"@rushstack/eslint-patch": "^1.1.0",
|
||||
"@types/lodash.clonedeep": "^4.5.6",
|
||||
"@types/lz-string": "^1.3.34",
|
||||
"@vue/babel-plugin-jsx": "^1.1.1",
|
||||
"@vue/cli-plugin-babel": "^5.0.3",
|
||||
"@vue/cli-plugin-eslint": "^5.0.3",
|
||||
|
|
|
@ -57,6 +57,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import projInfo from "data/projInfo.json";
|
||||
import Modal from "components/Modal.vue";
|
||||
import player, { PlayerData } from "game/player";
|
||||
import settings from "game/settings";
|
||||
|
@ -66,6 +67,7 @@ import Select from "./fields/Select.vue";
|
|||
import Text from "./fields/Text.vue";
|
||||
import Save from "./Save.vue";
|
||||
import Draggable from "vuedraggable";
|
||||
import LZString from "lz-string";
|
||||
|
||||
export type LoadablePlayerData = Omit<Partial<PlayerData>, "id"> & { id: string; error?: unknown };
|
||||
|
||||
|
@ -81,21 +83,32 @@ defineExpose({
|
|||
const importingFailed = ref(false);
|
||||
const saveToImport = ref("");
|
||||
|
||||
watch(saveToImport, save => {
|
||||
if (save) {
|
||||
watch(saveToImport, importedSave => {
|
||||
if (importedSave) {
|
||||
nextTick(() => {
|
||||
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") {
|
||||
importingFailed.value = true;
|
||||
return;
|
||||
}
|
||||
const id = getUniqueID();
|
||||
playerData.id = id;
|
||||
localStorage.setItem(
|
||||
id,
|
||||
btoa(unescape(encodeURIComponent(JSON.stringify(playerData))))
|
||||
);
|
||||
save(playerData);
|
||||
saveToImport.value = "";
|
||||
importingFailed.value = false;
|
||||
|
||||
|
@ -124,14 +137,30 @@ let bank = ref(
|
|||
const cachedSaves = shallowReactive<Record<string, LoadablePlayerData | undefined>>({});
|
||||
function getCachedSave(id: string) {
|
||||
if (cachedSaves[id] == null) {
|
||||
const save = localStorage.getItem(id);
|
||||
let save = localStorage.getItem(id);
|
||||
if (save == null) {
|
||||
cachedSaves[id] = { error: `Save doesn't exist in localStorage`, id };
|
||||
} else if (save === "dW5kZWZpbmVk") {
|
||||
cachedSaves[id] = { error: `Save is undefined`, id };
|
||||
} else {
|
||||
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) {
|
||||
cachedSaves[id] = { error, id };
|
||||
console.warn(
|
||||
|
@ -162,7 +191,19 @@ function exportSave(id: string) {
|
|||
if (player.id === id) {
|
||||
saveToExport = save();
|
||||
} 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
|
||||
|
@ -181,10 +222,7 @@ function duplicateSave(id: string) {
|
|||
}
|
||||
|
||||
const playerData = { ...saves.value[id], id: getUniqueID() };
|
||||
localStorage.setItem(
|
||||
playerData.id,
|
||||
btoa(unescape(encodeURIComponent(JSON.stringify(playerData))))
|
||||
);
|
||||
save(playerData as PlayerData);
|
||||
|
||||
settings.saves.push(playerData.id);
|
||||
}
|
||||
|
@ -207,12 +245,22 @@ function openSave(id: 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();
|
||||
localStorage.setItem(
|
||||
playerData.id,
|
||||
btoa(unescape(encodeURIComponent(JSON.stringify(playerData))))
|
||||
);
|
||||
save(playerData as PlayerData);
|
||||
|
||||
settings.saves.push(playerData.id);
|
||||
}
|
||||
|
@ -225,7 +273,7 @@ function editSave(id: string, newName: string) {
|
|||
player.name = newName;
|
||||
save();
|
||||
} else {
|
||||
localStorage.setItem(id, btoa(unescape(encodeURIComponent(JSON.stringify(currSave)))));
|
||||
save(currSave as PlayerData);
|
||||
cachedSaves[id] = undefined;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ function onUpdate(value: SelectOption) {
|
|||
}
|
||||
|
||||
.vue-dropdown-item {
|
||||
color: var(--feature-foreground);
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.vue-dropdown-item,
|
||||
|
|
|
@ -133,7 +133,7 @@ export function createResetButton<T extends ClickableOptions & ResetButtonOption
|
|||
export interface LayerTreeNodeOptions extends TreeNodeOptions {
|
||||
layerID: string;
|
||||
color: Computable<string>; // marking as required
|
||||
display?: Computable<string>;
|
||||
display?: Computable<CoercableComponent>;
|
||||
append?: Computable<boolean>;
|
||||
}
|
||||
export type LayerTreeNode<T extends LayerTreeNodeOptions> = Replace<
|
||||
|
@ -146,7 +146,7 @@ export type LayerTreeNode<T extends LayerTreeNodeOptions> = Replace<
|
|||
export type GenericLayerTreeNode = Replace<
|
||||
LayerTreeNode<LayerTreeNodeOptions>,
|
||||
{
|
||||
display: ProcessedComputable<string>;
|
||||
display: ProcessedComputable<CoercableComponent>;
|
||||
append?: ProcessedComputable<boolean>;
|
||||
}
|
||||
>;
|
||||
|
@ -161,7 +161,7 @@ export function createLayerTreeNode<T extends LayerTreeNodeOptions>(
|
|||
processComputable(options as T, "append");
|
||||
return {
|
||||
...options,
|
||||
display: options.layerID,
|
||||
display: options.display,
|
||||
onClick: unref((options as unknown as GenericLayerTreeNode).append)
|
||||
? function () {
|
||||
if (player.tabs.includes(options.layerID)) {
|
||||
|
|
|
@ -8,6 +8,8 @@ import { jsx } from "features/feature";
|
|||
import { createReset } from "features/reset";
|
||||
import MainDisplay from "features/resources/MainDisplay.vue";
|
||||
import { createResource } from "features/resources/resource";
|
||||
import { addTooltip } from "features/tooltips/tooltip";
|
||||
import { createResourceTooltip } from "features/trees/tree";
|
||||
import { createLayer } from "game/layers";
|
||||
import { DecimalSource } from "util/bignum";
|
||||
import { render } from "util/vue";
|
||||
|
@ -35,6 +37,10 @@ const layer = createLayer(id, () => {
|
|||
color,
|
||||
reset
|
||||
}));
|
||||
addTooltip(treeNode, {
|
||||
display: createResourceTooltip(points),
|
||||
pinnable: true
|
||||
});
|
||||
|
||||
const resetButton = createResetButton(() => ({
|
||||
conversion,
|
||||
|
|
|
@ -18,5 +18,6 @@
|
|||
|
||||
"maxTickLength": 3600,
|
||||
"offlineLimit": 1,
|
||||
"enablePausing": true
|
||||
"enablePausing": true,
|
||||
"saveEncoding": "lz"
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ const defaultTheme: Theme = {
|
|||
variables: {
|
||||
"--foreground": "#dfdfdf",
|
||||
"--background": "#0f0f0f",
|
||||
"--feature-foreground": "#eee",
|
||||
"--feature-foreground": "#0f0f0f",
|
||||
"--tooltip-background": "rgba(0, 0, 0, 0.75)",
|
||||
"--raised-background": "#0f0f0f",
|
||||
"--points": "#ffffff",
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
Visibility
|
||||
} from "features/feature";
|
||||
import "game/notifications";
|
||||
import { Persistent, PersistentState, persistent } from "game/persistence";
|
||||
import { Persistent, persistent } from "game/persistence";
|
||||
import {
|
||||
Computable,
|
||||
GetComputableType,
|
||||
|
@ -21,7 +21,7 @@ import {
|
|||
} from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { coerceComponent } from "util/vue";
|
||||
import { Ref, unref, watchEffect } from "vue";
|
||||
import { unref, watchEffect } from "vue";
|
||||
import { useToast } from "vue-toastification";
|
||||
|
||||
const toast = useToast();
|
||||
|
@ -39,9 +39,9 @@ export interface AchievementOptions {
|
|||
onComplete?: VoidFunction;
|
||||
}
|
||||
|
||||
export interface BaseAchievement extends Persistent<boolean> {
|
||||
export interface BaseAchievement {
|
||||
id: string;
|
||||
earned: Ref<boolean>;
|
||||
earned: Persistent<boolean>;
|
||||
complete: VoidFunction;
|
||||
type: typeof AchievementType;
|
||||
[Component]: typeof AchievementComponent;
|
||||
|
@ -68,17 +68,18 @@ export type GenericAchievement = Replace<
|
|||
>;
|
||||
|
||||
export function createAchievement<T extends AchievementOptions>(
|
||||
optionsFunc: OptionsFunc<T, Achievement<T>, BaseAchievement>
|
||||
optionsFunc?: OptionsFunc<T, Achievement<T>, BaseAchievement>
|
||||
): Achievement<T> {
|
||||
return createLazyProxy(persistent => {
|
||||
const achievement = Object.assign(persistent, optionsFunc());
|
||||
const earned = persistent<boolean>(false);
|
||||
return createLazyProxy(() => {
|
||||
const achievement = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>);
|
||||
achievement.id = getUniqueID("achievement-");
|
||||
achievement.type = AchievementType;
|
||||
achievement[Component] = AchievementComponent;
|
||||
|
||||
achievement.earned = achievement[PersistentState];
|
||||
achievement.earned = earned;
|
||||
achievement.complete = function () {
|
||||
achievement[PersistentState].value = true;
|
||||
earned.value = true;
|
||||
};
|
||||
|
||||
processComputable(achievement as T, "visibility");
|
||||
|
@ -122,5 +123,5 @@ export function createAchievement<T extends AchievementOptions>(
|
|||
}
|
||||
|
||||
return achievement as unknown as Achievement<T>;
|
||||
}, persistent<boolean>(false));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import ClickableComponent from "features/clickables/Clickable.vue";
|
||||
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 {
|
||||
Computable,
|
||||
|
@ -49,9 +49,9 @@ export interface BuyableOptions {
|
|||
onPurchase?: (cost: DecimalSource) => void;
|
||||
}
|
||||
|
||||
export interface BaseBuyable extends Persistent<DecimalSource> {
|
||||
export interface BaseBuyable {
|
||||
id: string;
|
||||
amount: Ref<DecimalSource>;
|
||||
amount: Persistent<DecimalSource>;
|
||||
maxed: Ref<boolean>;
|
||||
canAfford: Ref<boolean>;
|
||||
canClick: ProcessedComputable<boolean>;
|
||||
|
@ -90,8 +90,9 @@ export type GenericBuyable = Replace<
|
|||
export function createBuyable<T extends BuyableOptions>(
|
||||
optionsFunc: OptionsFunc<T, Buyable<T>, BaseBuyable>
|
||||
): Buyable<T> {
|
||||
return createLazyProxy(persistent => {
|
||||
const buyable = Object.assign(persistent, optionsFunc());
|
||||
const amount = persistent<DecimalSource>(0);
|
||||
return createLazyProxy(() => {
|
||||
const buyable = optionsFunc();
|
||||
|
||||
if (buyable.canPurchase == null && (buyable.resource == null || buyable.cost == null)) {
|
||||
console.warn(
|
||||
|
@ -105,7 +106,7 @@ export function createBuyable<T extends BuyableOptions>(
|
|||
buyable.type = BuyableType;
|
||||
buyable[Component] = ClickableComponent;
|
||||
|
||||
buyable.amount = buyable[PersistentState];
|
||||
buyable.amount = amount;
|
||||
buyable.canAfford = computed(() => {
|
||||
const genericBuyable = buyable as GenericBuyable;
|
||||
const cost = unref(genericBuyable.cost);
|
||||
|
@ -239,5 +240,5 @@ export function createBuyable<T extends BuyableOptions>(
|
|||
};
|
||||
|
||||
return buyable as unknown as Buyable<T>;
|
||||
}, persistent<DecimalSource>(0));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -138,4 +138,8 @@ export default defineComponent({
|
|||
.clickable.small {
|
||||
min-height: unset;
|
||||
}
|
||||
|
||||
.clickable > * {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -70,10 +70,10 @@ export type GenericClickable = Replace<
|
|||
>;
|
||||
|
||||
export function createClickable<T extends ClickableOptions>(
|
||||
optionsFunc: OptionsFunc<T, Clickable<T>, BaseClickable>
|
||||
optionsFunc?: OptionsFunc<T, Clickable<T>, BaseClickable>
|
||||
): Clickable<T> {
|
||||
return createLazyProxy(() => {
|
||||
const clickable = optionsFunc();
|
||||
const clickable = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>);
|
||||
clickable.id = getUniqueID("clickable-");
|
||||
clickable.type = ClickableType;
|
||||
clickable[Component] = ClickableComponent;
|
||||
|
|
|
@ -87,4 +87,8 @@ export default defineComponent({
|
|||
font-size: 10px;
|
||||
background-color: var(--layer-color);
|
||||
}
|
||||
|
||||
.tile > * {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
} from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
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");
|
||||
|
||||
|
@ -205,12 +205,13 @@ export interface GridOptions {
|
|||
onHold?: (id: string | number, state: State) => void;
|
||||
}
|
||||
|
||||
export interface BaseGrid extends Persistent<Record<string | number, State>> {
|
||||
export interface BaseGrid {
|
||||
id: string;
|
||||
getID: (id: string | number, state: State) => string;
|
||||
getState: (id: string | number) => State;
|
||||
setState: (id: string | number, state: State) => void;
|
||||
cells: Record<string | number, GridCell>;
|
||||
cellState: Persistent<Record<string | number, State>>;
|
||||
type: typeof GridType;
|
||||
[Component]: typeof GridComponent;
|
||||
[GatherProps]: () => Record<string, unknown>;
|
||||
|
@ -244,22 +245,25 @@ export type GenericGrid = Replace<
|
|||
export function createGrid<T extends GridOptions>(
|
||||
optionsFunc: OptionsFunc<T, Grid<T>, BaseGrid>
|
||||
): Grid<T> {
|
||||
return createLazyProxy(persistent => {
|
||||
const grid = Object.assign(persistent, optionsFunc());
|
||||
const cellState = persistent<Record<string | number, State>>({});
|
||||
return createLazyProxy(() => {
|
||||
const grid = optionsFunc();
|
||||
grid.id = getUniqueID("grid-");
|
||||
grid[Component] = GridComponent;
|
||||
|
||||
grid.cellState = cellState;
|
||||
|
||||
grid.getID = function (this: GenericGrid, cell: string | number) {
|
||||
return grid.id + "-" + cell;
|
||||
};
|
||||
grid.getState = function (this: GenericGrid, cell: string | number) {
|
||||
if (this[PersistentState].value[cell] != undefined) {
|
||||
return this[PersistentState].value[cell];
|
||||
if (this.cellState.value[cell] != undefined) {
|
||||
return cellState.value[cell];
|
||||
}
|
||||
return this.cells[cell].startState;
|
||||
};
|
||||
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);
|
||||
|
@ -301,5 +305,5 @@ export function createGrid<T extends GridOptions>(
|
|||
};
|
||||
|
||||
return grid as unknown as Grid<T>;
|
||||
}, persistent({}));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import InfoboxComponent from "features/infoboxes/Infobox.vue";
|
||||
import {
|
||||
CoercableComponent,
|
||||
Component,
|
||||
OptionsFunc,
|
||||
GatherProps,
|
||||
getUniqueID,
|
||||
OptionsFunc,
|
||||
Replace,
|
||||
setDefault,
|
||||
StyleValue,
|
||||
Visibility
|
||||
} from "features/feature";
|
||||
import InfoboxComponent from "features/infoboxes/Infobox.vue";
|
||||
import { Persistent, persistent } from "game/persistence";
|
||||
import {
|
||||
Computable,
|
||||
GetComputableType,
|
||||
|
@ -18,8 +19,7 @@ import {
|
|||
ProcessedComputable
|
||||
} from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { Ref, unref } from "vue";
|
||||
import { Persistent, PersistentState, persistent } from "game/persistence";
|
||||
import { unref } from "vue";
|
||||
|
||||
export const InfoboxType = Symbol("Infobox");
|
||||
|
||||
|
@ -34,9 +34,9 @@ export interface InfoboxOptions {
|
|||
display: Computable<CoercableComponent>;
|
||||
}
|
||||
|
||||
export interface BaseInfobox extends Persistent<boolean> {
|
||||
export interface BaseInfobox {
|
||||
id: string;
|
||||
collapsed: Ref<boolean>;
|
||||
collapsed: Persistent<boolean>;
|
||||
type: typeof InfoboxType;
|
||||
[Component]: typeof InfoboxComponent;
|
||||
[GatherProps]: () => Record<string, unknown>;
|
||||
|
@ -66,13 +66,14 @@ export type GenericInfobox = Replace<
|
|||
export function createInfobox<T extends InfoboxOptions>(
|
||||
optionsFunc: OptionsFunc<T, Infobox<T>, BaseInfobox>
|
||||
): Infobox<T> {
|
||||
return createLazyProxy(persistent => {
|
||||
const infobox = Object.assign(persistent, optionsFunc());
|
||||
const collapsed = persistent<boolean>(false);
|
||||
return createLazyProxy(() => {
|
||||
const infobox = optionsFunc();
|
||||
infobox.id = getUniqueID("infobox-");
|
||||
infobox.type = InfoboxType;
|
||||
infobox[Component] = InfoboxComponent;
|
||||
|
||||
infobox.collapsed = infobox[PersistentState];
|
||||
infobox.collapsed = collapsed;
|
||||
|
||||
processComputable(infobox as T, "visibility");
|
||||
setDefault(infobox, "visibility", Visibility.Visible);
|
||||
|
@ -112,5 +113,5 @@ export function createInfobox<T extends InfoboxOptions>(
|
|||
};
|
||||
|
||||
return infobox as unknown as Infobox<T>;
|
||||
}, persistent<boolean>(false));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ export interface Link extends SVGAttributes {
|
|||
}
|
||||
|
||||
export interface LinksOptions {
|
||||
links?: Computable<Link[]>;
|
||||
links: Computable<Link[]>;
|
||||
}
|
||||
|
||||
export interface BaseLinks {
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
import MilestoneComponent from "features/milestones/Milestone.vue";
|
||||
import { globalBus } from "game/events";
|
||||
import "game/notifications";
|
||||
import { persistent, Persistent, PersistentState } from "game/persistence";
|
||||
import { persistent, Persistent } from "game/persistence";
|
||||
import settings, { registerSettingField } from "game/settings";
|
||||
import { camelToTitle } from "util/common";
|
||||
import {
|
||||
|
@ -26,7 +26,7 @@ import {
|
|||
} from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { coerceComponent, isCoercableComponent } from "util/vue";
|
||||
import { computed, Ref, unref, watchEffect } from "vue";
|
||||
import { computed, unref, watchEffect } from "vue";
|
||||
import { useToast } from "vue-toastification";
|
||||
|
||||
const toast = useToast();
|
||||
|
@ -57,9 +57,9 @@ export interface MilestoneOptions {
|
|||
onComplete?: VoidFunction;
|
||||
}
|
||||
|
||||
export interface BaseMilestone extends Persistent<boolean> {
|
||||
export interface BaseMilestone {
|
||||
id: string;
|
||||
earned: Ref<boolean>;
|
||||
earned: Persistent<boolean>;
|
||||
complete: VoidFunction;
|
||||
type: typeof MilestoneType;
|
||||
[Component]: typeof MilestoneComponent;
|
||||
|
@ -84,17 +84,18 @@ export type GenericMilestone = Replace<
|
|||
>;
|
||||
|
||||
export function createMilestone<T extends MilestoneOptions>(
|
||||
optionsFunc: OptionsFunc<T, Milestone<T>, BaseMilestone>
|
||||
optionsFunc?: OptionsFunc<T, Milestone<T>, BaseMilestone>
|
||||
): Milestone<T> {
|
||||
return createLazyProxy(persistent => {
|
||||
const milestone = Object.assign(persistent, optionsFunc());
|
||||
const earned = persistent<boolean>(false);
|
||||
return createLazyProxy(() => {
|
||||
const milestone = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>);
|
||||
milestone.id = getUniqueID("milestone-");
|
||||
milestone.type = MilestoneType;
|
||||
milestone[Component] = MilestoneComponent;
|
||||
|
||||
milestone.earned = milestone[PersistentState];
|
||||
milestone.earned = earned;
|
||||
milestone.complete = function () {
|
||||
milestone[PersistentState].value = true;
|
||||
earned.value = true;
|
||||
};
|
||||
|
||||
processComputable(milestone as T, "visibility");
|
||||
|
@ -168,7 +169,7 @@ export function createMilestone<T extends MilestoneOptions>(
|
|||
}
|
||||
|
||||
return milestone as unknown as Milestone<T>;
|
||||
}, persistent<boolean>(false));
|
||||
});
|
||||
}
|
||||
|
||||
declare module "game/settings" {
|
||||
|
|
|
@ -42,10 +42,10 @@ export type Particles<T extends ParticlesOptions> = Replace<
|
|||
export type GenericParticles = Particles<ParticlesOptions>;
|
||||
|
||||
export function createParticles<T extends ParticlesOptions>(
|
||||
optionsFunc: OptionsFunc<T, Particles<T>, BaseParticles>
|
||||
optionsFunc?: OptionsFunc<T, Particles<T>, BaseParticles>
|
||||
): Particles<T> {
|
||||
return createLazyProxy(() => {
|
||||
const particles = optionsFunc();
|
||||
const particles = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>);
|
||||
particles.id = getUniqueID("particles-");
|
||||
particles.type = ParticlesType;
|
||||
particles[Component] = ParticlesComponent;
|
||||
|
|
|
@ -106,4 +106,8 @@ export default defineComponent({
|
|||
:not(.floating) .tabButton:not(.active) {
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
|
||||
.tabButton > * {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
} from "features/feature";
|
||||
import TabButtonComponent from "features/tabs/TabButton.vue";
|
||||
import TabFamilyComponent from "features/tabs/TabFamily.vue";
|
||||
import { Persistent, PersistentState, persistent } from "game/persistence";
|
||||
import { Persistent, persistent } from "game/persistence";
|
||||
import {
|
||||
Computable,
|
||||
GetComputableType,
|
||||
|
@ -65,11 +65,11 @@ export interface TabFamilyOptions {
|
|||
style?: Computable<StyleValue>;
|
||||
}
|
||||
|
||||
export interface BaseTabFamily extends Persistent<string> {
|
||||
export interface BaseTabFamily {
|
||||
id: string;
|
||||
tabs: Record<string, TabButtonOptions>;
|
||||
activeTab: Ref<GenericTab | CoercableComponent | null>;
|
||||
selected: Ref<string>;
|
||||
selected: Persistent<string>;
|
||||
type: typeof TabFamilyType;
|
||||
[Component]: typeof TabFamilyComponent;
|
||||
[GatherProps]: () => Record<string, unknown>;
|
||||
|
@ -99,8 +99,9 @@ export function createTabFamily<T extends TabFamilyOptions>(
|
|||
throw "Cannot create tab family with 0 tabs";
|
||||
}
|
||||
|
||||
return createLazyProxy(persistent => {
|
||||
const tabFamily = Object.assign(persistent, optionsFunc?.());
|
||||
const selected = persistent(Object.keys(tabs)[0]);
|
||||
return createLazyProxy(() => {
|
||||
const tabFamily = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>);
|
||||
|
||||
tabFamily.id = getUniqueID("tabFamily-");
|
||||
tabFamily.type = TabFamilyType;
|
||||
|
@ -124,15 +125,14 @@ export function createTabFamily<T extends TabFamilyOptions>(
|
|||
},
|
||||
{}
|
||||
);
|
||||
tabFamily.selected = tabFamily[PersistentState];
|
||||
tabFamily.selected = selected;
|
||||
tabFamily.activeTab = computed(() => {
|
||||
const tabs = unref(processedTabFamily.tabs);
|
||||
if (
|
||||
tabFamily[PersistentState].value in tabs &&
|
||||
unref(tabs[processedTabFamily[PersistentState].value].visibility) ===
|
||||
Visibility.Visible
|
||||
selected.value in tabs &&
|
||||
unref(tabs[selected.value].visibility) === Visibility.Visible
|
||||
) {
|
||||
return unref(tabs[processedTabFamily[PersistentState].value].tab);
|
||||
return unref(tabs[selected.value].tab);
|
||||
}
|
||||
const firstTab = Object.values(tabs).find(
|
||||
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
|
||||
const processedTabFamily = tabFamily as unknown as TabFamily<T>;
|
||||
return processedTabFamily;
|
||||
}, persistent(Object.keys(tabs)[0]));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import {
|
|||
ProcessedComputable
|
||||
} from "util/computed";
|
||||
import { VueFeature } from "util/vue";
|
||||
import { Ref } from "vue";
|
||||
import { nextTick, Ref, unref } from "vue";
|
||||
import { persistent } from "game/persistence";
|
||||
|
||||
declare module "@vue/runtime-dom" {
|
||||
|
@ -73,18 +73,6 @@ export function addTooltip<T extends TooltipOptions>(
|
|||
element: VueFeature,
|
||||
options: T & ThisType<Tooltip<T>> & Partial<BaseTooltip>
|
||||
): Tooltip<T> {
|
||||
if (options.pinnable) {
|
||||
if ("pinned" in element) {
|
||||
console.error(
|
||||
"Cannot add pinnable tooltip to element that already has a property called 'pinned'"
|
||||
);
|
||||
options.pinnable = false;
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(element as any).pinned = options.pinned = persistent<boolean>(false);
|
||||
}
|
||||
}
|
||||
|
||||
processComputable(options as T, "display");
|
||||
processComputable(options as T, "classes");
|
||||
processComputable(options as T, "style");
|
||||
|
@ -93,25 +81,39 @@ export function addTooltip<T extends TooltipOptions>(
|
|||
processComputable(options as T, "xoffset");
|
||||
processComputable(options as T, "yoffset");
|
||||
|
||||
const elementComponent = element[Component];
|
||||
element[Component] = TooltipComponent;
|
||||
const elementGratherProps = element[GatherProps].bind(element);
|
||||
element[GatherProps] = function gatherTooltipProps(this: GenericTooltip) {
|
||||
const { display, classes, style, direction, xoffset, yoffset, pinned } = this;
|
||||
return {
|
||||
element: {
|
||||
[Component]: elementComponent,
|
||||
[GatherProps]: elementGratherProps
|
||||
},
|
||||
display,
|
||||
classes,
|
||||
style,
|
||||
direction,
|
||||
xoffset,
|
||||
yoffset,
|
||||
pinned
|
||||
};
|
||||
}.bind(options as GenericTooltip);
|
||||
nextTick(() => {
|
||||
if (options.pinnable) {
|
||||
if ("pinned" in element) {
|
||||
console.error(
|
||||
"Cannot add pinnable tooltip to element that already has a property called 'pinned'"
|
||||
);
|
||||
options.pinnable = false;
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(element as any).pinned = options.pinned = persistent<boolean>(false);
|
||||
}
|
||||
}
|
||||
|
||||
const elementComponent = element[Component];
|
||||
element[Component] = TooltipComponent;
|
||||
const elementGratherProps = element[GatherProps].bind(element);
|
||||
element[GatherProps] = function gatherTooltipProps(this: GenericTooltip) {
|
||||
const { display, classes, style, direction, xoffset, yoffset, pinned } = this;
|
||||
return {
|
||||
element: {
|
||||
[Component]: elementComponent,
|
||||
[GatherProps]: elementGratherProps
|
||||
},
|
||||
display,
|
||||
classes,
|
||||
style: unref(style),
|
||||
direction,
|
||||
xoffset,
|
||||
yoffset,
|
||||
pinned
|
||||
};
|
||||
}.bind(options as GenericTooltip);
|
||||
});
|
||||
|
||||
return options as unknown as Tooltip<T>;
|
||||
}
|
||||
|
|
|
@ -112,4 +112,8 @@ export default defineComponent({
|
|||
text-transform: capitalize;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.treeNode > *:first-child > * {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -73,10 +73,10 @@ export type GenericTreeNode = Replace<
|
|||
>;
|
||||
|
||||
export function createTreeNode<T extends TreeNodeOptions>(
|
||||
optionsFunc: OptionsFunc<T, TreeNode<T>, BaseTreeNode>
|
||||
optionsFunc?: OptionsFunc<T, TreeNode<T>, BaseTreeNode>
|
||||
): TreeNode<T> {
|
||||
return createLazyProxy(() => {
|
||||
const treeNode = optionsFunc();
|
||||
const treeNode = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>);
|
||||
treeNode.id = getUniqueID("treeNode-");
|
||||
treeNode.type = TreeNodeType;
|
||||
treeNode[Component] = TreeNodeComponent;
|
||||
|
|
|
@ -141,4 +141,8 @@ export default defineComponent({
|
|||
width: 120px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.upgrade > * {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -24,7 +24,7 @@ import {
|
|||
} from "util/computed";
|
||||
import { createLazyProxy } from "util/proxies";
|
||||
import { computed, Ref, unref } from "vue";
|
||||
import { persistent, Persistent, PersistentState } from "game/persistence";
|
||||
import { persistent, Persistent } from "game/persistence";
|
||||
|
||||
export const UpgradeType = Symbol("Upgrade");
|
||||
|
||||
|
@ -47,9 +47,9 @@ export interface UpgradeOptions {
|
|||
onPurchase?: VoidFunction;
|
||||
}
|
||||
|
||||
export interface BaseUpgrade extends Persistent<boolean> {
|
||||
export interface BaseUpgrade {
|
||||
id: string;
|
||||
bought: Ref<boolean>;
|
||||
bought: Persistent<boolean>;
|
||||
canPurchase: Ref<boolean>;
|
||||
purchase: VoidFunction;
|
||||
type: typeof UpgradeType;
|
||||
|
@ -81,8 +81,9 @@ export type GenericUpgrade = Replace<
|
|||
export function createUpgrade<T extends UpgradeOptions>(
|
||||
optionsFunc: OptionsFunc<T, Upgrade<T>, BaseUpgrade>
|
||||
): Upgrade<T> {
|
||||
return createLazyProxy(persistent => {
|
||||
const upgrade = Object.assign(persistent, optionsFunc());
|
||||
const bought = persistent<boolean>(false);
|
||||
return createLazyProxy(() => {
|
||||
const upgrade = optionsFunc();
|
||||
upgrade.id = getUniqueID("upgrade-");
|
||||
upgrade.type = UpgradeType;
|
||||
upgrade[Component] = UpgradeComponent;
|
||||
|
@ -94,7 +95,7 @@ export function createUpgrade<T extends UpgradeOptions>(
|
|||
);
|
||||
}
|
||||
|
||||
upgrade.bought = upgrade[PersistentState];
|
||||
upgrade.bought = bought;
|
||||
if (upgrade.canAfford == null) {
|
||||
upgrade.canAfford = computed(() => {
|
||||
const genericUpgrade = upgrade as GenericUpgrade;
|
||||
|
@ -124,7 +125,7 @@ export function createUpgrade<T extends UpgradeOptions>(
|
|||
unref(genericUpgrade.cost)
|
||||
);
|
||||
}
|
||||
genericUpgrade[PersistentState].value = true;
|
||||
bought.value = true;
|
||||
genericUpgrade.onPurchase?.();
|
||||
};
|
||||
|
||||
|
@ -167,7 +168,7 @@ export function createUpgrade<T extends UpgradeOptions>(
|
|||
};
|
||||
|
||||
return upgrade as unknown as Upgrade<T>;
|
||||
}, persistent<boolean>(false));
|
||||
});
|
||||
}
|
||||
|
||||
export function setupAutoPurchase(
|
||||
|
|
|
@ -2,6 +2,7 @@ import projInfo from "data/projInfo.json";
|
|||
import { Themes } from "data/themes";
|
||||
import { CoercableComponent } from "features/feature";
|
||||
import { globalBus } from "game/events";
|
||||
import LZString from "lz-string";
|
||||
import { hardReset } from "util/save";
|
||||
import { reactive, watch } from "vue";
|
||||
|
||||
|
@ -23,20 +24,44 @@ const state = reactive<Partial<Settings>>({
|
|||
|
||||
watch(
|
||||
state,
|
||||
state =>
|
||||
localStorage.setItem(
|
||||
projInfo.id,
|
||||
btoa(unescape(encodeURIComponent(JSON.stringify(state))))
|
||||
),
|
||||
state => {
|
||||
let stringifiedSettings = JSON.stringify(state);
|
||||
switch (projInfo.saveEncoding) {
|
||||
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 }
|
||||
);
|
||||
export default window.settings = state as Settings;
|
||||
|
||||
export function loadSettings(): void {
|
||||
try {
|
||||
const item: string | null = localStorage.getItem(projInfo.id);
|
||||
let item: string | null = localStorage.getItem(projInfo.id);
|
||||
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") {
|
||||
Object.assign(state, settings);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import projInfo from "data/projInfo.json";
|
|||
import player, { Player, PlayerData, stringifySave } from "game/player";
|
||||
import settings, { loadSettings } from "game/settings";
|
||||
import { ProxyState } from "./proxies";
|
||||
import LZString from "lz-string";
|
||||
|
||||
export function setupInitialStore(player: Partial<PlayerData> = {}): Player {
|
||||
return Object.assign(
|
||||
|
@ -23,9 +24,21 @@ export function setupInitialStore(player: Partial<PlayerData> = {}): Player {
|
|||
) as Player;
|
||||
}
|
||||
|
||||
export function save(): string {
|
||||
const stringifiedSave = btoa(unescape(encodeURIComponent(stringifySave(player[ProxyState]))));
|
||||
localStorage.setItem(player.id, stringifiedSave);
|
||||
export function save(playerData?: PlayerData): string {
|
||||
let stringifiedSave = stringifySave(playerData ?? player[ProxyState]);
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -34,12 +47,24 @@ export async function load(): Promise<void> {
|
|||
loadSettings();
|
||||
|
||||
try {
|
||||
const save = localStorage.getItem(settings.active);
|
||||
let save = localStorage.getItem(settings.active);
|
||||
if (save == null) {
|
||||
await loadSave(newSave());
|
||||
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) {
|
||||
await loadSave(newSave());
|
||||
return;
|
||||
|
@ -55,7 +80,7 @@ export async function load(): Promise<void> {
|
|||
export function newSave(): PlayerData {
|
||||
const id = getUniqueID();
|
||||
const player = setupInitialStore({ id });
|
||||
localStorage.setItem(id, btoa(unescape(encodeURIComponent(stringifySave(player)))));
|
||||
save(player);
|
||||
|
||||
settings.saves.push(id);
|
||||
|
||||
|
|
Loading…
Reference in a new issue