Profectus-Demo/src/game/layers.tsx

188 lines
5.3 KiB
TypeScript
Raw Normal View History

2022-03-04 03:39:48 +00:00
import Modal from "components/Modal.vue";
import {
CoercableComponent,
jsx,
JSXFunction,
Replace,
setDefault,
StyleValue
2022-03-04 03:39:48 +00:00
} from "features/feature";
import { Link } from "features/links";
import Decimal from "util/bignum";
import {
2022-01-14 04:25:47 +00:00
Computable,
GetComputableType,
GetComputableTypeWithDefault,
processComputable,
ProcessedComputable
2022-03-04 03:39:48 +00:00
} from "util/computed";
import { createLazyProxy } from "util/proxies";
2022-01-14 04:25:47 +00:00
import { createNanoEvents, Emitter } from "nanoevents";
import { ref, unref } from "vue";
2022-01-25 04:23:30 +00:00
import { globalBus } from "./events";
import { persistent, PersistentRef } from "./persistence";
2022-01-14 04:25:47 +00:00
import player from "./player";
2022-01-25 04:23:30 +00:00
export interface LayerEvents {
// Generation
preUpdate: (diff: Decimal) => void;
// Actions (e.g. automation)
update: (diff: Decimal) => void;
// Effects (e.g. milestones)
postUpdate: (diff: Decimal) => void;
}
export const layers: Record<string, Readonly<GenericLayer> | undefined> = {};
window.layers = layers;
declare module "@vue/runtime-dom" {
interface CSSProperties {
"--layer-color"?: string;
}
}
2022-01-14 04:25:47 +00:00
export interface Position {
x: number;
y: number;
}
2022-01-14 04:25:47 +00:00
export interface LayerOptions {
id: string;
color?: Computable<string>;
display: Computable<CoercableComponent>;
classes?: Computable<Record<string, boolean>>;
style?: Computable<StyleValue>;
name?: Computable<string>;
minimizable?: Computable<boolean>;
forceHideGoBack?: Computable<boolean>;
minWidth?: Computable<number>;
links?: Computable<Link[]>;
}
2022-01-14 04:25:47 +00:00
export interface BaseLayer {
minimized: PersistentRef<boolean>;
emitter: Emitter<LayerEvents>;
on: OmitThisParameter<Emitter<LayerEvents>["on"]>;
emit: <K extends keyof LayerEvents>(event: K, ...args: Parameters<LayerEvents[K]>) => void;
}
2022-01-14 04:25:47 +00:00
export type Layer<T extends LayerOptions> = Replace<
T & BaseLayer,
{
color: GetComputableType<T["color"]>;
display: GetComputableType<T["display"]>;
classes: GetComputableType<T["classes"]>;
style: GetComputableType<T["style"]>;
name: GetComputableTypeWithDefault<T["name"], T["id"]>;
minWidth: GetComputableTypeWithDefault<T["minWidth"], 600>;
minimizable: GetComputableTypeWithDefault<T["minimizable"], true>;
forceHideGoBack: GetComputableType<T["forceHideGoBack"]>;
links: GetComputableType<T["links"]>;
}
>;
export type GenericLayer = Replace<
Layer<LayerOptions>,
{
name: ProcessedComputable<string>;
minWidth: ProcessedComputable<number>;
minimizable: ProcessedComputable<boolean>;
}
>;
export function createLayer<T extends LayerOptions>(optionsFunc: () => T): Layer<T> {
return createLazyProxy(() => {
const layer = optionsFunc() as T & Partial<BaseLayer>;
const emitter = (layer.emitter = createNanoEvents<LayerEvents>());
layer.on = emitter.on.bind(emitter);
layer.emit = emitter.emit.bind(emitter);
2022-01-28 04:47:26 +00:00
layer.minimized = persistent(false);
2022-01-28 04:47:26 +00:00
processComputable(layer as T, "color");
processComputable(layer as T, "display");
processComputable(layer as T, "name");
setDefault(layer, "name", layer.id);
processComputable(layer as T, "minWidth");
setDefault(layer, "minWidth", 600);
processComputable(layer as T, "minimizable");
setDefault(layer, "minimizable", true);
processComputable(layer as T, "links");
2022-01-28 04:47:26 +00:00
return layer as unknown as Layer<T>;
2022-01-28 04:47:26 +00:00
});
2022-01-14 04:25:47 +00:00
}
2022-01-14 04:25:47 +00:00
export function addLayer(
layer: GenericLayer,
player: { layers?: Record<string, Record<string, unknown>> }
): void {
2022-01-25 04:23:30 +00:00
console.info("Adding layer", layer.id);
if (layers[layer.id]) {
2022-01-14 04:25:47 +00:00
console.error(
"Attempted to add layer with same ID as existing layer",
2021-08-18 05:18:23 +00:00
layer.id,
2022-01-14 04:25:47 +00:00
layers[layer.id]
);
2022-01-14 04:25:47 +00:00
return;
}
2022-01-14 04:25:47 +00:00
setDefault(player, "layers", {});
if (player.layers[layer.id] == null) {
player.layers[layer.id] = {};
}
2022-01-14 04:25:47 +00:00
layers[layer.id] = layer;
2022-01-14 04:25:47 +00:00
globalBus.emit("addLayer", layer, player.layers[layer.id]);
}
2022-01-28 04:47:26 +00:00
export function getLayer<T extends GenericLayer>(layerID: string): T {
return layers[layerID] as T;
}
2022-01-14 04:25:47 +00:00
export function removeLayer(layer: GenericLayer): void {
2022-01-25 04:23:30 +00:00
console.info("Removing layer", layer.id);
2022-01-14 04:25:47 +00:00
globalBus.emit("removeLayer", layer);
2022-01-25 04:23:30 +00:00
layers[layer.id] = undefined;
}
2022-01-14 04:25:47 +00:00
export function reloadLayer(layer: GenericLayer): void {
removeLayer(layer);
// Re-create layer
addLayer(layer, player);
}
2022-01-25 04:23:30 +00:00
export function setupLayerModal(layer: GenericLayer): {
openModal: VoidFunction;
modal: JSXFunction;
} {
const showModal = ref(false);
return {
openModal: () => (showModal.value = true),
modal: jsx(() => (
<Modal
modelValue={showModal.value}
onUpdate:modelValue={value => (showModal.value = value)}
v-slots={{
header: () => <h2>{unref(layer.name)}</h2>,
body: unref(layer.display)
}}
/>
))
};
}
2022-01-25 04:23:30 +00:00
globalBus.on("update", function updateLayers(diff) {
Object.values(layers).forEach(layer => {
layer?.emit("preUpdate", diff);
});
Object.values(layers).forEach(layer => {
layer?.emit("update", diff);
});
Object.values(layers).forEach(layer => {
layer?.emit("postUpdate", diff);
});
});