Documented /game
This commit is contained in:
parent
23545a9d33
commit
8d1234a916
7 changed files with 139 additions and 7 deletions
|
@ -9,15 +9,45 @@ import type { App, Ref } from "vue";
|
||||||
import { watch } from "vue";
|
import { watch } from "vue";
|
||||||
import type { GenericLayer } from "./layers";
|
import type { GenericLayer } from "./layers";
|
||||||
|
|
||||||
|
/** All types of events able to be sent or emitted from the global event bus. */
|
||||||
export interface GlobalEvents {
|
export interface GlobalEvents {
|
||||||
|
/**
|
||||||
|
* Sent whenever a layer is added.
|
||||||
|
* @param layer The layer being added.
|
||||||
|
* @param saveData The layer's save data object within player.
|
||||||
|
*/
|
||||||
addLayer: (layer: GenericLayer, saveData: Record<string, unknown>) => void;
|
addLayer: (layer: GenericLayer, saveData: Record<string, unknown>) => void;
|
||||||
|
/**
|
||||||
|
* Sent whenever a layer is removed.
|
||||||
|
* @param layer The layer being removed.
|
||||||
|
*/
|
||||||
removeLayer: (layer: GenericLayer) => void;
|
removeLayer: (layer: GenericLayer) => void;
|
||||||
|
/**
|
||||||
|
* Sent every game tick. Runs the life cycle of the project.
|
||||||
|
* @param diff The delta time since last tick, in ms.
|
||||||
|
* @param trueDiff The delta time since last tick, in ms. Unaffected by time modifiers like {@link game/player.Player.devSpeed} or {@link game/player.Player.offlineTime}. Intended for things like updating animations.
|
||||||
|
*/
|
||||||
update: (diff: number, trueDiff: number) => void;
|
update: (diff: number, trueDiff: number) => void;
|
||||||
|
/**
|
||||||
|
* Sent when constructing the {@link Settings} object.
|
||||||
|
* Use it to add default values for custom properties to the object.
|
||||||
|
* @param settings The settings object being constructed.
|
||||||
|
* @see {@link features/features.setDefault} for setting default values.
|
||||||
|
*/
|
||||||
loadSettings: (settings: Partial<Settings>) => void;
|
loadSettings: (settings: Partial<Settings>) => void;
|
||||||
|
/**
|
||||||
|
* Sent when the game has ended.
|
||||||
|
*/
|
||||||
gameWon: VoidFunction;
|
gameWon: VoidFunction;
|
||||||
|
/**
|
||||||
|
* Sent when setting up the Vue Application instance.
|
||||||
|
* Use it to register global components or otherwise set up things that should affect Vue globally.
|
||||||
|
* @param vue The Vue App being constructed.
|
||||||
|
*/
|
||||||
setupVue: (vue: App) => void;
|
setupVue: (vue: App) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A global event bus for hooking into {@link GlobalEvents}. */
|
||||||
export const globalBus = createNanoEvents<GlobalEvents>();
|
export const globalBus = createNanoEvents<GlobalEvents>();
|
||||||
|
|
||||||
let intervalID: NodeJS.Timer | null = null;
|
let intervalID: NodeJS.Timer | null = null;
|
||||||
|
@ -103,6 +133,7 @@ function update() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Starts the game loop for the project, which updates the game in ticks. */
|
||||||
export async function startGameLoop() {
|
export async function startGameLoop() {
|
||||||
hasWon = (await import("data/projEntry")).hasWon;
|
hasWon = (await import("data/projEntry")).hasWon;
|
||||||
watch(hasWon, hasWon => {
|
watch(hasWon, hasWon => {
|
||||||
|
|
|
@ -53,11 +53,20 @@ export const BoundsInjectionKey: InjectionKey<Ref<DOMRect | undefined>> = Symbol
|
||||||
|
|
||||||
/** All types of events able to be sent or emitted from a layer's emitter. */
|
/** All types of events able to be sent or emitted from a layer's emitter. */
|
||||||
export interface LayerEvents {
|
export interface LayerEvents {
|
||||||
/** Sent every game tick, before the update event. Intended for "generation" type actions. */
|
/**
|
||||||
|
* Sent every game tick, before the update event. Intended for "generation" type actions.
|
||||||
|
* @param diff The delta time since last tick, in ms.
|
||||||
|
*/
|
||||||
preUpdate: (diff: number) => void;
|
preUpdate: (diff: number) => void;
|
||||||
/** Sent every game tick. Intended for "automation" type actions. */
|
/**
|
||||||
|
* Sent every game tick. Intended for "automation" type actions.
|
||||||
|
* @param diff The delta time since last tick, in ms.
|
||||||
|
*/
|
||||||
update: (diff: number) => void;
|
update: (diff: number) => void;
|
||||||
/** Sent every game tick, after the update event. Intended for checking state. */
|
/**
|
||||||
|
* Sent every game tick, after the update event. Intended for checking state.
|
||||||
|
* @param diff The delta time since last tick, in ms.
|
||||||
|
*/
|
||||||
postUpdate: (diff: number) => void;
|
postUpdate: (diff: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,12 @@ import "vue-toastification/dist/index.css";
|
||||||
|
|
||||||
globalBus.on("setupVue", vue => vue.use(Toast));
|
globalBus.on("setupVue", vue => vue.use(Toast));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gives a {@link CSSProperties} object that makes an object glow, to bring focus to it.
|
||||||
|
* Default values are for a "soft" white notif effect.
|
||||||
|
* @param color The color of the glow effect.
|
||||||
|
* @param strength The strength of the glow effect - affects its spread.
|
||||||
|
*/
|
||||||
export function getNotifyStyle(color = "white", strength = "8px") {
|
export function getNotifyStyle(color = "white", strength = "8px") {
|
||||||
return {
|
return {
|
||||||
transform: "scale(1.05, 1.05)",
|
transform: "scale(1.05, 1.05)",
|
||||||
|
@ -13,6 +19,7 @@ export function getNotifyStyle(color = "white", strength = "8px") {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Utility function to call {@link getNotifyStyle} with "high importance" parameters */
|
||||||
export function getHighNotifyStyle() {
|
export function getHighNotifyStyle() {
|
||||||
return getNotifyStyle("red", "20px");
|
return getNotifyStyle("red", "20px");
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,15 +8,32 @@ import { ProxyState } from "util/proxies";
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
import { isReactive, isRef, ref } from "vue";
|
import { isReactive, isRef, ref } from "vue";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A symbol used in {@link Persistent} objects.
|
||||||
|
* @see {@link Persistent[PersistentState]}
|
||||||
|
*/
|
||||||
export const PersistentState = Symbol("PersistentState");
|
export const PersistentState = Symbol("PersistentState");
|
||||||
|
/**
|
||||||
|
* A symbol used in {@link Persistent} objects.
|
||||||
|
* @see {@link Persistent[DefaultValue]}
|
||||||
|
*/
|
||||||
export const DefaultValue = Symbol("DefaultValue");
|
export const DefaultValue = Symbol("DefaultValue");
|
||||||
|
/**
|
||||||
|
* A symbol used in {@link Persistent} objects.
|
||||||
|
* @see {@link Persistent[StackTrace]}
|
||||||
|
*/
|
||||||
export const StackTrace = Symbol("StackTrace");
|
export const StackTrace = Symbol("StackTrace");
|
||||||
|
/**
|
||||||
|
* A symbol used in {@link Persistent} objects.
|
||||||
|
* @see {@link Persistent[Deleted]}
|
||||||
|
*/
|
||||||
export const Deleted = Symbol("Deleted");
|
export const Deleted = Symbol("Deleted");
|
||||||
|
|
||||||
// Note: This is a union of things that should be safely stringifiable without needing
|
/**
|
||||||
// special processes for knowing what to load them in as
|
* This is a union of things that should be safely stringifiable without needing special processes or knowing what to load them in as.
|
||||||
// - Decimals aren't allowed because we'd need to know to parse them back.
|
* - Decimals aren't allowed because we'd need to know to parse them back.
|
||||||
// - DecimalSources are allowed because the string is a valid value for them
|
* - DecimalSources are allowed because the string is a valid value for them
|
||||||
|
*/
|
||||||
export type State =
|
export type State =
|
||||||
| string
|
| string
|
||||||
| number
|
| number
|
||||||
|
@ -25,10 +42,20 @@ export type State =
|
||||||
| { [key: string]: State }
|
| { [key: string]: State }
|
||||||
| { [key: number]: State };
|
| { [key: number]: State };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link Ref} that has been augmented with properties to allow it to be saved and loaded within the player save data object.
|
||||||
|
*/
|
||||||
export type Persistent<T extends State = State> = Ref<T> & {
|
export type Persistent<T extends State = State> = Ref<T> & {
|
||||||
|
/** A flag that this is a persistent property. Typically a circular reference. */
|
||||||
[PersistentState]: Ref<T>;
|
[PersistentState]: Ref<T>;
|
||||||
|
/** The value the ref should be set to in a fresh save, or when updating an old save to the current version. */
|
||||||
[DefaultValue]: T;
|
[DefaultValue]: T;
|
||||||
|
/** The stack trace of where the persistent ref was created. This is used for debugging purposes when a persistent ref is created but not placed in its layer object. */
|
||||||
[StackTrace]: string;
|
[StackTrace]: string;
|
||||||
|
/**
|
||||||
|
* This is a flag that can be set once the option func is evaluated, to mark that a persistent ref should _not_ be saved to the player save data object.
|
||||||
|
* @see {@link deletePersistent} for marking a persistent ref as deleted.
|
||||||
|
*/
|
||||||
[Deleted]: boolean;
|
[Deleted]: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -42,6 +69,11 @@ function getStackTrace() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a persistent ref, which can be saved and loaded.
|
||||||
|
* All (non-deleted) persistent refs must be included somewhere within the layer object returned by that layer's options func.
|
||||||
|
* @param defaultValue The value the persistent ref should start at on fresh saves or when reset.
|
||||||
|
*/
|
||||||
export function persistent<T extends State>(defaultValue: T | Ref<T>): Persistent<T> {
|
export function persistent<T extends State>(defaultValue: T | Ref<T>): Persistent<T> {
|
||||||
const persistent = (
|
const persistent = (
|
||||||
isRef(defaultValue) ? defaultValue : (ref<T>(defaultValue) as unknown)
|
isRef(defaultValue) ? defaultValue : (ref<T>(defaultValue) as unknown)
|
||||||
|
@ -65,6 +97,12 @@ export function persistent<T extends State>(defaultValue: T | Ref<T>): Persisten
|
||||||
return persistent as Persistent<T>;
|
return persistent as Persistent<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark a {@link Persistent} as deleted, so it won't be saved and loaded.
|
||||||
|
* Since persistent refs must be created during a layer's options func, features can not create persistent refs after evaluating their own options funcs.
|
||||||
|
* As a result, it must create any persistent refs it _might_ need.
|
||||||
|
* This function can then be called after the options func is evaluated to mark the persistent ref to not be saved or loaded.
|
||||||
|
*/
|
||||||
export function deletePersistent(persistent: Persistent) {
|
export function deletePersistent(persistent: Persistent) {
|
||||||
if (addingLayers.length === 0) {
|
if (addingLayers.length === 0) {
|
||||||
console.warn("Deleting a persistent ref outside of a layer. Ignoring...", persistent);
|
console.warn("Deleting a persistent ref outside of a layer. Ignoring...", persistent);
|
||||||
|
|
|
@ -6,24 +6,40 @@ import { reactive, unref } from "vue";
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
import transientState from "./state";
|
import transientState from "./state";
|
||||||
|
|
||||||
|
/** The player save data object. */
|
||||||
export interface PlayerData {
|
export interface PlayerData {
|
||||||
|
/** The ID of this save. */
|
||||||
id: string;
|
id: string;
|
||||||
|
/** A multiplier for time passing. Set to 0 when the game is paused. */
|
||||||
devSpeed: number | null;
|
devSpeed: number | null;
|
||||||
|
/** The display name of this save. */
|
||||||
name: string;
|
name: string;
|
||||||
|
/** The open tabs. */
|
||||||
tabs: Array<string>;
|
tabs: Array<string>;
|
||||||
|
/** The current time this save was last opened at, in ms since the unix epoch. */
|
||||||
time: number;
|
time: number;
|
||||||
|
/** Whether or not to automatically save every couple of seconds and on tab close. */
|
||||||
autosave: boolean;
|
autosave: boolean;
|
||||||
|
/** Whether or not to apply offline time when loading this save. */
|
||||||
offlineProd: boolean;
|
offlineProd: boolean;
|
||||||
|
/** How much offline time has been accumulated and not yet processed. */
|
||||||
offlineTime: number | null;
|
offlineTime: number | null;
|
||||||
|
/** How long, in ms, this game has been played. */
|
||||||
timePlayed: number;
|
timePlayed: number;
|
||||||
|
/** Whether or not to continue playing after {@link data/projEntry.hasWon} is true. */
|
||||||
keepGoing: boolean;
|
keepGoing: boolean;
|
||||||
|
/** The ID of this project, to make sure saves aren't imported into the wrong project. */
|
||||||
modID: string;
|
modID: string;
|
||||||
|
/** The version of the project this save was created by. Used for upgrading saves for new versions. */
|
||||||
modVersion: string;
|
modVersion: string;
|
||||||
|
/** A dictionary of layer save data. */
|
||||||
layers: Record<string, LayerData<unknown>>;
|
layers: Record<string, LayerData<unknown>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The proxied player that is used to track NaN values. */
|
||||||
export type Player = ProxiedWithState<PlayerData>;
|
export type Player = ProxiedWithState<PlayerData>;
|
||||||
|
|
||||||
|
/** A layer's save data. Automatically unwraps refs. */
|
||||||
export type LayerData<T> = {
|
export type LayerData<T> = {
|
||||||
[P in keyof T]?: T[P] extends (infer U)[]
|
[P in keyof T]?: T[P] extends (infer U)[]
|
||||||
? LayerData<U>[]
|
? LayerData<U>[]
|
||||||
|
@ -52,6 +68,7 @@ const state = reactive<PlayerData>({
|
||||||
layers: {}
|
layers: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** Convert a player save data object into a JSON string. Unwraps refs. */
|
||||||
export function stringifySave(player: PlayerData): string {
|
export function stringifySave(player: PlayerData): string {
|
||||||
return JSON.stringify(player, (key, value) => unref(value));
|
return JSON.stringify(player, (key, value) => unref(value));
|
||||||
}
|
}
|
||||||
|
@ -133,6 +150,7 @@ declare global {
|
||||||
player: Player;
|
player: Player;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/** The player save data object. */
|
||||||
export default window.player = new Proxy(
|
export default window.player = new Proxy(
|
||||||
{ [ProxyState]: state, [ProxyPath]: ["player"] },
|
{ [ProxyState]: state, [ProxyPath]: ["player"] },
|
||||||
playerHandler
|
playerHandler
|
||||||
|
|
|
@ -6,11 +6,17 @@ 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";
|
||||||
|
|
||||||
|
/** The player's settings object. */
|
||||||
export interface Settings {
|
export interface Settings {
|
||||||
|
/** The ID of the active save. */
|
||||||
active: string;
|
active: string;
|
||||||
|
/** The IDs of all created saves. */
|
||||||
saves: string[];
|
saves: string[];
|
||||||
|
/** Whether or not to show the current ticks per second in the lower left corner of the page. */
|
||||||
showTPS: boolean;
|
showTPS: boolean;
|
||||||
|
/** The current theme to display the game in. */
|
||||||
theme: Themes;
|
theme: Themes;
|
||||||
|
/** Whether or not to cap the project at 20 ticks per second. */
|
||||||
unthrottled: boolean;
|
unthrottled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +46,12 @@ declare global {
|
||||||
hardResetSettings: VoidFunction;
|
hardResetSettings: VoidFunction;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* The player settings object. Stores data that persists across all saves.
|
||||||
|
* Automatically saved to localStorage whenever changed.
|
||||||
|
*/
|
||||||
export default window.settings = state as Settings;
|
export default window.settings = state as Settings;
|
||||||
|
/** A function that erases all player settings, including all saves. */
|
||||||
export const hardResetSettings = (window.hardResetSettings = () => {
|
export const hardResetSettings = (window.hardResetSettings = () => {
|
||||||
const settings = {
|
const settings = {
|
||||||
active: "",
|
active: "",
|
||||||
|
@ -53,6 +64,12 @@ export const hardResetSettings = (window.hardResetSettings = () => {
|
||||||
hardReset();
|
hardReset();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the player settings from localStorage.
|
||||||
|
* Calls the {@link GlobalEvents.loadSettings} event for custom properties to be included.
|
||||||
|
* Custom properties should be added by the file they relate to, so they won't be included if the file is tree shaken away.
|
||||||
|
* Custom properties should also register the field to modify said setting using {@link registerSettingField}.
|
||||||
|
*/
|
||||||
export function loadSettings(): void {
|
export function loadSettings(): void {
|
||||||
try {
|
try {
|
||||||
let item: string | null = localStorage.getItem(projInfo.id);
|
let item: string | null = localStorage.getItem(projInfo.id);
|
||||||
|
@ -80,17 +97,23 @@ export function loadSettings(): void {
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A list of fields to append to the settings modal. */
|
||||||
export const settingFields: CoercableComponent[] = reactive([]);
|
export const settingFields: CoercableComponent[] = reactive([]);
|
||||||
|
/** Register a field to be displayed in the settings modal. */
|
||||||
export function registerSettingField(component: CoercableComponent) {
|
export function registerSettingField(component: CoercableComponent) {
|
||||||
settingFields.push(component);
|
settingFields.push(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A list of components to show in the info modal. */
|
||||||
export const infoComponents: CoercableComponent[] = reactive([]);
|
export const infoComponents: CoercableComponent[] = reactive([]);
|
||||||
|
/** Register a component to be displayed in the info modal. */
|
||||||
export function registerInfoComponent(component: CoercableComponent) {
|
export function registerInfoComponent(component: CoercableComponent) {
|
||||||
infoComponents.push(component);
|
infoComponents.push(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A list of components to add to the root of the page. */
|
||||||
export const gameComponents: CoercableComponent[] = reactive([]);
|
export const gameComponents: CoercableComponent[] = reactive([]);
|
||||||
|
/** Register a component to be displayed at the root of the page. */
|
||||||
export function registerGameComponent(component: CoercableComponent) {
|
export function registerGameComponent(component: CoercableComponent) {
|
||||||
gameComponents.push(component);
|
gameComponents.push(component);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
import { shallowReactive } from "vue";
|
import { shallowReactive } from "vue";
|
||||||
|
|
||||||
|
/** An object of global data that is not persistent. */
|
||||||
export interface Transient {
|
export interface Transient {
|
||||||
|
/** A list of the duration, in ms, of the last 10 game ticks. Used for calculating TPS. */
|
||||||
lastTenTicks: number[];
|
lastTenTicks: number[];
|
||||||
|
/** Whether or not a NaN value has been detected and undealt with. */
|
||||||
hasNaN: boolean;
|
hasNaN: boolean;
|
||||||
|
/** The location within the player save data object of the NaN value. */
|
||||||
NaNPath?: string[];
|
NaNPath?: string[];
|
||||||
|
/** The parent object of the NaN value. */
|
||||||
NaNReceiver?: Record<string, unknown>;
|
NaNReceiver?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +18,7 @@ declare global {
|
||||||
state: Transient;
|
state: Transient;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/** The global transient state object. */
|
||||||
export default window.state = shallowReactive<Transient>({
|
export default window.state = shallowReactive<Transient>({
|
||||||
lastTenTicks: [],
|
lastTenTicks: [],
|
||||||
hasNaN: false,
|
hasNaN: false,
|
||||||
|
|
Loading…
Reference in a new issue