@origingame/create-game 0.2.0 → 0.3.0

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 (157) hide show
  1. package/README.md +2 -2
  2. package/archetypes/README.md +1 -0
  3. package/archetypes/arena/.origin/setup +14 -0
  4. package/archetypes/arena/LICENSES.md +8 -0
  5. package/archetypes/arena/README.md +67 -0
  6. package/archetypes/arena/assets/audio/coin.ogt +9 -0
  7. package/archetypes/arena/assets/audio/dash.ogt +9 -0
  8. package/archetypes/arena/assets/audio/win.ogt +9 -0
  9. package/archetypes/arena/assets/fx/coin.ogt +20 -0
  10. package/archetypes/arena/assets/looks/arena.ogt +10 -0
  11. package/archetypes/arena/gitattributes +22 -0
  12. package/archetypes/arena/gitignore +8 -0
  13. package/archetypes/arena/host.d.ts +46 -0
  14. package/archetypes/arena/package.json +18 -0
  15. package/archetypes/arena/project.ogt +14 -0
  16. package/archetypes/arena/src/game.ts +314 -0
  17. package/archetypes/arena/src/main.ts +100 -0
  18. package/archetypes/arena/tests/duel.og.ts +32 -0
  19. package/archetypes/arena/tests/solo.og.ts +22 -0
  20. package/archetypes/arena/tsconfig.json +17 -0
  21. package/archetypes/idle/LICENSES.md +1 -1
  22. package/archetypes/idle/README.md +3 -3
  23. package/archetypes/idle/assets/looks/idle.ogt +1 -1
  24. package/archetypes/idle/assets/{skins → styles}/sparkorb/LICENSES.md +4 -4
  25. package/archetypes/idle/assets/{skins/sparkorb/skin.ogt → styles/sparkorb/style.ogt} +47 -47
  26. package/archetypes/idle/package.json +1 -1
  27. package/archetypes/idle/project.ogt +1 -1
  28. package/archetypes/idle/references/metal/card.png +0 -0
  29. package/archetypes/idle/references/metal/orbit.png +0 -0
  30. package/archetypes/idle/references/metal/title.png +0 -0
  31. package/archetypes/idle/src/main.ts +2 -2
  32. package/archetypes/idle/src/ui.ts +11 -11
  33. package/archetypes/idle/tools/make-assets.py +4 -4
  34. package/archetypes/match3/package.json +1 -1
  35. package/archetypes/match3/project.ogt +1 -1
  36. package/archetypes/rhythm/LICENSES.md +1 -1
  37. package/archetypes/rhythm/README.md +3 -3
  38. package/archetypes/rhythm/assets/{skins → styles}/lanebeat/LICENSES.md +4 -4
  39. package/archetypes/rhythm/assets/{skins/lanebeat/skin.ogt → styles/lanebeat/style.ogt} +45 -45
  40. package/archetypes/rhythm/package.json +1 -1
  41. package/archetypes/rhythm/project.ogt +1 -1
  42. package/archetypes/rhythm/references/metal/results.png +0 -0
  43. package/archetypes/rhythm/references/metal/title.png +0 -0
  44. package/archetypes/rhythm/src/main.ts +2 -2
  45. package/archetypes/rhythm/src/ui.ts +11 -11
  46. package/archetypes/roguelike/LICENSES.md +1 -1
  47. package/archetypes/roguelike/README.md +3 -3
  48. package/archetypes/roguelike/assets/{skins → styles}/lampdive/LICENSES.md +4 -4
  49. package/archetypes/roguelike/assets/{skins/lampdive/skin.ogt → styles/lampdive/style.ogt} +41 -41
  50. package/archetypes/roguelike/package.json +1 -1
  51. package/archetypes/roguelike/project.ogt +1 -1
  52. package/archetypes/roguelike/references/metal/over.png +0 -0
  53. package/archetypes/roguelike/references/metal/title.png +0 -0
  54. package/archetypes/roguelike/src/main.ts +2 -2
  55. package/archetypes/roguelike/src/ui.ts +7 -7
  56. package/archetypes/runner/package.json +1 -1
  57. package/archetypes/runner/project.ogt +1 -1
  58. package/archetypes/third-person/LICENSES.md +1 -1
  59. package/archetypes/third-person/README.md +3 -3
  60. package/archetypes/third-person/assets/{skins → styles}/ember/LICENSES.md +2 -2
  61. package/archetypes/third-person/assets/{skins/ember/skin.ogt → styles/ember/style.ogt} +40 -41
  62. package/archetypes/third-person/package.json +1 -1
  63. package/archetypes/third-person/project.ogt +1 -1
  64. package/archetypes/third-person/references/metal/beacon.png +0 -0
  65. package/archetypes/third-person/references/metal/dawn.png +0 -0
  66. package/archetypes/third-person/references/metal/title.png +0 -0
  67. package/archetypes/third-person/references/webgl2/beacon.png +0 -0
  68. package/archetypes/third-person/src/main.ts +2 -2
  69. package/archetypes/third-person/src/ui.ts +10 -10
  70. package/archetypes/topdown/package.json +1 -1
  71. package/archetypes/topdown/project.ogt +1 -1
  72. package/archetypes/tower-defense/LICENSES.md +1 -1
  73. package/archetypes/tower-defense/README.md +5 -5
  74. package/archetypes/tower-defense/assets/{skins → styles}/pathguard/LICENSES.md +4 -4
  75. package/archetypes/tower-defense/assets/styles/pathguard/atlas.png +0 -0
  76. package/archetypes/tower-defense/assets/styles/pathguard/style.ogt +111 -0
  77. package/archetypes/tower-defense/package.json +1 -1
  78. package/archetypes/tower-defense/project.ogt +1 -1
  79. package/archetypes/tower-defense/references/metal/end.png +0 -0
  80. package/archetypes/tower-defense/references/metal/title.png +0 -0
  81. package/archetypes/tower-defense/references/metal/wave.png +0 -0
  82. package/archetypes/tower-defense/src/main.ts +2 -2
  83. package/archetypes/tower-defense/src/ui.ts +19 -17
  84. package/bin/create.ts +11 -8
  85. package/looks/catalogue.json +40 -40
  86. package/package.json +1 -1
  87. package/template/README.md +2 -1
  88. package/template/assets/audio/sting.ogt +10 -0
  89. package/template/assets/timelines/intro.ogt +101 -0
  90. package/template/assets/timelines/narration.srt +11 -0
  91. package/template/host.d.ts +46 -0
  92. package/template/package.json +1 -1
  93. package/template/project.ogt +1 -1
  94. package/template/references/metal/intro-4.png +0 -0
  95. package/template/references/metal/intro-8.png +0 -0
  96. package/template/src/main.ts +1 -1
  97. package/template/tests/frames.ts +134 -0
  98. package/template/tests/intro.og.ts +65 -0
  99. package/template/tests/smoke.og.ts +30 -36
  100. package/template/tsconfig.json +1 -1
  101. package/template-2d/LICENSES.md +1 -0
  102. package/template-2d/README.md +5 -2
  103. package/template-2d/assets/rigs/keeper.ogt +292 -0
  104. package/template-2d/assets/rigs/keeper.png +0 -0
  105. package/template-2d/assets/rigs/keeper.png.ogt +7 -0
  106. package/template-2d/package.json +1 -1
  107. package/template-2d/project.ogt +1 -1
  108. package/template-2d/src/main.ts +159 -4
  109. package/template-2d/tests/keeper.og.ts +93 -0
  110. package/archetypes/tower-defense/assets/skins/pathguard/atlas.png +0 -0
  111. package/archetypes/tower-defense/assets/skins/pathguard/skin.ogt +0 -111
  112. /package/archetypes/idle/assets/{skins → styles}/sparkorb/ChakraPetch-BoldItalic.ttf +0 -0
  113. /package/archetypes/idle/assets/{skins → styles}/sparkorb/ChakraPetch-BoldItalic.ttf.ogt +0 -0
  114. /package/archetypes/idle/assets/{skins → styles}/sparkorb/OFL.txt +0 -0
  115. /package/archetypes/idle/assets/{skins → styles}/sparkorb/Orbitron-Medium.ttf +0 -0
  116. /package/archetypes/idle/assets/{skins → styles}/sparkorb/Orbitron-Medium.ttf.ogt +0 -0
  117. /package/archetypes/idle/assets/{skins → styles}/sparkorb/Rajdhani-Bold.ttf +0 -0
  118. /package/archetypes/idle/assets/{skins → styles}/sparkorb/Rajdhani-Bold.ttf.ogt +0 -0
  119. /package/archetypes/idle/assets/{skins → styles}/sparkorb/atlas.png +0 -0
  120. /package/archetypes/idle/assets/{skins → styles}/sparkorb/atlas.png.ogt +0 -0
  121. /package/archetypes/idle/assets/{skins → styles}/sparkorb/icons.png +0 -0
  122. /package/archetypes/idle/assets/{skins → styles}/sparkorb/icons.png.ogt +0 -0
  123. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/ChakraPetch-Medium.ttf +0 -0
  124. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/ChakraPetch-Medium.ttf.ogt +0 -0
  125. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/JetBrainsMono-Regular.ttf +0 -0
  126. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/JetBrainsMono-Regular.ttf.ogt +0 -0
  127. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/OFL.txt +0 -0
  128. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/Orbitron-Bold.ttf +0 -0
  129. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/Orbitron-Bold.ttf.ogt +0 -0
  130. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/atlas.png +0 -0
  131. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/atlas.png.ogt +0 -0
  132. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/icons.png +0 -0
  133. /package/archetypes/rhythm/assets/{skins → styles}/lanebeat/icons.png.ogt +0 -0
  134. /package/archetypes/roguelike/assets/{skins → styles}/lampdive/DotGothic16-Regular.ttf +0 -0
  135. /package/archetypes/roguelike/assets/{skins → styles}/lampdive/DotGothic16-Regular.ttf.ogt +0 -0
  136. /package/archetypes/roguelike/assets/{skins → styles}/lampdive/OFL.txt +0 -0
  137. /package/archetypes/roguelike/assets/{skins → styles}/lampdive/atlas.png +0 -0
  138. /package/archetypes/roguelike/assets/{skins → styles}/lampdive/atlas.png.ogt +0 -0
  139. /package/archetypes/roguelike/assets/{skins → styles}/lampdive/icons.png +0 -0
  140. /package/archetypes/roguelike/assets/{skins → styles}/lampdive/icons.png.ogt +0 -0
  141. /package/archetypes/third-person/assets/{skins → styles}/ember/Cinzel-Bold.ttf +0 -0
  142. /package/archetypes/third-person/assets/{skins → styles}/ember/Cinzel-Bold.ttf.ogt +0 -0
  143. /package/archetypes/third-person/assets/{skins → styles}/ember/Newsreader-Medium.ttf +0 -0
  144. /package/archetypes/third-person/assets/{skins → styles}/ember/Newsreader-Medium.ttf.ogt +0 -0
  145. /package/archetypes/third-person/assets/{skins → styles}/ember/OFL.txt +0 -0
  146. /package/archetypes/third-person/assets/{skins → styles}/ember/atlas.png +0 -0
  147. /package/archetypes/third-person/assets/{skins → styles}/ember/atlas.png.ogt +0 -0
  148. /package/archetypes/third-person/assets/{skins → styles}/ember/icons.png +0 -0
  149. /package/archetypes/third-person/assets/{skins → styles}/ember/icons.png.ogt +0 -0
  150. /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/Fredoka-SemiBold.ttf +0 -0
  151. /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/Fredoka-SemiBold.ttf.ogt +0 -0
  152. /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/LilitaOne-Regular.ttf +0 -0
  153. /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/LilitaOne-Regular.ttf.ogt +0 -0
  154. /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/OFL.txt +0 -0
  155. /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/atlas.png.ogt +0 -0
  156. /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/icons.png +0 -0
  157. /package/archetypes/tower-defense/assets/{skins → styles}/pathguard/icons.png.ogt +0 -0
