@rpgjs/client 5.0.0-beta.11 → 5.0.0-beta.13

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.
Files changed (133) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/Game/AnimationManager.d.ts +1 -0
  3. package/dist/Game/AnimationManager.js +3 -0
  4. package/dist/Game/AnimationManager.js.map +1 -1
  5. package/dist/Game/ClientVisuals.d.ts +61 -0
  6. package/dist/Game/ClientVisuals.js +96 -0
  7. package/dist/Game/ClientVisuals.js.map +1 -0
  8. package/dist/Game/ClientVisuals.spec.d.ts +1 -0
  9. package/dist/Game/EventComponentResolver.d.ts +16 -0
  10. package/dist/Game/EventComponentResolver.js +52 -0
  11. package/dist/Game/EventComponentResolver.js.map +1 -0
  12. package/dist/Game/EventComponentResolver.spec.d.ts +1 -0
  13. package/dist/Game/Map.js +9 -0
  14. package/dist/Game/Map.js.map +1 -1
  15. package/dist/Game/Object.d.ts +2 -0
  16. package/dist/Game/Object.js +22 -8
  17. package/dist/Game/Object.js.map +1 -1
  18. package/dist/Game/Object.spec.d.ts +1 -0
  19. package/dist/Game/ProjectileManager.d.ts +11 -2
  20. package/dist/Game/ProjectileManager.js +19 -2
  21. package/dist/Game/ProjectileManager.js.map +1 -1
  22. package/dist/Gui/Gui.d.ts +3 -2
  23. package/dist/Gui/Gui.js +18 -6
  24. package/dist/Gui/Gui.js.map +1 -1
  25. package/dist/RpgClient.d.ts +85 -1
  26. package/dist/RpgClientEngine.d.ts +77 -2
  27. package/dist/RpgClientEngine.js +290 -31
  28. package/dist/RpgClientEngine.js.map +1 -1
  29. package/dist/components/animations/fx.ce.js +58 -0
  30. package/dist/components/animations/fx.ce.js.map +1 -0
  31. package/dist/components/animations/index.d.ts +1 -0
  32. package/dist/components/animations/index.js +3 -1
  33. package/dist/components/animations/index.js.map +1 -1
  34. package/dist/components/character.ce.js +192 -19
  35. package/dist/components/character.ce.js.map +1 -1
  36. package/dist/components/gui/dialogbox/index.ce.js +27 -12
  37. package/dist/components/gui/dialogbox/index.ce.js.map +1 -1
  38. package/dist/components/gui/gameover.ce.js +4 -3
  39. package/dist/components/gui/gameover.ce.js.map +1 -1
  40. package/dist/components/gui/menu/equip-menu.ce.js +9 -8
  41. package/dist/components/gui/menu/equip-menu.ce.js.map +1 -1
  42. package/dist/components/gui/menu/exit-menu.ce.js +7 -5
  43. package/dist/components/gui/menu/exit-menu.ce.js.map +1 -1
  44. package/dist/components/gui/menu/items-menu.ce.js +8 -7
  45. package/dist/components/gui/menu/items-menu.ce.js.map +1 -1
  46. package/dist/components/gui/menu/main-menu.ce.js +12 -11
  47. package/dist/components/gui/menu/main-menu.ce.js.map +1 -1
  48. package/dist/components/gui/menu/options-menu.ce.js +7 -5
  49. package/dist/components/gui/menu/options-menu.ce.js.map +1 -1
  50. package/dist/components/gui/menu/skills-menu.ce.js +4 -2
  51. package/dist/components/gui/menu/skills-menu.ce.js.map +1 -1
  52. package/dist/components/gui/notification/notification.ce.js +4 -1
  53. package/dist/components/gui/notification/notification.ce.js.map +1 -1
  54. package/dist/components/gui/save-load.ce.js +10 -9
  55. package/dist/components/gui/save-load.ce.js.map +1 -1
  56. package/dist/components/gui/shop/shop.ce.js +17 -16
  57. package/dist/components/gui/shop/shop.ce.js.map +1 -1
  58. package/dist/components/gui/title-screen.ce.js +4 -3
  59. package/dist/components/gui/title-screen.ce.js.map +1 -1
  60. package/dist/components/interaction-components.ce.js +20 -0
  61. package/dist/components/interaction-components.ce.js.map +1 -0
  62. package/dist/components/scenes/canvas.ce.js +12 -7
  63. package/dist/components/scenes/canvas.ce.js.map +1 -1
  64. package/dist/components/scenes/draw-map.ce.js +18 -13
  65. package/dist/components/scenes/draw-map.ce.js.map +1 -1
  66. package/dist/i18n.d.ts +55 -0
  67. package/dist/i18n.js +60 -0
  68. package/dist/i18n.js.map +1 -0
  69. package/dist/i18n.spec.d.ts +1 -0
  70. package/dist/index.d.ts +3 -0
  71. package/dist/index.js +5 -2
  72. package/dist/module.js +30 -3
  73. package/dist/module.js.map +1 -1
  74. package/dist/services/actionInput.d.ts +3 -1
  75. package/dist/services/actionInput.js +33 -1
  76. package/dist/services/actionInput.js.map +1 -1
  77. package/dist/services/interactions.d.ts +159 -0
  78. package/dist/services/interactions.js +460 -0
  79. package/dist/services/interactions.js.map +1 -0
  80. package/dist/services/interactions.spec.d.ts +1 -0
  81. package/dist/services/keyboardControls.d.ts +1 -0
  82. package/dist/services/keyboardControls.js +1 -0
  83. package/dist/services/keyboardControls.js.map +1 -1
  84. package/dist/services/standalone.d.ts +3 -1
  85. package/dist/services/standalone.js +31 -13
  86. package/dist/services/standalone.js.map +1 -1
  87. package/dist/utils/mapId.d.ts +1 -0
  88. package/dist/utils/mapId.js +6 -0
  89. package/dist/utils/mapId.js.map +1 -0
  90. package/package.json +4 -4
  91. package/src/Game/AnimationManager.ts +4 -0
  92. package/src/Game/ClientVisuals.spec.ts +56 -0
  93. package/src/Game/ClientVisuals.ts +184 -0
  94. package/src/Game/EventComponentResolver.spec.ts +84 -0
  95. package/src/Game/EventComponentResolver.ts +74 -0
  96. package/src/Game/Map.ts +10 -0
  97. package/src/Game/Object.spec.ts +59 -0
  98. package/src/Game/Object.ts +36 -12
  99. package/src/Game/ProjectileManager.spec.ts +111 -0
  100. package/src/Game/ProjectileManager.ts +24 -2
  101. package/src/Gui/Gui.spec.ts +67 -0
  102. package/src/Gui/Gui.ts +24 -7
  103. package/src/RpgClient.ts +96 -1
  104. package/src/RpgClientEngine.ts +378 -45
  105. package/src/components/animations/fx.ce +101 -0
  106. package/src/components/animations/index.ts +4 -2
  107. package/src/components/character.ce +243 -17
  108. package/src/components/gui/dialogbox/index.ce +35 -14
  109. package/src/components/gui/gameover.ce +4 -3
  110. package/src/components/gui/menu/equip-menu.ce +9 -8
  111. package/src/components/gui/menu/exit-menu.ce +4 -3
  112. package/src/components/gui/menu/items-menu.ce +8 -7
  113. package/src/components/gui/menu/main-menu.ce +12 -11
  114. package/src/components/gui/menu/options-menu.ce +4 -3
  115. package/src/components/gui/menu/skills-menu.ce +2 -1
  116. package/src/components/gui/notification/notification.ce +7 -1
  117. package/src/components/gui/save-load.ce +11 -10
  118. package/src/components/gui/shop/shop.ce +17 -16
  119. package/src/components/gui/title-screen.ce +4 -3
  120. package/src/components/interaction-components.ce +23 -0
  121. package/src/components/scenes/canvas.ce +12 -7
  122. package/src/components/scenes/draw-map.ce +16 -5
  123. package/src/i18n.spec.ts +39 -0
  124. package/src/i18n.ts +59 -0
  125. package/src/index.ts +3 -0
  126. package/src/module.ts +43 -10
  127. package/src/services/actionInput.spec.ts +54 -0
  128. package/src/services/actionInput.ts +68 -1
  129. package/src/services/interactions.spec.ts +175 -0
  130. package/src/services/interactions.ts +722 -0
  131. package/src/services/keyboardControls.ts +2 -1
  132. package/src/services/standalone.ts +39 -10
  133. package/src/utils/mapId.ts +2 -0
