@dcl-regenesislabs/web-editor 0.3.22 → 0.3.23-41465068147.commit-06ea194
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/editor.js +432 -432
- package/package.json +3 -2
- package/prefabs/catalog.json +1 -1
- package/prefabs/game-flow/ai.md +7 -0
- package/prefabs/game-flow/composite.json +1 -1
- package/prefabs/game-flow/data.json +9 -1
- package/prefabs/game-flow/scripts/game-flow.ts +42 -3
- package/prefabs/game-flow/scripts/pure/flow.ts +68 -12
|
@@ -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
|
-
/**
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
|
177
|
-
|
|
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)
|
|
180
|
-
|
|
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 (
|
|
183
|
-
|
|
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
|
}
|