I made a grave mistake in not committing some of these things separately
But now we both have to live with this, because I can't really be bothered to fix it Summary: +Save resources *Restructure Handlers -Remove StaticClass +Add Resource data files ~Change loading
This commit is contained in:
parent
69932a28f7
commit
0c107bc849
25 changed files with 368 additions and 302 deletions
|
@ -20,4 +20,7 @@ module.exports = {
|
|||
es2021: true,
|
||||
node: false,
|
||||
},
|
||||
rules: {
|
||||
"@typescript-eslint/no-unused-vars": ["warn", { args: "all", argsIgnorePattern: "^_", ignoreRestSiblings: false }],
|
||||
},
|
||||
};
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
onMount(() => {
|
||||
root = document.documentElement;
|
||||
|
||||
SharkGame.Settings.settings.subscribe((settings) => {
|
||||
SharkGame.SettingsHandler.settings.subscribe((settings) => {
|
||||
root.classList.toggle("no-theme", !settings.appearance.enableThemes.current);
|
||||
settings.appearance.theme.options.forEach((theme) => {
|
||||
root.classList.toggle(theme, theme === settings.appearance.theme.current);
|
||||
|
@ -33,12 +33,6 @@
|
|||
}
|
||||
|
||||
function handleBeforeUnload(event: BeforeUnloadEvent) {
|
||||
console.debug(
|
||||
"beforeUnload",
|
||||
Date.now() - SharkGame.SaveHandler.lastSaved >= 60 * 1000,
|
||||
Date.now() - SharkGame.SaveHandler.lastSaved,
|
||||
60 * 1000
|
||||
);
|
||||
// If last save is over a minute old
|
||||
if (Date.now() - SharkGame.SaveHandler.lastSaved >= 60 * 1000) {
|
||||
// Annotyingly, the standardized way isn't supported, so both outdated ones will have to suffice
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
return game.SaveHandler.save(game);
|
||||
},
|
||||
settings() {
|
||||
openModal(SettingsModal, { settings: game.Settings.settings }, { replace: true });
|
||||
openModal(SettingsModal, { settings: game.SettingsHandler.settings }, { replace: true });
|
||||
},
|
||||
help() {
|
||||
openModal(HelpModal, { discordLink }, { replace: true });
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
import type { Writable } from "svelte/store";
|
||||
import { slide } from "svelte/transition";
|
||||
|
||||
import type { AddMessageEvent, ResetLogEvent, Message } from "../shark/Message";
|
||||
import { MessageType } from "../shark/Message";
|
||||
import type { AddMessageEvent, ResetLogEvent, Message } from "../shark/helperTypes/Message";
|
||||
import { MessageType } from "../shark/helperTypes/Message";
|
||||
|
||||
export let messages: Writable<Message[]>;
|
||||
export let logLength: number;
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<ConfirmModal
|
||||
{isOpen}
|
||||
confirm={() => {
|
||||
game.SaveHandler.reset();
|
||||
game.SaveHandler.resetSave();
|
||||
}}
|
||||
deny={closeAllModals}>Do you want to reset your save?</ConfirmModal
|
||||
>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<script lang="ts">
|
||||
import type { Settings } from "../../shark/Settings";
|
||||
import type { SettingsHandler } from "../../shark/handlers/SettingsHandler";
|
||||
|
||||
import BaseModal from "./Base/Modal.svelte";
|
||||
|
||||
export let isOpen: boolean;
|
||||
|
||||
export let settings: typeof Settings["settings"];
|
||||
export let settings: typeof SettingsHandler["settings"];
|
||||
</script>
|
||||
|
||||
<BaseModal {isOpen}>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<svelte:options immutable />
|
||||
|
||||
<script lang="ts">
|
||||
import type { Resource } from "../../shark/data/Resources";
|
||||
import type { Resource } from "../../shark/helperTypes/Resource";
|
||||
|
||||
export let categoryName: string;
|
||||
export let collapsed: boolean;
|
||||
export let resources: [string, Resource][];
|
||||
export let resources: Resource[];
|
||||
</script>
|
||||
|
||||
<tr on:click class="subhead" tabindex="0" role="button">
|
||||
|
@ -13,9 +13,9 @@
|
|||
<td colspan="3"><h4>{categoryName}</h4> </td>
|
||||
</tr>
|
||||
{#if !collapsed}
|
||||
{#each resources as [resourceName, resource], index}
|
||||
<tr class:even={index % 2 === 0} class:odd={index % 2 === 1}>
|
||||
<td colspan="2">{resourceName}</td>
|
||||
{#each resources as resource, index}
|
||||
<tr style="--resource-color: {resource.color}" class:even={index % 2 === 0} class:odd={index % 2 === 1}>
|
||||
<td colspan="2">{resource.humanName}</td>
|
||||
<td>{resource.amount}</td>
|
||||
<td>{Math.round(100 * (resource.change + Number.EPSILON)) / 100}/s</td>
|
||||
</tr>
|
||||
|
@ -30,8 +30,12 @@
|
|||
background-color: var(--color-lighter);
|
||||
}
|
||||
|
||||
text-shadow: -1px -1px 2px var(--color-med), 1px -1px 2px var(--color-med), -1px 1px 2px var(--color-med),
|
||||
1px 1px 2px var(--color-med);
|
||||
|
||||
> td:first-child {
|
||||
text-align: left;
|
||||
color: var(--resource-color);
|
||||
}
|
||||
> td {
|
||||
padding: 2px 5px;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<script lang="ts">
|
||||
import type { Writable } from "svelte/store";
|
||||
|
||||
import type { Resource } from "../../shark/data/Resources";
|
||||
import type { Resource } from "../../shark/helperTypes/Resource";
|
||||
import ResourceGroup from "./ResourceGroup.svelte";
|
||||
|
||||
let collapsed: string[] = [];
|
||||
|
@ -15,16 +15,14 @@
|
|||
});
|
||||
|
||||
function resourceGroups(resources: Record<string, Resource>) {
|
||||
return Object.entries(
|
||||
Object.entries(resources).reduce((reduced, [resourceName, resource]) => {
|
||||
if (reduced[resource.category] === undefined) {
|
||||
reduced[resource.category] = [];
|
||||
const result: Record<string, Resource[]> = {};
|
||||
for (const resource of Object.values(resources)) {
|
||||
if (result[resource.category] === undefined) {
|
||||
result[resource.category] = [];
|
||||
}
|
||||
reduced[resource.category].push([resourceName, resource]);
|
||||
|
||||
return reduced;
|
||||
}, {} as Record<string, [string, Resource][]>)
|
||||
);
|
||||
result[resource.category].push(resource);
|
||||
}
|
||||
return Object.entries(result);
|
||||
}
|
||||
|
||||
function toggleCollapsed(categoryName: string) {
|
||||
|
|
|
@ -2,24 +2,34 @@
|
|||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
import type { HomeAction, HomeActions } from "../../shark/data/HomeActions";
|
||||
import type { AddMessageEvent } from "../../shark/Message";
|
||||
|
||||
import { Resources } from "../../shark/data/Resources";
|
||||
import type { ResourceHandler } from "../../shark/handlers/ResourceHandler";
|
||||
import { AddMessageEvent, MessageType } from "../../shark/helperTypes/Message";
|
||||
|
||||
export let homeActions: ReturnType<typeof HomeActions["getActionTable"]>;
|
||||
export let resourceHandler: typeof ResourceHandler;
|
||||
export let showIcons: boolean;
|
||||
|
||||
const dispatch = createEventDispatcher<{ addMessage: AddMessageEvent }>();
|
||||
|
||||
function homeActionClick(homeAction: HomeAction) {
|
||||
if (homeAction.effect.resource) {
|
||||
const resources = Resources.getResources(Object.keys(homeAction.effect.resource));
|
||||
|
||||
if (resources !== null) {
|
||||
for (const [resourceName, resource] of Object.entries(resources)) {
|
||||
resource.amount += homeAction.effect.resource[resourceName];
|
||||
if (homeAction.effect.addResources !== undefined) {
|
||||
const addResources = homeAction.effect.addResources;
|
||||
// Don't use ResourceHandlers's increaseResource method
|
||||
// it would call update once per resource instead of once total
|
||||
resourceHandler.allResources.update((allResources) => {
|
||||
for (const [resourceId, delta] of Object.entries(addResources)) {
|
||||
if (resourceId in addResources) {
|
||||
if (!(resourceId in allResources)) {
|
||||
dispatch("addMessage", {
|
||||
message: `Unknown resourceId '${resourceId}'`,
|
||||
messageType: MessageType.error,
|
||||
});
|
||||
}
|
||||
allResources[resourceId].amount += delta;
|
||||
}
|
||||
}
|
||||
return allResources;
|
||||
});
|
||||
}
|
||||
|
||||
if (homeAction.outcomes) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import type { AddMessageEvent } from "../shark/Message";
|
||||
import type { AddMessageEvent } from "../shark/helperTypes/Message";
|
||||
import type { SharkGame } from "../shark/SharkGame";
|
||||
import Log from "./Log.svelte";
|
||||
import ResourceTable from "./ResourceTable/ResourceTable.svelte";
|
||||
|
@ -21,7 +21,7 @@
|
|||
|
||||
let logLength: number;
|
||||
let showIcons: boolean;
|
||||
game.Settings.settings.subscribe((settings) => {
|
||||
game.SettingsHandler.settings.subscribe((settings) => {
|
||||
logLength = settings.layout.logLength.current;
|
||||
showIcons = settings.appearance.showIcons.current;
|
||||
});
|
||||
|
@ -29,7 +29,7 @@
|
|||
|
||||
<main>
|
||||
<div id="left-column" class:expanded={sidebarExpanded} class:collapsed={!sidebarExpanded}>
|
||||
<ResourceTable resources={game.Resources.Resources} />
|
||||
<ResourceTable resources={game.ResourceHandler.allResources} />
|
||||
<Log
|
||||
{logLength}
|
||||
messages={game.MessageHandler.messages}
|
||||
|
@ -63,6 +63,7 @@
|
|||
on:addMessage={handleAddMessage}
|
||||
{showIcons}
|
||||
homeActions={game.Data.HomeActions.getActionTable()}
|
||||
resourceHandler={game.ResourceHandler}
|
||||
/>
|
||||
{:else if game.TabHandler.currentTab === game.TabHandler.AllTabs.Lab}
|
||||
<svelte:component this={game.TabHandler.AllTabs.Lab} />
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import { StaticClass } from "./StaticClass";
|
||||
export const LZString = new (class LZString {
|
||||
readonly #KeyStrBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
||||
|
||||
export class LZString extends StaticClass {
|
||||
static readonly #KeyStrBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
||||
readonly #KeyStrBase64Dict = this.#createBaseDict(this.#KeyStrBase64);
|
||||
|
||||
static readonly #KeyStrBase64Dict = LZString.#createBaseDict(LZString.#KeyStrBase64);
|
||||
|
||||
static #createBaseDict(alphabet: string): Record<string, number> {
|
||||
#createBaseDict(alphabet: string): Record<string, number> {
|
||||
const dict: Record<string, number> = {};
|
||||
for (let i = 0; i < alphabet.length; i++) {
|
||||
dict[alphabet[i]] = i;
|
||||
|
@ -13,21 +11,21 @@ export class LZString extends StaticClass {
|
|||
return dict;
|
||||
}
|
||||
|
||||
static compressToBase64(input: string): string {
|
||||
const result = LZString.#compress(input, 6, (a) => LZString.#KeyStrBase64.charAt(a));
|
||||
compressToBase64(input: string): string {
|
||||
const result = this.#compress(input, 6, (a) => this.#KeyStrBase64.charAt(a));
|
||||
return result + "=".repeat((4 - (result.length % 4)) % 4);
|
||||
}
|
||||
static decompressFromBase64(input: string | null): string | null {
|
||||
decompressFromBase64(input: string | null): string | null {
|
||||
if (input === null || input === "") return null;
|
||||
return LZString.#decompress(input.length, 32, (index) => LZString.#KeyStrBase64Dict[input.charAt(index)]);
|
||||
return this.#decompress(input.length, 32, (index) => this.#KeyStrBase64Dict[input.charAt(index)]);
|
||||
}
|
||||
|
||||
static compress(uncompressed: string): string {
|
||||
return LZString.#compress(uncompressed);
|
||||
compress(uncompressed: string): string {
|
||||
return this.#compress(uncompressed);
|
||||
}
|
||||
static #compress(uncompressed: string): string;
|
||||
static #compress(uncompressed: string, bitsPerChar: number, getCharFromInt: (index: number) => string): string;
|
||||
static #compress(
|
||||
#compress(uncompressed: string): string;
|
||||
#compress(uncompressed: string, bitsPerChar: number, getCharFromInt: (index: number) => string): string;
|
||||
#compress(
|
||||
uncompressed: string,
|
||||
bitsPerChar = 16,
|
||||
getCharFromNumber = (code: number) => String.fromCharCode(code)
|
||||
|
@ -240,11 +238,11 @@ export class LZString extends StaticClass {
|
|||
return contextData;
|
||||
}
|
||||
|
||||
static decompress(compressed: string | null): string | null {
|
||||
decompress(compressed: string | null): string | null {
|
||||
if (compressed === null || compressed === "") return null;
|
||||
return LZString.#decompress(compressed.length, 32768, (index) => compressed.charCodeAt(index));
|
||||
return this.#decompress(compressed.length, 32768, (index) => compressed.charCodeAt(index));
|
||||
}
|
||||
static #decompress(length: number, resetValue: number, getNextValue: (index: number) => number): string | null {
|
||||
#decompress(length: number, resetValue: number, getNextValue: (index: number) => number): string | null {
|
||||
const dictionary: (number | string)[] = [];
|
||||
|
||||
let enlargeIn = 4;
|
||||
|
@ -411,6 +409,6 @@ export class LZString extends StaticClass {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
window.LZString = LZString;
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
// How to make current just be undefined here, but keep the type of default?
|
||||
|
||||
import { writable } from "svelte/store";
|
||||
import type { Writable } from "svelte/store";
|
||||
import { StaticClass } from "./StaticClass";
|
||||
|
||||
export class Settings extends StaticClass {
|
||||
static readonly #settings = {
|
||||
layout: {
|
||||
logLength: {
|
||||
current: 30,
|
||||
default: 30 as const,
|
||||
name: "Log Messages" as const,
|
||||
description: "The number of messages shown in the log" as const,
|
||||
options: [5, 10, 15, 20, 30, 60] as const,
|
||||
},
|
||||
},
|
||||
appearance: {
|
||||
enableThemes: {
|
||||
current: true,
|
||||
default: true as const,
|
||||
name: "Enable Planet-dependent Styles" as const,
|
||||
description: "Whether page colors should change for different planets" as const,
|
||||
options: [true, false] as const,
|
||||
},
|
||||
theme: {
|
||||
current: "marine",
|
||||
default: "marine" as const,
|
||||
name: "Currently enabled theme" as const,
|
||||
description: "Only applied if planet-dependent styles are enabled" as const,
|
||||
options: ["abandoned", "chaotic", "frigid", "haven", "marine", "shrouded", "tempestuous", "violent"] as const,
|
||||
},
|
||||
showIcons: {
|
||||
current: true,
|
||||
default: true as const,
|
||||
name: "Show icons" as const,
|
||||
description: "Whether to show decorative icons in various places" as const,
|
||||
options: [true, false] as const,
|
||||
},
|
||||
},
|
||||
behavior: {
|
||||
autoSave: {
|
||||
current: 1 as "Off" | number,
|
||||
default: 1 as const,
|
||||
name: "Autosave" as const,
|
||||
description: "How many seconds to wait between each autosave" as const,
|
||||
options: ["Off", 1, 5, 10, 15, 60] as const,
|
||||
},
|
||||
},
|
||||
};
|
||||
static readonly settings = writable(Settings.#settings);
|
||||
|
||||
static getSettings(): SettingsRecord {
|
||||
return Object.seal(Object.assign({}, Settings.#settings));
|
||||
}
|
||||
}
|
||||
|
||||
export type Setting = {
|
||||
current: unknown;
|
||||
readonly default: unknown;
|
||||
readonly name: unknown;
|
||||
readonly description: unknown;
|
||||
readonly options: readonly unknown[];
|
||||
};
|
||||
export type SettingsRecord = typeof Settings["settings"] extends Writable<infer X> ? X : never;
|
|
@ -1,14 +1,15 @@
|
|||
import { Resources } from "./data/Resources";
|
||||
import { ResourceHandler } from "./handlers/ResourceHandler";
|
||||
import { MessageHandler } from "./handlers/MessageHandler";
|
||||
import { SaveHandler } from "./handlers/SaveHandler";
|
||||
import { Settings } from "./Settings";
|
||||
import { StaticClass } from "./StaticClass";
|
||||
import { TabHandler } from "./handlers/TabHandler";
|
||||
import { SettingsHandler } from "./handlers/SettingsHandler";
|
||||
|
||||
import { HomeActions } from "./data/HomeActions";
|
||||
import { ResourceData } from "./data/Resources";
|
||||
import { SettingsData } from "./data/Settings";
|
||||
|
||||
export class SharkGame extends StaticClass {
|
||||
static readonly #GAME_NAMES = [
|
||||
export const SharkGame = new (class SharkGame {
|
||||
readonly #GAME_NAMES = [
|
||||
"Five Seconds A Shark",
|
||||
"Next Shark Game",
|
||||
"Next Shark Game: Barkfest",
|
||||
|
@ -57,28 +58,35 @@ export class SharkGame extends StaticClass {
|
|||
"what the crab doin",
|
||||
] as const;
|
||||
|
||||
static title: string;
|
||||
static readonly Settings = Settings;
|
||||
static readonly MessageHandler = MessageHandler;
|
||||
static readonly Resources = Resources;
|
||||
static readonly TabHandler = TabHandler;
|
||||
static readonly SaveHandler = SaveHandler;
|
||||
static readonly Data = Object.seal({
|
||||
title: string = this.#GAME_NAMES[Math.floor(Math.random() * this.#GAME_NAMES.length)];
|
||||
readonly SettingsHandler = SettingsHandler;
|
||||
readonly MessageHandler = MessageHandler;
|
||||
readonly ResourceHandler = ResourceHandler;
|
||||
readonly TabHandler = TabHandler;
|
||||
readonly SaveHandler = SaveHandler;
|
||||
readonly Data = Object.seal({
|
||||
HomeActions,
|
||||
ResourceData,
|
||||
SettingsData,
|
||||
});
|
||||
|
||||
static async initialize(): Promise<void> {
|
||||
async initialize(): Promise<void> {
|
||||
MessageHandler.initialize(this);
|
||||
|
||||
SaveHandler.reset();
|
||||
TabHandler.reset();
|
||||
ResourceHandler.reset();
|
||||
SettingsHandler.reset();
|
||||
MessageHandler.reset();
|
||||
|
||||
await SaveHandler.load(this);
|
||||
|
||||
SharkGame.title = SharkGame.#GAME_NAMES[Math.floor(Math.random() * SharkGame.#GAME_NAMES.length)];
|
||||
|
||||
MessageHandler.init();
|
||||
SaveHandler.init(this);
|
||||
SaveHandler.initialize(this);
|
||||
}
|
||||
|
||||
static save(): Promise<void> {
|
||||
save(): Promise<void> {
|
||||
return SaveHandler.save(this);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
window.SharkGame = SharkGame;
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
export class StaticClass {
|
||||
constructor() {
|
||||
if (this instanceof StaticClass) {
|
||||
throw new Error("A static class cannot be instantiated");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +1,14 @@
|
|||
import { StaticClass } from "../StaticClass";
|
||||
|
||||
export type HomeAction = {
|
||||
name: string;
|
||||
effect: {
|
||||
resource: Record<string, number>;
|
||||
};
|
||||
effect: Partial<{
|
||||
addResources: Record<string, number>;
|
||||
}>;
|
||||
outcomes: string[];
|
||||
multiOutcomes?: string[];
|
||||
image?: string;
|
||||
};
|
||||
|
||||
export class HomeActions extends StaticClass {
|
||||
export class HomeActions {
|
||||
static getActionTable(): Record<string, HomeAction> {
|
||||
return HomeActions.actionTable;
|
||||
}
|
||||
|
@ -20,7 +18,7 @@ export class HomeActions extends StaticClass {
|
|||
image: "catchFish.webp",
|
||||
name: "Steal fish",
|
||||
effect: {
|
||||
resource: {
|
||||
addResources: {
|
||||
fish: 1,
|
||||
},
|
||||
},
|
||||
|
@ -71,7 +69,7 @@ export class HomeActions extends StaticClass {
|
|||
image: "getShark.webp",
|
||||
name: "Kidnap shark",
|
||||
effect: {
|
||||
resource: {
|
||||
addResources: {
|
||||
shark: 1,
|
||||
},
|
||||
},
|
||||
|
@ -81,7 +79,7 @@ export class HomeActions extends StaticClass {
|
|||
image: "getManta.webp",
|
||||
name: "Summon ray",
|
||||
effect: {
|
||||
resource: {
|
||||
addResources: {
|
||||
get ray() {
|
||||
return Math.random() < 0.01 ? 1e9 : 1;
|
||||
},
|
||||
|
|
|
@ -1,68 +1,23 @@
|
|||
import { writable } from "svelte/store";
|
||||
|
||||
import { StaticClass } from "../StaticClass";
|
||||
|
||||
export class Resource {
|
||||
export type ResourceRaw = {
|
||||
color: string;
|
||||
humanName: string;
|
||||
category: string;
|
||||
amount: number;
|
||||
change: number;
|
||||
};
|
||||
|
||||
constructor(category: string) {
|
||||
this.category = category;
|
||||
this.amount = 0;
|
||||
this.change = 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class Resources extends StaticClass {
|
||||
static readonly Resources = writable<Record<string, Resource>>({
|
||||
shark: new Resource("Frenzy"),
|
||||
ray: new Resource("Frenzy"),
|
||||
fish: new Resource("Animals"),
|
||||
});
|
||||
|
||||
static createResource(name: string, category: string): void {
|
||||
Resources.Resources.update((res) => {
|
||||
res[name] = new Resource(category);
|
||||
return res;
|
||||
});
|
||||
}
|
||||
|
||||
static setResource(resourceName: string, amount: number): void {
|
||||
Resources.Resources.update((res) => {
|
||||
res[resourceName].amount = amount;
|
||||
return res;
|
||||
});
|
||||
}
|
||||
|
||||
static getResource(resourceName: string): Resource | null {
|
||||
let result: null | Resource = null;
|
||||
Resources.Resources.update((res) => {
|
||||
if (Object.hasOwnProperty.call(res, resourceName)) result = res[resourceName];
|
||||
return res;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
static getResources(resourceNames: string[]): Record<string, Resource> | null;
|
||||
static getResources(...resourceNames: string[]): Record<string, Resource> | null;
|
||||
static getResources(...resourceNames: string[] | string[][]): Record<string, Resource> | null {
|
||||
if (Array.isArray(resourceNames[0])) {
|
||||
resourceNames = resourceNames[0];
|
||||
}
|
||||
|
||||
let result: null | [string, Resource][] = null;
|
||||
Resources.Resources.update((res) => {
|
||||
for (const resourceName of resourceNames as string[]) {
|
||||
if (Object.hasOwnProperty.call(res, resourceName)) {
|
||||
if (result === null) {
|
||||
result = [];
|
||||
}
|
||||
result.push([resourceName, res[resourceName]]);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
});
|
||||
return result === null ? null : Object.fromEntries(result);
|
||||
}
|
||||
}
|
||||
export const ResourceData = {
|
||||
fish: {
|
||||
color: "yellow",
|
||||
humanName: "fish",
|
||||
category: "animals",
|
||||
},
|
||||
shark: {
|
||||
color: "#92C1E0",
|
||||
humanName: "shark",
|
||||
category: "frenzy",
|
||||
},
|
||||
ray: {
|
||||
color: "#8080FF",
|
||||
humanName: "ray",
|
||||
category: "frenzy",
|
||||
},
|
||||
} as const; // as Record<string, ResourceRaw>
|
||||
|
|
53
src/shark/data/Settings.ts
Normal file
53
src/shark/data/Settings.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
// How to make current just be undefined here, but keep the type of default?
|
||||
|
||||
export type Setting = {
|
||||
current: unknown;
|
||||
readonly default: unknown;
|
||||
readonly name: unknown;
|
||||
readonly description: unknown;
|
||||
readonly options: readonly unknown[];
|
||||
};
|
||||
|
||||
export const SettingsData = {
|
||||
layout: {
|
||||
logLength: {
|
||||
current: 30,
|
||||
default: 30 as const,
|
||||
name: "Log Messages" as const,
|
||||
description: "The number of messages shown in the log" as const,
|
||||
options: [5, 10, 15, 20, 30, 60] as const,
|
||||
},
|
||||
},
|
||||
appearance: {
|
||||
enableThemes: {
|
||||
current: true,
|
||||
default: true as const,
|
||||
name: "Enable Planet-dependent Styles" as const,
|
||||
description: "Whether page colors should change for different planets" as const,
|
||||
options: [true, false] as const,
|
||||
},
|
||||
theme: {
|
||||
current: "marine",
|
||||
default: "marine" as const,
|
||||
name: "Currently enabled theme" as const,
|
||||
description: "Only applied if planet-dependent styles are enabled" as const,
|
||||
options: ["abandoned", "chaotic", "frigid", "haven", "marine", "shrouded", "tempestuous", "violent"] as const,
|
||||
},
|
||||
showIcons: {
|
||||
current: true,
|
||||
default: true as const,
|
||||
name: "Show icons" as const,
|
||||
description: "Whether to show decorative icons in various places" as const,
|
||||
options: [true, false] as const,
|
||||
},
|
||||
},
|
||||
behavior: {
|
||||
autoSave: {
|
||||
current: 1 as "Off" | 1 | 5 | 10 | 15 | 60,
|
||||
default: 1 as const,
|
||||
name: "Autosave" as const,
|
||||
description: "How many seconds to wait between each autosave" as const,
|
||||
options: ["Off", 1, 5, 10, 15, 60] as const,
|
||||
},
|
||||
},
|
||||
};
|
3
src/shark/handlers/BaseHandler.ts
Normal file
3
src/shark/handlers/BaseHandler.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export interface BaseHandler {
|
||||
reset(): void;
|
||||
}
|
|
@ -1,27 +1,25 @@
|
|||
import { writable } from "svelte/store";
|
||||
import { get, writable } from "svelte/store";
|
||||
|
||||
import { Message, MessageType } from "../Message";
|
||||
import { Settings } from "../Settings";
|
||||
import { StaticClass } from "../StaticClass";
|
||||
import { Message, MessageType } from "../helperTypes/Message";
|
||||
import type { SharkGame } from "../SharkGame";
|
||||
import type { BaseHandler } from "./BaseHandler";
|
||||
|
||||
export class MessageHandler extends StaticClass {
|
||||
static messages = writable<Message[]>([]);
|
||||
static #maxLogLength: number;
|
||||
export const MessageHandler = new (class MessageHandler implements BaseHandler {
|
||||
readonly messages = writable<Message[]>([]);
|
||||
#maxLogLength = Infinity;
|
||||
|
||||
static init(): void {
|
||||
Settings.settings.subscribe((settings) => {
|
||||
MessageHandler.#maxLogLength = Math.max(...settings.layout.logLength.options);
|
||||
})();
|
||||
initialize(game: typeof SharkGame): void {
|
||||
this.#maxLogLength = Math.max(...get(game.SettingsHandler.settings).layout.logLength.options);
|
||||
}
|
||||
|
||||
static addMessage(message: string, messageType: MessageType = MessageType.message): void {
|
||||
addMessage(message: string, messageType: MessageType = MessageType.message): void {
|
||||
this.messages.update((oldMessages) =>
|
||||
[...oldMessages, new Message(message, messageType)].slice(-MessageHandler.#maxLogLength)
|
||||
[...oldMessages, new Message(message, messageType)].slice(-this.#maxLogLength)
|
||||
);
|
||||
}
|
||||
|
||||
static reset(): void {
|
||||
reset(): void {
|
||||
Message.even = true;
|
||||
this.messages.set([]);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
32
src/shark/handlers/ResourceHandler.ts
Normal file
32
src/shark/handlers/ResourceHandler.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { writable } from "svelte/store";
|
||||
|
||||
import { ResourceData } from "../data/Resources";
|
||||
|
||||
import { Resource } from "../helperTypes/Resource";
|
||||
|
||||
import type { BaseHandler } from "./BaseHandler";
|
||||
|
||||
type ResourceMap = Record<string, Resource>;
|
||||
|
||||
export const ResourceHandler = new (class ResourceHandler implements BaseHandler {
|
||||
allResources = writable<ResourceMap>({});
|
||||
|
||||
reset() {
|
||||
this.allResources.update((allResources) => {
|
||||
for (const [resourceId, resourceRaw] of Object.entries(ResourceData)) {
|
||||
allResources[resourceId] = new Resource(resourceRaw);
|
||||
}
|
||||
return allResources;
|
||||
});
|
||||
}
|
||||
|
||||
increaseResource(resourceId: string, amount: number) {
|
||||
this.allResources.update((resources) => {
|
||||
if (!(resourceId in resources)) {
|
||||
throw new Error(`Unknown resourceId '${resourceId}'`);
|
||||
}
|
||||
resources[resourceId].amount += amount;
|
||||
return resources;
|
||||
});
|
||||
}
|
||||
})();
|
|
@ -1,9 +1,18 @@
|
|||
import type { Message, MessageType } from "../Message";
|
||||
import type { SharkGame } from "../SharkGame";
|
||||
import { StaticClass } from "../StaticClass";
|
||||
import type { TabHandler } from "./TabHandler";
|
||||
import type { BaseHandler } from "./BaseHandler";
|
||||
|
||||
import { SettingsHandler } from "./SettingsHandler";
|
||||
|
||||
import { get } from "svelte/store";
|
||||
|
||||
import { Resource } from "../helperTypes/Resource";
|
||||
import { Message, MessageType } from "../helperTypes/Message";
|
||||
|
||||
import { ResourceData } from "../data/Resources";
|
||||
|
||||
import { LZString } from "../LZString";
|
||||
import { Settings } from "../Settings";
|
||||
import type { SettingsData } from "../data/Settings";
|
||||
|
||||
const __EMPTY_OBJECT = {};
|
||||
type Version0Save = typeof __EMPTY_OBJECT;
|
||||
|
@ -15,46 +24,50 @@ type Version1Save = Version0Save & {
|
|||
}[];
|
||||
selectedTab: keyof typeof TabHandler["AllTabs"];
|
||||
settings: Record<string, Record<string, unknown>>;
|
||||
resources: Record<string, { amount: number; total: number }>;
|
||||
};
|
||||
|
||||
type CurrentVersionSave = Version1Save;
|
||||
type Save = Version0Save | Version1Save;
|
||||
|
||||
export class SaveHandler extends StaticClass {
|
||||
static readonly saveName = "sharg-save";
|
||||
static #lastSaved = Date.now();
|
||||
static get lastSaved(): number {
|
||||
return SaveHandler.#lastSaved;
|
||||
export const SaveHandler = new (class SaveHandler implements BaseHandler {
|
||||
readonly saveName = "sharg-save";
|
||||
#lastSaved = Date.now();
|
||||
get lastSaved(): number {
|
||||
return this.#lastSaved;
|
||||
}
|
||||
private static set lastSaved(val) {
|
||||
SaveHandler.#lastSaved = val;
|
||||
private set lastSaved(val) {
|
||||
this.#lastSaved = val;
|
||||
}
|
||||
|
||||
static #saveInterval: ReturnType<typeof setInterval> | undefined = undefined;
|
||||
#saveInterval: ReturnType<typeof setInterval> | undefined = undefined;
|
||||
|
||||
static init(game: typeof SharkGame): void {
|
||||
Settings.settings.subscribe((settings) => {
|
||||
if (SaveHandler.#saveInterval !== undefined) {
|
||||
clearInterval(SaveHandler.#saveInterval);
|
||||
}
|
||||
initialize(game: typeof SharkGame) {
|
||||
let previousSetting: typeof SettingsData["behavior"]["autoSave"]["options"][number];
|
||||
SettingsHandler.settings.subscribe((settings) => {
|
||||
const currentSetting = settings.behavior.autoSave.current;
|
||||
if (previousSetting !== currentSetting) {
|
||||
this.reset();
|
||||
if (settings.behavior.autoSave.current !== "Off") {
|
||||
SaveHandler.#saveInterval = setInterval(
|
||||
() => SaveHandler.save(game),
|
||||
settings.behavior.autoSave.current * 1000
|
||||
);
|
||||
this.#saveInterval = setInterval(() => this.save(game), settings.behavior.autoSave.current * 1000);
|
||||
}
|
||||
}
|
||||
previousSetting = currentSetting;
|
||||
});
|
||||
}
|
||||
|
||||
static async save(game: typeof SharkGame): Promise<void> {
|
||||
reset(): void {
|
||||
if (this.#saveInterval !== undefined) {
|
||||
clearInterval(this.#saveInterval);
|
||||
}
|
||||
}
|
||||
|
||||
async save(game: typeof SharkGame): Promise<void> {
|
||||
console.debug("Saving");
|
||||
console.time("Done saving");
|
||||
const messages = await new Promise<Message[]>((resolve) => {
|
||||
game.MessageHandler.messages.subscribe((messages) => {
|
||||
resolve(messages);
|
||||
})();
|
||||
});
|
||||
const messages = get(game.MessageHandler.messages);
|
||||
const resources = get(game.ResourceHandler.allResources);
|
||||
|
||||
// Save tab
|
||||
const allTabsEntries = Object.entries(game.TabHandler.AllTabs) as unknown as [
|
||||
keyof typeof TabHandler["AllTabs"],
|
||||
typeof TabHandler["AllTabs"][keyof typeof TabHandler["AllTabs"]]
|
||||
|
@ -62,7 +75,8 @@ export class SaveHandler extends StaticClass {
|
|||
const selectedTabIndex = allTabsEntries.findIndex(([, tab]) => tab === game.TabHandler.currentTab);
|
||||
const selectedTabName = allTabsEntries[selectedTabIndex][0];
|
||||
|
||||
const currentSettings = game.Settings.getSettings();
|
||||
// Save settings
|
||||
const currentSettings = get(game.SettingsHandler.settings);
|
||||
const saveSettings: CurrentVersionSave["settings"] = {};
|
||||
for (const [categoryId, categorySettings] of Object.entries(currentSettings)) {
|
||||
const categorySave: CurrentVersionSave["settings"][string] = {};
|
||||
|
@ -76,16 +90,25 @@ export class SaveHandler extends StaticClass {
|
|||
}
|
||||
}
|
||||
|
||||
const save: CurrentVersionSave = {
|
||||
version: 1,
|
||||
selectedTab: selectedTabName,
|
||||
settings: saveSettings,
|
||||
messages: messages.map((message) => {
|
||||
return {
|
||||
// Save resources
|
||||
const saveResources: CurrentVersionSave["resources"] = {};
|
||||
for (const [resourceId, resource] of Object.entries(resources)) {
|
||||
if (resource.amount > 0 || resource.total > 0)
|
||||
saveResources[resourceId] = { amount: resource.amount, total: resource.total };
|
||||
}
|
||||
|
||||
// Save messages
|
||||
const saveMessages: CurrentVersionSave["messages"] = messages.map((message) => ({
|
||||
content: message.content,
|
||||
type: message.type,
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
const save: CurrentVersionSave = {
|
||||
version: 1,
|
||||
messages: saveMessages,
|
||||
selectedTab: selectedTabName,
|
||||
settings: saveSettings,
|
||||
resources: saveResources,
|
||||
};
|
||||
const stringifiedSave = JSON.stringify(save);
|
||||
const encodedSave = LZString.compressToBase64(stringifiedSave);
|
||||
|
@ -95,22 +118,22 @@ export class SaveHandler extends StaticClass {
|
|||
}% size`
|
||||
);
|
||||
|
||||
localStorage.setItem(SaveHandler.saveName, encodedSave);
|
||||
SaveHandler.#lastSaved = Date.now();
|
||||
localStorage.setItem(this.saveName, encodedSave);
|
||||
this.#lastSaved = Date.now();
|
||||
console.timeEnd("Done saving");
|
||||
}
|
||||
|
||||
static async load(game: typeof SharkGame): Promise<void> {
|
||||
async load(game: typeof SharkGame): Promise<void> {
|
||||
console.debug("Loading");
|
||||
console.time("Done loading");
|
||||
const localSave = LZString.decompressFromBase64(localStorage.getItem(SaveHandler.saveName));
|
||||
const localSave = LZString.decompressFromBase64(localStorage.getItem(this.saveName));
|
||||
const loadedSave = JSON.parse(localSave ?? "{}");
|
||||
const saveVersion = loadedSave.version ?? 0;
|
||||
|
||||
const migrators = SaveHandler.migrators.slice(saveVersion);
|
||||
const migrators = this.migrators.slice(saveVersion);
|
||||
|
||||
if (migrators.length > 0 && localSave !== null) {
|
||||
localStorage.setItem(SaveHandler.saveName + "-backup", localSave);
|
||||
localStorage.setItem(this.saveName + "-backup", localSave);
|
||||
}
|
||||
|
||||
let save = loadedSave;
|
||||
|
@ -121,44 +144,64 @@ export class SaveHandler extends StaticClass {
|
|||
|
||||
const fullSave = save as CurrentVersionSave;
|
||||
|
||||
game.Settings.settings.update((settings) => {
|
||||
Object.entries(settings).forEach(([categoryName, categorySettings]) => {
|
||||
Object.entries(categorySettings).forEach(([settingId, setting]) => {
|
||||
// Load settings
|
||||
game.SettingsHandler.reset();
|
||||
game.SettingsHandler.settings.update((settings) => {
|
||||
for (const [categoryName, categorySettings] of Object.entries(settings)) {
|
||||
for (const [settingId, setting] of Object.entries(categorySettings)) {
|
||||
setting.current = fullSave.settings[categoryName]?.[settingId] ?? setting.default;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
return settings;
|
||||
});
|
||||
|
||||
game.MessageHandler.reset();
|
||||
// Load resources
|
||||
game.ResourceHandler.reset();
|
||||
game.ResourceHandler.allResources.update((allResources) => {
|
||||
for (const [resourceId, resourceInfo] of Object.entries(fullSave.resources)) {
|
||||
if (resourceId in ResourceData) {
|
||||
const resource = new Resource(ResourceData[resourceId as keyof typeof ResourceData]);
|
||||
|
||||
fullSave.messages.forEach((message) => {
|
||||
game.MessageHandler.addMessage(message.content, message.type);
|
||||
resource.amount = resourceInfo.amount;
|
||||
resource.total = resourceInfo.total;
|
||||
|
||||
allResources[resourceId] = resource;
|
||||
}
|
||||
}
|
||||
return allResources;
|
||||
});
|
||||
|
||||
// Load messages
|
||||
game.MessageHandler.reset();
|
||||
game.MessageHandler.messages.set(
|
||||
fullSave.messages.map((saveMessage) => new Message(saveMessage.content, saveMessage.type))
|
||||
);
|
||||
|
||||
game.TabHandler.currentTab = game.TabHandler.AllTabs[fullSave.selectedTab];
|
||||
console.timeEnd("Done loading");
|
||||
}
|
||||
|
||||
static reset(): void {
|
||||
const localSave = localStorage.getItem(SaveHandler.saveName);
|
||||
resetSave(): void {
|
||||
const localSave = localStorage.getItem(this.saveName);
|
||||
if (localSave !== null) {
|
||||
localStorage.setItem(SaveHandler.saveName + "-backup", localSave);
|
||||
localStorage.setItem(this.saveName + "-backup", localSave);
|
||||
}
|
||||
|
||||
localStorage.removeItem(SaveHandler.saveName);
|
||||
localStorage.removeItem(this.saveName);
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
static migrators: ((save: Save) => Save)[] = [
|
||||
migrators: ((save: Save) => Save)[] = [
|
||||
(): Version1Save => {
|
||||
const newSave = {
|
||||
version: 1 as const,
|
||||
messages: [],
|
||||
selectedTab: "Home" as const,
|
||||
settings: {},
|
||||
resources: {},
|
||||
};
|
||||
|
||||
return newSave;
|
||||
},
|
||||
];
|
||||
}
|
||||
})();
|
||||
|
|
19
src/shark/handlers/SettingsHandler.ts
Normal file
19
src/shark/handlers/SettingsHandler.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { writable } from "svelte/store";
|
||||
import type { Writable } from "svelte/store";
|
||||
import type { BaseHandler } from "./BaseHandler";
|
||||
|
||||
import { SettingsData } from "../data/Settings";
|
||||
|
||||
export const SettingsHandler = new (class Settings implements BaseHandler {
|
||||
readonly settings = writable(SettingsData);
|
||||
|
||||
reset() {
|
||||
for (const category of Object.values(SettingsData)) {
|
||||
for (const setting of Object.values(category)) {
|
||||
setting.current = setting.default;
|
||||
}
|
||||
}
|
||||
this.settings.set(SettingsData);
|
||||
}
|
||||
})();
|
||||
export type SettingsRecord = typeof SettingsHandler["settings"] extends Writable<infer X> ? X : never;
|
|
@ -1,11 +1,15 @@
|
|||
import { StaticClass } from "../StaticClass";
|
||||
import Home from "../../components/Tabs/Home.svelte";
|
||||
import Lab from "../../components/Tabs/Lab.svelte";
|
||||
import type { BaseHandler } from "./BaseHandler";
|
||||
|
||||
export class TabHandler extends StaticClass {
|
||||
static readonly AllTabs = {
|
||||
export const TabHandler = new (class TabHandler implements BaseHandler {
|
||||
reset() {
|
||||
this.currentTab = this.AllTabs.Home;
|
||||
}
|
||||
|
||||
readonly AllTabs = {
|
||||
Home,
|
||||
Lab,
|
||||
} as const;
|
||||
static currentTab: typeof TabHandler.AllTabs[keyof typeof TabHandler.AllTabs] = TabHandler.AllTabs.Home;
|
||||
}
|
||||
currentTab: typeof this.AllTabs[keyof typeof this.AllTabs] = this.AllTabs.Home;
|
||||
})();
|
||||
|
|
17
src/shark/helperTypes/Resource.ts
Normal file
17
src/shark/helperTypes/Resource.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import type { ResourceRaw } from "../data/Resources";
|
||||
|
||||
export class Resource implements ResourceRaw {
|
||||
humanName: string;
|
||||
category: string;
|
||||
color: string;
|
||||
|
||||
amount = 0;
|
||||
total = 0;
|
||||
change = 0;
|
||||
|
||||
constructor(raw: ResourceRaw) {
|
||||
this.humanName = raw.humanName;
|
||||
this.category = raw.category;
|
||||
this.color = raw.color;
|
||||
}
|
||||
}
|
Reference in a new issue