@dcl-regenesislabs/web-editor 0.3.22 → 0.3.23

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.
@@ -19,12 +19,14 @@ import {
19
19
  advanceFlow,
20
20
  asFlowFact,
21
21
  configFromSeconds,
22
+ flowLabels,
22
23
  intermissionState,
23
24
  lobbyState,
24
25
  panelText,
25
26
  podiumLine,
26
27
  roundState,
27
28
  type FlowConfig,
29
+ type FlowLabels,
28
30
  type FlowState
29
31
  } from './pure/flow'
30
32
 
@@ -65,9 +67,46 @@ export class GameFlow {
65
67
  public boardKey: string = 'leaderboard',
66
68
  public display: '3D text' | '2D UI' = '3D text',
67
69
  /** For "2D UI": where the round timer sits on screen. */
68
- public position: 'top' | 'top left' | 'top right' | 'bottom' = 'top'
70
+ public position: 'top' | 'top left' | 'top right' | 'bottom' = 'top',
71
+ /** The game's name, shown while players gather. Empty shows the lobby label. */
72
+ public gameTitle: string = '',
73
+ public lobbyLabel: string = 'LOBBY',
74
+ /** Shown until a round can start. */
75
+ public waitingLabel: string = 'Waiting for players',
76
+ /** After the waiting label: {present} players in, {needed} to start. */
77
+ public playersLabel: string = '{present}/{needed}',
78
+ public startingLabel: string = 'Starting',
79
+ /** {round} is the round number. */
80
+ public roundLabel: string = 'ROUND {round}',
81
+ public roundOverLabel: string = 'ROUND {round} OVER',
82
+ /** The winners line; {winners} lists them. */
83
+ public winnersLabel: string = 'Round over — {winners}',
84
+ public noWinnersLabel: string = 'Round over — nobody scored.'
69
85
  ) {}
70
86
 
87
+ private labelsFor: readonly string[] = []
88
+ private labelsCache: FlowLabels | null = null
89
+
90
+ // The 2D panel reads this every frame; rebuild only when an inspector edit changes a label.
91
+ private get labels(): FlowLabels {
92
+ const given = [
93
+ this.gameTitle,
94
+ this.lobbyLabel,
95
+ this.waitingLabel,
96
+ this.playersLabel,
97
+ this.startingLabel,
98
+ this.roundLabel,
99
+ this.roundOverLabel,
100
+ this.winnersLabel,
101
+ this.noWinnersLabel
102
+ ]
103
+ if (this.labelsCache !== null && given.every((value, i) => value === this.labelsFor[i])) return this.labelsCache
104
+ const [gameTitle, lobby, waiting, players, starting, round, roundOver, winners, noWinners] = given
105
+ this.labelsFor = given
106
+ this.labelsCache = flowLabels({ gameTitle, lobby, waiting, players, starting, round, roundOver, winners, noWinners })
107
+ return this.labelsCache
108
+ }
109
+
71
110
  private get config(): FlowConfig {
72
111
  return configFromSeconds(this.roundSeconds, this.countdownSeconds, this.intermissionSeconds, this.minPlayers)
73
112
  }
@@ -153,7 +192,7 @@ export class GameFlow {
153
192
  }
154
193
 
155
194
  private announcePodium(): void {
156
- game.broadcast(ANNOUNCE, { text: podiumLine(game.state[this.boardKey], PODIUM_PLACES) })
195
+ game.broadcast(ANNOUNCE, { text: podiumLine(game.state[this.boardKey], PODIUM_PLACES, this.labels) })
157
196
  }
158
197
 
