@zhin.js/plugin-dungeon-expedition 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/dungeon/[[action]]/index.js +4 -4
- package/commands/dungeon/[[action]]/index.ts +4 -4
- package/lib/dungeon-command.d.ts +1 -0
- package/lib/dungeon-command.js +22 -14
- package/lib/dungeon-command.js.map +1 -1
- package/lib/engine.d.ts +3 -2
- package/lib/engine.js +119 -70
- package/lib/engine.js.map +1 -1
- package/lib/game-flow.js +27 -13
- 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/session-service.js +3 -2
- package/lib/session-service.js.map +1 -1
- package/lib/view.d.ts +2 -1
- package/lib/view.js +65 -31
- package/lib/view.js.map +1 -1
- package/package.json +7 -7
- package/plugin.js +9 -1
- package/src/dungeon-command.ts +22 -18
- package/src/engine.ts +120 -59
- package/src/game-flow.ts +27 -14
- package/src/models.ts +4 -2
- package/src/session-service.ts +3 -1
- package/src/view.ts +67 -32
package/src/view.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createMessages, resolveLocale, type Locale } from 'zhin.js/i18n';
|
|
1
2
|
import type { SendContent } from '@zhin.js/core/runtime';
|
|
2
3
|
import {
|
|
3
4
|
buildChoiceKeyboard,
|
|
@@ -20,35 +21,35 @@ export function parseDungeonSessionToken(
|
|
|
20
21
|
return { sessionId: match[1], revision: Number(match[2]) };
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
export function choicesForState(state: Readonly<DungeonState
|
|
24
|
+
export function choicesForState(state: Readonly<DungeonState>, locale?: Locale): ChoiceOption[] {
|
|
24
25
|
if (state.phase === 'completed') {
|
|
25
26
|
return [{
|
|
26
27
|
id: 'restart',
|
|
27
|
-
label: '
|
|
28
|
+
label: messages(locale, 'text1'),
|
|
28
29
|
style: 'primary',
|
|
29
30
|
keepEnabledWhenTerminal: true,
|
|
30
31
|
}];
|
|
31
32
|
}
|
|
32
33
|
if (state.phase === 'lobby') {
|
|
33
34
|
return [
|
|
34
|
-
{ id: 'join', label: '
|
|
35
|
-
{ id: 'ready', label: '
|
|
36
|
-
{ id: 'start', label: '
|
|
37
|
-
{ id: 'abort', label: '
|
|
35
|
+
{ id: 'join', label: messages(locale, 'text2'), style: 'secondary' },
|
|
36
|
+
{ id: 'ready', label: messages(locale, 'text3'), style: 'secondary' },
|
|
37
|
+
{ id: 'start', label: messages(locale, 'text4'), style: 'primary' },
|
|
38
|
+
{ id: 'abort', label: messages(locale, 'text5'), style: 'danger' },
|
|
38
39
|
];
|
|
39
40
|
}
|
|
40
41
|
if (state.phase === 'combat') {
|
|
41
42
|
return [
|
|
42
|
-
{ id: 'attack', label: '
|
|
43
|
-
{ id: 'defend', label: '
|
|
44
|
-
{ id: 'potion', label: '
|
|
45
|
-
{ id: 'abort', label: '
|
|
43
|
+
{ id: 'attack', label: messages(locale, 'text6'), style: 'primary' },
|
|
44
|
+
{ id: 'defend', label: messages(locale, 'text7'), style: 'secondary' },
|
|
45
|
+
{ id: 'potion', label: messages(locale, 'text8'), style: 'secondary' },
|
|
46
|
+
{ id: 'abort', label: messages(locale, 'text9'), style: 'danger' },
|
|
46
47
|
];
|
|
47
48
|
}
|
|
48
49
|
return [
|
|
49
|
-
{ id: 'explore', label: '
|
|
50
|
-
{ id: 'potion', label: '
|
|
51
|
-
{ id: 'abort', label: '
|
|
50
|
+
{ id: 'explore', label: messages(locale, 'text10'), style: 'primary' },
|
|
51
|
+
{ id: 'potion', label: messages(locale, 'text11'), style: 'secondary' },
|
|
52
|
+
{ id: 'abort', label: messages(locale, 'text9'), style: 'danger' },
|
|
52
53
|
];
|
|
53
54
|
}
|
|
54
55
|
|
|
@@ -57,38 +58,39 @@ export function buildDungeonView(
|
|
|
57
58
|
state: Readonly<DungeonState>,
|
|
58
59
|
channelType?: string,
|
|
59
60
|
): SendContent {
|
|
61
|
+
const locale = resolveLocale(session.locale);
|
|
60
62
|
const current = activePlayer(state);
|
|
61
63
|
const lines = [
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
messages(locale, 'text12', { p0: state.floor, p1: 3, p2: state.room, p3: 4 }),
|
|
65
|
+
messages(locale, 'text13', { p0: phaseLabel(state, locale) }),
|
|
64
66
|
'',
|
|
65
67
|
...state.players.map((player, index) => {
|
|
66
|
-
const turn = current?.id === player.id && state.phase !== 'lobby' ?
|
|
67
|
-
const ready = state.phase === 'lobby' ? player.ready ?
|
|
68
|
+
const turn = current?.id === player.id && state.phase !== 'lobby' ? messages(locale, 'text14') : '';
|
|
69
|
+
const ready = state.phase === 'lobby' ? player.ready ? messages(locale, 'text15') : messages(locale, 'text16') : '';
|
|
68
70
|
return `${index + 1}. ${player.name}${ready} HP ${player.hp}/${player.maxHp}`
|
|
69
|
-
+
|
|
71
|
+
+ messages(locale, 'text17', { p0: player.potions, p1: player.gold, p2: turn });
|
|
70
72
|
}),
|
|
71
73
|
];
|
|
72
74
|
if (state.enemy) {
|
|
73
75
|
lines.push(
|
|
74
76
|
'',
|
|
75
|
-
`${state.enemy.boss ? '
|
|
76
|
-
|
|
77
|
+
`${state.enemy.boss ? messages(locale, 'text18') : messages(locale, 'text19')}:${state.enemy.name}`,
|
|
78
|
+
messages(locale, 'text20', { p0: state.enemy.hp, p1: state.enemy.maxHp, p2: state.enemy.attack }),
|
|
77
79
|
);
|
|
78
80
|
}
|
|
79
|
-
lines.push('', '
|
|
81
|
+
lines.push('', messages(locale, 'text21'), ...state.log.slice(-4).map((line) => `- ${line}`));
|
|
80
82
|
if (state.phase === 'completed') {
|
|
81
83
|
lines.push(
|
|
82
84
|
'',
|
|
83
85
|
state.result === 'victory'
|
|
84
|
-
? '
|
|
86
|
+
? messages(locale, 'text22')
|
|
85
87
|
: state.result === 'aborted'
|
|
86
|
-
? '
|
|
87
|
-
: '
|
|
88
|
+
? messages(locale, 'text23')
|
|
89
|
+
: messages(locale, 'text24'),
|
|
88
90
|
);
|
|
89
91
|
}
|
|
90
92
|
|
|
91
|
-
const choices = choicesForState(state);
|
|
93
|
+
const choices = choicesForState(state, locale);
|
|
92
94
|
return buildChoiceKeyboard({
|
|
93
95
|
gamePrefix: DUNGEON_PREFIX,
|
|
94
96
|
sessionId: dungeonSessionToken(session),
|
|
@@ -103,11 +105,44 @@ export function buildDungeonView(
|
|
|
103
105
|
});
|
|
104
106
|
}
|
|
105
107
|
|
|
106
|
-
function phaseLabel(state: Readonly<DungeonState
|
|
107
|
-
if (state.phase === 'lobby') return '
|
|
108
|
-
if (state.phase === 'exploring') return '
|
|
109
|
-
if (state.phase === 'combat') return '
|
|
110
|
-
if (state.result === 'victory') return '
|
|
111
|
-
if (state.result === 'aborted') return '
|
|
112
|
-
return '
|
|
108
|
+
function phaseLabel(state: Readonly<DungeonState>, locale?: Locale): string {
|
|
109
|
+
if (state.phase === 'lobby') return messages(locale, 'text25');
|
|
110
|
+
if (state.phase === 'exploring') return messages(locale, 'text26');
|
|
111
|
+
if (state.phase === 'combat') return messages(locale, 'text27');
|
|
112
|
+
if (state.result === 'victory') return messages(locale, 'text28');
|
|
113
|
+
if (state.result === 'aborted') return messages(locale, 'text29');
|
|
114
|
+
return messages(locale, 'text30');
|
|
113
115
|
}
|
|
116
|
+
|
|
117
|
+
const messages = createMessages({
|
|
118
|
+
text1: { 'zh-CN': "再来一局", en: "Play again" },
|
|
119
|
+
text2: { 'zh-CN': "加入队伍", en: "Join party" },
|
|
120
|
+
text3: { 'zh-CN': "准备/取消", en: "Ready / Not ready" },
|
|
121
|
+
text4: { 'zh-CN': "开始远征", en: "Start expedition" },
|
|
122
|
+
text5: { 'zh-CN': "解散队伍", en: "Disband party" },
|
|
123
|
+
text6: { 'zh-CN': "攻击", en: "Attack" },
|
|
124
|
+
text7: { 'zh-CN': "防御", en: "Defend" },
|
|
125
|
+
text8: { 'zh-CN': "药水", en: "Potion" },
|
|
126
|
+
text9: { 'zh-CN': "结束远征", en: "End expedition" },
|
|
127
|
+
text10: { 'zh-CN': "探索下一房间", en: "Explore next room" },
|
|
128
|
+
text11: { 'zh-CN': "使用药水", en: "Use potion" },
|
|
129
|
+
text12: { 'zh-CN': "地牢远征 · 第 {p0}/{p1} 层 · 房间 {p2}/{p3}", en: "Dungeon Expedition · Floor {p0}/{p1} · Room {p2}/{p3}" },
|
|
130
|
+
text13: { 'zh-CN': "状态:{p0}", en: "Status: {p0}" },
|
|
131
|
+
text14: { 'zh-CN': " <- 当前回合", en: " <- Current turn" },
|
|
132
|
+
text15: { 'zh-CN': " [已准备]", en: " [Ready]" },
|
|
133
|
+
text16: { 'zh-CN': " [未准备]", en: " [Not ready]" },
|
|
134
|
+
text17: { 'zh-CN': " · 药水 {p0} · 金币 {p1}{p2}", en: " · Potions {p0} · Gold {p1}{p2}" },
|
|
135
|
+
text18: { 'zh-CN': "守层者", en: "Floor guardian" },
|
|
136
|
+
text19: { 'zh-CN': "敌人", en: "Enemy" },
|
|
137
|
+
text20: { 'zh-CN': "HP {p0}/{p1} · 攻击 {p2}", en: "HP {p0}/{p1} · Attack {p2}" },
|
|
138
|
+
text21: { 'zh-CN': "最近事件:", en: "Recent events:" },
|
|
139
|
+
text22: { 'zh-CN': "远征胜利,地牢已被征服。", en: "Expedition victory! The dungeon is conquered." },
|
|
140
|
+
text23: { 'zh-CN': "本次远征由队长结束。", en: "The leader ended this expedition." },
|
|
141
|
+
text24: { 'zh-CN': "远征失败,队伍全员倒下。", en: "Expedition failed. The entire party fell." },
|
|
142
|
+
text25: { 'zh-CN': "等待队员", en: "Waiting for players" },
|
|
143
|
+
text26: { 'zh-CN': "探索中", en: "Exploring" },
|
|
144
|
+
text27: { 'zh-CN': "战斗中", en: "In combat" },
|
|
145
|
+
text28: { 'zh-CN': "胜利", en: "Victory" },
|
|
146
|
+
text29: { 'zh-CN': "已结束", en: "Ended" },
|
|
147
|
+
text30: { 'zh-CN': "失败", en: "Defeat" },
|
|
148
|
+
});
|