Guns — gunpack'и и whitelist¶
Gun-секция админ-панели управляет оружейными паками (gunpack). Это самая болезненная часть документации потому что вокруг неё гора граблей: конфликты между гангами, между глушителями, проблемы с 3D и шейдерами.
Что такое gunpack¶
Gunpack — это набор custom-моделей оружия + текстуры + weapons.meta patch. Не одна пушка, а пак на 5-30 пушек. Юзеры могут:
- Установить весь gunpack одной кнопкой («Поставить FiveM Realism пак»).
- Выставить whitelist — взять из gunpack'а X 3 пушки, из gunpack'а Y 5 пушек, из gunpack'а Z 1 пушку. Pick'n'mix.
Поэтому в админке два under-section'а: catalog gunpack'ов и whitelist-config.
Структура таблиц¶
erDiagram
gunpacks ||--o{ gunpack_guns : "contains"
gunpacks {
uuid id PK
text name
text author
text cover_url
text rpf_url
int popularity
bool is_deleted
}
gunpack_guns {
uuid id PK
uuid gunpack_id FK
text internal_name
text display_name
text model_glb_url
text preview_png_url
jsonb meta_patch
}
internal_name — это внутренний ID GTA для оружия, например WEAPON_PISTOL или WEAPON_ASSAULTRIFLE_MK2. Один gunpack может содержать много пушек с одним internal_name (например 3 разные модели Pistol — но юзер выбирает только одну).
Workflow «залить gunpack»¶
flowchart TD
Start[admin кидает gunpack.zip] --> Parse[adminGunpackUpload
распаковка ZIP, парсинг weapons.meta]
Parse --> Models[Per-gun: extract .ydr → .glb через renderer]
Models --> Previews[Per-gun: render .png через headless Three.js]
Previews --> Upload[Upload в R2:
gunpacks/<id>/pack.rpf
gunpacks/<id>/<internal>.glb
gunpacks/<id>/<internal>.png]
Upload --> Insert[Insert gunpacks row + gunpack_guns rows]
Insert --> Done
Время — 5-20 минут на gunpack из 20 пушек, в основном из-за 3D-render'а (см. история 3D).
public async Task<GunpackUploadResultDto> AdminGunpackUploadAsync(GunpackUploadDraftDto draft)
{
using var temp = new TempDirectoryScope();
await ExtractZipAsync(draft.ZipPath, temp.Path);
var manifest = ParseWeaponsManifest(Path.Combine(temp.Path, "weapons.meta"));
var guns = new List<GunpackGunDto>();
foreach (var gun in manifest.Guns)
{
// 1. Конвертим .ydr в .glb для viewer'а
var glbBytes = await _ydrConverter.ConvertAsync(
Path.Combine(temp.Path, gun.YdrFileName));
// 2. Render PNG превью через headless renderer
var pngBytes = await _previewRenderer.RenderAsync(glbBytes);
// 3. Upload оба в R2
var glbUrl = await _r2.UploadAsync(
$"gunpacks/{draft.GunpackId}/{gun.InternalName}.glb", glbBytes);
var pngUrl = await _r2.UploadAsync(
$"gunpacks/{draft.GunpackId}/{gun.InternalName}.png", pngBytes);
guns.Add(new GunpackGunDto(
InternalName: gun.InternalName,
DisplayName: gun.DisplayName,
ModelGlbUrl: glbUrl,
PreviewPngUrl: pngUrl,
MetaPatch: gun.MetaPatchJson));
}
// 4. Upload .rpf со всеми ассетами
var rpfUrl = await _r2.UploadAsync(
$"gunpacks/{draft.GunpackId}/pack.rpf",
Path.Combine(temp.Path, "pack.rpf"));
// 5. Insert в Supabase
await _supa.InsertGunpackAsync(draft.GunpackId, draft.Name, draft.Author, rpfUrl);
foreach (var g in guns)
await _supa.InsertGunpackGunAsync(draft.GunpackId, g);
return new GunpackUploadResultDto(draft.GunpackId, guns.Count);
}
CRUD-операции¶
| Handler | Действие |
|---|---|
adminGunpackList |
Список всех gunpack'ов (для админ-таблицы) |
adminGunpackPatch |
Обновить name/author/cover |
adminGunpackDelete |
Soft-delete (is_deleted = true) |
adminGunpackGunPatch |
Обновить display name / meta_patch одной пушки |
adminGunpackGunDelete |
Удалить одну пушку из gunpack'а |
adminGunpackUpload |
См. workflow выше |
adminGunpackQueueList / adminGunpackQueueRemove |
Управление очередью загрузок |
adminUploadGunpackCover |
Поменять cover-картинку без re-upload всего пака |
Whitelist (для юзеров с PRO-подпиской)¶
Юзеры с PRO-подпиской могут собрать свой custom-пак из чужих gunpack'ов. Эта функция реализуется через selected_guns_installs таблицу — список «эта пушка из этого gunpack'а». При установке SelectedGunsInstallAsync:
- Качаем
pack.rpfнужного gunpack'а. - Извлекаем только файлы конкретной пушки (
weapon_pistol.ydr,weapon_pistol+hi.ydr, текстуры). - Мерджим
weapons.metapatch с уже-применёнными. - Результат — единый
hunter_guns_selected.rpfвdlcpacks/. Подробности в гайде по конфликтам.
gunpack_whitelist флаги¶
В gunpacks row есть is_whitelist_allowed — bool. Не все gunpack'и можно «разбирать» — некоторые авторы запрещают cherry-pick (только целиком). Админ выставляет в adminGunpackPatch. Если false, UI не показывает «Добавить в selected» кнопку.
Конфликты между gunpack'ами¶
Юзер не может поставить два gunpack'а одновременно — последний overwrites'ит. Это обусловлено архитектурой: оба пишут в dlcpacks/hunter_guns/ под одним именем, и gtaversion.dat указывает на один entry в dlclist.xml.
В админке это видно в Conflicts sub-tab — таблица показывает какие пушки overlap'ятся между gunpack'ами по internal_name. Это помогает админу понять «если юзер ставит пак X, что он теряет из пака Y».