159
198
  private publish(): void {
@@ -167,7 +206,7 @@ export class GameFlow {
167
206
 
168
207
  private text(): string {
169
208
  const fact = asFlowFact(game.state[FLOW_KEY])
170
- return panelText(fact, game.now(), this.endsWhen === 'timer', this.config.minPlayers)
209
+ return panelText(fact, game.now(), this.endsWhen === 'timer', this.config.minPlayers, this.labels)
171
210
  }
172
211
 
173
212
  private paint(): void {
@@ -166,20 +166,76 @@ export function shortName(player: string): string {
166
166
  return player.startsWith('0x') && player.length > 10 ? `${player.slice(0, 6)}…${player.slice(-4)}` : player
167
167
  }
168
168
 
169
- /** The winners line broadcast to every player at the end of a round. */
170
- export function podiumLine(value: unknown, places: number): string {
171
- const top = standingsOf(value).slice(0, Math.max(1, places))
172
- if (top.length === 0) return 'Round over — nobody scored.'
173
- return `Round over — ${top.map((row, i) => `${i + 1}. ${shortName(row.player)}`).join(' ')}`
169
+ /**
170
+ * Every word a player reads, so a creator can write them in the game's language. `{round}`,
171
+ * `{present}`, `{needed}` and `{winners}` are filled in where a label names them.
172
+ */
173
+ export interface FlowLabels {
174
+ /** The game's name while players gather; empty shows `lobby`. */
175
+ gameTitle: string
176
+ lobby: string
177
+ waiting: string
178
+ players: string
179
+ starting: string
180
+ round: string
181
+ roundOver: string
182
+ winners: string
183
+ noWinners: string
184
+ }
185
+
186
+ export const DEFAULT_LABELS: FlowLabels = {
187
+ gameTitle: '',
188
+ lobby: 'LOBBY',
189
+ waiting: 'Waiting for players',
190
+ players: '{present}/{needed}',
191
+ starting: 'Starting',
192
+ round: 'ROUND {round}',
193
+ roundOver: 'ROUND {round} OVER',
194
+ winners: 'Round over — {winners}',
195
+ noWinners: 'Round over — nobody scored.'
196
+ }
197
+
198
+ /** A cleared field falls back to English rather than a blank line; a line break would split the panel. */
199
+ export function flowLabels(given: Partial<Record<keyof FlowLabels, unknown>>): FlowLabels {
200
+ const out = { ...DEFAULT_LABELS }
201
+ for (const key of Object.keys(DEFAULT_LABELS) as Array<keyof FlowLabels>) {
202
+ const value = given[key]
203
+ if (typeof value === 'string' && value.trim() !== '') out[key] = value.replace(/\s*\n\s*/g, ' ').trim()
204
+ }
205
+ return out
174
206
  }
175
207
 
176
- export function panelText(fact: FlowFact | null, nowMs: number, showClock: boolean, needed: number): string {
177
- if (fact === null) return 'GAME FLOW\n--:--\nwaiting for the Multiplayer Server'
208
+ export function fillLabel(template: string, values: Record<string, string | number>): string {
209
+ return template.replace(/\{(\w+)\}/g, (whole, key: string) => (key in values ? String(values[key]) : whole))
210
+ }
211
+
212
+ /** The winners line broadcast to every player at the end of a round. */
213
+ export function podiumLine(value: unknown, places: number, labels: FlowLabels = DEFAULT_LABELS): string {
214
+ const top = standingsOf(value).slice(0, Math.max(1, places))
215
+ if (top.length === 0) return labels.noWinners
216
+ return fillLabel(labels.winners, { winners: top.map((row, i) => `${i + 1}. ${shortName(row.player)}`).join(' ') })
217
+ }
218
+
219
+ export function panelText(
220
+ fact: FlowFact | null,
221
+ nowMs: number,
222
+ showClock: boolean,
223
+ needed: number,
224
+ labels: FlowLabels = DEFAULT_LABELS
225
+ ): string {
226
+ const gathering = labels.gameTitle === '' ? labels.lobby : labels.gameTitle
227
+ // before the server's first answer nobody knows the head count yet
228
+ if (fact === null) return `${gathering}\n--:--\n${labels.waiting}`
229
+ const round = { round: fact.round }
178
230
  if (fact.phase === 'lobby') {
179
- if (fact.endsAtMs === 0) return `LOBBY\n--:--\n${fact.present}/${needed} players`
180
- return `LOBBY\n${formatCountdown(secondsLeft(fact, nowMs))}\nstarting`
231
+ if (fact.endsAtMs === 0) {
232
+ return `${gathering}\n--:--\n${labels.waiting} ${fillLabel(labels.players, { present: fact.present, needed })}`
233
+ }
234
+ return `${gathering}\n${formatCountdown(secondsLeft(fact, nowMs))}\n${labels.starting}`
235
+ }
236
+ if (fact.phase === 'intermission') {
237
+ return `${fillLabel(labels.roundOver, round)}\n${formatCountdown(secondsLeft(fact, nowMs))}`
181
238
  }
182
- if (fact.phase === 'intermission') return `ROUND ${fact.round} OVER\n${formatCountdown(secondsLeft(fact, nowMs))}`
183
- if (!showClock) return `ROUND ${fact.round}`
184
- return `ROUND ${fact.round}\n${formatCountdown(secondsLeft(fact, nowMs))}`
239
+ if (!showClock) return fillLabel(labels.round, round)
240
+ return `${fillLabel(labels.round, round)}\n${formatCountdown(secondsLeft(fact, nowMs))}`
185
241
  }