Перейти к содержанию

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

HunterGraphics.Shell/Bridge/WebViewBridgeHost.cs
["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 потому что предобработка разная:

HunterGraphics.Shell/Bridge/AppBridge.cs (для minimap)
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».

Дальше: GTA Presets →