@@ -1,52 +1,51 @@
1
- type skin
1
+ type style
2
2
  version 1
3
3
  ---
4
- # Ember Vale's skin: a copy of the fantasy style pack (templates/ui-kit/tools/make-skins.py; CC0 art, OFL fonts; LICENSES.md).
4
+ # Ember Vale's skin: a copy of the fantasy style pack (templates/ui-kit/tools/make-styles.py; CC0 art, OFL fonts; LICENSES.md).
5
5
  name "ember"
6
- base dark
7
- atlas "skins/ember/atlas.png" 384 256
6
+ scheme dark
7
+ atlas "styles/ember/atlas.png" 384 256
8
8
  scale 1
9
9
  palette {
10
- bg "#1a120b"
11
- bgSubtle "#241910"
12
- surface "#efdfb8"
13
- surfaceRaised "#efdfb8"
14
- surfaceHover "#f4e8c8"
15
- materialThick "#efdfb8"
16
- materialRegular "#efdfb8f2"
17
- materialThin "#efdfb8e6"
18
- fg "#2a1a0c"
19
- fgMuted "#3d2a18"
20
- fgSubtle "#654b31"
21
- fgInverse "#f4e8c8"
22
- fill "#e2cc98"
23
- fillStrong "#d6bc82"
24
- border "#9b7a4a"
25
- borderStrong "#5c4127"
26
- accent "#7a520f"
27
- accentHover "#74500e"
28
- accentActive "#6a470c"
29
- accentFg "#fff4dc"
30
- accentInk "#6e470d"
31
- accentSoft "#7a520f40"
10
+ backdrop "#1a120b"
11
+ well "#241910"
12
+ panel "#efdfb8"
13
+ panelHover "#f4e8c8"
14
+ panelRaised "#efdfb8"
15
+ line "#9b7a4a"
16
+ lineStrong "#5c4127"
17
+ ink "#2a1a0c"
18
+ inkMuted "#3d2a18"
19
+ inkFaint "#654b31"
20
+ inkInverse "#f4e8c8"
21
+ control "#e2cc98"
22
+ controlStrong "#d6bc82"
23
+ primary "#7a520f"
24
+ primaryHover "#74500e"
25
+ primaryActive "#6a470c"
26
+ onPrimary "#fff4dc"
27
+ primarySoft "#7a520f40"
28
+ highlight "#6e470d"
32
29
  focus "#8f2d1f"
33
- thumb "#f4e8c8"
30
+ knob "#f4e8c8"
31
+ hud "#efdfb8"
34
32
  scrim "#0d0805cc"
33
+ shadow "#000000aa"
34
+ sheen "#ffffff52"
35
35
  success "#2d5a1e"
36
36
  warn "#704713"
37
37
  danger "#6e1b10"
38
38
  info "#22517f"
39
- shadow "#000000aa"
40
- stage "#1a120b"
39
+ health "#2d5a1e"
40
+ mana "#0a84ff"
41
+ xp "#bf5af2"
42
+ gold "#7a520f"
41
43
  }
