Merge remote-tracking branch 'template/main'
This commit is contained in:
commit
c12345d98f
9 changed files with 142 additions and 10 deletions
|
@ -306,7 +306,7 @@ export function createLinearScaling(
|
|||
* @param base The base variable in the scaling formula.
|
||||
* @param exponent The exponent variable in the scaling formula.
|
||||
* @example
|
||||
* A scaling function created via `createLinearScaling(10, 0.5)` would produce the following values:
|
||||
* A scaling function created via `createPolynomialScaling(10, 0.5)` would produce the following values:
|
||||
* | Base Resource | Current Gain |
|
||||
* | ------------- | ------------ |
|
||||
* | 10 | 1 |
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { EmitterConfigV3 } from "@pixi/particle-emitter";
|
||||
import { Emitter, upgradeConfig } from "@pixi/particle-emitter";
|
||||
import type { OptionsFunc, Replace, StyleValue } from "features/feature";
|
||||
import type { GenericComponent, OptionsFunc, Replace, StyleValue } from "features/feature";
|
||||
import { Component, GatherProps, getUniqueID } from "features/feature";
|
||||
import ParticlesComponent from "features/particles/Particles.vue";
|
||||
import { Application } from "@pixi/app";
|
||||
|
@ -22,7 +22,7 @@ export interface BaseParticles {
|
|||
app: Ref<null | Application>;
|
||||
addEmitter: (config: EmitterConfigV3) => Promise<Emitter>;
|
||||
type: typeof ParticlesType;
|
||||
[Component]: typeof ParticlesComponent;
|
||||
[Component]: GenericComponent;
|
||||
[GatherProps]: () => Record<string, unknown>;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,15 +9,45 @@ import type { App, Ref } from "vue";
|
|||
import { watch } from "vue";
|
||||
import type { GenericLayer } from "./layers";
|
||||
|
||||
/** All types of events able to be sent or emitted from the global event bus. */
|
||||
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;
|
||||
/**
|
||||
* Sent whenever a layer is removed.
|
||||
* @param layer The layer being removed.
|
||||
*/
|
||||
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;
|
||||
/**
|
||||
* 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;
|
||||
/**
|
||||
* Sent when the game has ended.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/** A global event bus for hooking into {@link GlobalEvents}. */
|
||||
export const globalBus = createNanoEvents<GlobalEvents>();
|
||||
|
||||
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() {
|
||||
hasWon = (await import("data/projEntry")).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. */
|
||||
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;
|
||||
/** 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;
|
||||
/** 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,12 @@ import "vue-toastification/dist/index.css";
|
|||
|
||||
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") {
|
||||
return {
|
||||
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() {
|
||||
return getNotifyStyle("red", "20px");
|
||||
}
|
||||
|
|
|
@ -8,15 +8,32 @@ import { ProxyState } from "util/proxies";
|
|||
import type { 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");
|
||||
/**
|
||||
* A symbol used in {@link Persistent} objects.
|
||||
* @see {@link Persistent[DefaultValue]}
|
||||
*/
|
||||
export const DefaultValue = Symbol("DefaultValue");
|
||||
/**
|
||||
* A symbol used in {@link Persistent} objects.
|
||||
* @see {@link Persistent[StackTrace]}
|
||||
*/
|
||||
export const StackTrace = Symbol("StackTrace");
|
||||
/**
|
||||
* A symbol used in {@link Persistent} objects.
|
||||
* @see {@link Persistent[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
|
||||
// - 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
|
||||
/**
|
||||
* 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.
|
||||
* - DecimalSources are allowed because the string is a valid value for them
|
||||
*/
|
||||
export type State =
|
||||
| string
|
||||
| number
|
||||
|
@ -25,10 +42,20 @@ export type State =
|
|||
| { [key: string]: 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> & {
|
||||
/** A flag that this is a persistent property. Typically a circular reference. */
|
||||
[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;
|
||||
/** 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;
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
|
||||
|
@ -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> {
|
||||
const persistent = (
|
||||
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>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
if (addingLayers.length === 0) {
|
||||
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 transientState from "./state";
|
||||
|
||||
/** The player save data object. */
|
||||
export interface PlayerData {
|
||||
/** The ID of this save. */
|
||||
id: string;
|
||||
/** A multiplier for time passing. Set to 0 when the game is paused. */
|
||||
devSpeed: number | null;
|
||||
/** The display name of this save. */
|
||||
name: string;
|
||||
/** The open tabs. */
|
||||
tabs: Array<string>;
|
||||
/** The current time this save was last opened at, in ms since the unix epoch. */
|
||||
time: number;
|
||||
/** Whether or not to automatically save every couple of seconds and on tab close. */
|
||||
autosave: boolean;
|
||||
/** Whether or not to apply offline time when loading this save. */
|
||||
offlineProd: boolean;
|
||||
/** How much offline time has been accumulated and not yet processed. */
|
||||
offlineTime: number | null;
|
||||
/** How long, in ms, this game has been played. */
|
||||
timePlayed: number;
|
||||
/** Whether or not to continue playing after {@link data/projEntry.hasWon} is true. */
|
||||
keepGoing: boolean;
|
||||
/** The ID of this project, to make sure saves aren't imported into the wrong project. */
|
||||
modID: string;
|
||||
/** The version of the project this save was created by. Used for upgrading saves for new versions. */
|
||||
modVersion: string;
|
||||
/** A dictionary of layer save data. */
|
||||
layers: Record<string, LayerData<unknown>>;
|
||||
}
|
||||
|
||||
/** The proxied player that is used to track NaN values. */
|
||||
export type Player = ProxiedWithState<PlayerData>;
|
||||
|
||||
/** A layer's save data. Automatically unwraps refs. */
|
||||
export type LayerData<T> = {
|
||||
[P in keyof T]?: T[P] extends (infer U)[]
|
||||
? LayerData<U>[]
|
||||
|
@ -52,6 +68,7 @@ const state = reactive<PlayerData>({
|
|||
layers: {}
|
||||
});
|
||||
|
||||
/** Convert a player save data object into a JSON string. Unwraps refs. */
|
||||
export function stringifySave(player: PlayerData): string {
|
||||
return JSON.stringify(player, (key, value) => unref(value));
|
||||
}
|
||||
|
@ -133,6 +150,7 @@ declare global {
|
|||
player: Player;
|
||||
}
|
||||
}
|
||||
/** The player save data object. */
|
||||
export default window.player = new Proxy(
|
||||
{ [ProxyState]: state, [ProxyPath]: ["player"] },
|
||||
playerHandler
|
||||
|
|
|
@ -6,11 +6,17 @@ import LZString from "lz-string";
|
|||
import { hardReset } from "util/save";
|
||||
import { reactive, watch } from "vue";
|
||||
|
||||
/** The player's settings object. */
|
||||
export interface Settings {
|
||||
/** The ID of the active save. */
|
||||
active: string;
|
||||
/** The IDs of all created saves. */
|
||||
saves: string[];
|
||||
/** Whether or not to show the current ticks per second in the lower left corner of the page. */
|
||||
showTPS: boolean;
|
||||
/** The current theme to display the game in. */
|
||||
theme: Themes;
|
||||
/** Whether or not to cap the project at 20 ticks per second. */
|
||||
unthrottled: boolean;
|
||||
}
|
||||
|
||||
|
@ -40,7 +46,12 @@ declare global {
|
|||
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;
|
||||
/** A function that erases all player settings, including all saves. */
|
||||
export const hardResetSettings = (window.hardResetSettings = () => {
|
||||
const settings = {
|
||||
active: "",
|
||||
|
@ -53,6 +64,12 @@ export const hardResetSettings = (window.hardResetSettings = () => {
|
|||
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 {
|
||||
try {
|
||||
let item: string | null = localStorage.getItem(projInfo.id);
|
||||
|
@ -80,17 +97,23 @@ export function loadSettings(): void {
|
|||
} catch {}
|
||||
}
|
||||
|
||||
/** A list of fields to append to the settings modal. */
|
||||
export const settingFields: CoercableComponent[] = reactive([]);
|
||||
/** Register a field to be displayed in the settings modal. */
|
||||
export function registerSettingField(component: CoercableComponent) {
|
||||
settingFields.push(component);
|
||||
}
|
||||
|
||||
/** A list of components to show in the info modal. */
|
||||
export const infoComponents: CoercableComponent[] = reactive([]);
|
||||
/** Register a component to be displayed in the info modal. */
|
||||
export function registerInfoComponent(component: CoercableComponent) {
|
||||
infoComponents.push(component);
|
||||
}
|
||||
|
||||
/** A list of components to add to the root of the page. */
|
||||
export const gameComponents: CoercableComponent[] = reactive([]);
|
||||
/** Register a component to be displayed at the root of the page. */
|
||||
export function registerGameComponent(component: CoercableComponent) {
|
||||
gameComponents.push(component);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
import { shallowReactive } from "vue";
|
||||
|
||||
/** An object of global data that is not persistent. */
|
||||
export interface Transient {
|
||||
/** A list of the duration, in ms, of the last 10 game ticks. Used for calculating TPS. */
|
||||
lastTenTicks: number[];
|
||||
/** Whether or not a NaN value has been detected and undealt with. */
|
||||
hasNaN: boolean;
|
||||
/** The location within the player save data object of the NaN value. */
|
||||
NaNPath?: string[];
|
||||
/** The parent object of the NaN value. */
|
||||
NaNReceiver?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
|
@ -13,6 +18,7 @@ declare global {
|
|||
state: Transient;
|
||||
}
|
||||
}
|
||||
/** The global transient state object. */
|
||||
export default window.state = shallowReactive<Transient>({
|
||||
lastTenTicks: [],
|
||||
hasNaN: false,
|
||||
|
|
Loading…
Reference in a new issue