Library — отдельные компоненты¶
Library — это каталог отдельных компонентов в админ-панели. Не полных redux'ов, а частей: минимапа, прицел, трейсеры, звуки. Юзер заходит в Library, берёт «эту минимапу + этот прицел + эти трейсеры» из разных авторов, получает собственный mix.
Это альтернативный к customize flow: customize мутирует donor-redux'ы, а Library — это изолированные компоненты которые не привязаны к конкретному redux'у.
Структура¶
create table library_components (
id uuid primary key,
type text not null, -- "minimap" / "reticle" / "tracers" / "sounds"
name text not null,
author text,
description text,
preview_url text, -- PNG превью
payload_url text not null, -- R2-ссылка на .bin / .zip
gallery_urls text[], -- доп. скриншоты
video_url text, -- YouTube/MP4 demo
popularity int default 0,
is_deleted bool default false
);
type — это enum типов компонентов. Сейчас поддерживается:
| type | Что это | Payload |
|---|---|---|
minimap |
.gfx файл минимапы |
бинарь, готовый к replace в update.rpf |
reticle |
hud_reticle.gfx — прицелы |
бинарь |
tracers |
core.ypt с tracer settings |
бинарь |
sounds |
звуки оружия | ZIP с .awc / .dat15 файлами |
stub |
placeholder для experimental | пустой, используется для тестов UI |
Handlers¶
["adminCreateLibraryStub"] // placeholder
["adminCreateLibraryMinimap"] // minimap upload
["adminCreateLibraryReticle"] // reticle upload
["adminCreateLibrarySounds"] // sounds ZIP upload
["adminUploadLibraryPreview"] // PNG preview replace
["adminUploadLibraryGallery"] // gallery PNGs
["adminUploadLibraryVideo"] // demo video
["adminUploadComponentScreenshot"] // screenshot for catalog
Каждый тип компонента имеет свой upload-handler потому что предобработка разная:
public async Task<LibraryComponentDto> AdminCreateLibraryMinimapAsync(LibraryMinimapDraftDto draft)
{
// 1. Validate что это валидный .gfx
var bytes = await File.ReadAllBytesAsync(draft.GfxPath);
if (bytes.Length < 32 || (bytes[0] != 'F' && bytes[0] != 'C') || bytes[1] != 'W' || bytes[2] != 'S')
throw new InvalidOperationException("Не похоже на SWF/GFx файл (magic не совпал)");
// 2. Опционально рендерим PNG превью через jpexs (см. minimap-history)
var pngBytes = draft.PreviewPng ?? await _gfxPreviewRenderer.RenderAsync(draft.GfxPath);
// 3. Upload в R2
var payloadUrl = await _r2.UploadAsync($"library/{draft.Id}/payload.bin", bytes);
var previewUrl = await _r2.UploadAsync($"library/{draft.Id}/preview.png", pngBytes);
// 4. Insert
return await _supa.InsertLibraryComponentAsync(new LibraryComponentRow {
Id = draft.Id, Type = "minimap",
Name = draft.Name, Author = draft.Author,
PayloadUrl = payloadUrl, PreviewUrl = previewUrl,
});
}
Где юзер видит¶
В UI это вкладка Browse → Library. Фильтры по type, по author, по popularity. При клике на компонент — modal с превью, описанием, кнопкой «Установить».
Установка через LibraryComponentInstallAsync:
public async Task<InjectResultDto> LibraryComponentInstallAsync(string componentId)
{
using var _mtx = await UpdateRpfMutex.AcquireAsync("library-install");
var comp = await _supa.GetLibraryComponentAsync(componentId);
var payload = await _assetCache.GetOrDownloadAsync(comp.PayloadUrl);
// route по типу
PatchAction action = comp.Type switch
{
"minimap" => new PatchAction("Replace",
"update/update.rpf:/x64/textures/script_txds.rpf:/minimap.gfx", payload),
"reticle" => new PatchAction("Replace",
"update/update.rpf:/x64/textures/script_txds.rpf:/hud_reticle.gfx", payload),
"tracers" => new PatchAction("Replace",
"update/update.rpf:/x64/data/effects/core.ypt", payload),
"sounds" => /* ZIP-unpack flow с multiple replaces */,
_ => throw new NotSupportedException($"Unknown component type '{comp.Type}'"),
};
return await _injector.ApplyActionAsync(action);
}
Зачем нужно отдельно от Customize¶
Customize-flow требует donor redux — юзер кликает «возьми минимапу из мода X». Library — это standalone компоненты от авторов, которые не хотят выкатывать целый redux. Автор HUD-художник может опубликовать одну минимапу в Library, и юзеры её ставят без необходимости трогать остальной графический пакет.
Limitations¶
- Только single-file replace для большинства типов. Если компонент требует несколько файлов (например custom HUD overlay с PNG + ActionScript) — он не подходит для Library, должен быть отдельным redux'ом или DLC-импортом.
- Нет версионирования (одно поле — одна row, no
library_versions). Для multiple versions автор создаёт отдельные rows «Minimap by X v1», «Minimap by X v2».