@block_factory/lib 0.0.5 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_module/BlockFactory.ts +10 -20
- package/_module/util/Command.ts +1 -9
- package/_module/util/Forms/Form.ts +5 -31
- package/_module/util/Forms/FormAction.ts +2 -1
- package/_module/util/Forms/FormMessage.ts +2 -1
- package/_module/util/Forms/FormModal.ts +9 -10
- package/_module/util/Forms/FormRegistry.ts +20 -23
- package/_module/util/Forms/IForm.ts +31 -0
- package/_module/util/Globals.ts +26 -0
- package/_module/util/ISystem.ts +58 -0
- package/_module/util/IVector.ts +420 -0
- package/_module/util/Math.ts +2 -0
- package/_module/util/Navigation.ts +130 -0
- package/_module/util/Signal.ts +71 -7
- package/_module/util/TempLeaker.ts +137 -0
- package/_module/util/Wrapper/IEntity.ts +93 -25
- package/_module/util/Wrapper/IItemStack.ts +63 -0
- package/_module/util/Wrapper/IPlayer.ts +73 -29
- package/_module/util/Wrapper/_Common.ts +130 -0
- package/_module/util/Wrapper/{Container.ts → _Container.ts} +5 -5
- package/index.js +3911 -521
- package/package.json +10 -4
- package/typedoc.json +6 -0
- package/_module/Framework/EntityTasks.ts +0 -203
- package/_module/Framework/Threads.ts +0 -72
- package/_module/Framework/_INIT.ts +0 -39
- package/_module/Types.ts +0 -10
- package/_module/util/System.ts +0 -21
- package/_module/util/Vector.ts +0 -388
- package/_types/_module/BlockFactory.d.ts +0 -19
- package/_types/_module/BlockFactory.d.ts.map +0 -1
- package/_types/_module/Framework/EntityTasks.d.ts +0 -40
- package/_types/_module/Framework/EntityTasks.d.ts.map +0 -1
- package/_types/_module/Framework/Threads.d.ts +0 -22
- package/_types/_module/Framework/Threads.d.ts.map +0 -1
- package/_types/_module/Framework/_INIT.d.ts +0 -19
- package/_types/_module/Framework/_INIT.d.ts.map +0 -1
- package/_types/_module/Types.d.ts +0 -10
- package/_types/_module/Types.d.ts.map +0 -1
- package/_types/_module/util/Command.d.ts +0 -92
- package/_types/_module/util/Command.d.ts.map +0 -1
- package/_types/_module/util/Forms/Form.d.ts +0 -12
- package/_types/_module/util/Forms/Form.d.ts.map +0 -1
- package/_types/_module/util/Forms/FormAction.d.ts +0 -73
- package/_types/_module/util/Forms/FormAction.d.ts.map +0 -1
- package/_types/_module/util/Forms/FormMessage.d.ts +0 -24
- package/_types/_module/util/Forms/FormMessage.d.ts.map +0 -1
- package/_types/_module/util/Forms/FormModal.d.ts +0 -66
- package/_types/_module/util/Forms/FormModal.d.ts.map +0 -1
- package/_types/_module/util/Forms/FormRegistry.d.ts +0 -12
- package/_types/_module/util/Forms/FormRegistry.d.ts.map +0 -1
- package/_types/_module/util/Math.d.ts +0 -20
- package/_types/_module/util/Math.d.ts.map +0 -1
- package/_types/_module/util/RawText.d.ts +0 -60
- package/_types/_module/util/RawText.d.ts.map +0 -1
- package/_types/_module/util/Signal.d.ts +0 -11
- package/_types/_module/util/Signal.d.ts.map +0 -1
- package/_types/_module/util/System.d.ts +0 -4
- package/_types/_module/util/System.d.ts.map +0 -1
- package/_types/_module/util/Vector.d.ts +0 -212
- package/_types/_module/util/Vector.d.ts.map +0 -1
- package/_types/_module/util/Wrapper/Container.d.ts +0 -9
- package/_types/_module/util/Wrapper/Container.d.ts.map +0 -1
- package/_types/_module/util/Wrapper/IEntity.d.ts +0 -12
- package/_types/_module/util/Wrapper/IEntity.d.ts.map +0 -1
- package/_types/_module/util/Wrapper/IPlayer.d.ts +0 -13
- package/_types/_module/util/Wrapper/IPlayer.d.ts.map +0 -1
- package/_types/index.d.ts +0 -3
- package/_types/index.d.ts.map +0 -1
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { Entity, Player, system } from "@minecraft/server";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Relay utility that abuses `playAnimation(... stopExpression ...)`
|
|
5
|
+
* to write values into Molang temps (`t.*`) on the client.
|
|
6
|
+
*
|
|
7
|
+
* - Always includes `t.source_name` and `t.__nonce__`
|
|
8
|
+
* - Values are serialized into a single `stopExpression` string
|
|
9
|
+
* - Intended for lightweight HUD / animation-controller syncing
|
|
10
|
+
*
|
|
11
|
+
* @external @example
|
|
12
|
+
*
|
|
13
|
+
* Client json relay example
|
|
14
|
+
*
|
|
15
|
+
* "scripts": {
|
|
16
|
+
* "initialize": [
|
|
17
|
+
* "v._nonce_swing = -1;",
|
|
18
|
+
* "v.swing = 0;"
|
|
19
|
+
* ],
|
|
20
|
+
* "pre_animation": [
|
|
21
|
+
* "q.is_name_any(t.source_name??'') && (t.__nonce__??-1) != v._nonce_swing ? { v._nonce_swing = t.__nonce__??-1; v.swing = (t.swing??0); };"
|
|
22
|
+
* ]
|
|
23
|
+
* }
|
|
24
|
+
*/
|
|
25
|
+
export class TempLeak {
|
|
26
|
+
private static readonly ANIM = "5bb1702b_252a_4133_8aff_8ed11f168e4e";
|
|
27
|
+
private static readonly CTRL = "2ca2eabb_c7d9_4777_8497_57444ea2cd50";
|
|
28
|
+
|
|
29
|
+
private static nonceBySource = new Map<string, number>();
|
|
30
|
+
|
|
31
|
+
private static nextNonce(source: Player | Entity): number {
|
|
32
|
+
const n = (this.nonceBySource.get(source.id) ?? 0) + 1;
|
|
33
|
+
this.nonceBySource.set(source.id, n);
|
|
34
|
+
return n;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private static esc(str: string): string {
|
|
38
|
+
return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private static fmt(value: unknown): string {
|
|
42
|
+
if (typeof value === "number") return Number.isFinite(value) ? String(value) : "0";
|
|
43
|
+
if (typeof value === "boolean") return value ? "1" : "0";
|
|
44
|
+
if (typeof value === "string") return `'${this.esc(value)}'`;
|
|
45
|
+
return `'${this.esc(String(value))}'`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private static isPlayer(e: Player | Entity): e is Player {
|
|
49
|
+
return e instanceof Player;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private static buildStopExp(
|
|
53
|
+
source: Player | Entity,
|
|
54
|
+
nonce: number,
|
|
55
|
+
fields: Record<string, unknown>
|
|
56
|
+
): string {
|
|
57
|
+
const sourceName = this.isPlayer(source) ? source.name : source.typeId;
|
|
58
|
+
|
|
59
|
+
let exp =
|
|
60
|
+
`t.source_name='${this.esc(sourceName)}';` +
|
|
61
|
+
`t.__nonce__=${nonce};`;
|
|
62
|
+
|
|
63
|
+
for (const [k, v] of Object.entries(fields)) {
|
|
64
|
+
const key = k.includes(".") ? k : `t.${k}`;
|
|
65
|
+
exp += `${key}=${this.fmt(v)};`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
exp += "return 0;";
|
|
69
|
+
return exp;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Sends a “no-op” update that only bumps `t.__nonce__` (and sets `t.source_name`).
|
|
74
|
+
*
|
|
75
|
+
*
|
|
76
|
+
* @param source Target source to receive the update.
|
|
77
|
+
*/
|
|
78
|
+
public static refresh(source: Player | Entity): void {
|
|
79
|
+
TempLeak.send(source, {});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Emits a single payload update to the client by playing a hidden animation
|
|
84
|
+
* whose `stopExpression` assigns values into `t.*`.
|
|
85
|
+
*
|
|
86
|
+
* Notes:
|
|
87
|
+
* - A per-source monotonic nonce is included as `t.__nonce__`.
|
|
88
|
+
* - Keys in `fields` are written as `t.<key>` unless you pass a dotted path
|
|
89
|
+
* (e.g. `"t.swing"` or `"t.ui.swing"`).
|
|
90
|
+
* - Numbers are clamped to finite values, booleans become `1/0`, and strings
|
|
91
|
+
* are escaped.
|
|
92
|
+
*
|
|
93
|
+
* @param source Target source to receive the update.
|
|
94
|
+
* @param fields Key/value pairs to assign into `t.*` on the client.
|
|
95
|
+
*/
|
|
96
|
+
public static send(source: Player | Entity, fields: Record<string, unknown>): void {
|
|
97
|
+
const nonce = this.nextNonce(source);
|
|
98
|
+
source.playAnimation(this.ANIM, {
|
|
99
|
+
controller: this.CTRL,
|
|
100
|
+
stopExpression: this.buildStopExp(source, nonce, fields),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Convenience helper for impulse leaks.
|
|
106
|
+
*
|
|
107
|
+
* This sends `setFields` immediately, then (optionally) sends `clearFields`
|
|
108
|
+
* after `clearDelayTicks`.
|
|
109
|
+
*
|
|
110
|
+
* If `clearFields` is provided, it is used as-is.
|
|
111
|
+
*
|
|
112
|
+
* Intended use:
|
|
113
|
+
* - Fire-and-forget booleans like `t.swing = 1` then clear back to `0`
|
|
114
|
+
* - Short pulses for UI triggers where the client watches for nonce changes
|
|
115
|
+
*
|
|
116
|
+
* @param source Target source to receive the update(s).
|
|
117
|
+
* @param setFields Fields to set immediately.
|
|
118
|
+
* @param clearFields Fields to send after the delay (optional).
|
|
119
|
+
* @param clearDelayTicks Tick delay before sending `clearFields` (default: 1).
|
|
120
|
+
*/
|
|
121
|
+
public static pulse(source: Player | Entity,setFields: Record<string, unknown>,clearFields?: Record<string, unknown>,clearDelayTicks: number = 1): void {
|
|
122
|
+
this.send(source, setFields);
|
|
123
|
+
if (!clearFields) return;
|
|
124
|
+
system.runTimeout(() => {
|
|
125
|
+
const autoClear: Record<string, unknown> = {};
|
|
126
|
+
if (!clearFields) {
|
|
127
|
+
for (const [k, v] of Object.entries(setFields)) {
|
|
128
|
+
if (typeof v === "string") autoClear[k] = "";
|
|
129
|
+
else autoClear[k] = 0;
|
|
130
|
+
}
|
|
131
|
+
clearFields = autoClear;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
this.send(source, clearFields);
|
|
135
|
+
}, clearDelayTicks);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -1,35 +1,103 @@
|
|
|
1
|
-
import { Entity,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { Entity, EntityRemoveBeforeEvent, PlayAnimationOptions, system, world } from "@minecraft/server";
|
|
2
|
+
import { Inventory } from "./_Container";
|
|
3
|
+
import { iSystem } from "../ISystem";
|
|
4
|
+
import { MinecraftDimensionTypes } from "@minecraft/vanilla-data";
|
|
5
|
+
import { Ctor, PropertyRecord } from "../Globals";
|
|
6
|
+
import { CLASS_I_UUID, WrapperCore } from "./_Common";
|
|
5
7
|
|
|
6
|
-
export type IEntity =
|
|
8
|
+
export type IEntity<T extends ICustomEntity> = T & Entity;
|
|
7
9
|
|
|
8
|
-
export class
|
|
9
|
-
|
|
10
|
+
export abstract class ICustomEntity {
|
|
11
|
+
protected readonly source: Entity;
|
|
12
|
+
private intervalId: number = -1;
|
|
13
|
+
public isNavigating: boolean = false;
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
public constructor(source: Entity) {
|
|
16
|
+
this.source = source;
|
|
17
|
+
}
|
|
18
|
+
public static wrap<T extends ICustomEntity>(source: Entity, Ctor: Ctor<T>): IEntity<T> {
|
|
19
|
+
return EntityWrapper.wrap(source, Ctor);
|
|
20
|
+
}
|
|
15
21
|
|
|
16
|
-
|
|
17
|
-
return new IEntityWrapper(source) as IEntity;
|
|
18
|
-
}
|
|
22
|
+
public get inventory(): Inventory | undefined { return WrapperCore.getInventory(this.source); }
|
|
19
23
|
|
|
20
|
-
|
|
24
|
+
public loadData(): void { WrapperCore.loadDynData(this as any); }
|
|
25
|
+
public saveData(): void { WrapperCore.saveDynData(this as any); }
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
) as EntityInventoryComponent;
|
|
27
|
+
public async playAnimationAsync(id: string, length: number, options?: PlayAnimationOptions): Promise<void> {
|
|
28
|
+
await WrapperCore.playAnimationAsync(this.source, id, length, options);
|
|
29
|
+
}
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
public startInterval(tickInterval?: number): number {
|
|
32
|
+
//TODO
|
|
33
|
+
//add simulation distance range to pause intervals
|
|
34
|
+
this.intervalId = system.runInterval(() => {
|
|
35
|
+
if (!this.source.isValid) { system.clearRun(this.intervalId); return; }
|
|
36
|
+
this.interval?.();
|
|
37
|
+
}, tickInterval);
|
|
38
|
+
return this.intervalId;
|
|
39
|
+
}
|
|
29
40
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
41
|
+
public data?(): PropertyRecord;
|
|
42
|
+
public onReady?(): void;
|
|
43
|
+
public onRemove?(): void;
|
|
44
|
+
public interval?(): void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const cache = new Map<string, Map<Function, any>>();
|
|
48
|
+
|
|
49
|
+
class EntityWrapper {
|
|
50
|
+
public static wrap<T extends ICustomEntity>(source: Entity, Ctor: Ctor<T>): IEntity<T> {
|
|
51
|
+
if (iSystem.packId === "undefined") throw Error(`${this.name} - System.PackId is undefined.`);
|
|
52
|
+
return WrapperCore.wrap<Entity, IEntity<T>>(cache, source, Ctor);
|
|
53
|
+
}
|
|
34
54
|
|
|
55
|
+
public static repopulateEntity(entity: Entity): void {
|
|
56
|
+
if (!entity.typeId.startsWith(iSystem.packId)) return;
|
|
57
|
+
WrapperCore.repopulate(cache, entity, iSystem.iClassRegistry, ICustomEntity.wrap);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public static getAll<T extends ICustomEntity>(Ctor: Ctor<T>): IterableIterator<IEntity<T>> {
|
|
61
|
+
return WrapperCore.iterateByCtor<IEntity<T>>(cache, Ctor);
|
|
62
|
+
}
|
|
35
63
|
}
|
|
64
|
+
|
|
65
|
+
world.beforeEvents.entityRemove.subscribe((event) => {
|
|
66
|
+
const e: Entity = event.removedEntity;
|
|
67
|
+
const classId = e.getDynamicProperty(CLASS_I_UUID) as string | undefined;
|
|
68
|
+
system.run(() => {
|
|
69
|
+
if (!classId) return;
|
|
70
|
+
const Ctor = iSystem.iClassRegistry.get(classId);
|
|
71
|
+
if (!Ctor) return;
|
|
72
|
+
const wrapped = ICustomEntity.wrap(e, Ctor);
|
|
73
|
+
wrapped.onRemove?.();
|
|
74
|
+
})
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
//Delete from cache on remove
|
|
78
|
+
world.afterEvents.entityRemove.subscribe(({ removedEntityId, typeId }) => {
|
|
79
|
+
if (!typeId.startsWith(iSystem.packId) || !cache.has(removedEntityId)) return;
|
|
80
|
+
cache.delete(removedEntityId);
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
//Save entity data on shutdown or reset
|
|
84
|
+
system.beforeEvents.shutdown.subscribe(() => {
|
|
85
|
+
for (const ent of WrapperCore.iterateAll<IEntity<any>>(cache)) {
|
|
86
|
+
ent.saveData();
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
//Repopulate Entity data on load
|
|
91
|
+
world.afterEvents.entityLoad.subscribe(({ entity }) => {
|
|
92
|
+
EntityWrapper.repopulateEntity(entity);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
//Repopulate All Entity data on reload
|
|
96
|
+
world.afterEvents.worldLoad.subscribe(() => {
|
|
97
|
+
//TODO
|
|
98
|
+
//Replace dimension check with stored id array to improve init cycle speed
|
|
99
|
+
for (const dimensionId of Object.values(MinecraftDimensionTypes)) {
|
|
100
|
+
const entities = world.getDimension(dimensionId).getEntities();
|
|
101
|
+
entities.forEach((entity: Entity) => EntityWrapper.repopulateEntity(entity));
|
|
102
|
+
}
|
|
103
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { ItemStack, system } from "@minecraft/server";
|
|
2
|
+
import { iSystem } from "../ISystem";
|
|
3
|
+
import { Ctor, PropertyRecord } from "../Globals";
|
|
4
|
+
import { WrapperCore, CLASS_I_UUID } from "./_Common";
|
|
5
|
+
|
|
6
|
+
type DynPropsRW = { getDynamicProperty(k: string): any; setDynamicProperty(k: string, v: any): void };
|
|
7
|
+
type DynPropsW = { setDynamicProperty(k: string, v: any): void };
|
|
8
|
+
type WrapperLike = { loadData(): void; onReady?: () => void };
|
|
9
|
+
|
|
10
|
+
export type IItemStack<T extends ICustomItemStack> = T & ItemStack;
|
|
11
|
+
|
|
12
|
+
export abstract class ICustomItemStack {
|
|
13
|
+
protected readonly source: ItemStack;
|
|
14
|
+
private intervalId: number = -1;
|
|
15
|
+
|
|
16
|
+
public constructor(source: ItemStack) {
|
|
17
|
+
this.source = source;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public loadData(): void { WrapperCore.loadDynData(this as any); }
|
|
21
|
+
public saveData(): void { WrapperCore.saveDynData(this as any); }
|
|
22
|
+
|
|
23
|
+
public static wrap<T extends ICustomItemStack>(source: ItemStack, Ctor: Ctor<T>): IItemStack<T> {
|
|
24
|
+
return ItemStackWrapper.wrap(source, Ctor);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public startInterval(tickInterval?: number): number {
|
|
28
|
+
this.intervalId = system.runInterval(() => {
|
|
29
|
+
if (!this.source) { system.clearRun(this.intervalId); return; }
|
|
30
|
+
this.interval?.();
|
|
31
|
+
}, tickInterval);
|
|
32
|
+
return this.intervalId;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public data?(): PropertyRecord;
|
|
36
|
+
public onReady?(): void;
|
|
37
|
+
public interval?(): void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
class ItemStackWrapper {
|
|
41
|
+
public static wrap<S extends DynPropsW, W extends WrapperLike>(source: S, Ctor: Ctor<any>): W {
|
|
42
|
+
const inst = new (Ctor as any)(source);
|
|
43
|
+
const proxied = iSystem.ProxyConstructor(inst, source) as W;
|
|
44
|
+
|
|
45
|
+
proxied.loadData();
|
|
46
|
+
proxied.onReady?.();
|
|
47
|
+
|
|
48
|
+
source.setDynamicProperty(CLASS_I_UUID, Ctor.name);
|
|
49
|
+
return proxied;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public static repopulateItemStack<S extends DynPropsRW, W extends WrapperLike>(source: S): W | undefined {
|
|
53
|
+
const classId = source.getDynamicProperty(CLASS_I_UUID) as string | undefined;
|
|
54
|
+
if (!classId) return undefined;
|
|
55
|
+
|
|
56
|
+
const Ctor = iSystem.iClassRegistry.get(classId);
|
|
57
|
+
if (!Ctor) return undefined;
|
|
58
|
+
|
|
59
|
+
return ItemStackWrapper.wrap<S, W>(source as any, Ctor);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
@@ -1,40 +1,84 @@
|
|
|
1
|
-
import { EntityInventoryComponent, PlayAnimationOptions,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { Player, EntityInventoryComponent, PlayAnimationOptions, system, world, TicksPerSecond } from "@minecraft/server";
|
|
2
|
+
import { IContainer, Inventory } from "./_Container";
|
|
3
|
+
import { iSystem } from "../ISystem";
|
|
4
|
+
import { Ctor, PropertyRecord } from "../Globals";
|
|
5
|
+
import { WrapperCore } from "./_Common";
|
|
5
6
|
|
|
6
|
-
export type IPlayer =
|
|
7
|
+
export type IPlayer<T extends ICustomPlayer> = T & Player;
|
|
7
8
|
|
|
8
|
-
export class
|
|
9
|
-
|
|
9
|
+
export abstract class ICustomPlayer {
|
|
10
|
+
protected readonly source: Player;
|
|
11
|
+
private intervalId: number = -1;
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
this.source = source;
|
|
13
|
-
return System.ProxyConstructor(this, source);
|
|
14
|
-
}
|
|
13
|
+
public constructor(source: Player) { this.source = source; }
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
15
|
+
public loadData(): void { WrapperCore.loadDynData(this as any); }
|
|
16
|
+
public saveData(): void { WrapperCore.saveDynData(this as any); }
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
public static wrap<T extends ICustomPlayer>(source: Player, Ctor: Ctor<T>): IPlayer<T> {
|
|
19
|
+
return PlayerWrapper.wrap(source, Ctor);
|
|
20
|
+
}
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
public get inventory(): Inventory | undefined {
|
|
23
|
+
const i = this.source.getComponent(EntityInventoryComponent.componentId) as EntityInventoryComponent | undefined;
|
|
24
|
+
if (!i?.container) return undefined;
|
|
25
|
+
return IContainer.wrap(i.container);
|
|
26
|
+
}
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
public async playAnimationAsync(id: string, length: number, options?: PlayAnimationOptions): Promise<void> {
|
|
29
|
+
this.source.playAnimation(id, options);
|
|
30
|
+
await system.waitTicks(Math.round(length * TicksPerSecond));
|
|
31
|
+
}
|
|
29
32
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
public startInterval(tickInterval?: number): number {
|
|
34
|
+
this.intervalId = system.runInterval(() => {
|
|
35
|
+
if (!this.source.isValid) { system.clearRun(this.intervalId); return; }
|
|
36
|
+
this.interval?.();
|
|
37
|
+
}, tickInterval);
|
|
38
|
+
return this.intervalId;
|
|
39
|
+
}
|
|
34
40
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
public data?(): PropertyRecord;
|
|
42
|
+
public onReady?(): void;
|
|
43
|
+
public interval?(): void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const cache = new Map<string, Map<Function, any>>();
|
|
47
|
+
|
|
48
|
+
class PlayerWrapper {
|
|
49
|
+
public static wrap<T extends ICustomPlayer>(source: Player, Ctor: Ctor<T>): IPlayer<T> {
|
|
50
|
+
return WrapperCore.wrap<Player, IPlayer<T>>(cache, source, Ctor);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public static repopulatePlayer(player: Player): void {
|
|
54
|
+
WrapperCore.repopulate(cache, player, iSystem.iClassRegistry, ICustomPlayer.wrap);
|
|
55
|
+
}
|
|
39
56
|
|
|
57
|
+
public static getAll<T extends ICustomPlayer>(Ctor: Ctor<T>): IterableIterator<IPlayer<T>> {
|
|
58
|
+
return WrapperCore.iterateByCtor<IPlayer<T>>(cache, Ctor);
|
|
59
|
+
}
|
|
40
60
|
}
|
|
61
|
+
|
|
62
|
+
//Delete from cache on remove
|
|
63
|
+
world.afterEvents.playerLeave.subscribe(({ playerId }) => {
|
|
64
|
+
if (!cache.has(playerId)) return;
|
|
65
|
+
cache.delete(playerId);
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
//Save Player data on shutdown or reset
|
|
69
|
+
system.beforeEvents.shutdown.subscribe(() => {
|
|
70
|
+
for (const p of WrapperCore.iterateAll<IPlayer<any>>(cache)) {
|
|
71
|
+
p.saveData();
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
//Repopulate Player data on load
|
|
76
|
+
world.afterEvents.playerSpawn.subscribe(({ player }) => {
|
|
77
|
+
PlayerWrapper.repopulatePlayer(player);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
//Repopulate All Player data on reload
|
|
81
|
+
world.afterEvents.worldLoad.subscribe(() => {
|
|
82
|
+
const players = world.getAllPlayers();
|
|
83
|
+
players.forEach((player: Player) => PlayerWrapper.repopulatePlayer(player));
|
|
84
|
+
});
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { Ctor, PropertyRecord, PropertyValue } from "../Globals";
|
|
2
|
+
import { Entity, EntityInventoryComponent, PlayAnimationOptions, Player, system, TicksPerSecond } from "@minecraft/server";
|
|
3
|
+
import { IContainer } from "./_Container";
|
|
4
|
+
import { Inventory } from "./_Container";
|
|
5
|
+
import { iSystem } from "../ISystem";
|
|
6
|
+
|
|
7
|
+
export const CLASS_I_UUID = "269160b9a432";
|
|
8
|
+
|
|
9
|
+
export type DynStore = {
|
|
10
|
+
/** schema builder */
|
|
11
|
+
data?(): PropertyRecord;
|
|
12
|
+
getDynamicProperty(key: string): PropertyValue | undefined;
|
|
13
|
+
setDynamicProperty(key: string, value: PropertyValue | undefined): void;
|
|
14
|
+
[key: string]: any;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Shared wrapper core for Entity + Player.
|
|
19
|
+
*/
|
|
20
|
+
export class WrapperCore {
|
|
21
|
+
public static wrap<S extends { id: string; setDynamicProperty(k: string, v: any): void }, W extends { loadData(): void; onReady?: () => void }>(
|
|
22
|
+
cache: Map<string, Map<Function, any>>,
|
|
23
|
+
source: S,
|
|
24
|
+
Ctor: Ctor<any>
|
|
25
|
+
): W {
|
|
26
|
+
let byCtor = cache.get(source.id);
|
|
27
|
+
if (!byCtor) { byCtor = new Map(); cache.set(source.id, byCtor); }
|
|
28
|
+
|
|
29
|
+
const existing = byCtor.get(Ctor) as W | undefined;
|
|
30
|
+
if (existing) return existing;
|
|
31
|
+
|
|
32
|
+
const inst = new (Ctor as any)(source);
|
|
33
|
+
const proxied = iSystem.ProxyConstructor(inst, source) as W;
|
|
34
|
+
|
|
35
|
+
proxied.loadData();
|
|
36
|
+
proxied.onReady?.();
|
|
37
|
+
|
|
38
|
+
byCtor.set(Ctor, proxied);
|
|
39
|
+
source.setDynamicProperty(CLASS_I_UUID, Ctor.name);
|
|
40
|
+
|
|
41
|
+
return proxied;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public static repopulate<S extends { id: string; getDynamicProperty(k: string): any, isValid: boolean }>(
|
|
45
|
+
cache: Map<string, Map<Function, any>>,
|
|
46
|
+
source: S,
|
|
47
|
+
registry: Map<string, Ctor<any>>,
|
|
48
|
+
wrapFn: (source: any, ctor: Ctor<any>) => any
|
|
49
|
+
): void {
|
|
50
|
+
if (cache.has(source.id) || !source.isValid) return;
|
|
51
|
+
|
|
52
|
+
const classId = source.getDynamicProperty(CLASS_I_UUID) as string | undefined;
|
|
53
|
+
if (!classId) return;
|
|
54
|
+
|
|
55
|
+
const Ctor = registry.get(classId);
|
|
56
|
+
if (!Ctor) return;
|
|
57
|
+
|
|
58
|
+
wrapFn(source as any, Ctor);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public static *iterateAll<W>(
|
|
62
|
+
cache: Map<string, Map<Function, any>>
|
|
63
|
+
): IterableIterator<W> {
|
|
64
|
+
for (const [, byCtor] of cache) {
|
|
65
|
+
for (const [, instance] of byCtor) {
|
|
66
|
+
yield instance as W;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public static *iterateByCtor<W>(
|
|
72
|
+
cache: Map<string, Map<Function, any>>,
|
|
73
|
+
Ctor: Function
|
|
74
|
+
): IterableIterator<W> {
|
|
75
|
+
for (const [, byCtor] of cache) {
|
|
76
|
+
const inst = byCtor.get(Ctor);
|
|
77
|
+
if (inst) yield inst as W;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public static getAllByCtor<W>(
|
|
82
|
+
cache: Map<string, Map<Function, any>>,
|
|
83
|
+
Ctor: Function
|
|
84
|
+
): W[] {
|
|
85
|
+
const out: W[] = [];
|
|
86
|
+
for (const [, byCtor] of cache) {
|
|
87
|
+
const inst = byCtor.get(Ctor);
|
|
88
|
+
if (inst) out.push(inst as W);
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public static getAll<W>(cache: Map<string, Map<Function, any>>): W[] {
|
|
94
|
+
const out: W[] = [];
|
|
95
|
+
for (const [, byCtor] of cache) {
|
|
96
|
+
for (const [, inst] of byCtor) out.push(inst as W);
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public static loadDynData(target: DynStore): void {
|
|
102
|
+
if (!target.data) return;
|
|
103
|
+
|
|
104
|
+
const schema = target.data() as Record<string, unknown>;
|
|
105
|
+
for (const key of Object.keys(schema)) {
|
|
106
|
+
const v = target.getDynamicProperty(key);
|
|
107
|
+
if (v !== undefined) (target as any)[key] = v;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public static saveDynData(target: DynStore): void {
|
|
112
|
+
if (!target.isValid || !target.data) return;
|
|
113
|
+
|
|
114
|
+
const saveData = target.data() as Record<string, unknown>;
|
|
115
|
+
for (const [key, value] of Object.entries(saveData)) {
|
|
116
|
+
target.setDynamicProperty(key, value as PropertyValue);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
public static getInventory(source: Entity | Player): Inventory | undefined {
|
|
121
|
+
const i = source.getComponent(EntityInventoryComponent.componentId) as EntityInventoryComponent | undefined;
|
|
122
|
+
if (!i?.container) return undefined;
|
|
123
|
+
return IContainer.wrap(i.container);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
public static async playAnimationAsync(source: Entity | Player, id: string, length: number, options?: PlayAnimationOptions): Promise<void> {
|
|
127
|
+
source.playAnimation(id, options);
|
|
128
|
+
await system.waitTicks(Math.round(length * TicksPerSecond));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { Container, ItemStack} from "@minecraft/server";
|
|
2
|
-
import {
|
|
2
|
+
import { iSystem } from "../ISystem";
|
|
3
3
|
|
|
4
|
-
export type Inventory =
|
|
4
|
+
export type Inventory = IContainer & Container;
|
|
5
5
|
|
|
6
|
-
export class
|
|
6
|
+
export class IContainer {
|
|
7
7
|
public readonly source: Container;
|
|
8
8
|
|
|
9
9
|
private constructor(source: Container) {
|
|
10
10
|
this.source = source;
|
|
11
|
-
return
|
|
11
|
+
return iSystem.ProxyConstructor(this, source);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
public static wrap(source: Container): Inventory {
|
|
15
|
-
return new
|
|
15
|
+
return new IContainer(source) as Inventory;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
//======================== Interal ========================
|