@@ -1,6 +1,6 @@
1
1
  <DOMContainer width="100%" height="100%">
2
2
  <div class="rpg-ui-menu-panel rpg-ui-panel">
3
- <div class="rpg-ui-menu-panel-header">Equip</div>
3
+ <div class="rpg-ui-menu-panel-header">{t("rpg.menu.equip")}</div>
4
4
  <div class="rpg-ui-menu-panel-body rpg-ui-menu-panel-body-stacked">
5
5
  <div>
6
6
  <div class="rpg-ui-menu-panel-details rpg-ui-panel">
@@ -16,7 +16,7 @@
16
16
  </div>
17
17
  <div>
18
18
  <div class="rpg-ui-menu-panel-details-title">
19
- {detailsItem()?.name || currentSlot()?.label || "Equip"}
19
+ {detailsItem()?.name || currentSlot()?.label || t("rpg.menu.equip")}
20
20
  </div>
21
21
  <div class="rpg-ui-menu-panel-details-desc">
22
22
  {detailsItem()?.description || ""}
@@ -93,6 +93,7 @@
93
93
  import { getKeyboardControlBind } from "../../../services/actionInput";
94
94
 
95
95
  const engine = inject(RpgClientEngine);
96
+ const { t } = engine.i18n();
96
97
  const keyboardControls = engine.globalConfig.keyboardControls;
97
98
  const currentPlayer = engine.scene.currentPlayer;
98
99
 
@@ -104,8 +105,8 @@
104
105
  const slots = computed(() => data().slots);
105
106
 
106
107
  const defaultSlots = [
107
- { id: "weapon", label: "Weapon", types: ["weapon"] },
108
- { id: "armor", label: "Armor", types: ["armor"] }
108
+ { id: "weapon", label: t("rpg.menu.weapons"), types: ["weapon"] },
109
+ { id: "armor", label: t("rpg.menu.armor"), types: ["armor"] }
109
110
  ];
110
111
  const resolveProp = (value) => typeof value === "function" ? value() : value;