42
44
  radius {
45
+ small 4
43
46
  control 4
44
- field 4
45
- block 4
46
- card 4
47
- panel 6
48
- sheet 6
49
- button 6
47
+ bar 4
48
+ panel 4
50
49
  }
51
50
  ramp {
52
51
  body 15 22
@@ -67,12 +66,12 @@ durations {
67
66
  slow 480
68
67
  }
69
68
  fonts {
70
- body "skins/ember/Newsreader-Medium.ttf"
71
- display "skins/ember/Cinzel-Bold.ttf"
72
- numeric "skins/ember/Cinzel-Bold.ttf"
69
+ body "styles/ember/Newsreader-Medium.ttf"
70
+ display "styles/ember/Cinzel-Bold.ttf"
71
+ numeric "styles/ember/Cinzel-Bold.ttf"
73
72
  fallback "fonts/NotoSansSC-subset.ttf"
74
73
  }
75
- icons "skins/ember/icons.png" 8 4 {
74
+ icons "styles/ember/icons.png" 8 4 {
76
75
  names "heart" "star" "coin" "lock" "pause" "gear" "sword" "shield" "potion" "gem" "key" "map" "bolt" "clock" "trophy" "home" "plus" "close" "check" "play" "back" "bag" "bomb" "crown" "flag" "apple" "leaf" "fire" "drop" "skull" "music" "person"
77
76
  }
78
77
  slice panel 4 4 96 96 16 authored
@@ -84,7 +83,7 @@ slice buttonDisabled 328 4 48 48 10 tint
84
83
  slice field 4 104 48 48 8 tint
85
84
  slice slot 56 104 48 48 10 tint
86
85
  slice tooltip 108 104 32 32 8 tint
87
- slice capsule 144 104 32 32 12 tint
86
+ slice hud 144 104 32 32 12 tint
88
87
  slice track 180 104 32 16 6 tint
89
88
  slice fill 216 104 32 16 6 tint
90
89
  soft 252 104 64 64 24
@@ -96,7 +95,7 @@ tile 204 172 64 64 64 64 0.5 6 panel card
96
95
  shadow panel 0 10 18 2 "#000000b3"
97
96
  shadow card 0 4 8 0 "#00000066"
98
97
  shadow button 0 3 4 0 "#00000073"
99
- shadow capsule 0 3 6 0 "#00000080"
98
+ shadow hud 0 3 6 0 "#00000080"
100
99
  press 0.98 0.96 1
101
100
  hover 0 1.0
102
101
  motion {
@@ -13,6 +13,6 @@
13
13
  "test": "og test ."
14
14
  },
15
15
  "dependencies": {
16
- "@origingame/engine": "0.2.0"
16
+ "@origingame/engine": "0.3.0"
17
17
  }
18
18
  }
@@ -3,7 +3,7 @@ version 1
3
3
  ---
4
4
  name "Ember Vale"
5
5
  id "third-person"
6
- engine "0.2.0"
6
+ engine "0.3.0"
7
7
  entry "src/main.ts"
8
8
  profile "3d"
9
9
  tick 60
@@ -26,9 +26,9 @@ import { BEACON_AT, buildVale, ground, HOURS, must, setHour } from "./world.ts";
26
26
 
27
27
  function start(world: World): void {
28
28
  for (const l of LANGUAGES) must(og.i18n.load(world, `strings/${l.code}.ogt`));
29
- const skin = must(og.load.skin(world, "skins/ember/skin.ogt"));
29
+ const skin = must(og.load.style(world, "styles/ember/style.ogt"));
30
30
  const sound = (path: string) => must(og.load.sound(world, path));
31
- must(og.ui.configure(world, { skin, sounds: { press: sound("audio/ui-press.ogt"), open: sound("audio/ui-open.ogt"), close: sound("audio/ui-close.ogt"), error: sound("audio/ui-error.ogt"), success: sound("audio/ui-success.ogt") } }));
31
+ must(og.ui.configure(world, { style: skin, sounds: { press: sound("audio/ui-press.ogt"), open: sound("audio/ui-open.ogt"), close: sound("audio/ui-close.ogt"), error: sound("audio/ui-error.ogt"), success: sound("audio/ui-success.ogt") } }));
32
32
  buildVale(world);
33
33
  spawnHero(world);
34
34
  spawnSentinels(world);
@@ -1,4 +1,4 @@
1
- // Ember Vale's screens on og.ui and the skin in assets/skins/ember (the
1
+ // Ember Vale's screens on og.ui and the skin in assets/styles/ember (the
2
2
  // engine's fantasy pack): the title over an orbit of the vale, the HUD
3
3
  // (hearts and beacons top-left, the dusk clock and pause top-right, the
4
4
  // kindling prompt and its progress at the bottom; on a touch screen a
@@ -216,7 +216,7 @@ function clock(world: World, g: GameV, dt: number): void {
216
216
  }
217
217
 
218
218
  function capsule(id: string, build: () => void): void {
219
- og.ui.row(id, { gap: S.xs, cross: "center", padding: { left: S.sm, top: S.xs, right: S.md, bottom: S.xs }, frame: "capsule", background: { token: "surface" } }, build);
219
+ og.ui.row(id, { gap: S.xs, cross: "center", padding: { left: S.sm, top: S.xs, right: S.md, bottom: S.xs }, frame: "hud", background: { token: "panel" } }, build);
220
220
  }
221
221
 
222
222
  function clockText(g: GameV): string {
@@ -232,10 +232,10 @@ function hud(world: World, g: GameV): void {
232
232
  og.ui.setNavigation(false);
233
233
  og.ui.anchor("Status", { anchor: "topLeft", direction: "column", gap: S.sm }, () => {
234
234
  capsule("Hearts", () => {
235
- for (let i = 0; i < HEARTS; i++) og.ui.icon(`Heart${i}`, "heart", { size: 22, tint: { token: i < g.hearts ? "danger" : "fillStrong" } });
235
+ for (let i = 0; i < HEARTS; i++) og.ui.icon(`Heart${i}`, "heart", { size: 22, tint: { token: i < g.hearts ? "danger" : "controlStrong" } });
236
236
  });
237
237
  capsule("Beacons", () => {
238
- og.ui.icon("Fire", "fire", { size: 22, tint: { token: "accent" } });
238
+ og.ui.icon("Fire", "fire", { size: 22, tint: { token: "primary" } });
239
239
  og.ui.label("Count", say(world, "hud.beacons", { n: g.lit, total: BEACONS }), { step: "lg", font: "mono", nowrap: true });
240
240
  });
241
241
  });
@@ -243,9 +243,9 @@ function hud(world: World, g: GameV): void {
243
243
  og.ui.anchor("Dusk", { anchor: "topRight", gap: S.sm, cross: "center" }, () => {
244
244
  capsule("Clock", () => {
245
245
  const left = 1 - (g.hour - HOURS.start) / (HOURS.night - HOURS.start);
246
- og.ui.icon("Glyph", "clock", { size: 20, tint: { token: left < 0.25 ? "danger" : "fg" } });
246
+ og.ui.icon("Glyph", "clock", { size: 20, tint: { token: left < 0.25 ? "danger" : "ink" } });
247
247
  og.ui.label("Hour", clockText(g), { font: "mono", nowrap: true, width: px(48) });
248
- og.ui.meter("Left", Math.max(0, left), 1, { kind: "accent", width: px(touch ? 70 : 110) });
248
+ og.ui.meter("Left", Math.max(0, left), 1, { kind: "primary", width: px(touch ? 70 : 110) });
249
249
  });
250
250
  if (og.ui.actionButton("Pause", say(world, "hud.pause"), { icon: "pause", size: 48 }).clicked) paused = true;
251
251
  });
@@ -254,7 +254,7 @@ function hud(world: World, g: GameV): void {
254
254
  if (g.near >= 0 && g.ending === Ending.None) {
255
255
  og.ui.anchor("Prompt", { anchor: "bottom", offsetY: touch ? -150 : -24, direction: "column", cross: "center", gap: S.xs }, () => {
256
256
  capsule("Hint", () => {
257
- og.ui.icon("Fire", "fire", { size: 18, tint: { token: "accent" } });
257
+ og.ui.icon("Fire", "fire", { size: 18, tint: { token: "primary" } });
258
258
  const pad = must(world.resource(og.Gamepad)).connected();
259
259
  og.ui.label("Text", say(world, touch ? "hud.hint.touch" : pad ? "hud.hint.pad" : "hud.hint.keys"), { nowrap: true });
260
260
  });
@@ -346,7 +346,7 @@ function settings(world: World, g: GameV, p: PrefsV): void {
346
346
  p.master = slider("Master", "settings.master", p.master);
347
347
  p.music = slider("Music", "settings.music", p.music);
348
348
  p.effects = slider("Effects", "settings.effects", p.effects);
349
- og.ui.label("LanguageLabel", say(world, "settings.language"), { color: { token: "fgMuted" } });
349
+ og.ui.label("LanguageLabel", say(world, "settings.language"), { color: { token: "inkMuted" } });
350
350
  const r = og.ui.radio("Language", LANGUAGES.map((l) => say(world, l.key)), p.language, { horizontal: true });
351
351
  if (r.selected !== p.language) setLanguage(world, p, r.selected);
352
352
  p.reducedMotion = og.ui.toggle("Motion", p.reducedMotion, say(world, "settings.motion")).on;
@@ -375,7 +375,7 @@ function summary(world: World, g: GameV): "again" | "menu" | null {
375
375
  const title = won ? "over.won" : g.ending === Ending.Caught ? "over.caught" : "over.night";
376
376
  og.ui.modal("Summary", g.phase === Phase.Over, { title: say(world, title), locked: true, width: px(Math.min(400, og.ui.frameViewport().width - 32)) }, () => {
377
377
  og.ui.row("Mark", { width: grow, align: "center" }, () => {
378
- og.ui.transition("Icon", true, { kind: "pop" }, () => og.ui.icon("Glyph", won ? "fire" : g.ending === Ending.Caught ? "skull" : "clock", { size: 48, tint: { token: won ? "accent" : "fgMuted" } }));
378
+ og.ui.transition("Icon", true, { kind: "pop" }, () => og.ui.icon("Glyph", won ? "fire" : g.ending === Ending.Caught ? "skull" : "clock", { size: 48, tint: { token: won ? "primary" : "inkMuted" } }));
379
379
  // From a third of a second into the summary, every frame: a burst plays only while it is called.
380
380
  if (won && g.overTicks >= OVER_DELAY + 20) og.ui.burst({ count: 14 });
381
381
  });
@@ -385,7 +385,7 @@ function summary(world: World, g: GameV): "again" | "menu" | null {
385
385
  og.ui.keyValue("Hurts", say(world, "over.hurts"), `${g.hurts}`, true);
386
386
  og.ui.keyValue("Best", say(world, "over.best"), g.best > 0 ? seconds(g.best) + (g.newBest ? " ★" : "") : say(world, "over.none"), false);
387
387
  });
388
- og.ui.label("Record", say(world, "over.record", { runs: g.runs, wins: g.wins }), { color: { token: "fgMuted" }, align: "center", width: grow });
388
+ og.ui.label("Record", say(world, "over.record", { runs: g.runs, wins: g.wins }), { color: { token: "inkMuted" }, align: "center", width: grow });
389
389
  og.ui.row("Actions", { width: grow, gap: S.sm }, () => {
390
390
  if (og.ui.button("Again", say(world, "over.again"), { variant: "primary", width: grow }).clicked) chose = "again";
391
391
  if (og.ui.button("Menu", say(world, "over.menu"), { width: grow }).clicked) chose = "menu";
@@ -13,6 +13,6 @@
13
13
  "test": "og test ."
14
14
  },
15
15
  "dependencies": {
16
- "@origingame/engine": "0.2.0"
16
+ "@origingame/engine": "0.3.0"
17
17
  }
18
18
  }
@@ -3,7 +3,7 @@ version 1
3
3
  ---
4
4
  name "Tomb Room"
5
5
  id "topdown"
6
- engine "0.2.0"
6
+ engine "0.3.0"
7
7
  entry "src/main.ts"
8
8
  profile "2d"
9
9
  tick 60
@@ -11,5 +11,5 @@
11
11
  | `assets/audio/*.ogt` | CC0 1.0 | `og sfx` presets (click, select, back, error, levelup) that `og cook` synthesises, written by the same script |
12
12
  | `assets/strings/*.ogt` | CC0 1.0 | self-made: the English and Chinese string tables |
13
13
  | `assets/fonts/NotoSansSC-subset.ttf` | SIL Open Font License 1.1 (`assets/fonts/OFL.txt`) | Noto Sans SC Regular, subset to Latin-1 and the characters of `assets/strings/zh-CN.ogt` by the same script from the project template's `assets/fonts/NotoSansSC-Regular-subset.ttf` |
14
- | `assets/skins/pathguard/*` | CC0 1.0 art, SIL OFL 1.1 fonts | the engine's cartoon style pack (`templates/ui-kit/assets/skins/cartoon`, `assets/skins/pathguard/LICENSES.md`) with this game's palette |
14
+ | `assets/styles/pathguard/*` | CC0 1.0 art, SIL OFL 1.1 fonts | the engine's cartoon style pack (`templates/styles/cartoon`, `assets/styles/pathguard/LICENSES.md`) with this game's palette |
15
15
  | `references/*/*.png` | CC0 1.0 | frames of this game recorded by `tests/frames.og.ts` |
@@ -55,10 +55,10 @@ og build . --publish # dist/: the play page as it ships
55
55
  doorstep is a red number, a 6-tick hit stop, trauma and a kick, the
56
56
  lantern flashing red and squashing. Plants pop up when set and squash
57
57
  when they shoot.
58
- - **Screens on og.ui with a skin** (og-ui §2–§6, og-taste §1–§7): the
59
- engine's cartoon pack recoloured as `assets/skins/pathguard` (cream
58
+ - **Screens on og.ui with a style** (og-ui §2–§6, og-taste §1–§7): the
59
+ engine's cartoon pack recoloured as `assets/styles/pathguard` (cream
60
60
  panels on the night lawn, the lantern's orange as the one accent); the
61
- title (`og.ui.mainMenu`), the HUD as capsules in the corners with
61
+ title (`og.ui.mainMenu`), the HUD as boxes in the corners with
62
62
  counting numbers (`og.ui.counter`), the plant tray (right on a wide
63
63
  screen, along the bottom on a phone), `og.ui.pauseMenu`, a settings
64
64
  dialog and the end card built from widgets (stars landing with
@@ -176,7 +176,7 @@ The visitors' numbers are `KINDS` and the waves `WAVES` in `src/systems.ts`.
176
176
  `fire`.
177
177
  3. **The field**: `MAP` in `tools/make-assets.py` (H hedge, R stone, G the
178
178
  gate, D the doorstep), then `python3 tools/make-assets.py && og cook .`.
179
- 4. **The look**: `assets/skins/pathguard/skin.ogt`'s palette and the art
179
+ 4. **The look**: `assets/styles/pathguard/style.ogt`'s palette and the art
180
180
  in `tools/make-assets.py`.
181
181
  5. **Words**: `assets/strings/*.ogt`; a new Chinese string needs the
182
182
  script run again to subset the font.
@@ -225,7 +225,7 @@ in the engine repository).
225
225
  give the same bytes): the tileset, the map, the sprite sheet, the glow and
226
226
  particle textures and emitters, the sounds and the music, the `og sfx`
227
227
  preset files of the UI's sounds, and the font subset. `LICENSES.md` lists
228
- them; the skin's are in `assets/skins/pathguard/LICENSES.md`.
228
+ them; the style's are in `assets/styles/pathguard/LICENSES.md`.
229
229
 
230
230
  ## Known limits (engine gaps, not design)
231
231
 
@@ -1,13 +1,13 @@
1
- # Path Guard's skin (the engine's cartoon style pack, recoloured): licences
1
+ # Path Guard's style (the engine's cartoon style pack, recoloured): licences
2
2
 
3
3
  | File | Licence | Origin |
4
4
  |---|---|---|
5
- | `atlas.png` | CC0 1.0 | self-made by the Origin Engine authors: drawn by `templates/ui-kit/tools/make-skins.py` (deterministic) |
5
+ | `atlas.png` | CC0 1.0 | self-made by the Origin Engine authors: drawn by `templates/ui-kit/tools/make-styles.py` (deterministic) |
6
6
  | `icons.png` | CC0 1.0 | self-made: the kit's 32 icons (`tools/make-assets.py`) restyled by the same script |
7
- | `skin.ogt`, `*.ogt` | CC0 1.0 | written by the same script |
7
+ | `style.ogt`, `*.ogt` | CC0 1.0 | written by the same script |
8
8
  | `Fredoka-SemiBold.ttf` | SIL Open Font License 1.1 (`OFL.txt`) | Fredoka SemiBold (Google Fonts); unmodified. Copyright 2016 The Fredoka Project Authors (https://github.com/hafontia/Fredoka-One) |
9
9
  | `LilitaOne-Regular.ttf` | SIL Open Font License 1.1 (`OFL.txt`) | Lilita One (Google Fonts); unmodified (Reserved Font Name "Lilita One"). Copyright (c) 2011 Juan Montoreano (juan@remolacha.biz), with Reserved Font Names "Lilita One" |
10
10
 
11
11
  Characters these fonts lack (Chinese, Japanese, Korean) are drawn with the
12
- skin's `fallback` font, `fonts/NotoSansSC-Regular-subset.ttf` (the platform's
12
+ style's `fallback` font, `fonts/NotoSansSC-Regular-subset.ttf` (the platform's
13
13
  subset, OFL 1.1, `assets/fonts/OFL.txt`).
@@ -0,0 +1,111 @@
1
+ type style
2
+ version 1
3
+ ---
4
+ # Path Guard's style: the engine's cartoon style pack (templates/ui-kit/tools/make-styles.py; CC0 art, OFL fonts;
5
+ # LICENSES.md) in a night-garden palette: cream panels on the dark lawn, the lantern's orange as the one accent.
6
+ name "pathguard"
7
+ scheme light
8
+ atlas "styles/pathguard/atlas.png" 384 256
9
+ scale 1
10
+ palette {
11
+ backdrop "#10231a"
12
+ well "#15301f"
13
+ panel "#fff4dc"
14
+ panelHover "#ffe9bd"
15
+ panelRaised "#fff4dc"
16
+ line "#20302a"
17
+ lineStrong "#20302a"
18
+ ink "#0f1a14"
19
+ inkMuted "#3a483f"
20
+ inkFaint "#45534a"
21
+ inkInverse "#fff4dc"
22
+ control "#ffe08a"
23
+ controlStrong "#ffd466"
24
+ primary "#974009"
25
+ primaryHover "#8f3c0a"
26
+ primaryActive "#863809"
27
+ onPrimary "#fff4dc"
28
+ primarySoft "#ff8c3a40"
29
+ highlight "#a8400a"
30
+ focus "#5cb6e8"
31
+ knob "#ffffff"
32
+ hud "#fff4dcf5"
33
+ scrim "#07120ccc"
34
+ shadow "#0b1a1299"
35
+ sheen "#ffffff52"
36
+ success "#06681b"
37
+ warn "#7b5102"
38
+ danger "#700b1c"
39
+ info "#2256af"
40
+ health "#06681b"
41
+ mana "#007aff"
42
+ xp "#af52de"
43
+ gold "#b44c0d"
44
+ }
45
+ radius {
46
+ small 6
47
+ control 6
48
+ bar 6
49
+ panel 10
50
+ }
51
+ ramp {
52
+ body 15 20
53
+ meta 13 18
54
+ caption 13 18
55
+ lg 19 26
56
+ xl 22 28
57
+ xl2 26 30
58
+ xl3 30 34
59
+ displaySm 32 36
60
+ displayMd 40 44
61
+ displayLg 52 56
62
+ }
63
+ durations {
64
+ instant 110
65
+ fast 160
66
+ base 280
67
+ slow 420
68
+ }
69
+ fonts {
70
+ body "styles/pathguard/Fredoka-SemiBold.ttf"
71
+ display "styles/pathguard/LilitaOne-Regular.ttf"
72
+ numeric "styles/pathguard/LilitaOne-Regular.ttf"
73
+ fallback "fonts/NotoSansSC-subset.ttf"
74
+ }
75
+ icons "styles/pathguard/icons.png" 8 4 {
76
+ names "heart" "star" "coin" "lock" "pause" "gear" "sword" "shield" "potion" "gem" "key" "map" "bolt" "clock" "trophy" "home" "plus" "close" "check" "play" "back" "bag" "bomb" "crown" "flag" "apple" "leaf" "fire" "drop" "skull" "music" "person"
77
+ }
78
+ slice panel 4 4 96 96 16 tint
79
+ slice card 104 4 64 64 14 tint
80
+ slice button 172 4 48 48 12 tint
81
+ slice buttonHover 224 4 48 48 12 tint
82
+ slice buttonPressed 276 4 48 48 12 tint
83
+ slice buttonDisabled 328 4 48 48 12 tint
84
+ slice field 4 104 48 48 10 tint
85
+ slice slot 56 104 48 48 10 tint
86
+ slice tooltip 108 104 32 32 10 tint
87
+ slice hud 144 104 32 32 10 tint
88
+ slice track 180 104 32 16 7 tint
89
+ slice fill 216 104 32 16 7 tint
90
+ soft 252 104 64 64 24
91
+ divider 4 172 128 24 48 16 primary
92
+ flourish 136 172 64 24 22 10 primary 2
93
+ shadow panel 0 8 0 0 "#0b1a12cc"
94
+ shadow card 0 5 0 0 "#0b1a1299"
95
+ shadow button 0 5 0 0 "#0b1a12cc"
96
+ shadow hud 0 4 0 0 "#0b1a1299"
97
+ shadow slot 0 3 0 0 "#0b1a1266"
98
+ textShadow display 0 4 0 "#0b1a12"
99
+ # Body text gets a hairline of its own ink to the right: the Chinese fallback
100
+ # (Noto Sans SC Regular) reads at the weight of the Latin face.
101
+ textShadow body 1 0 0 "#20302a"
102
+ press 1.08 0.88 3
103
+ hover -3 1.04
104
+ motion {
105
+ enter pop
106
+ stagger 50
107
+ toast pop
108
+ progress 360
109
+ shake 8
110
+ pop 0.22
111
+ }
@@ -13,6 +13,6 @@
13
13
  "test": "og test ."
14
14
  },
15
15
  "dependencies": {
16
- "@origingame/engine": "0.2.0"
16
+ "@origingame/engine": "0.3.0"
17
17
  }
18
18
  }
@@ -3,7 +3,7 @@ version 1
3
3
  ---
4
4
  name "Path Guard"
5
5
  id "tower-defense"
6
- engine "0.2.0"
6
+ engine "0.3.0"
7
7
  entry "src/main.ts"
8
8
  profile "2d"
9
9
  tick 60
@@ -32,11 +32,11 @@ function must<T>(r: { ok: true; value?: T } | { ok: false; reason: string }): T
32
32
 
33
33
  function start(world: World): void {
34
34
  for (const l of LANGUAGES) must(og.i18n.load(world, `strings/${l.code}.ogt`));
35
- const skin = must(og.load.skin(world, "skins/pathguard/skin.ogt"));
35
+ const skin = must(og.load.style(world, "styles/pathguard/style.ogt"));
36
36
  const sound = (path: string) => must(og.load.sound(world, path));
37
37
  must(
38
38
  og.ui.configure(world, {
39
- skin,
39
+ style: skin,
40
40
  sounds: { press: sound("audio/ui-press.ogt"), open: sound("audio/ui-open.ogt"), close: sound("audio/ui-close.ogt"), error: sound("audio/ui-error.ogt"), success: sound("audio/ui-success.ogt") },
41
41
  }),
42
42
  );
@@ -1,4 +1,4 @@
1
- // Path Guard's screens on og.ui and the skin in assets/skins/pathguard:
1
+ // Path Guard's screens on og.ui and the skin in assets/styles/pathguard:
2
2
  // the title, the HUD (the light and the coins in the top-left corner, the
3
3
  // wave and pause in the top-right, the plant tray on the right on a wide
4
4
  // screen and along the bottom on a phone), the pause menu, the settings
@@ -30,8 +30,10 @@ type PrefsV = Value<typeof Prefs>;
30
30
  * millisecond), so the screens read it from here.
31
31
  */
32
32
  const SPACE = { xxs: 2, xs: 4, sm: 8, md: 12, lg: 16, xl: 24 } as const;
33
- type Theme = { readonly space: typeof SPACE };
34
- const THEME: Theme = { space: SPACE };
33
+ /** The pathguard style's radius block (assets/styles/pathguard/style.ogt), read from here for the same reason. */
34
+ const RADIUS = { bar: 6, panel: 10 } as const;
35
+ type Theme = { readonly space: typeof SPACE; readonly radius: typeof RADIUS };
36
+ const THEME: Theme = { space: SPACE, radius: RADIUS };
35
37
 
36
38
  /** A string of the active locale (og.i18n). */
37
39
  export function say(world: World, key: string, args?: Record<string, string | number>): string {
@@ -193,10 +195,10 @@ function platform(world: World, g: GameV, p: PrefsV): void {
193
195
  }
194
196
  }
195
197
 
196
- /** One HUD capsule: an icon and a number counting to its value. */
197
- function capsule(t: Theme, id: string, icon: string, value: number, tone: "accent" | "danger" | "fg"): void {
198
- og.ui.row(id, { gap: t.space.xs, cross: "center", padding: { left: t.space.sm, top: t.space.xs, right: t.space.md, bottom: t.space.xs }, frame: "capsule", background: { token: "surface" }, radius: 999 }, () => {
199
- og.ui.icon("Icon", icon, { size: 26, tint: { token: tone === "fg" ? "fg" : tone } });
198
+ /** One HUD box: an icon and a number counting to its value. */
199
+ function hudBox(t: Theme, id: string, icon: string, value: number, tone: "gold" | "danger" | "ink"): void {
200
+ og.ui.row(id, { gap: t.space.xs, cross: "center", padding: { left: t.space.sm, top: t.space.xs, right: t.space.md, bottom: t.space.xs }, frame: "hud", background: { token: "panel" }, radius: t.radius.bar }, () => {
201
+ og.ui.icon("Icon", icon, { size: 26, tint: { token: tone } });
200
202
  og.ui.counter("Value", value, { step: "xl", font: "display", durationMs: 300, width: px(56), align: "center" });
201
203
  });
202
204
  }
@@ -205,14 +207,14 @@ function hud(world: World, theme: Theme, g: GameV, t: TuneV): void {
205
207
  const small = phone();
206
208
  og.ui.setNavigation(false);
207
209
  og.ui.anchor("Purse", { anchor: "topLeft", gap: theme.space.sm }, () => {
208
- capsule(theme, "Light", "heart", g.lives, g.lives <= 3 ? "danger" : "fg");
209
- capsule(theme, "Coins", "coin", g.gold, "accent");
210
+ hudBox(theme, "Light", "heart", g.lives, g.lives <= 3 ? "danger" : "ink");
211
+ hudBox(theme, "Coins", "coin", g.gold, "gold");
210
212
  // A refused placement shakes the purse once, with the error sound (og-ui §6).
211
213
  if (g.refusedTick === world.tick) og.ui.shake({ sound: true });
212
214
  });
213
215
  let paused = false;
214
216
  og.ui.anchor("Waves", { anchor: "topRight", gap: theme.space.sm, cross: "center" }, () => {
215
- og.ui.row("Wave", { cross: "center", padding: { left: theme.space.md, top: theme.space.xs, right: theme.space.md, bottom: theme.space.xs }, frame: "capsule", background: { token: "surface" }, radius: 999 }, () => {
217
+ og.ui.row("Wave", { cross: "center", padding: { left: theme.space.md, top: theme.space.xs, right: theme.space.md, bottom: theme.space.xs }, frame: "hud", background: { token: "panel" }, radius: theme.radius.bar }, () => {
216
218
  og.ui.label("Label", say(world, "hud.wave", { n: Math.max(1, g.wave), total: WAVES.length }), { step: "lg", font: "display", nowrap: true });
217
219
  });
218
220
  if (og.ui.actionButton("Pause", say(world, "hud.pause"), { icon: "pause", size: 48 }).clicked) paused = true;
@@ -223,11 +225,11 @@ function hud(world: World, theme: Theme, g: GameV, t: TuneV): void {
223
225
  PLANTS.forEach((name, kind) => {
224
226
  const price = cost(t, kind);
225
227
  const chosen = g.selected === kind;
226
- og.ui.column(og.ui.idi("Plant", kind), { cross: "center", gap: theme.space.xxs, padding: { left: theme.space.sm, top: theme.space.xs, right: theme.space.sm, bottom: theme.space.sm }, frame: "card", background: { token: "surface" }, radius: 18 }, () => {
228
+ og.ui.column(og.ui.idi("Plant", kind), { cross: "center", gap: theme.space.xxs, padding: { left: theme.space.sm, top: theme.space.xs, right: theme.space.sm, bottom: theme.space.sm }, frame: "card", background: { token: "panel" }, radius: theme.radius.panel }, () => {
227
229
  og.ui.row("Top", { cross: "center", gap: theme.space.xs }, () => {
228
230
  og.ui.image("Picture", { texture: g.sheet as unknown as never, uv: uvOf(PICTURE[kind]!), width: px(small ? 32 : 44), height: px(small ? 32 : 44) });
229
231
  // The price: muted when the purse covers it, in danger's ink when it does not.
230
- og.ui.label("Price", `${price}`, { step: "lg", font: "display", color: { token: g.gold >= price ? "fgMuted" : "danger" }, nowrap: true, width: px(40), align: "center" });
232
+ og.ui.label("Price", `${price}`, { step: "lg", font: "display", color: { token: g.gold >= price ? "inkMuted" : "danger" }, nowrap: true, width: px(40), align: "center" });
231
233
  });
232
234
  if (og.ui.button("Pick", say(world, `tower.${name}`), { variant: chosen ? "primary" : "secondary", size: small ? "medium" : "small", width: px(small ? 86 : 104) }).clicked) g.selected = kind;
233
235
  });
@@ -237,7 +239,7 @@ function hud(world: World, theme: Theme, g: GameV, t: TuneV): void {
237
239
  // On a phone the call for the next wave sits over the tray, in the thumb's reach.
238
240
  if (small) og.ui.anchor("Call", { anchor: "bottom", offsetY: -112 }, () => callButton(world, g, small));
239
241
  if (g.wave === 0 && !small) {
240
- og.ui.anchor("Hint", { anchor: "bottom", padding: { left: theme.space.md, top: theme.space.xs, right: theme.space.md, bottom: theme.space.xs }, frame: "capsule", background: { token: "surface" }, radius: 999 }, () => {
242
+ og.ui.anchor("Hint", { anchor: "bottom", padding: { left: theme.space.md, top: theme.space.xs, right: theme.space.md, bottom: theme.space.xs }, frame: "hud", background: { token: "panel" }, radius: theme.radius.bar }, () => {
241
243
  og.ui.label("Text", say(world, "hud.hint"), { nowrap: true });
242
244
  });
243
245
  }
@@ -267,7 +269,7 @@ function settings(world: World, theme: Theme, g: GameV, p: PrefsV): void {
267
269
  p.master = slider("Master", "settings.master", p.master);
268
270
  p.music = slider("Music", "settings.music", p.music);
269
271
  p.effects = slider("Effects", "settings.effects", p.effects);
270
- og.ui.label("LanguageLabel", say(world, "settings.language"), { color: { token: "fgMuted" } });
272
+ og.ui.label("LanguageLabel", say(world, "settings.language"), { color: { token: "inkMuted" } });
271
273
  const r = og.ui.radio("Language", LANGUAGES.map((l) => say(world, l.key)), p.language, { horizontal: true });
272
274
  if (r.selected !== p.language) setLanguage(world, p, r.selected);
273
275
  p.reducedMotion = og.ui.toggle("Motion", p.reducedMotion, say(world, "settings.motion")).on;
@@ -293,15 +295,15 @@ function endCard(world: World, theme: Theme, g: GameV, t: TuneV): "retry" | "men
293
295
  for (let k = 0; k < 3; k++) {
294
296
  const lit = k < stars && since >= 20 + k * 12;
295
297
  og.ui.transition(og.ui.idi("Star", k), true, { kind: "scale" }, () => {
296
- og.ui.icon("Mark", "star", { size: 40, tint: { token: lit ? "accent" : "fillStrong" } });
298
+ og.ui.icon("Mark", "star", { size: 40, tint: { token: lit ? "primary" : "controlStrong" } });
297
299
  });
298
300
  if (lit && since === 20 + k * 12) og.ui.burst({ count: 10 });
299
301
  }
300
302
  });
301
303
  og.ui.counter("Score", g.score, { from: 0, durationMs: 900, step: "displayMd", font: "display", align: "center", width: grow });
302
304
  og.ui.row("BestRow", { width: grow, align: "center", gap: theme.space.sm, cross: "center" }, () => {
303
- og.ui.label("Best", say(world, "over.best", { n: g.best }), { color: { token: "fgMuted" }, nowrap: true });
304
- if (g.newBest) og.ui.transition("NewBest", since > 50, { kind: "pop" }, () => og.ui.badge("Badge", say(world, "over.newBest"), "accent"));
305
+ og.ui.label("Best", say(world, "over.best", { n: g.best }), { color: { token: "inkMuted" }, nowrap: true });
306
+ if (g.newBest) og.ui.transition("NewBest", since > 50, { kind: "pop" }, () => og.ui.badge("Badge", say(world, "over.newBest"), "primary"));
305
307
  });
306
308
  og.ui.column("Stats", { width: grow, gap: 0 }, () => {
307
309
  og.ui.keyValue("Waves", say(world, "over.waves"), `${g.won ? g.wave : Math.max(0, g.wave - 1)}/${WAVES.length}`, true);