@block_factory/lib 0.0.4 → 0.0.6
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 +4 -1
- package/_module/DataTypes.ts +10 -0
- package/_module/Framework/EntityTasks.ts +164 -0
- package/_module/Framework/ItemTasks.ts +157 -0
- package/_module/Framework/PlayerTasks.ts +125 -0
- package/_module/Framework/Threads.ts +72 -0
- package/_module/util/Signal.ts +73 -7
- package/_module/util/Wrapper/IEntity.ts +6 -5
- package/_module/util/Wrapper/IPlayer.ts +10 -4
- package/_types/_module/BlockFactory.d.ts +4 -1
- package/_types/_module/BlockFactory.d.ts.map +1 -1
- package/_types/_module/DataTypes.d.ts +10 -0
- package/_types/_module/DataTypes.d.ts.map +1 -0
- package/_types/_module/Framework/EntityTasks.d.ts +37 -0
- package/_types/_module/Framework/EntityTasks.d.ts.map +1 -0
- package/_types/_module/Framework/ItemTasks.d.ts +59 -0
- package/_types/_module/Framework/ItemTasks.d.ts.map +1 -0
- package/_types/_module/Framework/PlayerTasks.d.ts +28 -0
- package/_types/_module/Framework/PlayerTasks.d.ts.map +1 -0
- package/_types/_module/Framework/Threads.d.ts +22 -0
- package/_types/_module/Framework/Threads.d.ts.map +1 -0
- package/_types/_module/Types.d.ts +10 -0
- package/_types/_module/Types.d.ts.map +1 -0
- package/_types/_module/util/Signal.d.ts +63 -4
- package/_types/_module/util/Signal.d.ts.map +1 -1
- package/_types/_module/util/Wrapper/IEntity.d.ts +4 -4
- package/_types/_module/util/Wrapper/IEntity.d.ts.map +1 -1
- package/_types/_module/util/Wrapper/IPlayer.d.ts +4 -3
- package/_types/_module/util/Wrapper/IPlayer.d.ts.map +1 -1
- package/index.js +506 -60
- package/package.json +36 -34
- package/typedoc.json +6 -0
- package/_module/sys/Threads.ts +0 -43
- package/_types/_module/sys/Threads.d.ts +0 -16
- package/_types/_module/sys/Threads.d.ts.map +0 -1
package/_module/BlockFactory.ts
CHANGED
|
@@ -15,4 +15,7 @@ export { Inventory, ContainerWrapper } from "./util/Wrapper/Container";
|
|
|
15
15
|
export { type IEntity, IEntityWrapper } from "./util/Wrapper/IEntity";
|
|
16
16
|
export { type IPlayer, IPlayerWrapper } from "./util/Wrapper/IPlayer";
|
|
17
17
|
|
|
18
|
-
export {
|
|
18
|
+
export { Thread } from "./Framework/Threads";
|
|
19
|
+
export { PlayerHandler } from "./Framework/PlayerTasks";
|
|
20
|
+
export { EntityHandler, type EntityEmissionEvent } from "./Framework/EntityTasks";
|
|
21
|
+
export { ItemHandler, type ItemRegistration, type OnItemHeldEvent, type OnItemUnheldEvent, type WhileHoldingItemEvent } from "./Framework/ItemTasks";
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { Entity, system, ScriptEventCommandMessageAfterEvent, ScriptEventCommandMessageAfterEventSignal, world, WorldLoadAfterEventSignal, WorldLoadAfterEvent, EntitySpawnAfterEventSignal, EntitySpawnAfterEvent, EntityRemoveBeforeEvent, EntityRemoveBeforeEventSignal } from "@minecraft/server";
|
|
2
|
+
|
|
3
|
+
import { Signal } from "../util/Signal";
|
|
4
|
+
import { IEntityWrapper, IEntity } from "../util/Wrapper/IEntity";
|
|
5
|
+
|
|
6
|
+
export type EntityEmissionEvent = {
|
|
7
|
+
iEntity: IEntity;
|
|
8
|
+
parms: any;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
class SingletonEntityHandler {
|
|
12
|
+
private PACK_ID: string | undefined;
|
|
13
|
+
|
|
14
|
+
public readonly onEntityEmission = new Signal<EntityEmissionEvent>();
|
|
15
|
+
private readonly GLOBAL_MEMORY_ID = "GLB_MEM.ENTITY";
|
|
16
|
+
|
|
17
|
+
private readonly scriptEventSignal: ScriptEventCommandMessageAfterEventSignal = system.afterEvents.scriptEventReceive;
|
|
18
|
+
private readonly loadEventSignal: WorldLoadAfterEventSignal = world.afterEvents.worldLoad;
|
|
19
|
+
private readonly spawnEventSignal: EntitySpawnAfterEventSignal = world.afterEvents.entitySpawn;
|
|
20
|
+
private readonly removeBeforeEventSignal: EntityRemoveBeforeEventSignal = world.beforeEvents.entityRemove;
|
|
21
|
+
|
|
22
|
+
private readonly EM_INDEX = new Map<string, Entity>();
|
|
23
|
+
private readonly EM_KEYS: string[] = [];
|
|
24
|
+
private readonly SEARCH_TYPES = new Set<string>();
|
|
25
|
+
|
|
26
|
+
private _started: boolean = false;
|
|
27
|
+
private _wired: boolean = false;
|
|
28
|
+
|
|
29
|
+
public registerEntity(typeId: string | string[]): void {
|
|
30
|
+
if (Array.isArray(typeId)) typeId.forEach((t) => this.SEARCH_TYPES.add(t));
|
|
31
|
+
else this.SEARCH_TYPES.add(typeId);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public start(packId: string): void {
|
|
35
|
+
if (this._started) throw new Error("BFLIB: EntityHandler already started;");
|
|
36
|
+
this._started = true;
|
|
37
|
+
this.PACK_ID = packId;
|
|
38
|
+
this.loadEventSignal.subscribe(this.onWorldLoad);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public stop(): void {
|
|
42
|
+
if (!this._started) return;
|
|
43
|
+
|
|
44
|
+
this.loadEventSignal.unsubscribe(this.onWorldLoad);
|
|
45
|
+
if (this._wired) {
|
|
46
|
+
this.scriptEventSignal.unsubscribe(this.processScriptEvents);
|
|
47
|
+
this.spawnEventSignal.unsubscribe(this.onEntitySpawned);
|
|
48
|
+
this.removeBeforeEventSignal.unsubscribe(this.onEntityRemovedBefore);
|
|
49
|
+
this._wired = false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this._started = false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* ------------------------------------------------------------------------ */
|
|
56
|
+
/* Event Wiring */
|
|
57
|
+
/* ------------------------------------------------------------------------ */
|
|
58
|
+
|
|
59
|
+
private onWorldLoad = (): void => {
|
|
60
|
+
this.scriptEventSignal.subscribe(this.processScriptEvents);
|
|
61
|
+
|
|
62
|
+
if (this.SEARCH_TYPES.size > 0) {
|
|
63
|
+
this.spawnEventSignal.subscribe(this.onEntitySpawned);
|
|
64
|
+
this.removeBeforeEventSignal.subscribe(this.onEntityRemovedBefore);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this._wired = true;
|
|
68
|
+
this.reloadEntityMemory();
|
|
69
|
+
this.loadEventSignal.unsubscribe(this.onWorldLoad);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
private processScriptEvents = (event: ScriptEventCommandMessageAfterEvent): void => {
|
|
73
|
+
if (!event.sourceEntity) return;
|
|
74
|
+
if (event.id !== `${this.PACK_ID}:entity_emitter`) return;
|
|
75
|
+
|
|
76
|
+
let parms: any;
|
|
77
|
+
try {
|
|
78
|
+
parms = JSON.parse(event.message);
|
|
79
|
+
} catch (e) {
|
|
80
|
+
console.error(`BFLIB: entity_emitter JSON parse failed:`, e);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
this.onEntityEmission.emit({ iEntity: IEntityWrapper.wrap(event.sourceEntity), parms });
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/* ------------------------------------------------------------------------ */
|
|
88
|
+
/* Filters */
|
|
89
|
+
/* ------------------------------------------------------------------------ */
|
|
90
|
+
|
|
91
|
+
private isValidType(typeId: string): boolean {
|
|
92
|
+
if (this.PACK_ID && !typeId.startsWith(this.PACK_ID)) return false;
|
|
93
|
+
return this.SEARCH_TYPES.has(typeId);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private onEntitySpawned = (event: EntitySpawnAfterEvent): void => {
|
|
97
|
+
const entity = event.entity;
|
|
98
|
+
if (!this.isValidType(entity.typeId)) return;
|
|
99
|
+
this.saveEntityInMemory(entity);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
private onEntityRemovedBefore = (event: EntityRemoveBeforeEvent): void => {
|
|
103
|
+
const entity = event.removedEntity;
|
|
104
|
+
if (!this.isValidType(entity.typeId)) return;
|
|
105
|
+
this.deleteEntityInMemory(entity);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/* ------------------------------------------------------------------------ */
|
|
109
|
+
/* Persistence */
|
|
110
|
+
/* ------------------------------------------------------------------------ */
|
|
111
|
+
|
|
112
|
+
private reloadEntityMemory(): void {
|
|
113
|
+
this.EM_INDEX.clear();
|
|
114
|
+
this.EM_KEYS.length = 0;
|
|
115
|
+
|
|
116
|
+
const raw = world.getDynamicProperty(this.GLOBAL_MEMORY_ID) as string | undefined;
|
|
117
|
+
if (!raw) return;
|
|
118
|
+
|
|
119
|
+
let parsed: string[];
|
|
120
|
+
try {
|
|
121
|
+
parsed = JSON.parse(raw) as string[];
|
|
122
|
+
} catch (e) {
|
|
123
|
+
console.error(`BFLIB: Failed to parse ${this.GLOBAL_MEMORY_ID}:`, e);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for (const id of parsed) {
|
|
128
|
+
this.EM_KEYS.push(id);
|
|
129
|
+
const entity = world.getEntity(id);
|
|
130
|
+
if (entity) this.EM_INDEX.set(entity.id, entity);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private persistKeys(): void {
|
|
135
|
+
world.setDynamicProperty(this.GLOBAL_MEMORY_ID, JSON.stringify(this.EM_KEYS));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
public saveEntityInMemory(entity: Entity): boolean {
|
|
139
|
+
if (this.EM_INDEX.has(entity.id)) return false;
|
|
140
|
+
this.EM_KEYS.push(entity.id);
|
|
141
|
+
this.EM_INDEX.set(entity.id, entity);
|
|
142
|
+
this.persistKeys();
|
|
143
|
+
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
public deleteEntityInMemory(entity: Entity): boolean {
|
|
148
|
+
const existed = this.EM_INDEX.delete(entity.id);
|
|
149
|
+
if (!existed) return false;
|
|
150
|
+
|
|
151
|
+
const idx = this.EM_KEYS.indexOf(entity.id);
|
|
152
|
+
if (idx !== -1) this.EM_KEYS.splice(idx, 1);
|
|
153
|
+
|
|
154
|
+
this.persistKeys();
|
|
155
|
+
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
public getEntitiesInMemory(): Entity[] {
|
|
160
|
+
return Array.from(this.EM_INDEX.values());
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export const EntityHandler = new SingletonEntityHandler();
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { Signal } from "../util/Signal.js";
|
|
2
|
+
import { ItemCompleteUseAfterEvent, ItemCompleteUseAfterEventSignal, ItemReleaseUseAfterEvent, ItemReleaseUseAfterEventSignal, ItemStack, ItemStartUseAfterEvent, ItemStartUseAfterEventSignal, ItemStartUseOnAfterEvent, ItemStartUseOnAfterEventSignal, ItemStopUseAfterEvent, ItemStopUseAfterEventSignal, ItemStopUseOnAfterEvent, ItemStopUseOnAfterEventSignal, ItemUseAfterEvent, ItemUseAfterEventSignal, ItemUseBeforeEvent, ItemUseBeforeEventSignal, Player, PlayerHotbarSelectedSlotChangeAfterEvent, PlayerHotbarSelectedSlotChangeAfterEventSignal, PlayerInventoryItemChangeAfterEvent, PlayerInventoryItemChangeAfterEventSignal, system, world, WorldLoadAfterEvent, WorldLoadAfterEventSignal } from "@minecraft/server";
|
|
3
|
+
|
|
4
|
+
export type ItemRegistration = {
|
|
5
|
+
typeId: string;
|
|
6
|
+
emitWhileHolding?: boolean
|
|
7
|
+
emitOnUse?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type WhileHoldingItemEvent = { player: Player; itemStack: ItemStack };
|
|
11
|
+
export type OnItemHeldEvent = { player: Player; itemStack: ItemStack };
|
|
12
|
+
export type OnItemUnheldEvent = { player: Player; itemStack: ItemStack | undefined };
|
|
13
|
+
export type OnItemUsedEvent = { player: Player; itemStack: ItemStack };
|
|
14
|
+
|
|
15
|
+
interface HoldData {
|
|
16
|
+
instanceId: number;
|
|
17
|
+
itemStack: ItemStack;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class SingletonItemHandler {
|
|
21
|
+
private PACK_ID: string | undefined;
|
|
22
|
+
|
|
23
|
+
public readonly whileHoldingItemEvent = new Signal<WhileHoldingItemEvent>();
|
|
24
|
+
public readonly onItemHeldEvent = new Signal<OnItemHeldEvent>();
|
|
25
|
+
public readonly onItemUnheldEvent = new Signal<OnItemUnheldEvent>();
|
|
26
|
+
public readonly onItemUsedEvent = new Signal<OnItemUsedEvent>();
|
|
27
|
+
|
|
28
|
+
private readonly loadEventSignal: WorldLoadAfterEventSignal = world.afterEvents.worldLoad;
|
|
29
|
+
private readonly useBeforeSignal: ItemUseBeforeEventSignal = world.beforeEvents.itemUse;
|
|
30
|
+
private readonly useAfterSignal: ItemUseAfterEventSignal = world.afterEvents.itemUse;
|
|
31
|
+
private readonly completeUseSignal: ItemCompleteUseAfterEventSignal = world.afterEvents.itemCompleteUse;
|
|
32
|
+
private readonly releaseUseSignal: ItemReleaseUseAfterEventSignal = world.afterEvents.itemReleaseUse;
|
|
33
|
+
private readonly startUseSignal: ItemStartUseAfterEventSignal = world.afterEvents.itemStartUse;
|
|
34
|
+
private readonly startUseOnSignal: ItemStartUseOnAfterEventSignal = world.afterEvents.itemStartUseOn;
|
|
35
|
+
private readonly stopUseSignal: ItemStopUseAfterEventSignal = world.afterEvents.itemStopUse;
|
|
36
|
+
private readonly stopUseOnSignal: ItemStopUseOnAfterEventSignal = world.afterEvents.itemStopUseOn;
|
|
37
|
+
private readonly inventoryItemChangeSignal: PlayerInventoryItemChangeAfterEventSignal = world.afterEvents.playerInventoryItemChange;
|
|
38
|
+
private readonly hotbarChangeSignal: PlayerHotbarSelectedSlotChangeAfterEventSignal = world.afterEvents.playerHotbarSelectedSlotChange;
|
|
39
|
+
|
|
40
|
+
private readonly IM_INDEX = new Map<string, ItemRegistration>();
|
|
41
|
+
private readonly HOLD_INDEX: Map<string, HoldData> = new Map<string, HoldData>();
|
|
42
|
+
|
|
43
|
+
private _started = false;
|
|
44
|
+
private _wired = false;
|
|
45
|
+
|
|
46
|
+
public registerItem(itemRegistration: ItemRegistration | ItemRegistration[]): void {
|
|
47
|
+
if (Array.isArray(itemRegistration)) itemRegistration.forEach(i => this.IM_INDEX.set(i.typeId, i));
|
|
48
|
+
else this.IM_INDEX.set(itemRegistration.typeId, itemRegistration);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public start(packId: string): void {
|
|
52
|
+
if (this._started) throw new Error("BFLIB: ItemHandler already started;");
|
|
53
|
+
this._started = true;
|
|
54
|
+
this.PACK_ID = packId;
|
|
55
|
+
this.loadEventSignal.subscribe(this.onWorldLoad);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public stop(): void {
|
|
59
|
+
if (!this._started) return;
|
|
60
|
+
|
|
61
|
+
this.loadEventSignal.unsubscribe(this.onWorldLoad);
|
|
62
|
+
|
|
63
|
+
if (this._wired) {
|
|
64
|
+
this.useBeforeSignal.unsubscribe(this.onUseBefore);
|
|
65
|
+
this.useAfterSignal.unsubscribe(this.onUseAfter);
|
|
66
|
+
this.completeUseSignal.unsubscribe(this.onCompleteUse);
|
|
67
|
+
this.releaseUseSignal.unsubscribe(this.onReleaseUse);
|
|
68
|
+
this.startUseSignal.unsubscribe(this.onStartUse);
|
|
69
|
+
this.startUseOnSignal.unsubscribe(this.onStartUseOn);
|
|
70
|
+
this.stopUseSignal.unsubscribe(this.onStopUse);
|
|
71
|
+
this.stopUseOnSignal.unsubscribe(this.onStopUseOn);
|
|
72
|
+
this.hotbarChangeSignal.unsubscribe(this.onHotbarChange);
|
|
73
|
+
this.inventoryItemChangeSignal.unsubscribe(this.onInventoryItemChange);
|
|
74
|
+
|
|
75
|
+
this._wired = false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
this._started = false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
private onWorldLoad = (): void => {
|
|
83
|
+
if (this.IM_INDEX.size > 0) {
|
|
84
|
+
this.useBeforeSignal.subscribe(this.onUseBefore);
|
|
85
|
+
this.useAfterSignal.subscribe(this.onUseAfter);
|
|
86
|
+
this.completeUseSignal.subscribe(this.onCompleteUse);
|
|
87
|
+
this.releaseUseSignal.subscribe(this.onReleaseUse);
|
|
88
|
+
this.startUseSignal.subscribe(this.onStartUse);
|
|
89
|
+
this.startUseOnSignal.subscribe(this.onStartUseOn);
|
|
90
|
+
this.stopUseSignal.subscribe(this.onStopUse);
|
|
91
|
+
this.stopUseOnSignal.subscribe(this.onStopUseOn);
|
|
92
|
+
this.hotbarChangeSignal.subscribe(this.onHotbarChange);
|
|
93
|
+
this.inventoryItemChangeSignal.subscribe(this.onInventoryItemChange);
|
|
94
|
+
|
|
95
|
+
this._wired = true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
this.loadEventSignal.unsubscribe(this.onWorldLoad);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
private onUseBefore = (event: ItemUseBeforeEvent): void => {
|
|
103
|
+
const itemStack: ItemStack = event.itemStack;
|
|
104
|
+
if (!this.isValidType(itemStack.typeId)) return;
|
|
105
|
+
const i: ItemRegistration | undefined = this.IM_INDEX.get(itemStack.typeId);
|
|
106
|
+
if (!i || !i.emitOnUse) return;
|
|
107
|
+
const runId: number = system.run(() => {
|
|
108
|
+
try { this.onItemUsedEvent.emit({ player: event.source, itemStack: event.itemStack }) }
|
|
109
|
+
catch (error) { throw new Error(`${error}`) }
|
|
110
|
+
finally { system.clearRun(runId) }
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
};
|
|
114
|
+
private onUseAfter = (_event: ItemUseAfterEvent): void => { };
|
|
115
|
+
private onCompleteUse = (_event: ItemCompleteUseAfterEvent): void => { };
|
|
116
|
+
private onReleaseUse = (_event: ItemReleaseUseAfterEvent): void => { };
|
|
117
|
+
private onStartUse = (_event: ItemStartUseAfterEvent): void => { };
|
|
118
|
+
private onStartUseOn = (_event: ItemStartUseOnAfterEvent): void => { };
|
|
119
|
+
private onStopUse = (_event: ItemStopUseAfterEvent): void => { };
|
|
120
|
+
private onStopUseOn = (_event: ItemStopUseOnAfterEvent): void => { };
|
|
121
|
+
private onInventoryItemChange = (_event: PlayerInventoryItemChangeAfterEvent): void => { };
|
|
122
|
+
|
|
123
|
+
private onHotbarChange = (event: PlayerHotbarSelectedSlotChangeAfterEvent): void => {
|
|
124
|
+
const itemStack: ItemStack | undefined = event.itemStack;
|
|
125
|
+
if (this.HOLD_INDEX.has(event.player.id)) this.releaseHold(event.player);
|
|
126
|
+
if (!itemStack || (itemStack && !this.isValidType(itemStack.typeId))) return;
|
|
127
|
+
this.setNewHold(itemStack, event.player);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
private setNewHold(itemStack: ItemStack, player: Player) {
|
|
131
|
+
this.onItemHeldEvent.emit({ player: player, itemStack: itemStack });
|
|
132
|
+
|
|
133
|
+
const i: ItemRegistration | undefined = this.IM_INDEX.get(itemStack.typeId);
|
|
134
|
+
let instanceId: number = -1
|
|
135
|
+
if (i && i.emitWhileHolding) {
|
|
136
|
+
instanceId = system.runInterval(() => {
|
|
137
|
+
this.whileHoldingItemEvent.emit({ player: player, itemStack: itemStack });
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
this.HOLD_INDEX.set(player.id, { instanceId: instanceId, itemStack: itemStack });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private releaseHold(player: Player) {
|
|
144
|
+
const holdData: HoldData | undefined = this.HOLD_INDEX.get(player.id);
|
|
145
|
+
if (!holdData) return;
|
|
146
|
+
this.onItemUnheldEvent.emit({ player: player, itemStack: holdData.itemStack });
|
|
147
|
+
if (holdData.instanceId !== -1) system.clearRun(holdData.instanceId);
|
|
148
|
+
this.HOLD_INDEX.delete(player.id);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private isValidType(typeId: string): boolean {
|
|
152
|
+
if (this.PACK_ID && !typeId.startsWith(this.PACK_ID)) return false;
|
|
153
|
+
return this.IM_INDEX.has(typeId);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export const ItemHandler = new SingletonItemHandler();
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { IPlayerWrapper, IPlayer } from "../util/Wrapper/IPlayer";
|
|
2
|
+
import { ButtonState, InputButton, Player, PlayerButtonInputAfterEvent, PlayerButtonInputAfterEventSignal, PlayerLeaveBeforeEvent, PlayerLeaveBeforeEventSignal, PlayerSpawnAfterEvent, PlayerSpawnAfterEventSignal, world, WorldLoadAfterEvent, WorldLoadAfterEventSignal } from "@minecraft/server";
|
|
3
|
+
|
|
4
|
+
class SingletonPlayerHandler {
|
|
5
|
+
private readonly GLOBAL_MEMORY_ID = "GLB_MEM.PLAYER";
|
|
6
|
+
|
|
7
|
+
private readonly loadEventSignal: WorldLoadAfterEventSignal = world.afterEvents.worldLoad;
|
|
8
|
+
private readonly playerSpawnSignal: PlayerSpawnAfterEventSignal = world.afterEvents.playerSpawn;
|
|
9
|
+
private readonly playerLeaveBeforeSignal: PlayerLeaveBeforeEventSignal = world.beforeEvents.playerLeave;
|
|
10
|
+
private readonly buttonInputSignal: PlayerButtonInputAfterEventSignal = world.afterEvents.playerButtonInput;
|
|
11
|
+
|
|
12
|
+
private readonly PR_INDEX = new Map<string, Player>();
|
|
13
|
+
private readonly PR_KEYS: string[] = [];
|
|
14
|
+
|
|
15
|
+
private _started: boolean = false;
|
|
16
|
+
private _wired: boolean = false;
|
|
17
|
+
|
|
18
|
+
public start(): void {
|
|
19
|
+
if (this._started) throw new Error("BFLIB: PlayerHandler already started;");
|
|
20
|
+
this._started = true;
|
|
21
|
+
this.onSystemLoad();
|
|
22
|
+
this.loadEventSignal.subscribe(this.onWorldLoad);
|
|
23
|
+
this.buttonInputSignal.subscribe(this.onButtonPress);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public stop(): void {
|
|
27
|
+
if (!this._started) return;
|
|
28
|
+
if (this._wired) {
|
|
29
|
+
this.playerSpawnSignal.unsubscribe(this.onPlayerSpawned);
|
|
30
|
+
this.playerLeaveBeforeSignal.unsubscribe(this.onPlayerLeaveBefore);
|
|
31
|
+
this.buttonInputSignal.unsubscribe(this.onButtonPress);
|
|
32
|
+
this._wired = false;
|
|
33
|
+
}
|
|
34
|
+
this._started = false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private onWorldLoad = (): void => {
|
|
38
|
+
this.reloadPlayerMemory();
|
|
39
|
+
this.loadEventSignal.unsubscribe(this.onWorldLoad);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
private onSystemLoad(): void {
|
|
43
|
+
this.playerSpawnSignal.subscribe(this.onPlayerSpawned);
|
|
44
|
+
this.playerLeaveBeforeSignal.subscribe(this.onPlayerLeaveBefore);
|
|
45
|
+
this._wired = true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private onPlayerSpawned = (event: PlayerSpawnAfterEvent): void => {
|
|
49
|
+
if (!event) return;
|
|
50
|
+
if (this.hasPlayer(event.player.id)) return;
|
|
51
|
+
this.savePlayerInMemory(event.player);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
private onPlayerLeaveBefore = (event: PlayerLeaveBeforeEvent): void => {
|
|
55
|
+
if (!this.hasPlayer(event.player.id)) return;
|
|
56
|
+
this.deletePlayerInMemory(event.player);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
private onButtonPress = (event: PlayerButtonInputAfterEvent): void => {
|
|
60
|
+
if (event.button === InputButton.Jump) {
|
|
61
|
+
|
|
62
|
+
}
|
|
63
|
+
else if (event.button === InputButton.Sneak) {
|
|
64
|
+
|
|
65
|
+
}
|
|
66
|
+
console.warn(`Player ${event.player.name} pressed button ${InputButton[event.button]}: ${ButtonState[event.newButtonState]}`);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
public hasPlayer(playerId: string): boolean {
|
|
70
|
+
return this.PR_INDEX.has(playerId);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* ------------------------------------------------------------------------ */
|
|
74
|
+
/* Persistence */
|
|
75
|
+
/* ------------------------------------------------------------------------ */
|
|
76
|
+
|
|
77
|
+
private reloadPlayerMemory(): void {
|
|
78
|
+
this.PR_INDEX.clear();
|
|
79
|
+
this.PR_KEYS.length = 0;
|
|
80
|
+
|
|
81
|
+
const players: Player[] = world.getAllPlayers();
|
|
82
|
+
for (const player of players) {
|
|
83
|
+
this.PR_INDEX.set(player.id, player);
|
|
84
|
+
this.PR_KEYS.push(player.id);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
private persistKeys(): void {
|
|
91
|
+
world.setDynamicProperty(this.GLOBAL_MEMORY_ID, JSON.stringify(this.PR_KEYS));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
public savePlayerInMemory(player: Player): boolean {
|
|
95
|
+
if (this.PR_INDEX.has(player.id)) return false;
|
|
96
|
+
this.PR_KEYS.push(player.id);
|
|
97
|
+
this.PR_INDEX.set(player.id, player);
|
|
98
|
+
this.persistKeys();
|
|
99
|
+
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public deletePlayerInMemory(player: Player): boolean {
|
|
104
|
+
const existed = this.PR_INDEX.delete(player.id);
|
|
105
|
+
if (!existed) return false;
|
|
106
|
+
|
|
107
|
+
const idx = this.PR_KEYS.indexOf(player.id);
|
|
108
|
+
if (idx !== -1) this.PR_KEYS.splice(idx, 1);
|
|
109
|
+
|
|
110
|
+
this.persistKeys();
|
|
111
|
+
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
public getPlayersInMemory(): Player[] {
|
|
116
|
+
return Array.from(this.PR_INDEX.values());
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public getIPlayersInMemory(): IPlayer[] {
|
|
120
|
+
return Array.from(this.PR_INDEX.values())
|
|
121
|
+
.map(player => IPlayerWrapper.wrap(player));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export const PlayerHandler = new SingletonPlayerHandler();
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { system, TicksPerSecond } from "@minecraft/server";
|
|
2
|
+
import { Signal } from "../util/Signal";
|
|
3
|
+
|
|
4
|
+
export type ThreadConfig = {
|
|
5
|
+
mainRate?: number; // interval ticks
|
|
6
|
+
lateRate?: number; // interval ticks
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
class SingletonThreadManager {
|
|
10
|
+
public readonly MAIN = new Signal<number>();
|
|
11
|
+
public readonly LATE = new Signal<void>();
|
|
12
|
+
|
|
13
|
+
private _mainRate = 0;
|
|
14
|
+
private _lateRate = TicksPerSecond;
|
|
15
|
+
|
|
16
|
+
private _MAIN_ID: number | undefined;
|
|
17
|
+
private _LATE_ID: number | undefined;
|
|
18
|
+
|
|
19
|
+
private _delta = this.createDeltaTimer();
|
|
20
|
+
private _started = false;
|
|
21
|
+
|
|
22
|
+
public configure(cfg: ThreadConfig) {
|
|
23
|
+
if (this._started) throw new Error("BFLIB: _THREAD_ already started; configure before start().");
|
|
24
|
+
if (cfg.mainRate !== undefined) this._mainRate = cfg.mainRate;
|
|
25
|
+
if (cfg.lateRate !== undefined) this._lateRate = cfg.lateRate;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public start() {
|
|
29
|
+
if (this._started) return; // idempotent
|
|
30
|
+
this._started = true;
|
|
31
|
+
|
|
32
|
+
this._MAIN_ID = system.runInterval(() => {
|
|
33
|
+
if (this.MAIN.count <= 0) return;
|
|
34
|
+
try {
|
|
35
|
+
const delta = this._delta();
|
|
36
|
+
this.MAIN.emit(delta);
|
|
37
|
+
} catch (e) {
|
|
38
|
+
console.error(`ERROR: _THREAD_.MAIN:${this._MAIN_ID} |`, e);
|
|
39
|
+
}
|
|
40
|
+
}, this._mainRate);
|
|
41
|
+
|
|
42
|
+
this._LATE_ID = system.runInterval(() => {
|
|
43
|
+
if (this.LATE.count <= 0) return;
|
|
44
|
+
try {
|
|
45
|
+
this.LATE.emit();
|
|
46
|
+
} catch (e) {
|
|
47
|
+
console.error(`ERROR: _THREAD_.LATE:${this._LATE_ID} |`, e);
|
|
48
|
+
}
|
|
49
|
+
}, this._lateRate);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public stop() {
|
|
53
|
+
if (this._MAIN_ID !== undefined) system.clearRun(this._MAIN_ID);
|
|
54
|
+
if (this._LATE_ID !== undefined) system.clearRun(this._LATE_ID);
|
|
55
|
+
this._MAIN_ID = undefined;
|
|
56
|
+
this._LATE_ID = undefined;
|
|
57
|
+
this._started = false;
|
|
58
|
+
this._delta = this.createDeltaTimer(); // reset delta timing
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private createDeltaTimer() {
|
|
62
|
+
let last = Date.now();
|
|
63
|
+
return () => {
|
|
64
|
+
const now = Date.now();
|
|
65
|
+
const delta = (now - last) / 1000;
|
|
66
|
+
last = now;
|
|
67
|
+
return delta;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const Thread = new SingletonThreadManager();
|
package/_module/util/Signal.ts
CHANGED
|
@@ -1,31 +1,97 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Function signature for signal subscription callbacks.
|
|
3
|
+
*
|
|
4
|
+
* @template T Payload type emitted by the signal
|
|
5
|
+
*/
|
|
6
|
+
export type SubscriptionCallback<T> = (data: T) => void;
|
|
2
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Lightweight signal / event dispatcher inspired by Godot's signal system.
|
|
10
|
+
*
|
|
11
|
+
* Signals allow decoupled communication by emitting typed payloads to
|
|
12
|
+
* subscribed listeners.
|
|
13
|
+
*
|
|
14
|
+
* @template T Payload type (use `void` for no data)
|
|
15
|
+
*/
|
|
3
16
|
export class Signal<T = void> {
|
|
4
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Registered signal listeners.
|
|
19
|
+
*/
|
|
20
|
+
private readonly listeners = new Set<SubscriptionCallback<T>>();
|
|
5
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Number of currently subscribed listeners.
|
|
24
|
+
*/
|
|
6
25
|
public get count(): number {
|
|
7
26
|
return this.listeners.size;
|
|
8
27
|
}
|
|
9
28
|
|
|
10
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Subscribes a callback to this signal.
|
|
31
|
+
*
|
|
32
|
+
* The callback will be invoked every time the signal is emitted
|
|
33
|
+
* until it is explicitly unsubscribed or the signal is cleared.
|
|
34
|
+
*
|
|
35
|
+
* @param callback Function invoked on signal emission
|
|
36
|
+
*/
|
|
37
|
+
public subscribe(callback: SubscriptionCallback<T>): void {
|
|
11
38
|
this.listeners.add(callback);
|
|
12
39
|
}
|
|
13
40
|
|
|
14
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Unsubscribes a previously registered callback.
|
|
43
|
+
*
|
|
44
|
+
* @param callback Callback to remove
|
|
45
|
+
* @returns `true` if the callback was removed, `false` otherwise
|
|
46
|
+
*/
|
|
47
|
+
public unsubscribe(callback: SubscriptionCallback<T>): boolean {
|
|
15
48
|
return this.listeners.delete(callback);
|
|
16
49
|
}
|
|
17
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Removes all subscribed listeners from this signal.
|
|
53
|
+
*/
|
|
18
54
|
public clear(): void {
|
|
19
55
|
this.listeners.clear();
|
|
20
56
|
}
|
|
21
57
|
|
|
22
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Checks whether a callback is currently subscribed.
|
|
60
|
+
*
|
|
61
|
+
* @param callback Callback to test
|
|
62
|
+
* @returns `true` if the callback is subscribed
|
|
63
|
+
*/
|
|
64
|
+
public isSubscribed(callback: SubscriptionCallback<T>): boolean {
|
|
23
65
|
return this.listeners.has(callback);
|
|
24
66
|
}
|
|
25
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Emits the signal immediately, invoking all subscribed callbacks.
|
|
70
|
+
*
|
|
71
|
+
* Listener errors are caught and logged to prevent a single failure
|
|
72
|
+
* from interrupting signal propagation.
|
|
73
|
+
*
|
|
74
|
+
* @param data Payload to pass to listeners
|
|
75
|
+
*/
|
|
26
76
|
public emit(data: T): void {
|
|
27
|
-
for (const
|
|
28
|
-
|
|
77
|
+
for (const callback of Array.from(this.listeners)) {
|
|
78
|
+
try {
|
|
79
|
+
callback(data);
|
|
80
|
+
} catch (err) {
|
|
81
|
+
console.error("BFLIB: Subscription listener error:", err);
|
|
82
|
+
}
|
|
29
83
|
}
|
|
30
84
|
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Emits the signal asynchronously on the microtask queue.
|
|
88
|
+
*
|
|
89
|
+
* Useful for deferring execution to avoid re-entrancy issues
|
|
90
|
+
* or emitting during unsafe execution phases.
|
|
91
|
+
*
|
|
92
|
+
* @param data Payload to pass to listeners
|
|
93
|
+
*/
|
|
94
|
+
public emitDeferred(data: T): void {
|
|
95
|
+
queueMicrotask(() => this.emit(data));
|
|
96
|
+
}
|
|
31
97
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { Entity, EntityInventoryComponent } from "@minecraft/server";
|
|
1
|
+
import { Entity, EntityInventoryComponent, PlayAnimationOptions, system, TicksPerSecond } from "@minecraft/server";
|
|
2
2
|
import { ContainerWrapper, Inventory } from "./Container";
|
|
3
3
|
import { System } from "../System";
|
|
4
|
+
import { Animation } from "../../DataTypes";
|
|
4
5
|
|
|
5
6
|
export type IEntity = IEntityWrapper & Entity;
|
|
6
7
|
|
|
@@ -17,9 +18,8 @@ export class IEntityWrapper {
|
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
//======================== Interal ========================
|
|
20
|
-
public isAlive: boolean = false;
|
|
21
21
|
|
|
22
|
-
public get inventory(): Inventory {
|
|
22
|
+
public get inventory(): Inventory | undefined {
|
|
23
23
|
const i = this.source.getComponent(
|
|
24
24
|
EntityInventoryComponent.componentId
|
|
25
25
|
) as EntityInventoryComponent;
|
|
@@ -27,8 +27,9 @@ export class IEntityWrapper {
|
|
|
27
27
|
return ContainerWrapper.wrap(i.container);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
public
|
|
31
|
-
|
|
30
|
+
public async playAnimationAsync(animation: Animation, options: PlayAnimationOptions): Promise<void> {
|
|
31
|
+
this.source.playAnimation(animation.id, options);
|
|
32
|
+
await system.waitTicks(animation.length * TicksPerSecond);
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
}
|