111
112
  const safeEquips = computed(() => {
@@ -170,15 +171,15 @@
170
171
  const items = slotItems().map((item) => ({
171
172
  ...item,
172
173
  kind: "item",
173
- tag: itemEquipped(item) ? "Equipped" : ""
174
+ tag: itemEquipped(item) ? t("rpg.shop.equipped") : ""
174
175
  }));
175
176
  const equipped = currentEquippedItem();
176
177
  if (!equipped) return items;
177
178
  return [
178
179
  {
179
180
  id: "__unequip__",
180
- name: "Unequip",
181
- description: "Remove the current equipment",
181
+ name: t("rpg.menu.unequip"),
182
+ description: t("rpg.menu.remove-equipment"),
182
183
  kind: "unequip",
183
184
  tag: ""
184
185
  },
@@ -197,7 +198,7 @@
197
198
  const slot = currentSlot();
198
199
  if (!slot) return "";
199
200
  const equipped = currentEquippedItem();
200
- return equipped ? `${slot.label}: ${equipped.name}` : `${slot.label}: Empty`;
201
+ return equipped ? `${slot.label}: ${equipped.name}` : `${slot.label}: ${t("rpg.menu.empty")}`;
201
202
  });
202
203
  const listEmpty = computed(() => listEntries().length === 0);
203
204
 
@@ -1,11 +1,11 @@
1
1
  <DOMContainer width="100%" height="100%" controls={controls}>
2
2
  <div class="rpg-ui-menu-panel rpg-ui-panel">
3
- <div class="rpg-ui-menu-panel-header">Exit</div>
3
+ <div class="rpg-ui-menu-panel-header">{t("rpg.menu.exit")}</div>
4
4
  <div class="rpg-ui-menu-panel-body">
5
5
  <div class="rpg-ui-menu-panel-details">
6
- <div class="rpg-ui-menu-panel-details-title">Leave the game?</div>
6
+ <div class="rpg-ui-menu-panel-details-title">{t("rpg.menu.leave-game")}</div>
7
7
  <div class="rpg-ui-menu-panel-details-desc">
8
- Press Action to confirm or Escape to go back.
8
+ {t("rpg.menu.exit-help")}
9
9
  </div>
10
10
  </div>
11
11
  </div>
@@ -19,6 +19,7 @@
19
19
  import { getKeyboardControlBind } from "../../../services/actionInput";
20
20
 
21
21
  const engine = inject(RpgClientEngine);
22
+ const { t } = engine.i18n();
22
23
  const keyboardControls = engine.globalConfig.keyboardControls;
23
24
  const { onConfirm, onBack } = defineProps();
24
25
 
@@ -1,6 +1,6 @@
1
1
  <DOMContainer width="100%" height="100%">
2
2
  <div class="rpg-ui-menu-panel rpg-ui-panel">
3
- <div class="rpg-ui-menu-panel-header">Items</div>
3
+ <div class="rpg-ui-menu-panel-header">{t("rpg.menu.items")}</div>
4
4
  <div class="rpg-ui-menu-panel-body rpg-ui-menu-panel-body-stacked">
5
5
  <div>
6
6
  @if (currentItem) {
@@ -65,7 +65,7 @@
65
65
  @if (confirmOpen) {
66
66
  <div class="rpg-ui-menu-confirm">
67
67
  <div class="rpg-ui-menu-confirm-card">
68
- <div class="rpg-ui-menu-confirm-title">Use {confirmItem()?.name}?</div>
68
+ <div class="rpg-ui-menu-confirm-title">{t("rpg.menu.use")} {confirmItem()?.name}?</div>
69
69
  <Navigation tabindex={selectedConfirm} controls={confirmControls}>
70
70
  <div class="rpg-ui-menu-confirm-actions">
71
71
  @for ((option,index) of confirmOptions) {
@@ -93,6 +93,7 @@
93
93
  import { getKeyboardControlBind } from "../../../services/actionInput";
94
94
 
95
95
  const engine = inject(RpgClientEngine);
96
+ const { t } = engine.i18n();
96
97
  const keyboardControls = engine.globalConfig.keyboardControls;
97
98
 
98
99
  const selectedItem = signal(0);
@@ -106,9 +107,9 @@
106
107
  const items = computed(() => data().items);
107
108
 
108
109
  const tabs = [
109
- { id: "item", label: "Items" },
110
- { id: "weapon", label: "Weapons" },
111
- { id: "armor", label: "Armor" }
110
+ { id: "item", label: t("rpg.menu.items") },
111
+ { id: "weapon", label: t("rpg.menu.weapons") },
112
+ { id: "armor", label: t("rpg.menu.armor") }
112
113
  ];
113
114
 
114
115
  const resolveProp = (value) => typeof value === "function" ? value() : value;
@@ -125,8 +126,8 @@
125
126
  });
126
127
 
127
128
  const confirmOptions = [
128
- { id: "use", label: "Use" },
129
- { id: "cancel", label: "Cancel" }
129
+ { id: "use", label: t("rpg.menu.use") },
130
+ { id: "cancel", label: t("rpg.menu.cancel") }
130
131
  ];
131
132
 
132
133
  const nav = createTabindexNavigator(selectedItem, { count: () => filteredItems().length }, "wrap");
@@ -2,7 +2,7 @@
2
2
  <div class="rpg-ui-main-menu rpg-anim-fade-in">
3
3
  <div class="rpg-ui-main-menu-layout">
4
4
  <div class="rpg-ui-main-menu-left rpg-ui-menu rpg-ui-panel">
5
- <div class="rpg-ui-menu-header">Menu</div>
5
+ <div class="rpg-ui-menu-header">{t("rpg.menu.title")}</div>
6
6
  <div class="rpg-ui-main-menu-list">
7
7
  @for ((entry,index) of menuEntries()) {
8
8
  <div
@@ -18,14 +18,14 @@
18
18
  <div class="rpg-ui-main-menu-right">
19
19
  @if (view() === "menu") {
20
20
  <div class="rpg-ui-panel">
21
- <div class="rpg-ui-main-menu-section-title">Status</div>
21
+ <div class="rpg-ui-main-menu-section-title">{t("rpg.menu.status")}</div>
22
22
  <div class="rpg-ui-main-menu-status-card">
23
23
  <div class="rpg-ui-main-menu-status-block">
24
- <div class="rpg-ui-main-menu-status-label">Level</div>
24
+ <div class="rpg-ui-main-menu-status-label">{t("rpg.menu.level")}</div>
25
25
  <div class="rpg-ui-main-menu-status-value">{level()}</div>
26
26
  </div>
27
27
  <div class="rpg-ui-main-menu-status-block">
28
- <div class="rpg-ui-main-menu-status-label">Gold</div>
28
+ <div class="rpg-ui-main-menu-status-label">{t("rpg.menu.gold")}</div>
29
29
  <div class="rpg-ui-main-menu-status-value">{gold()}</div>
30
30
  </div>
31
31
  <div class="rpg-ui-main-menu-status-block">
@@ -47,7 +47,7 @@
47
47
  </div>
48
48
  </div>
49
49
  </div>
50
- <div class="rpg-ui-main-menu-section-title">Parameters</div>
50
+ <div class="rpg-ui-main-menu-section-title">{t("rpg.menu.parameters")}</div>
51
51
  <div class="rpg-ui-main-menu-params">
52
52
  @for ((param,index) of paramsList) {
53
53
  <div class="rpg-ui-main-menu-param">
@@ -104,6 +104,7 @@
104
104
  import { getKeyboardControlBind } from "../../../services/actionInput";
105
105
 
106
106
  const engine = inject(RpgClientEngine);
107
+ const { t } = engine.i18n();
107
108
  const currentPlayer = engine.scene.currentPlayer;
108
109
  const keyboardControls = engine.globalConfig.keyboardControls;
109
110
 
@@ -111,12 +112,12 @@
111
112
  const { menus, items, skills, equips, saveLoad } = data();
112
113
 
113
114
  const defaultMenus = [
114
- { id: "items", label: "Items" },
115
- { id: "skills", label: "Skills" },
116
- { id: "equip", label: "Equip" },
117
- { id: "options", label: "Options" },
118
- { id: "save", label: "Save" },
119
- { id: "exit", label: "Exit" }
115
+ { id: "items", label: t("rpg.menu.items") },
116
+ { id: "skills", label: t("rpg.menu.skills") },
117
+ { id: "equip", label: t("rpg.menu.equip") },
118
+ { id: "options", label: t("rpg.menu.options") },
119
+ { id: "save", label: t("rpg.menu.save") },
120
+ { id: "exit", label: t("rpg.menu.exit") }
120
121
  ];
121
122
 
122
123
  const menuEntries = computed(() => {
@@ -1,11 +1,11 @@
1
1
  <DOMContainer width="100%" height="100%" controls={controls}>
2
2
  <div class="rpg-ui-menu-panel rpg-ui-panel">
3
- <div class="rpg-ui-menu-panel-header">Options</div>
3
+ <div class="rpg-ui-menu-panel-header">{t("rpg.menu.options")}</div>
4
4
  <div class="rpg-ui-menu-panel-body">
5
5
  <div class="rpg-ui-menu-panel-details">
6
- <div class="rpg-ui-menu-panel-details-title">Options</div>
6
+ <div class="rpg-ui-menu-panel-details-title">{t("rpg.menu.options")}</div>
7
7
  <div class="rpg-ui-menu-panel-details-desc">
8
- Configure your preferences here.
8
+ {t("rpg.menu.options-help")}
9
9
  </div>
10
10
  </div>
11
11
  </div>
@@ -18,6 +18,7 @@
18
18
  import { RpgClientEngine } from "../../../RpgClientEngine";
19
19
 
20
20
  const engine = inject(RpgClientEngine);
21
+ const { t } = engine.i18n();
21
22
  const keyboardControls = engine.globalConfig.keyboardControls;
22
23
  const { onBack } = defineProps();
23
24
 
@@ -1,6 +1,6 @@
1
1
  <DOMContainer width="100%" height="100%">
2
2
  <div class="rpg-ui-menu-panel rpg-ui-panel">
3
- <div class="rpg-ui-menu-panel-header">Skills</div>
3
+ <div class="rpg-ui-menu-panel-header">{t("rpg.menu.skills")}</div>
4
4
  <div class="rpg-ui-menu-panel-body rpg-ui-menu-panel-body-stacked">
5
5
  <div class="rpg-ui-menu-panel-details rpg-ui-panel">
6
6
  @if (currentSkill) {
@@ -38,6 +38,7 @@
38
38
  import { RpgClientEngine } from "../../../RpgClientEngine";
39
39
 
40
40
  const engine = inject(RpgClientEngine);
41
+ const { t } = engine.i18n();
41
42
  const keyboardControls = engine.globalConfig.keyboardControls;
42
43
 
43
44
  const selectedSkill = signal(0);
@@ -1,4 +1,10 @@
1
- <DOMContainer width="100%" height="100%" zIndex={100} class="notification">
1
+ <DOMContainer
2
+ width="100%"
3
+ height="100%"
4
+ zIndex={100}
5
+ class="notification"
6
+ style="pointer-events: none !important;"
7
+ >
2
8
  <div class="rpg-ui-notifications">
3
9
  @for ((notif,index) of notifications) {
4
10
  <div
@@ -17,14 +17,14 @@
17
17
  <div class="rpg-ui-save-load-slot-index">{item.label}</div>
18
18
  @if (item.slot) {
19
19
  <div class="rpg-ui-save-load-slot-meta">
20
- <div class="rpg-ui-save-load-slot-line">Level: {item.slot.level ?? "-"}</div>
21
- <div class="rpg-ui-save-load-slot-line">Exp: {item.slot.exp ?? "-"}</div>
22
- <div class="rpg-ui-save-load-slot-line">Map: {item.slot.map ?? "-"}</div>
23
- <div class="rpg-ui-save-load-slot-line">Date: {item.slot.date ?? "-"}</div>
20
+ <div class="rpg-ui-save-load-slot-line">{t("rpg.save.level")}: {item.slot.level ?? "-"}</div>
21
+ <div class="rpg-ui-save-load-slot-line">{t("rpg.save.exp")}: {item.slot.exp ?? "-"}</div>
22
+ <div class="rpg-ui-save-load-slot-line">{t("rpg.save.map")}: {item.slot.map ?? "-"}</div>
23
+ <div class="rpg-ui-save-load-slot-line">{t("rpg.save.date")}: {item.slot.date ?? "-"}</div>
24
24
  </div>
25
25
  }
26
26
  @else {
27
- <div class="rpg-ui-save-load-slot-empty">Empty Slot</div>
27
+ <div class="rpg-ui-save-load-slot-empty">{t("rpg.save.empty-slot")}</div>
28
28
  }
29
29
  </div>
30
30
  }
@@ -43,6 +43,7 @@
43
43
  import { getKeyboardControlBind } from "../../services/actionInput";
44
44
 
45
45
  const engine = inject(RpgClientEngine);
46
+ const { t } = engine.i18n();
46
47
  const saveClient = inject(SaveClientService);
47
48
  const gui = inject(RpgGui);
48
49
  const keyboardControls = engine.globalConfig.keyboardControls;
@@ -54,10 +55,10 @@
54
55
 
55
56
  const { data, onFinish } = defineProps();
56
57
 
57
- const title = computed(() => data().mode === "save" ? "Save Game" : "Load Game");
58
+ const title = computed(() => data().mode === "save" ? t("rpg.save.title") : t("rpg.load.title"));
58
59
  const subtitle = computed(() => data().mode === "save"
59
- ? "Choose a slot to overwrite or create."
60
- : "Select a slot to load your progress."
60
+ ? t("rpg.save.subtitle")
61
+ : t("rpg.load.subtitle")
61
62
  );
62
63
 
63
64
  const slotsValue = computed(() => localSlots());
@@ -67,7 +68,7 @@
67
68
  kind: "slot",
68
69
  slot,
69
70
  slotIndex: index,
70
- label: `Slot ${index + 1}`,
71
+ label: t("rpg.save.slot", { index: index + 1 }),
71
72
  readonly: false
72
73
  }));
73
74
  if (!data().showAutoSlot) return items;
@@ -79,7 +80,7 @@
79
80
  kind: "auto",
80
81
  slot: autoSlot,
81
82
  slotIndex: index,
82
- label: data().autoSlotLabel || "Auto Save",
83
+ label: data().autoSlotLabel || t("rpg.save.auto"),
83
84
  readonly
84
85
  },
85
86
  ...items
@@ -30,7 +30,7 @@
30
30
  <div class="rpg-shop-details rpg-shop-details-mode">
31
31
  <div class="rpg-shop-details-header">
32
32
  <div class="rpg-shop-details-icon">🛒</div>
33
- <h2 style="margin: 0;">Choose an action</h2>
33
+ <h2 style="margin: 0;">{t("rpg.shop.choose-action")}</h2>
34
34
  </div>
35
35
  <div class="rpg-shop-trade">
36
36
  <Navigation tabindex={selectedModeIndex} controls={modeControls}>
@@ -39,12 +39,12 @@
39
39
  class="rpg-shop-tab"
40
40
  class={{active: selectedModeIndex() === 0}}
41
41
  click={selectMode('buy')}
42
- >Buy</div>
42
+ >{t("rpg.shop.buy")}</div>
43
43
  <div
44
44
  class="rpg-shop-tab"
45
45
  class={{active: selectedModeIndex() === 1}}
46
46
  click={selectMode('sell')}
47
- >Sell</div>
47
+ >{t("rpg.shop.sell")}</div>
48
48
  </div>
49
49
  </Navigation>
50
50
  </div>
@@ -88,7 +88,7 @@
88
88
  <div class="rpg-shop-card-qty">x{{ item.quantity }}</div>
89
89
  }
90
90
  @if (item.equipped) {
91
- <div class="rpg-shop-card-tag">Equipped</div>
91
+ <div class="rpg-shop-card-tag">{t("rpg.shop.equipped")}</div>
92
92
  }
93
93
  </div>
94
94
  }
@@ -109,12 +109,12 @@
109
109
  <h2 style="margin: 0;">{{ currentItem()?.name || "" }}</h2>
110
110
  <p style="color: #ffd700; font-weight: bold; margin: 8px 0;">{{ currentItem()?.price ?? 0 }} {{ goldTerm }}</p>
111
111
  @if (currentItem()?.quantity !== undefined) {
112
- <div class="rpg-shop-details-qty">Qty: x{{ currentItem()?.quantity }}</div>
112
+ <div class="rpg-shop-details-qty">{t("rpg.shop.qty")}: x{{ currentItem()?.quantity }}</div>
113
113
  }
114
114
  </div>
115
115
  <div>
116
116
  @if (currentItem()?.equipped) {
117
- <div class="rpg-shop-equipped">Already equipped</div>
117
+ <div class="rpg-shop-equipped">{t("rpg.shop.already-equipped")}</div>
118
118
  }
119
119
  </div>
120
120
  <div class="rpg-shop-details-desc">
@@ -137,7 +137,7 @@
137
137
  </div>
138
138
  }
139
139
  </div>
140
- <button class="rpg-shop-btn" click={backToMode()}>Back</button>
140
+ <button class="rpg-shop-btn" click={backToMode()}>{t("rpg.shop.back")}</button>
141
141
  </div>
142
142
  </div>
143
143
  </div>
@@ -148,10 +148,10 @@
148
148
  <div class="rpg-shop-modal-title">{{ actionLabel }}</div>
149
149
  <div class="rpg-shop-modal-item">{{ currentItem()?.name || "" }}</div>
150
150
  @if (currentItem()?.quantity !== undefined) {
151
- <div class="rpg-shop-modal-qty">Available: x{{ currentItem()?.quantity }}</div>
151
+ <div class="rpg-shop-modal-qty">{t("rpg.shop.available")}: x{{ currentItem()?.quantity }}</div>
152
152
  }
153
153
  <div class="rpg-shop-quantity">
154
- <div class="rpg-shop-quantity-label">Quantity</div>
154
+ <div class="rpg-shop-quantity-label">{t("rpg.shop.quantity")}</div>
155
155
  <div class="rpg-shop-quantity-controls">
156
156
  <button class="rpg-shop-btn" click={changeQuantity(-1)}>-</button>
157
157
  <div class="rpg-shop-quantity-value">{{ quantity }}</div>
@@ -159,11 +159,11 @@
159
159
  </div>
160
160
  </div>
161
161
  <div class="rpg-shop-modal-total">
162
- <span>Total</span>
162
+ <span>{t("rpg.shop.total")}</span>
163
163
  <span>{{ totalPrice() }} {{ goldTerm }}</span>
164
164
  </div>
165
165
  <div class="rpg-shop-modal-actions">
166
- <button class="rpg-shop-btn rpg-shop-btn-secondary" click={closeQuantityDialog()}>Cancel</button>
166
+ <button class="rpg-shop-btn rpg-shop-btn-secondary" click={closeQuantityDialog()}>{t("rpg.menu.cancel")}</button>
167
167
  <button class="rpg-shop-btn" click={confirmTrade()}>
168
168
  {{ actionLabel() }} x{{ quantity() }}
169
169
  </button>
@@ -183,6 +183,7 @@
183
183
  import { getKeyboardControlBind } from "../../../services/actionInput";
184
184
 
185
185
  const engine = inject(RpgClientEngine)
186
+ const { t } = engine.i18n()
186
187
  const currentPlayer = engine.scene.currentPlayer
187
188
  const keyboardControls = engine.globalConfig.keyboardControls
188
189
  const iconSheet = (iconId) => ({
@@ -198,11 +199,11 @@
198
199
  const selectedModeIndex = signal(0)
199
200
  const quantity = signal(1)
200
201
  const quantityDialogOpen = signal(false)
201
- const defaultMessage = 'Welcome to my shop!'
202
+ const defaultMessage = t("rpg.shop.default-message")
202
203
  const tabs = [
203
- { id: 'item', label: 'Items' },
204
- { id: 'weapon', label: 'Weapons' },
205
- { id: 'armor', label: 'Armor' }
204
+ { id: 'item', label: t("rpg.menu.items") },
205
+ { id: 'weapon', label: t("rpg.menu.weapons") },
206
+ { id: 'armor', label: t("rpg.menu.armor") }
206
207
  ]
207
208
 
208
209
  const { data, onInteraction , onFinish } = defineProps()
@@ -229,7 +230,7 @@
229
230
  })
230
231
  const currentItem = computed(() => filteredItems()[selectedItem()])
231
232
  const gold = computed(() => currentPlayer()._gold())
232
- const actionLabel = computed(() => tradeMode() === 'buy' ? 'Buy' : 'Sell')
233
+ const actionLabel = computed(() => tradeMode() === 'buy' ? t("rpg.shop.buy") : t("rpg.shop.sell"))
233
234
  const faceSheet = (graphicId, animationName) => ({
234
235
  definition: engine.getSpriteSheet(graphicId),
235
236
  playing: animationName || "default"
@@ -34,6 +34,7 @@
34
34
  import { getKeyboardControlBind } from "../../services/actionInput";
35
35
 
36
36
  const engine = inject(RpgClientEngine);
37
+ const { t } = engine.i18n();
37
38
  const guiService = inject(RpgGui);
38
39
  const keyboardControls = engine.globalConfig.keyboardControls;
39
40
 
@@ -52,12 +53,12 @@
52
53
  const { entries, title, subtitle, version, saveLoad, localActions } = data();
53
54
 
54
55
  const defaultEntries = [
55
- { id: "start", label: "Start" },
56
- { id: "load", label: "Load" }
56
+ { id: "start", label: t("rpg.title.start") },
57
+ { id: "load", label: t("rpg.title.load") }
57
58
  ];
58
59
 
59
60
  const resolveProp = (value) => typeof value === "function" ? value() : value;
60
- const titleText = computed(() => resolveProp(title) || "RPG");
61
+ const titleText = computed(() => resolveProp(title) || t("rpg.title.default"));
61
62
  const subtitleText = computed(() => resolveProp(subtitle) || "");
62
63
  const versionText = computed(() => resolveProp(version) || "");
63
64
  const localActionsEnabled = computed(() => resolveProp(localActions) === true);
@@ -0,0 +1,23 @@
1
+ <Container>
2
+ @for (entry of renderedComponents) {
3
+ <Container dependencies={entry.dependencies}>
4
+ <entry.component ...entry.props />
5
+ </Container>
6
+ }
7
+ </Container>
8
+
9
+ <script>
10
+ import { computed } from "canvasengine";
11
+ import { RpgClientEngine } from "../RpgClientEngine";
12
+ import { inject } from "../core/inject";
13
+
14
+ const { object, bounds, hitboxBounds, graphicBounds } = defineProps();
15
+ const client = inject(RpgClientEngine);
16
+ const sprite = object();
17
+
18
+ const renderedComponents = computed(() => client.interactions.getRenderedComponents(sprite, {
19
+ bounds: typeof bounds === "function" ? bounds() : undefined,
20
+ hitbox: typeof hitboxBounds === "function" ? hitboxBounds() : undefined,
21
+ graphic: typeof graphicBounds === "function" ? graphicBounds() : undefined,
22
+ }));
23
+ </script>
@@ -33,8 +33,8 @@
33
33
  height={engine.height}
34
34
  >
35
35
  @if (gui.display) {
36
- <gui.component data={gui.data} dependencies={gui.dependencies} onFinish={(data) => {
37
- onGuiFinish(gui, data)
36
+ <gui.component data={gui.data} dependencies={gui.dependencies} guiOpenId={gui.openId} onFinish={(data, guiOpenId) => {
37
+ onGuiFinish(gui, data, guiOpenId)
38
38
  }} onInteraction={(name, data) => {
39
39
  onGuiInteraction(gui, name, data)
40
40
  }} />
@@ -48,7 +48,6 @@
48
48
  import { inject } from "../../core/inject";
49
49
  import { RpgClientEngine } from "../../RpgClientEngine";
50
50
  import { RpgGui } from "../../Gui/Gui";
51
- import { delay } from "@rpgjs/common";
52
51
  import { NightAmbiant, SpriteShadows } from '@canvasengine/presets'
53
52
 
54
53
  const engine = inject(RpgClientEngine);
@@ -76,10 +75,16 @@
76
75
  }
77
76
  })
78
77
 
79
- const onGuiFinish = (gui, data) => {
80
- delay(() => {
81
- guiService.guiClose(gui.name, data)
82
- })
78
+ const normalizeOpenId = (value) => {
79
+ const resolved = typeof value === "function" ? value() : value
80
+ return typeof resolved === "string" && resolved.length > 0 ? resolved : undefined
81
+ }
82
+
83
+ const onGuiFinish = (gui, data, guiOpenId) => {
84
+ const completedOpenId = normalizeOpenId(guiOpenId)
85
+ const currentOpenId = normalizeOpenId(gui.openId)
86
+ if (completedOpenId && currentOpenId && completedOpenId !== currentOpenId) return
87
+ guiService.guiClose(gui.name, data, completedOpenId ?? currentOpenId)
83
88
  }
84
89
 
85
90
  const onGuiInteraction = (gui, name, data) => {
@@ -1,5 +1,11 @@
1
- <Container sound={backgroundMusic} shake={shakeConfig} freeze={engine.gamePause}>
2
- <Container sound={backgroundAmbientSound} />
1
+ <Container shake={shakeConfig} freeze={engine.gamePause}>
2
+ @if (backgroundMusic()) {
3
+ <Container sound={backgroundMusic()} />
4
+ }
5
+
6
+ @if (backgroundAmbientSound()) {
7
+ <Container sound={backgroundAmbientSound()} />
8
+ }
3
9
 
4
10
  <Container>
5
11
  @if (map() && sceneComponent()) {
@@ -42,10 +48,15 @@
42
48
  const projectiles = engine.projectiles.current
43
49
  const map = engine.sceneMap?.data
44
50
  const sceneComponent = computed(() => map()?.component)
45
- const mapParams = map()?.params
46
51
  const weather = engine.sceneMap.weather
47
- const backgroundMusic = { src: mapParams?.backgroundMusic, autoplay: true, loop: true }
48
- const backgroundAmbientSound = { src: mapParams?.backgroundAmbientSound, autoplay: true, loop: true }
52
+ const backgroundMusic = computed(() => {
53
+ const src = map()?.params?.backgroundMusic
54
+ return src ? { src, autoplay: true, loop: true } : undefined
55
+ })
56
+ const backgroundAmbientSound = computed(() => {
57
+ const src = map()?.params?.backgroundAmbientSound
58
+ return src ? { src, autoplay: true, loop: true } : undefined
59
+ })
49
60
 
50
61
  const shakeConfig = {
51
62
  trigger: engine.mapShakeTrigger,
@@ -0,0 +1,39 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { describe, expect, test } from "vitest";
4
+ import { Context, injector } from "@signe/di";
5
+ import { getOrCreateI18nService } from "@rpgjs/common";
6
+ import { provideClientModules } from "./module";
7
+ import { provideI18n } from "./i18n";
8
+
9
+ describe("client i18n", () => {
10
+ test("merges client module translations with game overrides", async () => {
11
+ const context = new Context();
12
+
13
+ await injector(context, [
14
+ provideClientModules([
15
+ {
16
+ i18n: {
17
+ fr: {
18
+ "menu.title": "Titre du module",
19
+ "menu.module-only": "Module",
20
+ },
21
+ },
22
+ },
23
+ ]),
24
+ provideI18n({
25
+ defaultLocale: "fr",
26
+ messages: {
27
+ fr: {
28
+ "menu.title": "Titre du jeu",
29
+ },
30
+ },
31
+ }),
32
+ ]);
33
+
34
+ const service = getOrCreateI18nService(context);
35
+
36
+ expect(service.t("menu.title", undefined, "fr")).toBe("Titre du jeu");
37
+ expect(service.t("menu.module-only", undefined, "fr")).toBe("Module");
38
+ });
39
+ });
package/src/i18n.ts ADDED
@@ -0,0 +1,59 @@
1
+ import { createI18nProvider, type I18nConfig } from "@rpgjs/common";
2
+
3
+ export const RpgClientBuiltinI18n = {
4
+ en: {
5
+ "rpg.menu.title": "Menu",
6
+ "rpg.menu.status": "Status",
7
+ "rpg.menu.level": "Level",
8
+ "rpg.menu.gold": "Gold",
9
+ "rpg.menu.parameters": "Parameters",
10
+ "rpg.menu.items": "Items",
11
+ "rpg.menu.skills": "Skills",
12
+ "rpg.menu.equip": "Equip",
13
+ "rpg.menu.options": "Options",
14
+ "rpg.menu.save": "Save",
15
+ "rpg.menu.exit": "Exit",
16
+ "rpg.menu.weapons": "Weapons",
17
+ "rpg.menu.armor": "Armor",
18
+ "rpg.menu.use": "Use",
19
+ "rpg.menu.cancel": "Cancel",
20
+ "rpg.menu.unequip": "Unequip",
21
+ "rpg.menu.remove-equipment": "Remove the current equipment",
22
+ "rpg.menu.empty": "Empty",
23
+ "rpg.menu.leave-game": "Leave the game?",
24
+ "rpg.menu.exit-help": "Press Action to confirm or Escape to go back.",
25
+ "rpg.menu.options-help": "Configure your preferences here.",
26
+ "rpg.save.title": "Save Game",
27
+ "rpg.save.subtitle": "Choose a slot to overwrite or create.",
28
+ "rpg.load.title": "Load Game",
29
+ "rpg.load.subtitle": "Select a slot to load your progress.",
30
+ "rpg.save.auto": "Auto Save",
31
+ "rpg.save.slot": "Slot {index}",
32
+ "rpg.save.empty-slot": "Empty Slot",
33
+ "rpg.save.level": "Level",
34
+ "rpg.save.exp": "Exp",
35
+ "rpg.save.map": "Map",
36
+ "rpg.save.date": "Date",
37
+ "rpg.title.default": "RPG",
38
+ "rpg.title.start": "Start",
39
+ "rpg.title.load": "Load",
40
+ "rpg.gameover.title": "Game Over",
41
+ "rpg.gameover.title-screen": "Title Screen",
42
+ "rpg.gameover.load-game": "Load Game",
43
+ "rpg.shop.default-message": "Welcome to my shop!",
44
+ "rpg.shop.choose-action": "Choose an action",
45
+ "rpg.shop.buy": "Buy",
46
+ "rpg.shop.sell": "Sell",
47
+ "rpg.shop.back": "Back",
48
+ "rpg.shop.equipped": "Equipped",
49
+ "rpg.shop.already-equipped": "Already equipped",
50
+ "rpg.shop.qty": "Qty",
51
+ "rpg.shop.available": "Available",
52
+ "rpg.shop.quantity": "Quantity",
53
+ "rpg.shop.total": "Total",
54
+ },
55
+ };
56
+
57
+ export function provideI18n(config: I18nConfig = {}) {
58
+ return createI18nProvider(config);
59
+ }