vimp-engine 0.24.1 → 0.25.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.
- package/package.json +2 -1
- package/src/client/components/controller/Games.js +99 -0
- package/src/client/components/model/Games.js +209 -0
- package/src/client/components/model/LobbyAuth.js +8 -0
- package/src/client/components/view/Games.js +341 -0
- package/src/client/lib/gameActivator.js +13 -5
- package/src/client/main.js +78 -9
- package/src/client/style.css +101 -0
- package/src/client/views/includes/games.pug +36 -0
- package/src/client/views/includes/lobby.pug +7 -1
- package/src/client/views/index.pug +1 -0
- package/src/config/env.js +6 -0
- package/src/config/lobby.js +82 -3
- package/src/config/master.js +33 -13
- package/src/devtools/contract/rules/b2-engine-api.js +21 -6
- package/src/devtools/contract/rules/c10-auth-schema.js +17 -2
- package/src/lib/loadGamePackage.js +39 -20
- package/src/lib/validators.js +10 -9
|
@@ -2,6 +2,7 @@ import { access, readFile } from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
4
|
import { checkPluginCompatibility } from './gamePlugin.js';
|
|
5
|
+
import { ENGINE_CAPABILITIES } from './capabilities.js';
|
|
5
6
|
|
|
6
7
|
// Загрузка пакета игры в Node по его собранному dist/ (Этап 4 плана
|
|
7
8
|
// standalone-sdk). В браузере плагин грузится по URL из GameManifest мастера;
|
|
@@ -107,25 +108,28 @@ function assertPluginMatchesManifest(manifest, plugins) {
|
|
|
107
108
|
}
|
|
108
109
|
}
|
|
109
110
|
|
|
110
|
-
|
|
111
|
+
warnOnRequiresMismatch(manifest, plugins);
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
// `requires` пишут ТРИ независимых места одного пакета игры: скрипт сборки
|
|
114
115
|
// манифеста и обе половины плагина (последние — ради standalone SDK, у
|
|
115
116
|
// которого манифеста нет вовсе). Разъехавшись, они дают игру, которая в
|
|
116
|
-
// лобби честно отвергается, а в solo-режиме тихо
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
//
|
|
117
|
+
// лобби честно отвергается, а в solo-режиме тихо недоигрывает.
|
|
118
|
+
//
|
|
119
|
+
// Здесь это ПРЕДУПРЕЖДЕНИЕ, а не отказ, и разница принципиальная. Node-путь
|
|
120
|
+
// (dedicated, vimp-sim, inline host) читает авторитетный манифест, и его
|
|
121
|
+
// совместимость уже проверена выше (checkPluginCompatibility): рассинхрон с
|
|
122
|
+
// половинами ничего здесь не ломает. Бросить значило бы отвергнуть пакет,
|
|
123
|
+
// который грузился раньше, — прямое нарушение И4 плана
|
|
124
|
+
// plugin-forward-compat ради дефекта упаковки, который движок переживает.
|
|
125
|
+
// Место отказа — правило контракта B2: чекер запускает автор игры, и он
|
|
126
|
+
// узнаёт о расхождении до публикации, а не игрок вместо матча.
|
|
120
127
|
//
|
|
121
128
|
// Половина, вовсе НЕ объявившая поле, из сверки исключена: старый пакет,
|
|
122
|
-
// собранный до его появления,
|
|
123
|
-
// (`requires: []`) — уже утверждение «игре ничего не
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const wanted = new Set(
|
|
127
|
-
Array.isArray(manifest.requires) ? manifest.requires : [],
|
|
128
|
-
);
|
|
129
|
+
// собранный до его появления, ничего не утверждает (И1/И2). Объявленное
|
|
130
|
+
// пустым (`requires: []`) — уже утверждение «игре ничего не нужно».
|
|
131
|
+
function warnOnRequiresMismatch(manifest, plugins) {
|
|
132
|
+
const wanted = new Set(resolveNames(manifest.requires));
|
|
129
133
|
|
|
130
134
|
for (const [half, plugin] of Object.entries(plugins)) {
|
|
131
135
|
if (plugin.requires === undefined || plugin.requires === null) {
|
|
@@ -133,16 +137,19 @@ function assertRequiresMatchManifest(manifest, plugins) {
|
|
|
133
137
|
}
|
|
134
138
|
|
|
135
139
|
if (!Array.isArray(plugin.requires)) {
|
|
136
|
-
|
|
140
|
+
console.warn(
|
|
137
141
|
`game "${manifest.id}": ${half} plugin requires must be an array ` +
|
|
138
|
-
'of capability names',
|
|
142
|
+
'of capability names — the standalone SDK reads it',
|
|
139
143
|
);
|
|
144
|
+
continue;
|
|
140
145
|
}
|
|
141
146
|
|
|
142
|
-
const extra = plugin.requires.filter(
|
|
147
|
+
const extra = resolveNames(plugin.requires).filter(
|
|
148
|
+
name => !wanted.has(name),
|
|
149
|
+
);
|
|
143
150
|
|
|
144
151
|
if (extra.length) {
|
|
145
|
-
|
|
152
|
+
console.warn(
|
|
146
153
|
`game "${manifest.id}": ${half} plugin requires ` +
|
|
147
154
|
`${extra.join(', ')}, which manifest.requires does not list — ` +
|
|
148
155
|
'stale dist/ (the manifest is what the lobby master reads)',
|
|
@@ -156,19 +163,18 @@ function assertRequiresMatchManifest(manifest, plugins) {
|
|
|
156
163
|
// (`requires: []`) — это утверждение «ничего не нужно», и оно расходится
|
|
157
164
|
// с манифестом наравне с неполным списком
|
|
158
165
|
const halves = Object.values(plugins).filter(
|
|
159
|
-
plugin =>
|
|
166
|
+
plugin => Array.isArray(plugin.requires),
|
|
160
167
|
);
|
|
161
168
|
|
|
162
169
|
if (halves.length === 0) {
|
|
163
170
|
return;
|
|
164
171
|
}
|
|
165
172
|
|
|
166
|
-
const declared = halves.flatMap(plugin => plugin.requires);
|
|
167
|
-
|
|
173
|
+
const declared = resolveNames(halves.flatMap(plugin => plugin.requires));
|
|
168
174
|
const missing = [...wanted].filter(name => !declared.includes(name));
|
|
169
175
|
|
|
170
176
|
if (missing.length) {
|
|
171
|
-
|
|
177
|
+
console.warn(
|
|
172
178
|
`game "${manifest.id}": manifest.requires names ${missing.join(', ')}, ` +
|
|
173
179
|
'which neither plugin half declares — stale dist/ (the standalone ' +
|
|
174
180
|
'SDK reads the halves, there is no manifest in solo mode)',
|
|
@@ -176,6 +182,19 @@ function assertRequiresMatchManifest(manifest, plugins) {
|
|
|
176
182
|
}
|
|
177
183
|
}
|
|
178
184
|
|
|
185
|
+
// Сравнивать имена возможностей буквально нельзя: реестр append-only, и
|
|
186
|
+
// выведенное алиасом имя движок принимает вечно наравне с активным
|
|
187
|
+
// (ENGINE_CAPABILITIES.has в checkPluginCompatibility). Манифест, назвавший
|
|
188
|
+
// новое имя, и половина, оставшаяся на выведенном, просят ОДНО И ТО ЖЕ —
|
|
189
|
+
// объявить это расхождением значило бы ругаться на игру за то, что движок
|
|
190
|
+
// переименовал возможность. Неизвестное имя резолва не имеет и сравнивается
|
|
191
|
+
// как есть: о нём говорит checkPluginCompatibility, а не эта сверка
|
|
192
|
+
function resolveNames(names) {
|
|
193
|
+
return (Array.isArray(names) ? names : []).map(
|
|
194
|
+
name => ENGINE_CAPABILITIES.resolve(name) ?? name,
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
179
198
|
function importDefault(baseDir, entry, assetsBase) {
|
|
180
199
|
const file = path.resolve(baseDir, stripBase(entry, assetsBase));
|
|
181
200
|
|
package/src/lib/validators.js
CHANGED
|
@@ -56,15 +56,16 @@ const numericError = (options, value) => {
|
|
|
56
56
|
return `must be <= ${options.max}`;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
// regExp
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
59
|
+
// regExp числового поля хост НЕ проверяет, и это осознанно. Клиент
|
|
60
|
+
// сверяет паттерн с сырой строкой, которую набрал игрок
|
|
61
|
+
// (formBuilder.validateField: field.getRaw()); до хоста доезжает уже
|
|
62
|
+
// число, и восстановить ту строку нечем — `String(значение)` даёт
|
|
63
|
+
// каноническую запись, а не набранную. Паттерн, принимающий
|
|
64
|
+
// неканоническую («^\d+\.\d{2}$» и ввод «1.50»), совпал бы с формой и
|
|
65
|
+
// разошёлся бы с хостом: игрок видит поле валидным, а вход не пускает, и
|
|
66
|
+
// набрать строку, годную обеим сторонам, нельзя вовсе. Блокировка входа
|
|
67
|
+
// хуже той щели, которую паттерн закрывает: диапазон уже проверен, а
|
|
68
|
+
// ведущий ноль до хоста и не доезжает (Number('08') === 8)
|
|
68
69
|
return null;
|
|
69
70
|
};
|
|
70
71
|
|