@occultus/article-api 0.24.0-beta.1 → 0.24.0-beta.3
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/package.json
CHANGED
package/src/api/Article.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { FormLike } from "@occultus/core";
|
|
|
10
10
|
* @since 1.0.0
|
|
11
11
|
*/
|
|
12
12
|
export class Article extends FormLike {
|
|
13
|
+
static DYNAMIC_DATA_ID = "starock:unlocked_articles";
|
|
13
14
|
/**
|
|
14
15
|
* @param id 文章ID
|
|
15
16
|
* @param title 文章标题
|
|
@@ -46,15 +47,18 @@ export class Article extends FormLike {
|
|
|
46
47
|
.title(title)
|
|
47
48
|
.body(body)
|
|
48
49
|
.button({ translate: "gui.ok" });
|
|
49
|
-
mainForm
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
50
|
+
mainForm
|
|
51
|
+
.show(player)
|
|
52
|
+
.then((response) => {
|
|
53
|
+
if (response.canceled || response.selection === 0) {
|
|
54
|
+
this.quit(player, backTo);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
.catch((err) => {
|
|
59
|
+
console.error(`[Article] Failed to display article: ${err}`);
|
|
60
|
+
this.quit(player, backTo as readonly FormLike[]);
|
|
61
|
+
});
|
|
58
62
|
}
|
|
59
63
|
/**
|
|
60
64
|
* 向玩家展示文章(文章有章节时)
|
|
@@ -71,25 +75,35 @@ export class Article extends FormLike {
|
|
|
71
75
|
parseText(this.body, player)
|
|
72
76
|
];
|
|
73
77
|
const contentsForm = new ActionFormData().title(title).body(body);
|
|
74
|
-
this.chapters
|
|
78
|
+
this.chapters?.forEach((chapter) => {
|
|
75
79
|
const chapterTitle = parseText(chapter.title, player);
|
|
76
80
|
contentsForm.button(chapterTitle, chapter.iconPath);
|
|
77
81
|
});
|
|
78
|
-
contentsForm
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
82
|
+
contentsForm
|
|
83
|
+
.show(player)
|
|
84
|
+
.then((response) => {
|
|
85
|
+
if (response.canceled || response.selection === undefined) {
|
|
86
|
+
this.quit(player, backTo);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const [chapterTitle, chapterBody] = [
|
|
90
|
+
parseText(this.chapters![response.selection].title, player),
|
|
91
|
+
parseText(this.chapters![response.selection].body, player)
|
|
92
|
+
];
|
|
93
|
+
const chapter = new Article(
|
|
94
|
+
"temp:chapter",
|
|
95
|
+
chapterTitle,
|
|
96
|
+
chapterBody,
|
|
97
|
+
undefined,
|
|
98
|
+
undefined,
|
|
99
|
+
false
|
|
100
|
+
);
|
|
101
|
+
this.jumpTo(player, chapter, backTo);
|
|
102
|
+
})
|
|
103
|
+
.catch((err) => {
|
|
104
|
+
console.error(`[Article] Failed to display article: ${err}`);
|
|
105
|
+
this.quit(player, backTo as readonly FormLike[]);
|
|
106
|
+
});
|
|
93
107
|
}
|
|
94
108
|
/**
|
|
95
109
|
* 向玩家展示文章
|
|
@@ -106,10 +120,26 @@ export class Article extends FormLike {
|
|
|
106
120
|
/**
|
|
107
121
|
* 在文章中心里解锁文章
|
|
108
122
|
* @param player
|
|
123
|
+
* @return `true`如果文章需要解锁,并且成功解锁;否则返回`false`
|
|
109
124
|
*/
|
|
110
|
-
unlock(player: Player):
|
|
111
|
-
if (this.needUnlock)
|
|
112
|
-
|
|
125
|
+
unlock(player: Player): boolean {
|
|
126
|
+
if (!this.needUnlock || this.checkUnlock(player)) return false;
|
|
127
|
+
try {
|
|
128
|
+
const raw = player.getDynamicProperty(Article.DYNAMIC_DATA_ID);
|
|
129
|
+
let unlockedIds: string[] = [];
|
|
130
|
+
if (raw && typeof raw === "string") {
|
|
131
|
+
const parsed = JSON.parse(raw);
|
|
132
|
+
unlockedIds = Array.isArray(parsed) ? parsed : [];
|
|
133
|
+
}
|
|
134
|
+
player.setDynamicProperty(
|
|
135
|
+
Article.DYNAMIC_DATA_ID,
|
|
136
|
+
JSON.stringify([...unlockedIds, this.id])
|
|
137
|
+
);
|
|
138
|
+
return true;
|
|
139
|
+
} catch (err) {
|
|
140
|
+
console.error(`Failed to unlock article ${this.id}:`, err);
|
|
141
|
+
player.setDynamicProperty(Article.DYNAMIC_DATA_ID, "[]");
|
|
142
|
+
return false;
|
|
113
143
|
}
|
|
114
144
|
}
|
|
115
145
|
/**
|
|
@@ -117,9 +147,16 @@ export class Article extends FormLike {
|
|
|
117
147
|
* @param player
|
|
118
148
|
*/
|
|
119
149
|
checkUnlock(player: Player): boolean {
|
|
120
|
-
if (this.needUnlock)
|
|
121
|
-
|
|
150
|
+
if (!this.needUnlock) return true;
|
|
151
|
+
try {
|
|
152
|
+
const raw = player.getDynamicProperty(Article.DYNAMIC_DATA_ID);
|
|
153
|
+
if (!raw || typeof raw !== "string") return false;
|
|
154
|
+
const json = JSON.parse(raw);
|
|
155
|
+
if (!Array.isArray(json)) return false;
|
|
156
|
+
return json.includes(this.id);
|
|
157
|
+
} catch (err) {
|
|
158
|
+
console.error(`Failed to check status of article ${this.id}:`, err);
|
|
122
159
|
}
|
|
123
|
-
return
|
|
160
|
+
return false;
|
|
124
161
|
}
|
|
125
162
|
}
|
package/src/api/ArticleCenter.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { Player } from "@minecraft/server";
|
|
2
2
|
import { MessageFormData } from "@minecraft/server-ui";
|
|
3
3
|
import { ArticleRegistries } from "./Base/ArticleRegistries";
|
|
4
|
-
import {
|
|
5
|
-
import { generateContentForm } from "../lib/utils";
|
|
4
|
+
import { autoGetArticles, generateContentForm } from "../lib/utils";
|
|
6
5
|
import { TextProvider } from "@occultus/text-api";
|
|
7
|
-
import { FormLike,
|
|
6
|
+
import { FormLike, OccultusSDKError } from "@occultus/core";
|
|
8
7
|
import { ReadableArticle } from "../types/ReadableArticle";
|
|
9
8
|
|
|
10
9
|
/**
|
|
@@ -32,13 +31,13 @@ export class ArticleCenter extends FormLike {
|
|
|
32
31
|
* @param id 文章中心ID
|
|
33
32
|
* @param title 文章中心标题
|
|
34
33
|
* @param body 文章中心内容
|
|
35
|
-
* @param
|
|
34
|
+
* @param articleSource 文章来源,如果为`true`,则会从该脚本环境注册的所有文章中显示已经解锁的文章,反之则显示无文章提示,如果为文章列表,则只从提供的文章中显示已解锁的文章
|
|
36
35
|
*/
|
|
37
36
|
constructor(
|
|
38
37
|
public readonly id: string,
|
|
39
38
|
public title: TextProvider,
|
|
40
39
|
public body: TextProvider,
|
|
41
|
-
public
|
|
40
|
+
public articleSource: ReadableArticle[] | "registry" | "auto",
|
|
42
41
|
public readonly bindTo?: ArticleRegistries,
|
|
43
42
|
public iconPath?: string
|
|
44
43
|
) {
|
|
@@ -53,7 +52,7 @@ export class ArticleCenter extends FormLike {
|
|
|
53
52
|
player,
|
|
54
53
|
this.title,
|
|
55
54
|
this.body,
|
|
56
|
-
this.getAvailableArticles()
|
|
55
|
+
this.getAvailableArticles(player)
|
|
57
56
|
);
|
|
58
57
|
|
|
59
58
|
if (!contentForm) {
|
|
@@ -76,9 +75,8 @@ export class ArticleCenter extends FormLike {
|
|
|
76
75
|
this.quit(player, backTo as readonly FormLike[]);
|
|
77
76
|
});
|
|
78
77
|
}
|
|
79
|
-
private getAvailableArticles(): ReadableArticle[] {
|
|
80
|
-
if (
|
|
81
|
-
if (!this.articles) return [];
|
|
78
|
+
private getAvailableArticles(player: Player): ReadableArticle[] {
|
|
79
|
+
if (this.articleSource === "registry") {
|
|
82
80
|
if (!this.bindTo) {
|
|
83
81
|
throw new OccultusSDKError(
|
|
84
82
|
"You must bind article registries to the center when using the boolean parameter!"
|
|
@@ -86,7 +84,10 @@ export class ArticleCenter extends FormLike {
|
|
|
86
84
|
}
|
|
87
85
|
return this.bindTo.getAll();
|
|
88
86
|
}
|
|
89
|
-
|
|
87
|
+
if (this.articleSource === "auto") {
|
|
88
|
+
return autoGetArticles(player);
|
|
89
|
+
}
|
|
90
|
+
return this.articleSource;
|
|
90
91
|
}
|
|
91
92
|
/**
|
|
92
93
|
* 当没有可用的文章时,向玩家显示警告
|
|
@@ -1,21 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { StartupEvent, system } from "@minecraft/server";
|
|
2
2
|
import { ArticleBindingConfig } from "./BindingConfig";
|
|
3
3
|
import { OccultusSDKError } from "@occultus/core";
|
|
4
|
-
import {
|
|
4
|
+
import { TextProvider } from "@occultus/text-api";
|
|
5
5
|
import { ChapterData } from "../../interface/ChapterData";
|
|
6
6
|
import { ReadableArticle } from "../../types/ReadableArticle";
|
|
7
|
-
import { Article } from "../Article";
|
|
8
|
-
import {
|
|
9
|
-
ArticleCenterComponentParams,
|
|
10
|
-
ArticleComponentParams,
|
|
11
|
-
ArticleContentParams
|
|
12
|
-
} from "./ComponentParams";
|
|
13
7
|
import { ArticleCenter } from "../ArticleCenter";
|
|
8
|
+
import {
|
|
9
|
+
getArticle,
|
|
10
|
+
} from "../../lib/utils";
|
|
14
11
|
|
|
15
12
|
/**
|
|
16
13
|
* 文章内容类型别名
|
|
17
14
|
*/
|
|
18
|
-
type ArticleContent =
|
|
15
|
+
export type ArticleContent =
|
|
19
16
|
| TextProvider
|
|
20
17
|
| { chapter: ChapterData[]; description: TextProvider };
|
|
21
18
|
|
|
@@ -24,32 +21,16 @@ export class ArticleServerBindings {
|
|
|
24
21
|
private static center: ArticleCenter | undefined;
|
|
25
22
|
private preloadedContent: Map<string, ArticleContent> = new Map();
|
|
26
23
|
private articles: Map<string, ReadableArticle> = new Map();
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (typeof params === "string") {
|
|
38
|
-
return this.preloadedContent.get(params);
|
|
39
|
-
}
|
|
40
|
-
return params;
|
|
41
|
-
}
|
|
42
|
-
private parseDescription(text: ArticleContent | RawMessage[]): TextProvider {
|
|
43
|
-
if (typeof text === "string") return text;
|
|
44
|
-
if (Array.isArray(text)) {
|
|
45
|
-
return {
|
|
46
|
-
rawtext: text
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
if ("description" in text) {
|
|
50
|
-
return text.description;
|
|
51
|
-
}
|
|
52
|
-
return text;
|
|
24
|
+
private initCenter(config: ArticleBindingConfig) {
|
|
25
|
+
const center = new ArticleCenter(
|
|
26
|
+
"starock:bindings.article_center",
|
|
27
|
+
config.centerConfig.title,
|
|
28
|
+
config.centerConfig.description,
|
|
29
|
+
"auto",
|
|
30
|
+
undefined,
|
|
31
|
+
config.centerConfig.icon_path
|
|
32
|
+
);
|
|
33
|
+
ArticleServerBindings.center = center;
|
|
53
34
|
}
|
|
54
35
|
private registryArticleComponent(
|
|
55
36
|
event: StartupEvent,
|
|
@@ -57,38 +38,14 @@ export class ArticleServerBindings {
|
|
|
57
38
|
) {
|
|
58
39
|
const self = this;
|
|
59
40
|
event.itemComponentRegistry.registerCustomComponent(config.componentName, {
|
|
60
|
-
onUse(event
|
|
41
|
+
onUse(event) {
|
|
61
42
|
const { itemStack, source } = event;
|
|
62
|
-
const params = componentParams.params as ArticleComponentParams;
|
|
63
43
|
if (!itemStack) return;
|
|
64
|
-
// 该文章已经被注册
|
|
65
44
|
if (self.articles.has(itemStack.typeId)) {
|
|
66
45
|
self.articles.get(itemStack.typeId)?.display(source, []);
|
|
67
46
|
return;
|
|
68
47
|
}
|
|
69
|
-
|
|
70
|
-
const content = self.getArticleContent(itemStack, config);
|
|
71
|
-
let article: ReadableArticle;
|
|
72
|
-
if (!content) return;
|
|
73
|
-
if (typeof content !== "string" && "chapter" in content) {
|
|
74
|
-
article = new Article(
|
|
75
|
-
itemStack.typeId,
|
|
76
|
-
params.title,
|
|
77
|
-
content.description,
|
|
78
|
-
content.chapter,
|
|
79
|
-
params.icon_path
|
|
80
|
-
);
|
|
81
|
-
self.articles.set(itemStack.typeId, article);
|
|
82
|
-
article.display(source, []);
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
article = new Article(
|
|
86
|
-
itemStack.typeId,
|
|
87
|
-
params.title,
|
|
88
|
-
self.parseDescription(content),
|
|
89
|
-
undefined,
|
|
90
|
-
params.icon_path
|
|
91
|
-
);
|
|
48
|
+
const article = getArticle(itemStack);
|
|
92
49
|
self.articles.set(itemStack.typeId, article);
|
|
93
50
|
article.display(source, []);
|
|
94
51
|
}
|
|
@@ -107,26 +64,14 @@ export class ArticleServerBindings {
|
|
|
107
64
|
event: StartupEvent,
|
|
108
65
|
config: ArticleBindingConfig
|
|
109
66
|
) {
|
|
110
|
-
const self = this;
|
|
111
67
|
event.itemComponentRegistry.registerCustomComponent(
|
|
112
68
|
config.centerComponentName,
|
|
113
69
|
{
|
|
114
|
-
onUse(event
|
|
70
|
+
onUse(event) {
|
|
115
71
|
const { itemStack, source } = event;
|
|
116
|
-
const params = componentParams.params as ArticleCenterComponentParams;
|
|
117
72
|
if (!itemStack) return;
|
|
118
73
|
const center = ArticleServerBindings.center;
|
|
119
74
|
if (!center) {
|
|
120
|
-
const newCenter = new ArticleCenter(
|
|
121
|
-
itemStack.typeId,
|
|
122
|
-
params.title,
|
|
123
|
-
params.description,
|
|
124
|
-
Array.from(self.articles.values()),
|
|
125
|
-
undefined,
|
|
126
|
-
params.icon_path
|
|
127
|
-
);
|
|
128
|
-
ArticleServerBindings.center = newCenter;
|
|
129
|
-
newCenter.display(source, []);
|
|
130
75
|
return;
|
|
131
76
|
}
|
|
132
77
|
center.display(source, []);
|
|
@@ -141,6 +86,7 @@ export class ArticleServerBindings {
|
|
|
141
86
|
system.beforeEvents.startup.subscribe((event) => {
|
|
142
87
|
this.registryArticleComponent(event, config);
|
|
143
88
|
this.registryArticleContentComponent(event, config);
|
|
89
|
+
this.initCenter(config);
|
|
144
90
|
this.registryArticleCenterContent(event, config);
|
|
145
91
|
});
|
|
146
92
|
}
|
|
@@ -179,4 +125,15 @@ export class ArticleServerBindings {
|
|
|
179
125
|
}
|
|
180
126
|
this.preloadedContent.set(id, content);
|
|
181
127
|
}
|
|
128
|
+
readPreloadedContent(
|
|
129
|
+
id: string
|
|
130
|
+
):
|
|
131
|
+
| TextProvider
|
|
132
|
+
| { chapter: ChapterData[]; description: TextProvider }
|
|
133
|
+
| undefined {
|
|
134
|
+
return this.preloadedContent.get(id);
|
|
135
|
+
}
|
|
136
|
+
getArticle(id: string): ReadableArticle | undefined {
|
|
137
|
+
return this.articles.get(id);
|
|
138
|
+
}
|
|
182
139
|
}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
import { RawMessage } from "@minecraft/server";
|
|
2
|
+
|
|
1
3
|
export type ArticleBindingConfig = {
|
|
2
4
|
componentName: string;
|
|
3
5
|
centerComponentName: string;
|
|
4
6
|
contentComponentName: string;
|
|
7
|
+
centerConfig: {
|
|
8
|
+
title: RawMessage | string;
|
|
9
|
+
description: RawMessage | string;
|
|
10
|
+
icon_path?: string;
|
|
11
|
+
};
|
|
5
12
|
};
|
|
@@ -7,8 +7,4 @@ export type ArticleComponentParams = {
|
|
|
7
7
|
|
|
8
8
|
export type ArticleContentParams = RawMessage[] | string;
|
|
9
9
|
|
|
10
|
-
export type ArticleCenterComponentParams = {
|
|
11
|
-
title: RawMessage | string;
|
|
12
|
-
description: RawMessage | string;
|
|
13
|
-
icon_path?: string;
|
|
14
|
-
};
|
|
10
|
+
export type ArticleCenterComponentParams = {};
|
package/src/lib/utils.ts
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
|
-
import { Player, StartupEvent } from "@minecraft/server";
|
|
1
|
+
import { ItemStack, Player, RawMessage, StartupEvent } from "@minecraft/server";
|
|
2
2
|
import { ActionFormData } from "@minecraft/server-ui";
|
|
3
3
|
import { Article } from "../api/Article";
|
|
4
4
|
import { ReadableArticle } from "../types/ReadableArticle";
|
|
5
5
|
import { TextProvider, parseText } from "@occultus/text-api";
|
|
6
|
+
import { ArticleBindingConfig } from "../api/Bindings/BindingConfig";
|
|
7
|
+
import {
|
|
8
|
+
ArticleComponentParams,
|
|
9
|
+
ArticleContentParams
|
|
10
|
+
} from "../api/Bindings/ComponentParams";
|
|
11
|
+
import {
|
|
12
|
+
ArticleContent,
|
|
13
|
+
ArticleServerBindings
|
|
14
|
+
} from "../api/Bindings/ArticleServerBindings";
|
|
15
|
+
import { ArticleCenter } from "../api/ArticleCenter";
|
|
16
|
+
import { OccultusSDKError } from "@occultus/core";
|
|
6
17
|
|
|
7
18
|
/**
|
|
8
19
|
* 根据提供的资源生成目录表单
|
|
@@ -57,3 +68,79 @@ export function articleRegister(
|
|
|
57
68
|
}
|
|
58
69
|
});
|
|
59
70
|
}
|
|
71
|
+
|
|
72
|
+
export function parseDescription(
|
|
73
|
+
text: ArticleContent | RawMessage[]
|
|
74
|
+
): TextProvider {
|
|
75
|
+
if (typeof text === "string") return text;
|
|
76
|
+
if (Array.isArray(text)) {
|
|
77
|
+
return {
|
|
78
|
+
rawtext: text
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if ("description" in text) {
|
|
82
|
+
return text.description;
|
|
83
|
+
}
|
|
84
|
+
return text;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function getArticleContent(item: ItemStack) {
|
|
88
|
+
const config = ArticleServerBindings.getInstance().config;
|
|
89
|
+
const params = item.getComponent(config.contentComponentName)
|
|
90
|
+
?.customComponentParameters.params as ArticleContentParams;
|
|
91
|
+
if (!params) return;
|
|
92
|
+
if (typeof params === "string") {
|
|
93
|
+
return ArticleServerBindings.getInstance().readPreloadedContent(params);
|
|
94
|
+
}
|
|
95
|
+
return params;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function getArticle(item: ItemStack): Article {
|
|
99
|
+
const config = ArticleServerBindings.getInstance().config;
|
|
100
|
+
const mainParams = item.getComponent(config.componentName)
|
|
101
|
+
?.customComponentParameters.params as ArticleComponentParams;
|
|
102
|
+
const content = getArticleContent(item);
|
|
103
|
+
let article: Article | undefined;
|
|
104
|
+
if (!content){
|
|
105
|
+
throw new OccultusSDKError("Article content not found!");
|
|
106
|
+
}
|
|
107
|
+
if (typeof content !== "string" && "chapter" in content) {
|
|
108
|
+
article = new Article(
|
|
109
|
+
item.typeId,
|
|
110
|
+
mainParams.title,
|
|
111
|
+
content.description,
|
|
112
|
+
content.chapter,
|
|
113
|
+
mainParams.icon_path
|
|
114
|
+
);
|
|
115
|
+
return article;
|
|
116
|
+
}
|
|
117
|
+
article = new Article(
|
|
118
|
+
item.typeId,
|
|
119
|
+
mainParams.title,
|
|
120
|
+
parseDescription(content),
|
|
121
|
+
undefined,
|
|
122
|
+
mainParams.icon_path
|
|
123
|
+
);
|
|
124
|
+
return article;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* 自动获得文章
|
|
129
|
+
* @param player
|
|
130
|
+
*/
|
|
131
|
+
export function autoGetArticles(player: Player): ReadableArticle[] {
|
|
132
|
+
try {
|
|
133
|
+
const raw = player.getDynamicProperty(Article.DYNAMIC_DATA_ID);
|
|
134
|
+
if (!raw || typeof raw !== "string") return [];
|
|
135
|
+
const json = JSON.parse(raw);
|
|
136
|
+
if (!Array.isArray(json)) return [];
|
|
137
|
+
return json.map(
|
|
138
|
+
(id: string) =>
|
|
139
|
+
ArticleServerBindings.getInstance().getArticle(id) ??
|
|
140
|
+
getArticle(new ItemStack(id))
|
|
141
|
+
);
|
|
142
|
+
} catch (e) {
|
|
143
|
+
console.error("Failed to get articles from player:" + e);
|
|
144
|
+
}
|
|
145
|
+
return [];
|
|
146
|
+
}
|