@block_factory/lib 0.0.2 → 0.0.4
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 +18 -0
- package/_module/sys/Threads.ts +43 -0
- package/_module/util/Forms/Form.ts +31 -0
- package/{util → _module/util/Forms}/FormAction.ts +15 -26
- package/_module/util/Forms/FormMessage.ts +87 -0
- package/_module/util/Forms/FormModal.ts +183 -0
- package/_module/util/Forms/FormRegistry.ts +43 -0
- package/_module/util/RawText.ts +75 -0
- package/_module/util/Signal.ts +31 -0
- package/_module/util/System.ts +21 -0
- package/_module/util/Wrapper/Container.ts +34 -0
- package/_module/util/Wrapper/IEntity.ts +34 -0
- package/_module/util/Wrapper/IPlayer.ts +34 -0
- package/_types/_module/BlockFactory.d.ts +16 -0
- package/_types/_module/BlockFactory.d.ts.map +1 -0
- package/_types/_module/sys/Threads.d.ts +16 -0
- package/_types/_module/sys/Threads.d.ts.map +1 -0
- package/_types/_module/util/Command.d.ts +92 -0
- package/_types/_module/util/Command.d.ts.map +1 -0
- package/_types/_module/util/Forms/Form.d.ts +12 -0
- package/_types/_module/util/Forms/Form.d.ts.map +1 -0
- package/_types/_module/util/Forms/FormAction.d.ts +73 -0
- package/_types/_module/util/Forms/FormAction.d.ts.map +1 -0
- package/_types/_module/util/Forms/FormMessage.d.ts +24 -0
- package/_types/_module/util/Forms/FormMessage.d.ts.map +1 -0
- package/_types/_module/util/Forms/FormModal.d.ts +66 -0
- package/_types/_module/util/Forms/FormModal.d.ts.map +1 -0
- package/_types/_module/util/Forms/FormRegistry.d.ts +12 -0
- package/_types/_module/util/Forms/FormRegistry.d.ts.map +1 -0
- package/{util → _types/_module/util}/Math.d.ts +8 -2
- package/_types/_module/util/Math.d.ts.map +1 -0
- package/_types/_module/util/RawText.d.ts +60 -0
- package/_types/_module/util/RawText.d.ts.map +1 -0
- package/_types/_module/util/Signal.d.ts +11 -0
- package/_types/_module/util/Signal.d.ts.map +1 -0
- package/_types/_module/util/System.d.ts +4 -0
- package/_types/_module/util/System.d.ts.map +1 -0
- package/_types/_module/util/Vector.d.ts +212 -0
- package/_types/_module/util/Vector.d.ts.map +1 -0
- package/_types/_module/util/Wrapper/Container.d.ts +9 -0
- package/_types/_module/util/Wrapper/Container.d.ts.map +1 -0
- package/_types/_module/util/Wrapper/IEntity.d.ts +12 -0
- package/_types/_module/util/Wrapper/IEntity.d.ts.map +1 -0
- package/_types/_module/util/Wrapper/IPlayer.d.ts +12 -0
- package/_types/_module/util/Wrapper/IPlayer.d.ts.map +1 -0
- package/_types/index.d.ts +3 -0
- package/_types/index.d.ts.map +1 -0
- package/index.js +303 -215
- package/index.ts +2 -21
- package/package.json +9 -7
- package/tsconfig.types.json +13 -0
- package/index.d.ts +0 -20
- package/util/Command.d.ts +0 -23
- package/util/Form.d.ts +0 -10
- package/util/Form.ts +0 -15
- package/util/FormAction.d.ts +0 -41
- package/util/RawText.d.ts +0 -33
- package/util/RawText.ts +0 -76
- package/util/Signal.d.ts +0 -9
- package/util/Signal.ts +0 -73
- package/util/Vector.d.ts +0 -60
- package/util/_Form.ts +0 -246
- /package/{util → _module/util}/Command.ts +0 -0
- /package/{util → _module/util}/Math.ts +0 -0
- /package/{util → _module/util}/Vector.ts +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { MathUtils } from "./util/Math";
|
|
2
|
+
export { Signal } from "./util/Signal";
|
|
3
|
+
export { Vec2, Vec3 } from "./util/Vector";
|
|
4
|
+
export { RawText } from "./util/RawText";
|
|
5
|
+
export { Command } from "./util/Command";
|
|
6
|
+
export { System } from "./util/System";
|
|
7
|
+
|
|
8
|
+
export { IForm, IFormValue } from "./util/Forms/Form";
|
|
9
|
+
export { ActionFormCtor, type IButtonOptions, type IActionFormResponse, type IActionFormData } from "./util/Forms/FormAction";
|
|
10
|
+
export { type IModalFormResponse, type ITextFieldOptions, type IDropdownOptions, type ISliderOptions, type IToggleOptions, type IModalFormData } from "./util/Forms/FormModal";
|
|
11
|
+
export { type IMessageFormData, type IMessageFormResponse } from "./util/Forms/FormMessage";
|
|
12
|
+
export { type IFormRegistration, RegisterForm } from "./util/Forms/FormRegistry";
|
|
13
|
+
|
|
14
|
+
export { Inventory, ContainerWrapper } from "./util/Wrapper/Container";
|
|
15
|
+
export { type IEntity, IEntityWrapper } from "./util/Wrapper/IEntity";
|
|
16
|
+
export { type IPlayer, IPlayerWrapper } from "./util/Wrapper/IPlayer";
|
|
17
|
+
|
|
18
|
+
export { _THREAD_ } from "./sys/Threads";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { system, TicksPerSecond } from "@minecraft/server";
|
|
2
|
+
import { Signal } from "../util/Signal";
|
|
3
|
+
|
|
4
|
+
class Threads {
|
|
5
|
+
public static create(): Threads { return new Threads() }
|
|
6
|
+
public readonly MAIN = new Signal<number>();
|
|
7
|
+
public readonly LATE = new Signal();
|
|
8
|
+
public MAIN_INTERVAL_RATE: number = 0;
|
|
9
|
+
public LATE_INTERVAL_RATE: number = TicksPerSecond;
|
|
10
|
+
|
|
11
|
+
private _MAIN_ID: number;
|
|
12
|
+
private _LATE_ID: number;
|
|
13
|
+
private _delta = this.createDeltaTimer();
|
|
14
|
+
|
|
15
|
+
private constructor() {
|
|
16
|
+
this._MAIN_ID = system.runInterval(() => {
|
|
17
|
+
if (this.MAIN.count <= 0) return;
|
|
18
|
+
try { const delta = this._delta(); this.MAIN.emit(delta) }
|
|
19
|
+
catch (error) {
|
|
20
|
+
throw Error(`ERROR: _THREAD_.MAIN:${this._MAIN_ID} | ${error}`)
|
|
21
|
+
}
|
|
22
|
+
}, this.MAIN_INTERVAL_RATE);
|
|
23
|
+
|
|
24
|
+
this._LATE_ID = system.runInterval(() => {
|
|
25
|
+
if (this.LATE.count <= 0) return;
|
|
26
|
+
try { this.LATE.emit() }
|
|
27
|
+
catch (error) { throw Error(`ERROR: _THREAD_.LATE:${this._LATE_ID} | ${error}`) }
|
|
28
|
+
}, this.LATE_INTERVAL_RATE);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private createDeltaTimer() {
|
|
32
|
+
let last = Date.now();
|
|
33
|
+
|
|
34
|
+
return function nextDelta(): number {
|
|
35
|
+
const now = Date.now();
|
|
36
|
+
const delta = (now - last) / 1000; // seconds
|
|
37
|
+
last = now;
|
|
38
|
+
return delta;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const _THREAD_ = Threads.create();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Player, RawMessage, world } from "@minecraft/server";
|
|
2
|
+
import { uiManager } from "@minecraft/server-ui";
|
|
3
|
+
|
|
4
|
+
export declare type IFormValue = string | boolean | number | undefined;
|
|
5
|
+
|
|
6
|
+
export abstract class IForm {
|
|
7
|
+
private static readonly occupiedPlayers: Set<string> = new Set();
|
|
8
|
+
public static returnText: RawMessage | string = "Back";
|
|
9
|
+
|
|
10
|
+
public static isOccupied(player: Player): boolean {
|
|
11
|
+
return IForm.occupiedPlayers.has(player.id);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public static closeForms(player: Player): void {
|
|
15
|
+
uiManager.closeAllForms(player);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public static closeOccupiedForms(): void {
|
|
19
|
+
for (const playerId of IForm.occupiedPlayers) {
|
|
20
|
+
const player = world.getEntity(playerId) as Player | undefined;
|
|
21
|
+
if (player) uiManager.closeAllForms(player);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
protected setOccupied(player: Player, occupied: boolean): void {
|
|
26
|
+
if (occupied) IForm.occupiedPlayers.add(player.id);
|
|
27
|
+
else IForm.occupiedPlayers.delete(player.id);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
protected abstract build(player: Player): void;
|
|
31
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IForm } from "./Form";
|
|
2
2
|
import { Player, RawMessage } from "@minecraft/server";
|
|
3
3
|
import { ActionFormData, ActionFormResponse, FormCancelationReason } from "@minecraft/server-ui";
|
|
4
4
|
|
|
@@ -47,24 +47,13 @@ type ButtonMeta = {
|
|
|
47
47
|
* Normalized response object returned by IActionFormData.
|
|
48
48
|
*/
|
|
49
49
|
export interface IActionFormResponse {
|
|
50
|
-
/**
|
|
51
|
-
* Reason the form was canceled, if applicable.
|
|
52
|
-
*/
|
|
50
|
+
/**Reason the form was canceled, if applicable.*/
|
|
53
51
|
readonly cancelationReason?: FormCancelationReason;
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Whether the form was canceled by the player.
|
|
57
|
-
*/
|
|
52
|
+
/** Whether the form was canceled by the player.*/
|
|
58
53
|
readonly canceled: boolean;
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Raw button index selected by the player.
|
|
62
|
-
*/
|
|
54
|
+
/**Raw button index selected by the player.*/
|
|
63
55
|
readonly selection?: number;
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Logical button id associated with the selection.
|
|
67
|
-
*/
|
|
56
|
+
/**Logical button id associated with the selection.*/
|
|
68
57
|
readonly id?: string | number;
|
|
69
58
|
}
|
|
70
59
|
|
|
@@ -77,7 +66,7 @@ export interface IActionFormResponse {
|
|
|
77
66
|
* - Sub-form chaining with automatic Back navigation
|
|
78
67
|
* - Normalized response objects
|
|
79
68
|
*/
|
|
80
|
-
export abstract class IActionFormData extends
|
|
69
|
+
export abstract class IActionFormData extends IForm {
|
|
81
70
|
protected form: ActionFormData = new ActionFormData();
|
|
82
71
|
|
|
83
72
|
private _buttons = new Map<number, ButtonMeta>();
|
|
@@ -86,7 +75,7 @@ export abstract class IActionFormData extends Form {
|
|
|
86
75
|
|
|
87
76
|
protected abstract build(player: Player): void;
|
|
88
77
|
|
|
89
|
-
protected abstract onSubmit(
|
|
78
|
+
protected abstract onSubmit(player: Player, response: IActionFormResponse): void;
|
|
90
79
|
|
|
91
80
|
protected onCancel?(player: Player, response: IActionFormResponse): void;
|
|
92
81
|
|
|
@@ -96,13 +85,13 @@ export abstract class IActionFormData extends Form {
|
|
|
96
85
|
return this;
|
|
97
86
|
}
|
|
98
87
|
|
|
99
|
-
public body(bodyText: RawMessage | string): this { this.form.body(bodyText); return this;}
|
|
88
|
+
public body(bodyText: RawMessage | string): this { this.form.body(bodyText); return this; }
|
|
100
89
|
|
|
101
|
-
public divider(): this { this.form.divider(); return this;}
|
|
90
|
+
public divider(): this { this.form.divider(); return this; }
|
|
102
91
|
|
|
103
|
-
public header(text: RawMessage | string): this { this.form.header(text); return this;}
|
|
92
|
+
public header(text: RawMessage | string): this { this.form.header(text); return this; }
|
|
104
93
|
|
|
105
|
-
public label(text: RawMessage | string): this { this.form.label(text); return this;}
|
|
94
|
+
public label(text: RawMessage | string): this { this.form.label(text); return this; }
|
|
106
95
|
|
|
107
96
|
public button(text: RawMessage | string, options: IButtonOptions = {}): this {
|
|
108
97
|
if (options.iconPath) this.form.button(text, options.iconPath);
|
|
@@ -122,7 +111,7 @@ export abstract class IActionFormData extends Form {
|
|
|
122
111
|
}
|
|
123
112
|
|
|
124
113
|
public async show(player: Player): Promise<IActionFormResponse> {
|
|
125
|
-
if (
|
|
114
|
+
if (IForm.isOccupied(player)) {
|
|
126
115
|
throw new Error(`Player ${player.id} is already occupied by form: ${this._id}`);
|
|
127
116
|
}
|
|
128
117
|
|
|
@@ -135,11 +124,11 @@ export abstract class IActionFormData extends Form {
|
|
|
135
124
|
}
|
|
136
125
|
}
|
|
137
126
|
|
|
138
|
-
protected async _showChained(player: Player,returnStack: ActionFormCtor[]): Promise<IActionFormResponse> {
|
|
127
|
+
protected async _showChained(player: Player, returnStack: ActionFormCtor[]): Promise<IActionFormResponse> {
|
|
139
128
|
return await this._showInternal(player, returnStack);
|
|
140
129
|
}
|
|
141
130
|
|
|
142
|
-
private _wrapResponse(response: ActionFormResponse,id?: string | number): IActionFormResponse {
|
|
131
|
+
private _wrapResponse(response: ActionFormResponse, id?: string | number): IActionFormResponse {
|
|
143
132
|
return {
|
|
144
133
|
canceled: response.canceled,
|
|
145
134
|
cancelationReason: response.cancelationReason,
|
|
@@ -158,7 +147,7 @@ export abstract class IActionFormData extends Form {
|
|
|
158
147
|
this.build(player);
|
|
159
148
|
|
|
160
149
|
if (returnStack.length > 0) {
|
|
161
|
-
this.form.button(
|
|
150
|
+
this.form.button(IForm.returnText);
|
|
162
151
|
this._buttons.set(this._nextButtonIndex, {
|
|
163
152
|
id: RETURN_ID,
|
|
164
153
|
subForm: returnStack[returnStack.length - 1],
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { IForm } from "./Form";
|
|
2
|
+
import { Player, RawMessage } from "@minecraft/server";
|
|
3
|
+
import {
|
|
4
|
+
MessageFormData,
|
|
5
|
+
MessageFormResponse,
|
|
6
|
+
} from "@minecraft/server-ui";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Abstract base class for MessageFormData.
|
|
10
|
+
*
|
|
11
|
+
* Provides:
|
|
12
|
+
* - occupied player handling
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export type IMessageFormResponse = MessageFormResponse; // re-export wrapper for consistency
|
|
16
|
+
|
|
17
|
+
export abstract class IMessageFormData extends IForm {
|
|
18
|
+
protected form: MessageFormData = new MessageFormData();
|
|
19
|
+
|
|
20
|
+
private _id: string = "untitled";
|
|
21
|
+
|
|
22
|
+
protected abstract build(player: Player): void;
|
|
23
|
+
|
|
24
|
+
protected abstract onSubmit(
|
|
25
|
+
player: Player,
|
|
26
|
+
response: IMessageFormResponse
|
|
27
|
+
): void;
|
|
28
|
+
|
|
29
|
+
protected onCancel?(player: Player, response: IMessageFormResponse): void;
|
|
30
|
+
|
|
31
|
+
public title(titleText: RawMessage | string): this {
|
|
32
|
+
this.form.title(titleText);
|
|
33
|
+
this._id =
|
|
34
|
+
typeof titleText === "string"
|
|
35
|
+
? titleText
|
|
36
|
+
: JSON.stringify(titleText);
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public body(bodyText: RawMessage | string): this {
|
|
41
|
+
this.form.body(bodyText);
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
public button0(text: RawMessage | string,id: string | number = 0): this {
|
|
45
|
+
this.form.button1(text);
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public button1(text: RawMessage | string,id: string | number = 1): this {
|
|
50
|
+
this.form.button2(text);
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public async show(player: Player): Promise<IMessageFormResponse> {
|
|
55
|
+
if (IForm.isOccupied(player)) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`Player ${player.id} is already occupied by form: ${this._id}`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
this.setOccupied(player, true);
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
return await this._showInternal(player);
|
|
65
|
+
} finally {
|
|
66
|
+
this.setOccupied(player, false);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private async _showInternal(
|
|
71
|
+
player: Player
|
|
72
|
+
): Promise<IMessageFormResponse> {
|
|
73
|
+
this.form = new MessageFormData();
|
|
74
|
+
|
|
75
|
+
this.build(player);
|
|
76
|
+
|
|
77
|
+
const res = await this.form.show(player);
|
|
78
|
+
|
|
79
|
+
if (res.canceled || res.selection === undefined) {
|
|
80
|
+
this.onCancel?.(player, res);
|
|
81
|
+
return res;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
this.onSubmit(player, res);
|
|
85
|
+
return res;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { IForm } from "./Form";
|
|
2
|
+
import { Player, RawMessage } from "@minecraft/server";
|
|
3
|
+
import {
|
|
4
|
+
ModalFormData,
|
|
5
|
+
ModalFormResponse,
|
|
6
|
+
FormCancelationReason,
|
|
7
|
+
ModalFormDataDropdownOptions,
|
|
8
|
+
ModalFormDataSliderOptions,
|
|
9
|
+
ModalFormDataTextFieldOptions,
|
|
10
|
+
ModalFormDataToggleOptions,
|
|
11
|
+
} from "@minecraft/server-ui";
|
|
12
|
+
|
|
13
|
+
export type FormValue = string | boolean | number | undefined;
|
|
14
|
+
|
|
15
|
+
/** Normalized response for Modal forms */
|
|
16
|
+
export interface IModalFormResponse {
|
|
17
|
+
/**Reason the form was canceled, if applicable.*/
|
|
18
|
+
readonly cancelationReason?: FormCancelationReason;
|
|
19
|
+
/** Whether the form was canceled by the player.*/
|
|
20
|
+
readonly canceled: boolean;
|
|
21
|
+
/** Raw values array from the form */
|
|
22
|
+
readonly formValues?: FormValue[];
|
|
23
|
+
/** Mapped values by widget id */
|
|
24
|
+
readonly widget?: Map<string, FormValue>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Options for each widget type */
|
|
28
|
+
export interface ITextFieldOptions {
|
|
29
|
+
id: string;
|
|
30
|
+
placeholder: string | RawMessage;
|
|
31
|
+
defaultValue?: string;
|
|
32
|
+
}
|
|
33
|
+
export interface IDropdownOptions {
|
|
34
|
+
id: string;
|
|
35
|
+
choices: string[];
|
|
36
|
+
defaultValueIndex?: number;
|
|
37
|
+
}
|
|
38
|
+
export interface ISliderOptions {
|
|
39
|
+
id: string;
|
|
40
|
+
min: number;
|
|
41
|
+
max: number;
|
|
42
|
+
step: number;
|
|
43
|
+
defaultValue?: number;
|
|
44
|
+
}
|
|
45
|
+
export interface IToggleOptions {
|
|
46
|
+
id: string;
|
|
47
|
+
defaultValue?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type WidgetMeta =
|
|
51
|
+
| { type: "textField"; id: string }
|
|
52
|
+
| { type: "dropdown"; id: string }
|
|
53
|
+
| { type: "slider"; id: string }
|
|
54
|
+
| { type: "toggle"; id: string };
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Abstract base class for ModalFormData-based menus.
|
|
58
|
+
*
|
|
59
|
+
* Provides:
|
|
60
|
+
* - Occupied player handling
|
|
61
|
+
* - Widget id mapping into a Map<string, FormValue>
|
|
62
|
+
* - Normalized response object
|
|
63
|
+
*/
|
|
64
|
+
export abstract class IModalFormData extends IForm {
|
|
65
|
+
protected form: ModalFormData = new ModalFormData();
|
|
66
|
+
|
|
67
|
+
private _id: string = "untitled";
|
|
68
|
+
private _widgetMeta: WidgetMeta[] = [];
|
|
69
|
+
|
|
70
|
+
protected abstract build(player: Player): void;
|
|
71
|
+
|
|
72
|
+
protected abstract onSubmit(player: Player, response: IModalFormResponse): void;
|
|
73
|
+
|
|
74
|
+
protected onCancel?(player: Player, response: IModalFormResponse): void;
|
|
75
|
+
|
|
76
|
+
public title(titleText: RawMessage | string): this {
|
|
77
|
+
this.form.title(titleText);
|
|
78
|
+
this._id = typeof titleText === "string" ? titleText : JSON.stringify(titleText);
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public divider(): this {
|
|
83
|
+
this.form.divider();
|
|
84
|
+
return this;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public header(text: RawMessage | string): this {
|
|
88
|
+
this.form.header(text);
|
|
89
|
+
return this;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public label(text: RawMessage | string): this {
|
|
93
|
+
this.form.label(text);
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public textField(label: string | RawMessage, options: ITextFieldOptions): this {
|
|
98
|
+
const opts: ModalFormDataTextFieldOptions = { defaultValue: options.defaultValue };
|
|
99
|
+
this.form.textField(label, options.placeholder, opts);
|
|
100
|
+
this._widgetMeta.push({ type: "textField", id: options.id });
|
|
101
|
+
return this;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
public dropdown(label: string | RawMessage, options: IDropdownOptions): this {
|
|
105
|
+
const opts: ModalFormDataDropdownOptions = { defaultValueIndex: options.defaultValueIndex };
|
|
106
|
+
this.form.dropdown(label, options.choices, opts);
|
|
107
|
+
this._widgetMeta.push({ type: "dropdown", id: options.id });
|
|
108
|
+
return this;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public slider(label: string | RawMessage, options: ISliderOptions): this {
|
|
112
|
+
const opts: ModalFormDataSliderOptions = {
|
|
113
|
+
defaultValue: options.defaultValue,
|
|
114
|
+
valueStep: options.step,
|
|
115
|
+
};
|
|
116
|
+
this.form.slider(label, options.min, options.max, opts);
|
|
117
|
+
this._widgetMeta.push({ type: "slider", id: options.id });
|
|
118
|
+
return this;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
public toggle(label: string | RawMessage, options: IToggleOptions): this {
|
|
122
|
+
const opts: ModalFormDataToggleOptions = { defaultValue: options.defaultValue };
|
|
123
|
+
this.form.toggle(label, opts);
|
|
124
|
+
this._widgetMeta.push({ type: "toggle", id: options.id });
|
|
125
|
+
return this;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
public async show(player: Player): Promise<IModalFormResponse> {
|
|
129
|
+
if (IForm.isOccupied(player)) {
|
|
130
|
+
throw new Error(`Player ${player.id} is already occupied by form: ${this._id}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
this.setOccupied(player, true);
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
return await this._showInternal(player);
|
|
137
|
+
} finally {
|
|
138
|
+
this.setOccupied(player, false);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private _wrapResponse(res: ModalFormResponse, values?: Map<string, FormValue>): IModalFormResponse {
|
|
143
|
+
return {
|
|
144
|
+
canceled: res.canceled,
|
|
145
|
+
cancelationReason: res.cancelationReason,
|
|
146
|
+
formValues: res.formValues as FormValue[] | undefined,
|
|
147
|
+
widget: values,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private _mapValues(formValues: FormValue[] | undefined): Map<string, FormValue> | undefined {
|
|
152
|
+
if (!formValues) return undefined;
|
|
153
|
+
|
|
154
|
+
const out = new Map<string, FormValue>();
|
|
155
|
+
for (let i = 0; i < formValues.length; i++) {
|
|
156
|
+
const meta = this._widgetMeta[i];
|
|
157
|
+
if (!meta) continue;
|
|
158
|
+
out.set(meta.id, formValues[i]);
|
|
159
|
+
}
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private async _showInternal(player: Player): Promise<IModalFormResponse> {
|
|
164
|
+
this.form = new ModalFormData();
|
|
165
|
+
this._widgetMeta = [];
|
|
166
|
+
|
|
167
|
+
this.build(player);
|
|
168
|
+
|
|
169
|
+
const res = await this.form.show(player);
|
|
170
|
+
|
|
171
|
+
if (res.canceled) {
|
|
172
|
+
const wrapped = this._wrapResponse(res);
|
|
173
|
+
this.onCancel?.(player, wrapped);
|
|
174
|
+
return wrapped;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const values = this._mapValues(res.formValues as FormValue[] | undefined);
|
|
178
|
+
const wrapped = this._wrapResponse(res, values);
|
|
179
|
+
|
|
180
|
+
this.onSubmit(player, wrapped);
|
|
181
|
+
return wrapped;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ItemUseAfterEvent, Player, world } from "@minecraft/server";
|
|
2
|
+
import { IActionFormData } from "./FormAction";
|
|
3
|
+
import { IModalFormData } from "./FormModal";
|
|
4
|
+
import { IMessageFormData } from "./FormMessage";
|
|
5
|
+
|
|
6
|
+
type AnyShowableForm = IActionFormData | IModalFormData | IMessageFormData;
|
|
7
|
+
type AnyShowableCtor = new () => AnyShowableForm;
|
|
8
|
+
|
|
9
|
+
export interface IFormRegistration {
|
|
10
|
+
itemId: string;
|
|
11
|
+
formCtor: AnyShowableCtor;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const registeredForms: IFormRegistration[] = [];
|
|
15
|
+
|
|
16
|
+
export function RegisterForm(itemId: string) {
|
|
17
|
+
return function <T extends AnyShowableCtor>(formCtor: T): void {
|
|
18
|
+
const i = registeredForms.findIndex(r => r.itemId === itemId);
|
|
19
|
+
if (i !== -1) registeredForms[i] = { itemId, formCtor };
|
|
20
|
+
else registeredForms.push({ itemId, formCtor });
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getFormForItem(itemId: string): AnyShowableCtor | undefined {
|
|
25
|
+
return registeredForms.find(r => r.itemId === itemId)?.formCtor;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
world.afterEvents.itemUse.subscribe((event: ItemUseAfterEvent) => {
|
|
29
|
+
const player = event.source;
|
|
30
|
+
if (!(player instanceof Player)) return;
|
|
31
|
+
|
|
32
|
+
const item = event.itemStack;
|
|
33
|
+
if (!item) return;
|
|
34
|
+
|
|
35
|
+
const formCtor = getFormForItem(item.typeId);
|
|
36
|
+
if (!formCtor) return;
|
|
37
|
+
|
|
38
|
+
const form = new formCtor();
|
|
39
|
+
|
|
40
|
+
form.show(player).catch((e) => {
|
|
41
|
+
player.sendMessage(`§cForm error: ${String(e)}`);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { RawMessage } from '@minecraft/server';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Utility helpers for building rawtext messages.
|
|
5
|
+
*/
|
|
6
|
+
export class RawText {
|
|
7
|
+
/**
|
|
8
|
+
* Creates a simple text component.
|
|
9
|
+
* @param value - The text value.
|
|
10
|
+
* @returns Text component object.
|
|
11
|
+
*/
|
|
12
|
+
public static text(value: string): RawMessage {
|
|
13
|
+
return { text: value };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Creates a translation component with optional 'with' parameters.
|
|
18
|
+
* @param key - The translation key.
|
|
19
|
+
* @param params - The optional parameters for translation.
|
|
20
|
+
* @returns Translation component object.
|
|
21
|
+
*/
|
|
22
|
+
public static translate(key: string, ...params: string[]): RawMessage {
|
|
23
|
+
return {
|
|
24
|
+
translate: key,
|
|
25
|
+
with: params.length ? params : undefined
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Creates a score component.
|
|
31
|
+
* @param name - The entity's name whose score is being displayed.
|
|
32
|
+
* @param objective - The name of the score objective.
|
|
33
|
+
* @returns Score component object.
|
|
34
|
+
*/
|
|
35
|
+
public static score(name: string, objective: string): RawMessage {
|
|
36
|
+
return { score: { name, objective } };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Wraps various text components into a rawtext structure.
|
|
41
|
+
* @param rawText - The raw text components.
|
|
42
|
+
* @returns Raw text object.
|
|
43
|
+
*/
|
|
44
|
+
public static message(...rawText: RawMessage[]): RawMessage {
|
|
45
|
+
return { rawtext: rawText };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Common formatting and color codes.
|
|
50
|
+
*/
|
|
51
|
+
public static FORMAT = {
|
|
52
|
+
DarkRed: "§4",
|
|
53
|
+
Red: "§c",
|
|
54
|
+
Gold: "§6",
|
|
55
|
+
Yellow: "§e",
|
|
56
|
+
Green: "§2",
|
|
57
|
+
Lime: "§a",
|
|
58
|
+
Aqua: "§b",
|
|
59
|
+
Cyan: "§3",
|
|
60
|
+
DarkBlue: "§1",
|
|
61
|
+
Blue: "§9",
|
|
62
|
+
Magenta: "§d",
|
|
63
|
+
Purple: "§5",
|
|
64
|
+
White: "§f",
|
|
65
|
+
Gray: "§7",
|
|
66
|
+
DarkGray: "§8",
|
|
67
|
+
Black: "§0",
|
|
68
|
+
Obfuscated: "§k",
|
|
69
|
+
Bold: "§l",
|
|
70
|
+
StrikeThrough: "§m",
|
|
71
|
+
Italic: "§o",
|
|
72
|
+
Reset: "§r",
|
|
73
|
+
NewLine: "\n"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type Callback<T> = (data: T) => void;
|
|
2
|
+
|
|
3
|
+
export class Signal<T = void> {
|
|
4
|
+
private readonly listeners = new Set<Callback<T>>();
|
|
5
|
+
|
|
6
|
+
public get count(): number {
|
|
7
|
+
return this.listeners.size;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public connect(callback: Callback<T>): void {
|
|
11
|
+
this.listeners.add(callback);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public disconnect(callback: Callback<T>): boolean {
|
|
15
|
+
return this.listeners.delete(callback);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public clear(): void {
|
|
19
|
+
this.listeners.clear();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public isConnected(callback: Callback<T>): boolean {
|
|
23
|
+
return this.listeners.has(callback);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public emit(data: T): void {
|
|
27
|
+
for (const cb of Array.from(this.listeners)) {
|
|
28
|
+
cb(data);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export namespace System {
|
|
2
|
+
export const ProxyConstructor = ((_instance: any, source: any) => {
|
|
3
|
+
return new Proxy(_instance, {
|
|
4
|
+
get: (target, prop) => {
|
|
5
|
+
if (prop in target) return (target as any)[prop];
|
|
6
|
+
const v = (source as any)[prop];
|
|
7
|
+
return typeof v === "function" ? v.bind(source) : v;
|
|
8
|
+
},
|
|
9
|
+
|
|
10
|
+
set: (target, prop, value) => {
|
|
11
|
+
if (prop in target) { (target as any)[prop] = value; return true; }
|
|
12
|
+
(source as any)[prop] = value;
|
|
13
|
+
return true;
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
has: (target, prop) => prop in target || prop in source,
|
|
17
|
+
ownKeys: () => [...Reflect.ownKeys(source), ...Reflect.ownKeys(_instance)],
|
|
18
|
+
getOwnPropertyDescriptor: (_t, prop) => Object.getOwnPropertyDescriptor((prop in _instance ? _instance : source) as any, prop)
|
|
19
|
+
}) as any;
|
|
20
|
+
})
|
|
21
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Container, ItemStack} from "@minecraft/server";
|
|
2
|
+
import { System } from "../System";
|
|
3
|
+
|
|
4
|
+
export type Inventory = ContainerWrapper & Container;
|
|
5
|
+
|
|
6
|
+
export class ContainerWrapper {
|
|
7
|
+
public readonly source: Container;
|
|
8
|
+
|
|
9
|
+
private constructor(source: Container) {
|
|
10
|
+
this.source = source;
|
|
11
|
+
return System.ProxyConstructor(this, source);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public static wrap(source: Container): Inventory {
|
|
15
|
+
return new ContainerWrapper(source) as Inventory;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
//======================== Interal ========================
|
|
19
|
+
public reduce(slot: number, amount: number = 1) {
|
|
20
|
+
let itemStack: ItemStack | undefined = this.source.getItem(slot);
|
|
21
|
+
let currentAmount: number = itemStack?.amount || 0;
|
|
22
|
+
currentAmount -= amount;
|
|
23
|
+
|
|
24
|
+
if (itemStack != undefined && currentAmount > 0) {
|
|
25
|
+
const newItemStack: ItemStack = itemStack;
|
|
26
|
+
newItemStack.amount = currentAmount;
|
|
27
|
+
itemStack = newItemStack
|
|
28
|
+
|
|
29
|
+
} else itemStack = undefined;
|
|
30
|
+
|
|
31
|
+
this.source.setItem(slot, itemStack);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
}
|