@zhin.js/plugin-text-adventure 1.1.15 → 1.1.16
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/commands/adv/[[action]]/index.js +4 -4
- package/commands/adv/[[action]]/index.ts +4 -4
- package/lib/achievements.d.ts +4 -1
- package/lib/achievements.js +18 -17
- package/lib/achievements.js.map +1 -1
- package/lib/adv-command.d.ts +1 -0
- package/lib/adv-command.js +31 -20
- package/lib/adv-command.js.map +1 -1
- package/lib/game-flow.d.ts +1 -1
- package/lib/game-flow.js +50 -21
- package/lib/game-flow.js.map +1 -1
- package/lib/models.d.ts +4 -1
- package/lib/models.js +2 -1
- package/lib/models.js.map +1 -1
- package/lib/profile-format.d.ts +4 -3
- package/lib/profile-format.js +14 -13
- package/lib/profile-format.js.map +1 -1
- package/lib/scene-view.js +5 -3
- package/lib/scene-view.js.map +1 -1
- package/lib/session-service.js +1 -0
- package/lib/session-service.js.map +1 -1
- package/lib/story-catalog.d.ts +4 -2
- package/lib/story-catalog.js +59 -9
- package/lib/story-catalog.js.map +1 -1
- package/lib/story-items.d.ts +2 -1
- package/lib/story-items.js +19 -2
- package/lib/story-items.js.map +1 -1
- package/lib/story-scenes.d.ts +2 -1
- package/lib/story-scenes.js +1358 -464
- package/lib/story-scenes.js.map +1 -1
- package/lib/story.d.ts +4 -3
- package/lib/story.js +8 -8
- package/lib/story.js.map +1 -1
- package/middlewares/adv-alias/index.js +5 -3
- package/middlewares/adv-alias/index.ts +5 -3
- package/package.json +7 -7
- package/plugin.js +4 -0
- package/src/achievements.ts +21 -17
- package/src/adv-command.ts +35 -24
- package/src/game-flow.ts +51 -22
- package/src/models.ts +4 -2
- package/src/profile-format.ts +15 -13
- package/src/scene-view.ts +5 -3
- package/src/session-service.ts +1 -0
- package/src/story-catalog.ts +63 -10
- package/src/story-items.ts +21 -2
- package/src/story-scenes.ts +1103 -206
- package/src/story.ts +9 -8
package/src/game-flow.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { createMessages, resolveLocale } from 'zhin.js/i18n';
|
|
2
|
+
import { gameCommandHint, type GameMessageLike, type GameReply } from '@zhin.js/game-kit';
|
|
3
|
+
import { regionLabel, endingLabel } from './story-catalog.js';
|
|
4
|
+
import { itemLabel } from './story-items.js';
|
|
2
5
|
import type { AdvProfileRow, AdvSessionRow } from './models.js';
|
|
3
6
|
import { formatNewAchievements } from './profile-format.js';
|
|
4
7
|
import { buildSceneInteractive } from './scene-view.js';
|
|
@@ -22,7 +25,7 @@ export async function sendOrEditScene(
|
|
|
22
25
|
?? await services.profiles.getOrCreate(session.player_id, session.player_name);
|
|
23
26
|
const content = buildSceneInteractive(session, extraNarrative, prof, message.$channel.type);
|
|
24
27
|
if (!content) {
|
|
25
|
-
return
|
|
28
|
+
return messages(resolveLocale(session.locale), 'invalidScene');
|
|
26
29
|
}
|
|
27
30
|
return content;
|
|
28
31
|
}
|
|
@@ -35,9 +38,11 @@ export async function startAdventure(
|
|
|
35
38
|
const active = await services.sessions.getActiveByChannel(ch);
|
|
36
39
|
if (active) {
|
|
37
40
|
if (active.player_id === message.$sender.id) {
|
|
38
|
-
|
|
41
|
+
const locale = resolveLocale(active.locale);
|
|
42
|
+
const command = message.formatCommand?.('adv');
|
|
43
|
+
return messages(locale, 'active') + (command === undefined ? '' : messages(locale, 'activeHint', { command }));
|
|
39
44
|
}
|
|
40
|
-
return
|
|
45
|
+
return messages(resolveLocale(message.locale), 'channelBusy', { player: active.player_name });
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
await services.profiles.onRunStarted(message.$sender.id, message.$sender.name?.trim() || message.$sender.id);
|
|
@@ -58,25 +63,27 @@ export async function handleChoice(
|
|
|
58
63
|
choiceId: string,
|
|
59
64
|
): Promise<GameReply | null> {
|
|
60
65
|
const session = await services.sessions.getById(sessionId);
|
|
66
|
+
const locale = resolveLocale(session ? session.locale : message.locale);
|
|
61
67
|
// 踏进 terminal 场景时 status 必为 completed,终局需放行「再玩一次」(restart)
|
|
62
68
|
if (!session || (session.status !== 'active' && !(session.status === 'completed' && choiceId === 'restart'))) {
|
|
63
|
-
return '
|
|
69
|
+
return messages(locale, 'ended');
|
|
64
70
|
}
|
|
65
71
|
if (session.player_id !== message.$sender.id) {
|
|
66
|
-
return '
|
|
72
|
+
return messages(locale, 'otherPlayer');
|
|
67
73
|
}
|
|
68
74
|
|
|
69
75
|
const state = stateFromSession(session);
|
|
70
76
|
const scene = getScene(state.sceneId);
|
|
71
|
-
if (!scene) return
|
|
77
|
+
if (!scene) return messages(locale, 'invalidScene');
|
|
72
78
|
|
|
73
79
|
if (scene.terminal && choiceId !== 'restart') {
|
|
74
|
-
return
|
|
80
|
+
return messages(locale, 'restart');
|
|
75
81
|
}
|
|
76
82
|
|
|
77
83
|
if (choiceId === 'restart') {
|
|
78
84
|
await services.sessions.updateSession(session.id, {
|
|
79
85
|
scene_id: 'start',
|
|
86
|
+
locale: resolveLocale(message.locale),
|
|
80
87
|
hp: 100,
|
|
81
88
|
inventory: '[]',
|
|
82
89
|
flags: '{}',
|
|
@@ -92,11 +99,11 @@ export async function handleChoice(
|
|
|
92
99
|
|
|
93
100
|
// 服务端校验:选项必须当前可见(requires 门槛不能只靠视图层过滤,payload 可绕过)
|
|
94
101
|
if (!visibleChoices(scene, state).some((c) => c.id === choiceId)) {
|
|
95
|
-
return '
|
|
102
|
+
return messages(locale, 'unavailable');
|
|
96
103
|
}
|
|
97
104
|
|
|
98
105
|
const result = resolveChoice(state, choiceId);
|
|
99
|
-
if (!result) return '
|
|
106
|
+
if (!result) return messages(locale, 'unavailable');
|
|
100
107
|
|
|
101
108
|
const next = applyChoiceResult(state, result);
|
|
102
109
|
const nextScene = getScene(next.sceneId);
|
|
@@ -139,7 +146,7 @@ export async function handleChoice(
|
|
|
139
146
|
}
|
|
140
147
|
|
|
141
148
|
const updated = (await services.sessions.getById(session.id))!;
|
|
142
|
-
const extra = formatNewAchievements(newAchievements);
|
|
149
|
+
const extra = formatNewAchievements(newAchievements, locale);
|
|
143
150
|
return sendOrEditScene(services, message, updated, extra);
|
|
144
151
|
}
|
|
145
152
|
|
|
@@ -151,23 +158,45 @@ export async function continueAdventure(
|
|
|
151
158
|
`${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`,
|
|
152
159
|
message.$sender.id,
|
|
153
160
|
);
|
|
154
|
-
if (!session) return '
|
|
161
|
+
if (!session) return messages(resolveLocale(message.locale), 'noGame') + gameCommandHint(message, 'adv');
|
|
155
162
|
return sendOrEditScene(services, message, session);
|
|
156
163
|
}
|
|
157
164
|
|
|
158
165
|
export function sessionSummary(session: AdvSessionRow): string {
|
|
159
|
-
const
|
|
166
|
+
const locale = resolveLocale(session.locale);
|
|
160
167
|
const state = stateFromSession(session);
|
|
161
|
-
const items = state.inventory;
|
|
162
168
|
const lines = [
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
169
|
+
messages(locale, 'summary', {
|
|
170
|
+
id: session.id,
|
|
171
|
+
player: session.player_name,
|
|
172
|
+
scene: regionLabel(session.scene_id, locale),
|
|
173
|
+
hp: session.hp,
|
|
174
|
+
items: state.inventory.length ? state.inventory.map((id) => itemLabel(id, locale)).join(', ') : messages(locale, 'none'),
|
|
175
|
+
steps: session.step_count,
|
|
176
|
+
status: messages(locale, session.status === 'active' ? 'activeStatus' : session.status === 'completed' ? 'completedStatus' : 'abortedStatus'),
|
|
177
|
+
}),
|
|
170
178
|
];
|
|
171
|
-
if (session.ending_id) lines.push(
|
|
179
|
+
if (session.ending_id) lines.push(messages(locale, 'ending', { ending: endingLabel(session.ending_id, locale) }));
|
|
172
180
|
return lines.join('\n');
|
|
173
181
|
}
|
|
182
|
+
|
|
183
|
+
const messages = createMessages({
|
|
184
|
+
invalidScene: { 'zh-CN': '场景数据异常,请结束当前冒险后重新开始。', en: 'The scene is unavailable. End this adventure and start again.' },
|
|
185
|
+
active: { 'zh-CN': '你已有进行中的冒险。', en: 'You already have an active adventure.' },
|
|
186
|
+
activeHint: { 'zh-CN': '发送「{command} continue」继续,或「{command} quit」放弃。', en: 'Send “{command} continue” to continue or “{command} quit” to leave.' },
|
|
187
|
+
channelBusy: { 'zh-CN': '本频道已有玩家 {player} 的冒险进行中。', en: '{player} already has an adventure in progress in this channel.' },
|
|
188
|
+
ended: { 'zh-CN': '冒险不存在或已结束。', en: 'This adventure does not exist or has ended.' },
|
|
189
|
+
otherPlayer: { 'zh-CN': '这是别人的冒险。', en: 'This adventure belongs to another player.' },
|
|
190
|
+
restart: { 'zh-CN': '本局已结束,请点击「再玩一次」开始新冒险。', en: 'This adventure has ended. Select “Play again” to start a new one.' },
|
|
191
|
+
unavailable: { 'zh-CN': '该选项不可用。', en: 'That choice is not available.' },
|
|
192
|
+
noGame: { 'zh-CN': '你没有进行中的冒险。', en: 'You have no active adventure.' },
|
|
193
|
+
none: { 'zh-CN': '无', en: 'None' },
|
|
194
|
+
activeStatus: { 'zh-CN': '进行中', en: 'Active' },
|
|
195
|
+
completedStatus: { 'zh-CN': '已完成', en: 'Completed' },
|
|
196
|
+
abortedStatus: { 'zh-CN': '已放弃', en: 'Abandoned' },
|
|
197
|
+
summary: {
|
|
198
|
+
'zh-CN': '冒险 {id}\n玩家:{player}\n场景:{scene}\n生命:{hp}\n物品:{items}\n步数:{steps}\n状态:{status}',
|
|
199
|
+
en: 'Adventure {id}\nPlayer: {player}\nScene: {scene}\nHealth: {hp}\nItems: {items}\nSteps: {steps}\nStatus: {status}',
|
|
200
|
+
},
|
|
201
|
+
ending: { 'zh-CN': '结局:{ending}', en: 'Ending: {ending}' },
|
|
202
|
+
});
|
package/src/models.ts
CHANGED
|
@@ -6,6 +6,7 @@ declare module '@zhin.js/core' {
|
|
|
6
6
|
interface Models {
|
|
7
7
|
adv_sessions: {
|
|
8
8
|
id: string;
|
|
9
|
+
locale?: 'zh-CN' | 'en' | null;
|
|
9
10
|
adapter: string;
|
|
10
11
|
endpoint: string;
|
|
11
12
|
channel_type: string;
|
|
@@ -46,10 +47,11 @@ export type AdvModelName = 'adv_sessions' | 'adv_profiles';
|
|
|
46
47
|
|
|
47
48
|
|
|
48
49
|
export function defineHostTables(
|
|
49
|
-
db: { define: (name: string, definition: Record<string, unknown
|
|
50
|
+
db: { define: (name: string, definition: Record<string, unknown>, options?: { readonly addColumns?: readonly string[] }) => void },
|
|
50
51
|
): void {
|
|
51
52
|
db.define('adv_sessions', {
|
|
52
53
|
id: { type: 'text', primary: true },
|
|
54
|
+
locale: { type: 'text', nullable: true },
|
|
53
55
|
adapter: { type: 'text', nullable: false },
|
|
54
56
|
endpoint: { type: 'text', nullable: false },
|
|
55
57
|
channel_type: { type: 'text', nullable: false },
|
|
@@ -66,7 +68,7 @@ export function defineHostTables(
|
|
|
66
68
|
step_count: { type: 'integer', default: 0 },
|
|
67
69
|
updated_at: { type: 'bigint', default: 0 },
|
|
68
70
|
created_at: { type: 'bigint', default: 0 },
|
|
69
|
-
});
|
|
71
|
+
}, { addColumns: ['locale'] });
|
|
70
72
|
db.define('adv_profiles', {
|
|
71
73
|
player_id: { type: 'text', primary: true },
|
|
72
74
|
player_name: { type: 'text', default: '' },
|
package/src/profile-format.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Locale } from 'zhin.js/i18n';
|
|
1
2
|
import { achievementById, ACHIEVEMENTS, totalAchievements } from './achievements.js';
|
|
2
3
|
import {
|
|
3
4
|
endingIds,
|
|
@@ -18,45 +19,46 @@ export function formatProgressCompact(profile: AdvProfileRow): string {
|
|
|
18
19
|
return `🗺️ ${visited}/${totalRegions} · 🎬 ${endings}/${totalEndings} · 🏅 ${achievements}/${totalAchievements()}`;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
export function formatMapProgress(profile: AdvProfileRow): string {
|
|
22
|
+
export function formatMapProgress(profile: AdvProfileRow, locale: Locale = 'zh-CN'): string {
|
|
22
23
|
const visited = parseStringSet(profile.visited_scenes);
|
|
23
24
|
const total = playableSceneIds().length;
|
|
24
25
|
const pct = total ? Math.round((visited.size / total) * 100) : 0;
|
|
25
26
|
const lines = [
|
|
26
|
-
`🗺️
|
|
27
|
+
`🗺️ **${locale === 'en' ? 'Exploration' : '探索进度'}** ${visited.size}/${total} (${pct}%)`,
|
|
27
28
|
'',
|
|
28
29
|
];
|
|
29
30
|
|
|
30
31
|
for (const zone of MAP_ZONES) {
|
|
31
32
|
const parts = zone.sceneIds.map((id) => {
|
|
32
33
|
const mark = visited.has(id) ? '✅' : '⬜';
|
|
33
|
-
return `${mark} ${regionLabel(id)}`;
|
|
34
|
+
return `${mark} ${regionLabel(id, locale)}`;
|
|
34
35
|
});
|
|
35
|
-
lines.push(`**${zone.name}**`);
|
|
36
|
+
lines.push(`**${locale === 'en' ? zone.nameEn : zone.name}**`);
|
|
36
37
|
lines.push(parts.join(' '));
|
|
37
38
|
lines.push('');
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
const endings = parseStringSet(profile.endings_seen);
|
|
41
42
|
const allEndings = endingIds();
|
|
42
|
-
lines.push(
|
|
43
|
+
lines.push(`**${locale === 'en' ? 'Endings' : '结局图鉴'}** ${endings.size}/${allEndings.length}`);
|
|
43
44
|
lines.push(
|
|
44
45
|
allEndings
|
|
45
|
-
.map((id) => `${endings.has(id) ? '✅' : '⬜'} ${endingLabel(id)}`)
|
|
46
|
+
.map((id) => `${endings.has(id) ? '✅' : '⬜'} ${endingLabel(id, locale)}`)
|
|
46
47
|
.join(' '),
|
|
47
48
|
);
|
|
48
49
|
return lines.join('\n').trimEnd();
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
export function formatAchievements(profile: AdvProfileRow, showHidden = false): string {
|
|
52
|
+
export function formatAchievements(profile: AdvProfileRow, showHidden = false, locale: Locale = 'zh-CN'): string {
|
|
52
53
|
const unlocked = parseStringSet(profile.achievements);
|
|
53
54
|
const lines = [
|
|
54
|
-
`🏅
|
|
55
|
-
`完成局数:${profile.runs_completed} · 生涯步数:${profile.total_steps}`,
|
|
55
|
+
`🏅 **${locale === 'en' ? 'Achievements' : '成就'}** ${unlocked.size}/${totalAchievements()}`,
|
|
56
|
+
locale === 'en' ? `Completed adventures: ${profile.runs_completed} · Lifetime steps: ${profile.total_steps}` : `完成局数:${profile.runs_completed} · 生涯步数:${profile.total_steps}`,
|
|
56
57
|
'',
|
|
57
58
|
];
|
|
58
59
|
|
|
59
|
-
for (const
|
|
60
|
+
for (const entry of ACHIEVEMENTS) {
|
|
61
|
+
const def = achievementById(entry.id, locale)!;
|
|
60
62
|
const got = unlocked.has(def.id);
|
|
61
63
|
if (def.hidden && !got && !showHidden) continue;
|
|
62
64
|
const mark = got ? '✅' : '⬜';
|
|
@@ -65,11 +67,11 @@ export function formatAchievements(profile: AdvProfileRow, showHidden = false):
|
|
|
65
67
|
return lines.join('\n');
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
export function formatNewAchievements(ids: string[]): string {
|
|
70
|
+
export function formatNewAchievements(ids: string[], locale: Locale = 'zh-CN'): string {
|
|
69
71
|
if (!ids.length) return '';
|
|
70
|
-
const lines = ['', '🏅 **新成就解锁!**'];
|
|
72
|
+
const lines = ['', locale === 'en' ? '🏅 **New achievement unlocked!**' : '🏅 **新成就解锁!**'];
|
|
71
73
|
for (const id of ids) {
|
|
72
|
-
const def = achievementById(id);
|
|
74
|
+
const def = achievementById(id, locale);
|
|
73
75
|
if (def) lines.push(`${def.icon} ${def.title} — ${def.description}`);
|
|
74
76
|
}
|
|
75
77
|
return lines.join('\n');
|
package/src/scene-view.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveLocale } from 'zhin.js/i18n';
|
|
1
2
|
import type { SendContent } from '@zhin.js/core/runtime';
|
|
2
3
|
import { buildChoiceKeyboard } from '@zhin.js/game-kit';
|
|
3
4
|
import type { AdvProfileRow, AdvSessionRow } from './models.js';
|
|
@@ -16,14 +17,15 @@ export function buildSceneInteractive(
|
|
|
16
17
|
profile?: AdvProfileRow,
|
|
17
18
|
channelType?: string,
|
|
18
19
|
): SendContent | null {
|
|
19
|
-
const
|
|
20
|
+
const locale = resolveLocale(session.locale);
|
|
21
|
+
const scene = getScene(session.scene_id, locale);
|
|
20
22
|
if (!scene) return null;
|
|
21
23
|
|
|
22
24
|
const state = stateFromSession(session);
|
|
23
25
|
const choices = visibleChoices(scene, state);
|
|
24
26
|
const terminal = scene.terminal || session.status !== 'active';
|
|
25
27
|
|
|
26
|
-
let narrative = sceneNarrative(scene, state);
|
|
28
|
+
let narrative = sceneNarrative(scene, state, locale);
|
|
27
29
|
if (profile && !terminal) {
|
|
28
30
|
narrative = `${formatProgressCompact(profile)}\n\n${narrative}`;
|
|
29
31
|
}
|
|
@@ -42,7 +44,7 @@ export function buildSceneInteractive(
|
|
|
42
44
|
})),
|
|
43
45
|
terminal,
|
|
44
46
|
buttonsPerRow: 2,
|
|
45
|
-
fallbackHint: '回复数字选择(仅编号)',
|
|
47
|
+
fallbackHint: locale === 'en' ? 'Reply with the choice number only' : '回复数字选择(仅编号)',
|
|
46
48
|
interactionProfile: terminal ? 'terminal' : 'gameplay',
|
|
47
49
|
channelType,
|
|
48
50
|
});
|
package/src/session-service.ts
CHANGED
|
@@ -37,6 +37,7 @@ export class SessionService extends BaseSessionService<AdvSessionRow> {
|
|
|
37
37
|
const id = generateSessionId();
|
|
38
38
|
const row: AdvSessionRow = {
|
|
39
39
|
id,
|
|
40
|
+
locale: message.locale ?? 'zh-CN',
|
|
40
41
|
adapter: String(message.$adapter),
|
|
41
42
|
endpoint: message.$endpoint,
|
|
42
43
|
channel_type: message.$channel.type,
|
package/src/story-catalog.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Locale } from 'zhin.js/i18n';
|
|
1
2
|
import { SCENES } from './story-scenes.js';
|
|
2
3
|
|
|
3
4
|
/** 区域中文名(用于地图 / 成就展示) */
|
|
@@ -54,25 +55,25 @@ export const ENDING_LABELS: Record<string, string> = {
|
|
|
54
55
|
};
|
|
55
56
|
|
|
56
57
|
/** 地图分区(便于 adv map 分组展示) */
|
|
57
|
-
export const MAP_ZONES: Array<{ name: string; sceneIds: string[] }> = [
|
|
58
|
+
export const MAP_ZONES: Array<{ name: string; nameEn: string; sceneIds: string[] }> = [
|
|
58
59
|
{
|
|
59
|
-
name: '入口与塔',
|
|
60
|
+
name: '入口与塔', nameEn: 'Entrance and Tower',
|
|
60
61
|
sceneIds: ['start', 'rubble', 'watchtower', 'observatory', 'vine_path'],
|
|
61
62
|
},
|
|
62
63
|
{
|
|
63
|
-
name: '主廊与泉',
|
|
64
|
+
name: '主廊与泉', nameEn: 'Corridor and Spring',
|
|
64
65
|
sceneIds: ['corridor', 'mural_room', 'whisper_gallery', 'spring', 'flooded_hall', 'bell_tower'],
|
|
65
66
|
},
|
|
66
67
|
{
|
|
67
|
-
name: '庭院书库',
|
|
68
|
+
name: '庭院书库', nameEn: 'Garden and Library',
|
|
68
69
|
sceneIds: ['garden', 'greenhouse', 'library', 'ember_forge'],
|
|
69
70
|
},
|
|
70
71
|
{
|
|
71
|
-
name: '冥域水线',
|
|
72
|
+
name: '冥域水线', nameEn: 'River of the Dead',
|
|
72
73
|
sceneIds: ['market_ruins', 'ghost_bridge', 'crystal_cave', 'moon_pool', 'sunken_shrine'],
|
|
73
74
|
},
|
|
74
75
|
{
|
|
75
|
-
name: '墓穴深处',
|
|
76
|
+
name: '墓穴深处', nameEn: 'Deep Tombs',
|
|
76
77
|
sceneIds: [
|
|
77
78
|
'depths',
|
|
78
79
|
'crypt_entry',
|
|
@@ -101,10 +102,62 @@ export function endingIds(): string[] {
|
|
|
101
102
|
.map((s) => s.id);
|
|
102
103
|
}
|
|
103
104
|
|
|
104
|
-
export function regionLabel(id: string): string {
|
|
105
|
-
return REGION_LABELS[id] ??
|
|
105
|
+
export function regionLabel(id: string, locale: Locale = 'zh-CN'): string {
|
|
106
|
+
return (locale === 'en' ? EN_REGION_LABELS : REGION_LABELS)[id] ?? endingLabel(id, locale);
|
|
106
107
|
}
|
|
107
108
|
|
|
108
|
-
export function endingLabel(id: string): string {
|
|
109
|
-
return ENDING_LABELS[id] ?? id;
|
|
109
|
+
export function endingLabel(id: string, locale: Locale = 'zh-CN'): string {
|
|
110
|
+
return (locale === 'en' ? EN_ENDING_LABELS : ENDING_LABELS)[id] ?? id;
|
|
110
111
|
}
|
|
112
|
+
|
|
113
|
+
const EN_REGION_LABELS: Readonly<Record<string,string>> = {
|
|
114
|
+
"start": "Entrance to the Ruins",
|
|
115
|
+
"rubble": "Rubble Passage",
|
|
116
|
+
"watchtower": "Watchtower",
|
|
117
|
+
"observatory": "Observatory",
|
|
118
|
+
"vine_path": "Hidden Path",
|
|
119
|
+
"corridor": "Dark Corridor",
|
|
120
|
+
"mural_room": "Inscribed Mural",
|
|
121
|
+
"whisper_gallery": "Whispering Gallery",
|
|
122
|
+
"spring": "Spirit Spring",
|
|
123
|
+
"flooded_hall": "Flooded Hall",
|
|
124
|
+
"bell_tower": "Bell Tower",
|
|
125
|
+
"garden": "Ancient Garden",
|
|
126
|
+
"greenhouse": "Greenhouse",
|
|
127
|
+
"library": "Dusty Library",
|
|
128
|
+
"ember_forge": "Forge",
|
|
129
|
+
"market_ruins": "Ruined Market",
|
|
130
|
+
"ghost_bridge": "Ghost Bridge",
|
|
131
|
+
"crystal_cave": "Crystal Cave",
|
|
132
|
+
"moon_pool": "Moon Pool",
|
|
133
|
+
"sunken_shrine": "Sunken Shrine",
|
|
134
|
+
"depths": "Tomb Crossroads",
|
|
135
|
+
"crypt_entry": "Crypt Entrance",
|
|
136
|
+
"catacombs": "Catacombs",
|
|
137
|
+
"ossuary": "Ossuary",
|
|
138
|
+
"prison_cell": "Prison Cell",
|
|
139
|
+
"spider_nest": "Spider Nest",
|
|
140
|
+
"shadow_maze": "Shadow Maze",
|
|
141
|
+
"sealed_gates": "Sealed Chamber",
|
|
142
|
+
"vault": "Treasure Antechamber",
|
|
143
|
+
"vault_open": "Vault",
|
|
144
|
+
"throne_room": "Throne Room"
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const EN_ENDING_LABELS: Readonly<Record<string,string>> = {
|
|
148
|
+
"treasure": "Treasure of the Ruins",
|
|
149
|
+
"ascension": "Starlight Restored",
|
|
150
|
+
"wisdom": "A Scholar's Return",
|
|
151
|
+
"curse": "The Price of Greed",
|
|
152
|
+
"escape": "Survival",
|
|
153
|
+
"defeat": "Fallen",
|
|
154
|
+
"coward": "A Coward's Return",
|
|
155
|
+
"hermit": "Garden Hermit",
|
|
156
|
+
"redemption": "A Spirit Set Free",
|
|
157
|
+
"drowned": "Cold-Water Slumber",
|
|
158
|
+
"spider": "Soul in a Cocoon",
|
|
159
|
+
"labyrinth": "Lost in the Maze",
|
|
160
|
+
"star_keeper": "Star Keeper's Oath",
|
|
161
|
+
"mask_king": "King of Masks",
|
|
162
|
+
"merchant": "The Merchant's Ending"
|
|
163
|
+
};
|
package/src/story-items.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Locale } from 'zhin.js/i18n';
|
|
1
2
|
/** 15 种可收集道具 */
|
|
2
3
|
export const ITEM_LABELS: Record<string, string> = {
|
|
3
4
|
torch: '火把',
|
|
@@ -19,6 +20,24 @@ export const ITEM_LABELS: Record<string, string> = {
|
|
|
19
20
|
|
|
20
21
|
export const ITEM_IDS = Object.keys(ITEM_LABELS);
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
const EN_ITEM_LABELS: Readonly<Record<string, string>> = {
|
|
24
|
+
"torch": "Torch",
|
|
25
|
+
"key": "Rusty key",
|
|
26
|
+
"gem": "Blue gem",
|
|
27
|
+
"herb": "Luminous herb",
|
|
28
|
+
"scroll": "Torn scroll",
|
|
29
|
+
"amulet": "Elven amulet",
|
|
30
|
+
"silver_coin": "Ancient silver coin",
|
|
31
|
+
"rusted_blade": "Rusty blade",
|
|
32
|
+
"moon_shard": "Moon shard",
|
|
33
|
+
"elixir": "Healing elixir",
|
|
34
|
+
"compass": "Astral compass",
|
|
35
|
+
"spirit_bell": "Echo bell",
|
|
36
|
+
"elf_feather": "Silver feather",
|
|
37
|
+
"masquerade_mask": "Ritual mask",
|
|
38
|
+
"star_chart": "Star chart fragment"
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export function itemLabel(id: string, locale: Locale = 'zh-CN'): string {
|
|
42
|
+
return (locale === 'en' ? EN_ITEM_LABELS : ITEM_LABELS)[id] ?? id;
|
|
24
43
|
}
|