3UFL673QX2JG7KHTAM7DFH4BBYVHDNMXBS2RW45G3JBNFZFLSRRQC
FEYZALRNZQFN7QWCJGGTHHAYUN2OSSWD7SITQMOZIGQFUMWRFFCQC
TQ57VE45BHV7MOZ6GKTYZEAMAOTXLPQ3ROCWJ2FUCITQWOYVMUIAC
DFFMZSJOCLTBA3IGRYS2ZIJNO2MC3VLBBU42QL5DFY2SMCNFXPIAC
ZHOSSPNKGFIKSFPDXCGLMSYMMX2J433VU2BUUWBKUH7TOLQUBSPQC
QUT2VGNOS2XSDLSYCSMILCKF343M56MRPRSUWN4GXWPGCK6APK2QC
HXHNGFB2VCXB6YXDND35HJI22GBJC3QTSUR2TK3M3LXGJHVNXVHAC
TQAGEMW7FKIQCPQOJN44XVUZJQFQYNGSYRVIYSXFYF36HU7YDMZQC
ZWX5PV44JLFYTQXGXNQG64CKTJJC6I2TVMNRII3EAEMUXMV5XNYQC
return Object.keys(this._values).map(key => this._values[key]);
return Object.values(this._values);
// return Object.keys(this._values).map(key => this._values[key]); // why grant???
}
*[Symbol.iterator]() {
// construct a new iterator. note that as usual
for (let key of Object.keys(this._values)) {
yield key;
}
export type UpdaterFnParam2<T, W> = T | ((prev: T, prevWhole: W) => T);
export type UpdaterFn2<T, W> = (arg: UpdaterFnParam2<T, W>) => void;
export type UpdaterGeneratorType2<T, W = T> = {
[k in keyof T]: ((T[k] extends { [kkt: string]: any } ? UpdaterGeneratorType2<T[k], W> : {}) & {
getUpdater: () => UpdaterFn2<T[k], W>,
set: UpdaterFn2<T[k], W>,
update: UpdaterFn2<T[k], W>,
})
} & {
getUpdater: () => UpdaterFn2<T, W>,
set: UpdaterFn2<T, W>,
update: UpdaterFn2<T, W>,
}
function isObject(o: any): o is { [x: string]: any } {
return (typeof o === "object");
}
export function updaterGenerator2Helper<T, W>(_dataObject: T, dataUpdater: UpdaterFn2<T, W>, wholeDataObject: W): UpdaterGeneratorType2<T, W> {
const updaters: UpdaterGeneratorType2<T, W> = {} as any;
updaters.getUpdater = () => dataUpdater;
updaters.set = dataUpdater;
updaters.update = dataUpdater;
if (typeof _dataObject !== "object") return updaters;
else {
const dataObject: T = _dataObject;
const keys: (keyof T)[] = Object.keys(dataObject) as any;
keys.forEach((key: (keyof T)) => {
if (key === "set" || key === "getUpdater" || key === "update") {
throw Error(`Invalid key in updaterGenerator: ${key} conflicts with reserved keywords set, update, getUpdater.`);
}
function keyUpdater(newValueOrCallback: UpdaterFnParam2<T[typeof key], W>) {
if (typeof newValueOrCallback === "function") {
dataUpdater((oldData) => {
const newData = {
...oldData,
[key]: (newValueOrCallback as ((prev: T[typeof key], whole: W) => T[typeof key]))(oldData[key], wholeDataObject),
};
return newData;
});
} else {
dataUpdater((oldData) => ({ ...oldData, [key]: newValueOrCallback }));
}
}
updaters[key] = (updaterGenerator2Helper<T[typeof key], W>(dataObject[key], keyUpdater, wholeDataObject) as unknown as (typeof updaters)[typeof key]);
});
return updaters;
}
}
export function updaterGenerator2<T>(dataObject: T, dataUpdater: UpdaterFn<T>): UpdaterGeneratorType2<T> {
const dataUpdater2 = (arg: UpdaterFnParam2<T, T>) => {
if (typeof arg === 'function') {
dataUpdater((prev) => {
// if T is a function type already, typescript correctly notifies us that this will fail
return (arg as ((prev: T, prevWhole: T) => T))(prev, prev);
})
} else {
dataUpdater(arg);
}
};
return updaterGenerator2Helper<T, T>(dataObject, dataUpdater2, dataObject);
}
type UpdaterFnParam<T> = T | ((prev: T) => T);
// type UpdaterFnParam<T> = ((prev: T) => T);
export type UpdaterFn<T> = (arg: UpdaterFnParam<T>) => void;
export type UpdaterGeneratorType<T> = {
[k in keyof T]: T[k] extends { [kkt: string]: any }
? (UpdaterGeneratorType<T[k]> & {
getUpdater: () => UpdaterFn<T[k]>,
set: UpdaterFn<T[k]>,
update: UpdaterFn<T[k]>,
})
: {
getUpdater: () => UpdaterFn<T[k]>,
set: UpdaterFn<T[k]>,
update: UpdaterFn<T[k]>,
}
} & {
getUpdater: () => UpdaterFn<T>,
set: UpdaterFn<T>,
update: UpdaterFn<T>,
}
/**
* Convenience method for generating setState<FancyObject.sub.component>() from setState<FancyObject> callbacks.
* If used in react, recommended that this be memoized.
*
* @generic T should be a data-only object - nested objects are allowed but arrays, sets not supported
* @param dataObject ANY instance of T, used only for its keys. MUST have all keys present
* @param dataUpdater an updater function, which can be called as: dataUpdater(newT) or
* dataUpdater((oldT) => { return newTFromOldT(oldT) }) ; e.g. react setState() function.
* @return a deep object that has the same keys as T, except each key also has a getUpdater()/set/update member;
* the getUpdater() on a subobject of T acts similarly to the dataUpdater<T> but to the subobject rather than the whole object.
* e.g. :
* let gameStateUpdater = updaterGenerator(skeletonObject, setGameState);
* let setName = gameStateUpdater.player.name.getUpdater();
* gameStateUpdater.player.name.set(newName);
* gameStateUpdater.player.name.update(oldName => oldName + " ");
*
*/
export function updaterGenerator<T>(dataObject: T, dataUpdater: UpdaterFn<T>): UpdaterGeneratorType<T> {
const updaters: UpdaterGeneratorType<T> = {} as any;
updaters.getUpdater = () => dataUpdater;
updaters.set = dataUpdater;
updaters.update = dataUpdater;
if (typeof dataObject !== "object") return updaters;
const keys : (keyof T)[] = Object.keys(dataObject) as any as (keyof T)[];
keys.forEach((key: (keyof T)) => {
if (key === "set" || key === "getUpdater" || key === "update") {
throw Error(`Invalid key in updaterGenerator: ${key} conflicts with reserved keywords set, update, getUpdater.`);
}
function keyUpdater(newValueOrCallback: UpdaterFnParam<T[typeof key]>) {
if (typeof newValueOrCallback === "function") {
dataUpdater((oldData) => {
const newData = {
...oldData,
[key]: (newValueOrCallback as Function)(oldData[key]),
};
return newData;
});
} else {
dataUpdater((oldData) => ({ ...oldData, [key]: newValueOrCallback }));
}
}
updaters[key] = updaterGenerator(dataObject[key], keyUpdater) as any;
});
return updaters;
}
import * as Pixi from "pixi.js";
import { HashSet, KeyedHashMap } from "../../lib/util/data_structures/hash";
import { ChunkGen, ChunkGenConstants, ChunkRef, GameState, PointNodeRef } from "../../data/GameState";
import { PointNodeComponent } from "./PointNodeComponent";
import { UpdaterGeneratorType2 } from "../../lib/util/updaterGenerator";
import { Vector2 } from "../../lib/util/geometry/vector2";
import { PixiPointFrom } from "../../lib/pixi/pixify";
export class RenderedChunkConstants {
public static SPACING_PX: number = 24;
public static CHUNK_SPACING_PX: number = (ChunkGenConstants.CHUNK_DIM + 0.5) * RenderedChunkConstants.SPACING_PX;
public static NODE_SIZE_PX: number = 14;
public static NODE_HITAREA_PX: number = 18;
public static NODE_ROUNDED_PX: number = 4;
}
type Props = {
delta: number,
args: {
pointNodeTexture: Pixi.Texture,
selfChunkRef: ChunkRef,
},
updaters: UpdaterGeneratorType2<GameState>,
position: Vector2,
chunkGen: ChunkGen,
selectedPointNode: PointNodeRef | undefined,
allocatedPointNodeSubset: HashSet<PointNodeRef>,
}
export class ChunkComponent {
public container: Pixi.Container;
staleProps!: Props;
state!: {};
public children: KeyedHashMap<PointNodeRef, PointNodeComponent>;
constructor(props: Props) {
this.staleProps = props;
this.state = {};
this.container = new Pixi.Container();
this.children = new KeyedHashMap();
for (let [pointNodeCoord, pointNodeGen] of props.chunkGen.pointNodes.entries()) {
const pointNodeRef = new PointNodeRef({
z: props.args.selfChunkRef.z,
chunkCoord: props.args.selfChunkRef.chunkCoord,
pointNodeCoord: pointNodeCoord,
pointNodeId: pointNodeGen.id
})
let childProps = {
delta: props.delta,
args: {
pointNodeTexture: props.args.pointNodeTexture,
selfPointNodeRef: pointNodeRef,
},
updaters: props.updaters,
position: pointNodeRef.pointNodeCoord.multiply(RenderedChunkConstants.SPACING_PX),
isSelected: props.selectedPointNode?.pointNodeId == pointNodeRef.pointNodeId,
isAllocated: props.allocatedPointNodeSubset.contains(pointNodeRef),
};
let childComponent = new PointNodeComponent(childProps);
this.children.put(pointNodeRef, childComponent);
this.container.addChild(childComponent.container);
}
}
renderSelf(props: Props) {
this.container.position = PixiPointFrom(props.position);
}
updateSelf(props: Props) { }
public update(props: Props) {
this.updateSelf(props);
for (let [pointNodeCoord, pointNodeGen] of props.chunkGen.pointNodes.entries()) {
const pointNodeRef = new PointNodeRef({
z: props.args.selfChunkRef.z,
chunkCoord: props.args.selfChunkRef.chunkCoord,
pointNodeCoord: pointNodeCoord,
pointNodeId: pointNodeGen.id
})
let childProps = {
delta: props.delta,
args: {
pointNodeTexture: props.args.pointNodeTexture,
selfPointNodeRef: pointNodeRef,
},
updaters: props.updaters,
position: pointNodeRef.pointNodeCoord.multiply(RenderedChunkConstants.SPACING_PX),
isSelected: props.selectedPointNode?.pointNodeId == pointNodeRef.pointNodeId,
isAllocated: props.allocatedPointNodeSubset.contains(pointNodeRef),
};
let childComponent = this.children.get(pointNodeRef);
childComponent.update(childProps);
}
this.renderSelf(props);
}
}
import * as Pixi from "pixi.js";
import { RenderedChunkConstants } from "./ChunkComponent";
import { UpdaterGeneratorType2 } from "../../lib/util/updaterGenerator";
import { GameState, PointNodeRef } from "../../data/GameState";
import { Vector2 } from "../../lib/util/geometry/vector2";
import { PixiPointFrom } from "../../lib/pixi/pixify";
/**
* Usage:
* class RenderedChunk {
* constructor(stateUpdaterQueue) {
* this.nodes = 0...10.map(i => new RenderedPointNode({texture, new NodeRef(i), stateUpdaterQueue}))
* // this.nodes[0].render({ some, stuff })
* this.nodes[0] should listen to gameState.playerUI.selectedPointNode and allocatedPointNodes, and
* updating gameState.playerUI.selectedPointNode or gameState.playerSave.allocatedPointNodes or their
* parents should trigger queueing of the rerender
* or rather, rerendering
* }
* }
*/
type Props = {
delta: number,
args: {
pointNodeTexture: Pixi.Texture,
selfPointNodeRef: PointNodeRef,
},
updaters: UpdaterGeneratorType2<GameState>,
position: Vector2,
isSelected: boolean,
isAllocated: boolean
};
export class PointNodeComponent {
public container: Pixi.Sprite;
staleProps!: Props;
state!: {};
constructor(props: Props) {
this.staleProps = props;
this.state = {};
this.container = new Pixi.Sprite(props.args.pointNodeTexture);
this.container.anchor.x = 0.5;
this.container.anchor.y = 0.5;
// this.container.x = node.x * RenderedChunkConstants.SPACING_PX;
// this.container.y = node.y * RenderedChunkConstants.SPACING_PX;
this.container.interactive = true;
this.container.buttonMode = true;
this.container.hitArea = new Pixi.Rectangle(
- RenderedChunkConstants.NODE_HITAREA_PX / 2,
- RenderedChunkConstants.NODE_HITAREA_PX / 2,
RenderedChunkConstants.NODE_HITAREA_PX,
RenderedChunkConstants.NODE_HITAREA_PX,
);
this.renderSelf(props);
this.didMount();
}
renderSelf(props: Props) {
this.container.position = PixiPointFrom(props.position);
if (props.isAllocated) {
this.container.tint = 0x00AAFF;
} else {
if (props.isSelected) {
this.container.tint = 0xBBBBBB;
} else {
this.container.tint = 0xFFFFFF;
}
}
}
updateSelf(props: Props) { }
public update(props: Props) {
this.updateSelf(props);
this.renderSelf(props);
}
didMount() {
const { args, updaters } = this.staleProps; // we assume this will never change
this.container.addListener("pointerdown", () => {
updaters.playerSave.allocatedPointNodeSet.update((prev, prevGameState) => {
// if we were already selected, allocate us
if (prevGameState.playerUI.selectedPointNode?.pointNodeId == args.selfPointNodeRef.pointNodeId) {
prev.put(args.selfPointNodeRef);
return prev.clone();
}
return prev;
})
updaters.playerSave.allocatedPointNodeHistory.update((prev, prevGameState) => {
// if we were already selected, allocate us and add to the history (maybe this should be managed elsewhere??)
if (prevGameState.playerUI.selectedPointNode?.pointNodeId == args.selfPointNodeRef.pointNodeId) {
prev.push(args.selfPointNodeRef);
return [...prev];
}
return prev;
})
updaters.playerUI.selectedPointNode.update((prev, gameState) => {
return args.selfPointNodeRef;
})
});
}
}
import * as Pixi from "pixi.js";
import { ChunkRef, GameState, PointNodeRef, ZLevelGen } from "../../data/GameState";
import { PixiPointFrom } from "../../lib/pixi/pixify";
import { HashSet, KeyedHashMap } from "../../lib/util/data_structures/hash";
import { Vector2 } from "../../lib/util/geometry/vector2";
import { UpdaterGeneratorType2 } from "../../lib/util/updaterGenerator";
import { RenderedChunkConstants } from "../RenderedChunk";
import { ChunkComponent } from "./ChunkComponent";
type Props = {
delta: number,
args: {
pointNodeTexture: Pixi.Texture,
z: number,
},
updaters: UpdaterGeneratorType2<GameState>,
position: Vector2,
zLevelGen: ZLevelGen,
selectedPointNode: PointNodeRef | undefined,
allocatedPointNodeSubset: HashSet<PointNodeRef>,
}
export class RenderedZLevel {
public container: Pixi.Container;
staleProps!: Props;
state!: {};
public children: KeyedHashMap<ChunkRef, ChunkComponent> = new KeyedHashMap();
constructor(props: Props) {
this.staleProps = props;
this.state = {};
this.container = new Pixi.Container();
for (let [chunkCoord, chunkGen] of props.zLevelGen.chunks.entries()) {
const chunkRef = new ChunkRef({
z: props.args.z,
chunkCoord,
chunkId: chunkGen.id,
});
let allocatedPointNodeSubset = new HashSet(
props.allocatedPointNodeSubset.values()
.filter((pointNodeRef) => {
return pointNodeRef.chunkCoord.x === chunkRef.chunkCoord.x &&
pointNodeRef.chunkCoord.y === chunkRef.chunkCoord.y;
})
);
let childProps = {
delta: props.delta,
args: {
pointNodeTexture: props.args.pointNodeTexture,
selfChunkRef: chunkRef,
},
updaters: props.updaters,
position: chunkRef.chunkCoord.multiply(RenderedChunkConstants.CHUNK_SPACING_PX),
chunkGen: chunkGen,
selectedPointNode: props.selectedPointNode,
allocatedPointNodeSubset,
}
const childComponent = new ChunkComponent(childProps);
this.children.put(chunkRef, childComponent);
this.container.addChild(childComponent.container);
}
}
renderSelf(props: Props) {
this.container.position = PixiPointFrom(props.position);
}
updateSelf(props: Props) { }
public update(props: Props) {
this.updateSelf(props);
for (let [chunkCoord, chunkGen] of props.zLevelGen.chunks.entries()) {
const chunkRef = new ChunkRef({
z: props.args.z,
chunkCoord,
chunkId: chunkGen.id,
});
let allocatedPointNodeSubset = new HashSet(
props.allocatedPointNodeSubset.values()
.filter((pointNodeRef) => {
return pointNodeRef.chunkCoord.x === chunkRef.chunkCoord.x &&
pointNodeRef.chunkCoord.y === chunkRef.chunkCoord.y;
})
);
let childProps = {
delta: props.delta,
args: {
pointNodeTexture: props.args.pointNodeTexture,
selfChunkRef: chunkRef,
},
updaters: props.updaters,
position: chunkRef.chunkCoord.multiply(RenderedChunkConstants.CHUNK_SPACING_PX),
chunkGen: chunkGen,
selectedPointNode: props.selectedPointNode,
allocatedPointNodeSubset,
}
this.children.get(chunkRef).update(childProps);
}
this.renderSelf(props);
}
}