Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c905d0558e |
@@ -51,6 +51,8 @@
|
||||
"key": "my_mount",
|
||||
"name": "My Mount",
|
||||
"name_ru": "Мой транспорт",
|
||||
"race_mask": 1101,
|
||||
"icon": "my_icon.jpg",
|
||||
"archive": "Creature/MyMount",
|
||||
"model_file": "my_mount.m2",
|
||||
"template_display_id": 2404,
|
||||
@@ -67,6 +69,23 @@
|
||||
компаньон-кот с display ID `5448`. Шаблон задаёт физические размеры, звук и прочие поля DBC;
|
||||
параметры объекта позволяют их переопределить.
|
||||
|
||||
Для `pets` поле `race_mask` ограничивает строку коллекции набором рас (`1101` — Альянс,
|
||||
`690` — Орда, `0` или отсутствие поля — все расы). Поле `icon` указывает JPG, PNG или TGA рядом
|
||||
с моделью либо путь от корня проекта. Сборщик конвертирует изображение в BLP2, выдаёт стабильный
|
||||
`spellIconID`, добавляет запись в `SpellIcon.dbc` через MoonWell provider и назначает её summon-spell.
|
||||
|
||||
## Конвертация иконок
|
||||
|
||||
Встроенная команда CLI преобразует JPG, PNG или TGA в совместимый с клиентом BLP2:
|
||||
|
||||
```powershell
|
||||
tool.exe icon "путь/к/icon.jpg" ["путь/к/icon.blp"]
|
||||
```
|
||||
|
||||
Если выходной путь не указан, `.blp` создаётся рядом с исходным файлом. Конвертер автоматически
|
||||
приводит изображение к ближайшему квадратному размеру степени двойки (не более 512×512), кодирует
|
||||
его в DXT1 и создаёт mipmap-уровни. Например, иконка 56×56 будет преобразована в 64×64.
|
||||
|
||||
## Установка на сервер
|
||||
|
||||
Для каждого элемента `pets` сборщик автоматически выдаёт `creatureEntry`, `summonSpellID` и
|
||||
|
||||
+20
-1
@@ -5,6 +5,7 @@
|
||||
"creature_entry_id_start": 5000000,
|
||||
"summon_spell_id_start": 110000,
|
||||
"skill_line_ability_id_start": 30000,
|
||||
"spell_icon_id_start": 50000,
|
||||
"defaults": {
|
||||
"mount_template_display_id": 2404,
|
||||
"pet_template_display_id": 5448
|
||||
@@ -15,7 +16,25 @@
|
||||
"folder": "src/Data/patch-Z/Creature/Catslime",
|
||||
"name": "Cat Slime",
|
||||
"name_ru": "Котослизень",
|
||||
"display_scale": 1.0
|
||||
"display_scale": 0.3
|
||||
},
|
||||
{
|
||||
"folder": "src/Data/patch-Z/Creature/MoonkinHatchling_Alliance",
|
||||
"key": "moonkin_hatchling_alliance",
|
||||
"race_mask": 1101,
|
||||
"icon": "inv_misc_petmoonkinne.jpg",
|
||||
"name": "Moonkin Hatchling",
|
||||
"name_ru": "Лунный совушек",
|
||||
"display_scale": 0.3
|
||||
},
|
||||
{
|
||||
"folder": "src/Data/patch-Z/Creature/MoonkinHatchling_Horde",
|
||||
"key": "moonkin_hatchling_horde",
|
||||
"race_mask": 690,
|
||||
"icon": "inv_misc_petmoonkinta.jpg",
|
||||
"name": "Moonkin Hatchling",
|
||||
"name_ru": "Лунный совушек",
|
||||
"display_scale": 0.3
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -6,6 +6,22 @@
|
||||
"creature_entry": 5000000,
|
||||
"summon_spell_id": 110000,
|
||||
"skill_line_ability_id": 30000
|
||||
},
|
||||
"moonkin_hatchling_alliance": {
|
||||
"model_data_id": 50001,
|
||||
"display_info_id": 50001,
|
||||
"creature_entry": 5000001,
|
||||
"summon_spell_id": 110001,
|
||||
"skill_line_ability_id": 30001,
|
||||
"spell_icon_id": 50000
|
||||
},
|
||||
"moonkin_hatchling_horde": {
|
||||
"model_data_id": 50002,
|
||||
"display_info_id": 50002,
|
||||
"creature_entry": 5000002,
|
||||
"summon_spell_id": 110002,
|
||||
"skill_line_ability_id": 30002,
|
||||
"spell_icon_id": 50001
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,10 +22,15 @@ namespace moonwell::host
|
||||
constexpr std::string_view kOverridesPath = "WXLSpellOverrides.tsv";
|
||||
constexpr std::string_view kSpellPath = "DBFilesClient\\Spell.dbc";
|
||||
constexpr std::string_view kSkillPath = "DBFilesClient\\SkillLineAbility.dbc";
|
||||
constexpr std::string_view kSpellIconPath = "DBFilesClient\\SpellIcon.dbc";
|
||||
constexpr uint32_t kSpellFields = 234;
|
||||
constexpr uint32_t kSpellRecordSize = kSpellFields * 4;
|
||||
constexpr uint32_t kSkillFields = 14;
|
||||
constexpr uint32_t kSkillRecordSize = kSkillFields * 4;
|
||||
constexpr uint32_t kSpellIconFields = 2;
|
||||
constexpr uint32_t kSpellIconRecordSize = kSpellIconFields * 4;
|
||||
constexpr char8_t kDescriptionPrefixUtf8[] =
|
||||
u8"\u041F\u0440\u0438\u0437\u044B\u0432\u0430\u0435\u0442 \u0438\u043B\u0438 \u043E\u0442\u043F\u0443\u0441\u043A\u0430\u0435\u0442 \u0441\u043F\u0443\u0442\u043D\u0438\u043A\u0430: ";
|
||||
|
||||
struct Override
|
||||
{
|
||||
@@ -33,6 +38,9 @@ namespace moonwell::host
|
||||
uint32_t templateSpellId = 0;
|
||||
uint32_t creatureEntry = 0;
|
||||
uint32_t skillLineAbilityId = 0;
|
||||
uint32_t raceMask = 0;
|
||||
uint32_t spellIconId = 0;
|
||||
std::string iconPath;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
@@ -40,6 +48,7 @@ namespace moonwell::host
|
||||
bool g_ready = false;
|
||||
std::vector<uint8_t> g_spellDbc;
|
||||
std::vector<uint8_t> g_skillDbc;
|
||||
std::vector<uint8_t> g_spellIconDbc;
|
||||
|
||||
uint32_t ReadU32(const uint8_t* data)
|
||||
{
|
||||
@@ -93,26 +102,50 @@ namespace moonwell::host
|
||||
lineStart = lineEnd + 1;
|
||||
if (line.empty() || line.front() == '#') continue;
|
||||
|
||||
std::string_view fields[5];
|
||||
std::vector<std::string_view> fields;
|
||||
size_t fieldStart = 0;
|
||||
bool valid = true;
|
||||
for (size_t i = 0; i < 4; ++i)
|
||||
while (true)
|
||||
{
|
||||
const size_t tab = line.find('\t', fieldStart);
|
||||
if (tab == std::string_view::npos) { valid = false; break; }
|
||||
fields[i] = line.substr(fieldStart, tab - fieldStart);
|
||||
if (tab == std::string_view::npos)
|
||||
{
|
||||
fields.push_back(Trim(line.substr(fieldStart)));
|
||||
break;
|
||||
}
|
||||
fields.push_back(Trim(line.substr(fieldStart, tab - fieldStart)));
|
||||
fieldStart = tab + 1;
|
||||
}
|
||||
if (!valid) return false;
|
||||
fields[4] = Trim(line.substr(fieldStart));
|
||||
|
||||
Override item;
|
||||
if (!ParseU32(fields[0], item.spellId) ||
|
||||
!ParseU32(fields[1], item.templateSpellId) ||
|
||||
!ParseU32(fields[2], item.creatureEntry) ||
|
||||
!ParseU32(fields[3], item.skillLineAbilityId) || fields[4].empty())
|
||||
if (fields.size() == 5)
|
||||
{
|
||||
if (!ParseU32(fields[0], item.spellId) ||
|
||||
!ParseU32(fields[1], item.templateSpellId) ||
|
||||
!ParseU32(fields[2], item.creatureEntry) ||
|
||||
!ParseU32(fields[3], item.skillLineAbilityId) || fields[4].empty())
|
||||
return false;
|
||||
item.name.assign(fields[4]);
|
||||
}
|
||||
else if (fields.size() == 8)
|
||||
{
|
||||
if (!ParseU32(fields[0], item.spellId) ||
|
||||
!ParseU32(fields[1], item.templateSpellId) ||
|
||||
!ParseU32(fields[2], item.creatureEntry) ||
|
||||
!ParseU32(fields[3], item.skillLineAbilityId) ||
|
||||
!ParseU32(fields[4], item.raceMask) ||
|
||||
!ParseU32(fields[5], item.spellIconId) || fields[7].empty())
|
||||
return false;
|
||||
if (item.spellIconId != 0)
|
||||
{
|
||||
if (fields[6].empty() || fields[6] == "-") return false;
|
||||
item.iconPath.assign(fields[6]);
|
||||
}
|
||||
item.name.assign(fields[7]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
item.name.assign(fields[4]);
|
||||
}
|
||||
out.push_back(std::move(item));
|
||||
}
|
||||
return !out.empty();
|
||||
@@ -170,11 +203,13 @@ namespace moonwell::host
|
||||
uint8_t* row = records.data() + recordStart;
|
||||
WriteU32(row, item.spellId);
|
||||
WriteU32(row + 110 * 4, item.creatureEntry);
|
||||
if (item.spellIconId != 0) WriteU32(row + 133 * 4, item.spellIconId);
|
||||
|
||||
const uint32_t nameOffset = stringSize + static_cast<uint32_t>(strings.size());
|
||||
strings.insert(strings.end(), item.name.begin(), item.name.end());
|
||||
strings.push_back(0);
|
||||
const std::string description = "Призывает или отпускает спутника: " + item.name + ".";
|
||||
const std::string description =
|
||||
std::string(reinterpret_cast<const char*>(kDescriptionPrefixUtf8)) + item.name + ".";
|
||||
const uint32_t descriptionOffset = stringSize + static_cast<uint32_t>(strings.size());
|
||||
strings.insert(strings.end(), description.begin(), description.end());
|
||||
strings.push_back(0);
|
||||
@@ -213,6 +248,7 @@ namespace moonwell::host
|
||||
uint8_t* row = records.data() + recordStart;
|
||||
WriteU32(row, item.skillLineAbilityId);
|
||||
WriteU32(row + 8, item.spellId);
|
||||
WriteU32(row + 12, item.raceMask);
|
||||
}
|
||||
|
||||
out.reserve(base.size() + records.size());
|
||||
@@ -224,19 +260,63 @@ namespace moonwell::host
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BuildSpellIconDbc(const std::vector<uint8_t>& base, const std::vector<Override>& overrides,
|
||||
std::vector<uint8_t>& out)
|
||||
{
|
||||
uint32_t count = 0, stringSize = 0;
|
||||
size_t recordsEnd = 0;
|
||||
if (!ValidateWdbc(base, kSpellIconFields, kSpellIconRecordSize, count, stringSize, recordsEnd))
|
||||
return false;
|
||||
|
||||
std::vector<uint8_t> records;
|
||||
std::vector<uint8_t> strings;
|
||||
for (const Override& item : overrides)
|
||||
{
|
||||
if (item.spellIconId == 0) continue;
|
||||
for (uint32_t i = 0; i < count; ++i)
|
||||
{
|
||||
const uint8_t* row = base.data() + 20 + static_cast<size_t>(i) * kSpellIconRecordSize;
|
||||
if (ReadU32(row) == item.spellIconId) return false;
|
||||
}
|
||||
for (size_t i = 0; i < records.size(); i += kSpellIconRecordSize)
|
||||
if (ReadU32(records.data() + i) == item.spellIconId) return false;
|
||||
|
||||
const uint32_t pathOffset = stringSize + static_cast<uint32_t>(strings.size());
|
||||
const size_t recordStart = records.size();
|
||||
records.resize(recordStart + kSpellIconRecordSize);
|
||||
WriteU32(records.data() + recordStart, item.spellIconId);
|
||||
WriteU32(records.data() + recordStart + 4, pathOffset);
|
||||
strings.insert(strings.end(), item.iconPath.begin(), item.iconPath.end());
|
||||
strings.push_back(0);
|
||||
}
|
||||
|
||||
if (records.empty()) return true;
|
||||
out.reserve(base.size() + records.size() + strings.size());
|
||||
out.insert(out.end(), base.begin(), base.begin() + 20);
|
||||
WriteU32(out.data() + 4, count + static_cast<uint32_t>(records.size() / kSpellIconRecordSize));
|
||||
WriteU32(out.data() + 16, stringSize + static_cast<uint32_t>(strings.size()));
|
||||
out.insert(out.end(), base.begin() + 20, base.begin() + recordsEnd);
|
||||
out.insert(out.end(), records.begin(), records.end());
|
||||
out.insert(out.end(), base.begin() + recordsEnd, base.end());
|
||||
out.insert(out.end(), strings.begin(), strings.end());
|
||||
return true;
|
||||
}
|
||||
|
||||
void Load()
|
||||
{
|
||||
const std::string root = wxl::host::ClientRoot();
|
||||
wxl::host::mpq::MpqStore store;
|
||||
std::vector<uint8_t> overrideBytes, spellBase, skillBase;
|
||||
std::vector<uint8_t> overrideBytes, spellBase, skillBase, spellIconBase;
|
||||
if (root.empty() || !store.Mount(root) || !store.ReadAll(kOverridesPath, overrideBytes))
|
||||
return;
|
||||
|
||||
std::vector<Override> overrides;
|
||||
if (!ParseOverrides(overrideBytes, overrides) ||
|
||||
!store.ReadAll(kSpellPath, spellBase) || !store.ReadAll(kSkillPath, skillBase) ||
|
||||
!store.ReadAll(kSpellIconPath, spellIconBase) ||
|
||||
!BuildSpellDbc(spellBase, overrides, g_spellDbc) ||
|
||||
!BuildSkillDbc(skillBase, overrides, g_skillDbc))
|
||||
!BuildSkillDbc(skillBase, overrides, g_skillDbc) ||
|
||||
!BuildSpellIconDbc(spellIconBase, overrides, g_spellIconDbc))
|
||||
{
|
||||
WLOG_ERROR("moonwell-spells: failed to build custom companion DBCs");
|
||||
return;
|
||||
@@ -247,10 +327,20 @@ namespace moonwell::host
|
||||
|
||||
bool Provide(std::string_view name, std::vector<uint8_t>& out)
|
||||
{
|
||||
if (!SamePath(name, kSpellPath) && !SamePath(name, kSkillPath)) return false;
|
||||
if (!SamePath(name, kSpellPath) && !SamePath(name, kSkillPath) &&
|
||||
!SamePath(name, kSpellIconPath))
|
||||
return false;
|
||||
std::call_once(g_loadOnce, &Load);
|
||||
if (!g_ready) return false;
|
||||
out = SamePath(name, kSpellPath) ? g_spellDbc : g_skillDbc;
|
||||
if (SamePath(name, kSpellPath))
|
||||
out = g_spellDbc;
|
||||
else if (SamePath(name, kSkillPath))
|
||||
out = g_skillDbc;
|
||||
else
|
||||
{
|
||||
if (g_spellIconDbc.empty()) return false;
|
||||
out = g_spellIconDbc;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -110,11 +110,19 @@ function Stop-WowRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
# --- Ensure tool exists
|
||||
if (!(Test-Path $TOOL)) {
|
||||
Write-Host "Building Rust tool..."
|
||||
Push-Location (Join-Path $ROOT "tool")
|
||||
cargo build --release
|
||||
# --- Always rebuild the tool so generated DBC/SQL formats match the current sources.
|
||||
$cargo = Get-Command cargo -ErrorAction SilentlyContinue
|
||||
if (-not $cargo) {
|
||||
throw "Cargo was not found; the MPQ/DBC builder cannot be refreshed."
|
||||
}
|
||||
Write-Host "Building Rust tool..."
|
||||
Push-Location (Join-Path $ROOT "tool")
|
||||
try {
|
||||
& $cargo.Source build --release
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Rust tool build failed with exit code $LASTEXITCODE."
|
||||
}
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
@@ -135,6 +143,11 @@ if (!(Test-Path (Join-Path $DIST_DIR "Data"))) {
|
||||
# --- Build/package/deploy WarcraftXL before the dist sync. The packaged files
|
||||
# are uploaded by upload_to_s3.py and consumed by the launcher manifest.
|
||||
Stop-WowRuntime -ClientPath $WOW_HOME
|
||||
$creatureCache = Join-Path $WOW_HOME "Cache\WDB\ruRU\creaturecache.wdb"
|
||||
if (Test-Path -LiteralPath $creatureCache -PathType Leaf) {
|
||||
Remove-Item -LiteralPath $creatureCache -Force
|
||||
Write-Host "Cleared stale creature cache: $creatureCache"
|
||||
}
|
||||
Write-Host "Building and packaging WarcraftXL..."
|
||||
& $WXL_BUILD_SCRIPT -Configuration Release -ClientPath $WOW_HOME `
|
||||
-PackagePath $DIST_DIR -Deploy
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 8.3 MiB |
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"fileDataID": 467885,
|
||||
"textures": [
|
||||
{
|
||||
"fileDataID": 467884,
|
||||
"file": "babymoonkinne.blp"
|
||||
},
|
||||
{
|
||||
"fileDataID": 944915,
|
||||
"file": "armorreflect4.blp"
|
||||
}
|
||||
],
|
||||
"skins": [
|
||||
{
|
||||
"fileDataID": 473055,
|
||||
"file": "babymoonkin_ne00.skin"
|
||||
}
|
||||
],
|
||||
"lodSkins": [],
|
||||
"anims": [
|
||||
{
|
||||
"fileDataID": 473051,
|
||||
"file": "babymoonkin_ne0074-00.anim",
|
||||
"animID": 74,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473072,
|
||||
"file": "babymoonkin_ne0062-00.anim",
|
||||
"animID": 62,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473068,
|
||||
"file": "babymoonkin_ne0060-00.anim",
|
||||
"animID": 60,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473061,
|
||||
"file": "babymoonkin_ne0070-00.anim",
|
||||
"animID": 70,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473038,
|
||||
"file": "babymoonkin_ne0097-00.anim",
|
||||
"animID": 97,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473067,
|
||||
"file": "babymoonkin_ne0096-00.anim",
|
||||
"animID": 96,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473047,
|
||||
"file": "babymoonkin_ne0098-00.anim",
|
||||
"animID": 98,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473040,
|
||||
"file": "babymoonkin_ne0100-00.anim",
|
||||
"animID": 100,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473035,
|
||||
"file": "babymoonkin_ne0099-00.anim",
|
||||
"animID": 99,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473041,
|
||||
"file": "babymoonkin_ne0101-00.anim",
|
||||
"animID": 101,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473031,
|
||||
"file": "babymoonkin_ne0069-00.anim",
|
||||
"animID": 69,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473050,
|
||||
"file": "babymoonkin_ne0069-01.anim",
|
||||
"animID": 69,
|
||||
"subAnimID": 1
|
||||
},
|
||||
{
|
||||
"fileDataID": 473046,
|
||||
"file": "babymoonkin_ne0069-02.anim",
|
||||
"animID": 69,
|
||||
"subAnimID": 2
|
||||
},
|
||||
{
|
||||
"fileDataID": 473042,
|
||||
"file": "babymoonkin_ne0069-03.anim",
|
||||
"animID": 69,
|
||||
"subAnimID": 3
|
||||
},
|
||||
{
|
||||
"fileDataID": 473060,
|
||||
"file": "babymoonkin_ne0061-00.anim",
|
||||
"animID": 61,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473030,
|
||||
"file": "babymoonkin_ne0067-00.anim",
|
||||
"animID": 67,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473037,
|
||||
"file": "babymoonkin_ne0075-00.anim",
|
||||
"animID": 75,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473069,
|
||||
"file": "babymoonkin_ne0133-00.anim",
|
||||
"animID": 133,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473057,
|
||||
"file": "babymoonkin_ne0134-00.anim",
|
||||
"animID": 134,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473074,
|
||||
"file": "babymoonkin_ne0123-00.anim",
|
||||
"animID": 123,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473056,
|
||||
"file": "babymoonkin_ne0128-00.anim",
|
||||
"animID": 128,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473029,
|
||||
"file": "babymoonkin_ne0129-00.anim",
|
||||
"animID": 129,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473073,
|
||||
"file": "babymoonkin_ne0079-00.anim",
|
||||
"animID": 79,
|
||||
"subAnimID": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"fileDataID": 467887,
|
||||
"textures": [
|
||||
{
|
||||
"fileDataID": 467886,
|
||||
"file": "babymoonkinta.blp"
|
||||
},
|
||||
{
|
||||
"fileDataID": 249237,
|
||||
"file": "..\\..\\item\\objectcomponents\\shield\\armorreflect4.blp"
|
||||
}
|
||||
],
|
||||
"skins": [
|
||||
{
|
||||
"fileDataID": 473058,
|
||||
"file": "babymoonkin_ta00.skin"
|
||||
}
|
||||
],
|
||||
"lodSkins": [],
|
||||
"anims": [
|
||||
{
|
||||
"fileDataID": 473066,
|
||||
"file": "babymoonkin_ta0074-00.anim",
|
||||
"animID": 74,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473039,
|
||||
"file": "babymoonkin_ta0062-00.anim",
|
||||
"animID": 62,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473064,
|
||||
"file": "babymoonkin_ta0060-00.anim",
|
||||
"animID": 60,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473075,
|
||||
"file": "babymoonkin_ta0070-00.anim",
|
||||
"animID": 70,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473053,
|
||||
"file": "babymoonkin_ta0097-00.anim",
|
||||
"animID": 97,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473032,
|
||||
"file": "babymoonkin_ta0096-00.anim",
|
||||
"animID": 96,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473076,
|
||||
"file": "babymoonkin_ta0098-00.anim",
|
||||
"animID": 98,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473071,
|
||||
"file": "babymoonkin_ta0100-00.anim",
|
||||
"animID": 100,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473036,
|
||||
"file": "babymoonkin_ta0099-00.anim",
|
||||
"animID": 99,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473048,
|
||||
"file": "babymoonkin_ta0101-00.anim",
|
||||
"animID": 101,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473065,
|
||||
"file": "babymoonkin_ta0069-00.anim",
|
||||
"animID": 69,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473044,
|
||||
"file": "babymoonkin_ta0069-01.anim",
|
||||
"animID": 69,
|
||||
"subAnimID": 1
|
||||
},
|
||||
{
|
||||
"fileDataID": 473070,
|
||||
"file": "babymoonkin_ta0069-02.anim",
|
||||
"animID": 69,
|
||||
"subAnimID": 2
|
||||
},
|
||||
{
|
||||
"fileDataID": 473052,
|
||||
"file": "babymoonkin_ta0069-03.anim",
|
||||
"animID": 69,
|
||||
"subAnimID": 3
|
||||
},
|
||||
{
|
||||
"fileDataID": 473062,
|
||||
"file": "babymoonkin_ta0061-00.anim",
|
||||
"animID": 61,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473043,
|
||||
"file": "babymoonkin_ta0067-00.anim",
|
||||
"animID": 67,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473045,
|
||||
"file": "babymoonkin_ta0075-00.anim",
|
||||
"animID": 75,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473034,
|
||||
"file": "babymoonkin_ta0133-00.anim",
|
||||
"animID": 133,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473063,
|
||||
"file": "babymoonkin_ta0134-00.anim",
|
||||
"animID": 134,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473054,
|
||||
"file": "babymoonkin_ta0123-00.anim",
|
||||
"animID": 123,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473049,
|
||||
"file": "babymoonkin_ta0128-00.anim",
|
||||
"animID": 128,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473033,
|
||||
"file": "babymoonkin_ta0129-00.anim",
|
||||
"animID": 129,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473059,
|
||||
"file": "babymoonkin_ta0079-00.anim",
|
||||
"animID": 79,
|
||||
"subAnimID": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
Generated
+285
-7
@@ -82,6 +82,18 @@ version = "1.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
|
||||
|
||||
[[package]]
|
||||
name = "bit_field"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.11.0"
|
||||
@@ -103,6 +115,12 @@ version = "3.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.25.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
@@ -170,6 +188,12 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.5"
|
||||
@@ -253,6 +277,12 @@ version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
@@ -313,12 +343,38 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exr"
|
||||
version = "1.74.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711fe42c9964295e01ee3fba3f9fe0e1d24b98886950d68efe81b1c76e21adf3"
|
||||
dependencies = [
|
||||
"bit_field",
|
||||
"half",
|
||||
"lebe",
|
||||
"miniz_oxide",
|
||||
"num-complex",
|
||||
"pulp",
|
||||
"rayon-core",
|
||||
"smallvec",
|
||||
"zune-inflate",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "fdeflate"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
|
||||
dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.9"
|
||||
@@ -381,6 +437,27 @@ dependencies = [
|
||||
"wasip3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gif"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b"
|
||||
dependencies = [
|
||||
"color_quant",
|
||||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crunchy",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
@@ -416,6 +493,39 @@ version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.24.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder",
|
||||
"color_quant",
|
||||
"exr",
|
||||
"gif",
|
||||
"jpeg-decoder",
|
||||
"num-traits",
|
||||
"png",
|
||||
"qoi",
|
||||
"tiff",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image-blp"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b863207a84a413c9e3e94909828ac796d24b1a6f53baa88f186d4cf61eca691f"
|
||||
dependencies = [
|
||||
"color_quant",
|
||||
"image",
|
||||
"log",
|
||||
"nom",
|
||||
"spin",
|
||||
"texpresso",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "implode"
|
||||
version = "0.1.1"
|
||||
@@ -459,6 +569,15 @@ version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||
|
||||
[[package]]
|
||||
name = "jpeg-decoder"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07"
|
||||
dependencies = [
|
||||
"rayon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.91"
|
||||
@@ -484,6 +603,12 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
|
||||
[[package]]
|
||||
name = "lebe"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
|
||||
|
||||
[[package]]
|
||||
name = "libbz2-rs-sys"
|
||||
version = "0.2.2"
|
||||
@@ -558,6 +683,12 @@ version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
@@ -568,6 +699,16 @@ dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.6"
|
||||
@@ -594,6 +735,16 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-complex"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.46"
|
||||
@@ -665,6 +816,12 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pem-rfc7468"
|
||||
version = "0.7.0"
|
||||
@@ -703,7 +860,20 @@ checksum = "5a7ff566c5cb263cf6a9f3b8b8ccbeaf236c0b3516c184134c062863258b3789"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"indicatif",
|
||||
"thiserror",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.17.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"crc32fast",
|
||||
"fdeflate",
|
||||
"flate2",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -740,6 +910,38 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulp"
|
||||
version = "0.22.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "046aa45b989642ec2e4717c8e72d677b13edd831a4d3b6cf37d9a3e54912496a"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"cfg-if",
|
||||
"libm",
|
||||
"num-complex",
|
||||
"paste",
|
||||
"pulp-wasm-simd-flag",
|
||||
"raw-cpuid",
|
||||
"reborrow",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulp-wasm-simd-flag"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d8f70e07b9c3962945a74e59ca1c511bba65b6419468acc217c457d93f3c740"
|
||||
|
||||
[[package]]
|
||||
name = "qoi"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
@@ -819,6 +1021,15 @@ dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-cpuid"
|
||||
version = "11.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.11.0"
|
||||
@@ -839,13 +1050,19 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reborrow"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.11.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -874,7 +1091,7 @@ version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.11.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
@@ -989,6 +1206,9 @@ name = "spin"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
@@ -1036,13 +1256,43 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "texpresso"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8550677e2259d675a7841cb1403db35f330cc9e58674c8c5caa12dd12c51dc71"
|
||||
dependencies = [
|
||||
"libm",
|
||||
"rayon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
"thiserror-impl 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1056,10 +1306,23 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiff"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"jpeg-decoder",
|
||||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tool"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"image",
|
||||
"image-blp",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"walkdir",
|
||||
@@ -1209,7 +1472,7 @@ version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.11.0",
|
||||
"hashbrown 0.15.5",
|
||||
"indexmap",
|
||||
"semver",
|
||||
@@ -1225,6 +1488,12 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "weezl"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
@@ -1380,7 +1649,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags",
|
||||
"bitflags 2.11.0",
|
||||
"indexmap",
|
||||
"log",
|
||||
"serde",
|
||||
@@ -1437,7 +1706,7 @@ dependencies = [
|
||||
"rsa",
|
||||
"sha1",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1471,3 +1740,12 @@ name = "zmij"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
|
||||
[[package]]
|
||||
name = "zune-inflate"
|
||||
version = "0.2.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
|
||||
dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
@@ -8,3 +8,5 @@ walkdir = "2.5.0"
|
||||
wow-mpq = "0.6.4"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
image = { version = "0.24.9", default-features = false, features = ["jpeg", "png", "tga"] }
|
||||
image-blp = "1.2.0"
|
||||
|
||||
+124
-14
@@ -23,6 +23,8 @@ pub struct Registry {
|
||||
pub summon_spell_id_start: u32,
|
||||
#[serde(default = "default_skill_line_ability_id")]
|
||||
pub skill_line_ability_id_start: u32,
|
||||
#[serde(default = "default_spell_icon_id")]
|
||||
pub spell_icon_id_start: u32,
|
||||
pub defaults: Defaults,
|
||||
#[serde(default)]
|
||||
pub mounts: Vec<EntrySpec>,
|
||||
@@ -45,6 +47,9 @@ fn default_summon_spell_id() -> u32 {
|
||||
fn default_skill_line_ability_id() -> u32 {
|
||||
30_000
|
||||
}
|
||||
fn default_spell_icon_id() -> u32 {
|
||||
50_000
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Defaults {
|
||||
@@ -56,7 +61,7 @@ pub struct Defaults {
|
||||
#[serde(untagged)]
|
||||
pub enum EntrySpec {
|
||||
Folder(String),
|
||||
Detailed(EntryOptions),
|
||||
Detailed(Box<EntryOptions>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
@@ -74,6 +79,8 @@ pub struct EntryOptions {
|
||||
pub flags: Option<u32>,
|
||||
pub name: Option<String>,
|
||||
pub name_ru: Option<String>,
|
||||
pub race_mask: Option<u32>,
|
||||
pub icon: Option<String>,
|
||||
}
|
||||
|
||||
impl EntrySpec {
|
||||
@@ -97,6 +104,8 @@ impl EntrySpec {
|
||||
flags: options.flags,
|
||||
name: options.name.clone(),
|
||||
name_ru: options.name_ru.clone(),
|
||||
race_mask: options.race_mask,
|
||||
icon: options.icon.clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -118,6 +127,8 @@ struct LockedIds {
|
||||
summon_spell_id: Option<u32>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
skill_line_ability_id: Option<u32>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
spell_icon_id: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -134,10 +145,14 @@ struct PreparedEntry {
|
||||
summon: bool,
|
||||
name: String,
|
||||
name_ru: String,
|
||||
icon_path: Option<String>,
|
||||
icon_source: Option<PathBuf>,
|
||||
icon_bytes: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
pub struct GeneratedCreatures {
|
||||
pub assets: Vec<(PathBuf, String)>,
|
||||
pub icons: Vec<(PathBuf, Vec<u8>, String)>,
|
||||
pub file_data_csv: Vec<u8>,
|
||||
pub creature_model_data: Vec<u8>,
|
||||
pub creature_display_info: Vec<u8>,
|
||||
@@ -355,7 +370,13 @@ fn next_free(start: u32, used: &HashSet<u32>) -> u32 {
|
||||
value
|
||||
}
|
||||
|
||||
fn allocate_ids(lock: &mut LockFile, key: &str, registry: &Registry, summon: bool) -> LockedIds {
|
||||
fn allocate_ids(
|
||||
lock: &mut LockFile,
|
||||
key: &str,
|
||||
registry: &Registry,
|
||||
summon: bool,
|
||||
custom_icon: bool,
|
||||
) -> LockedIds {
|
||||
let used_models: HashSet<u32> = lock.models.values().map(|ids| ids.model_data_id).collect();
|
||||
let used_displays: HashSet<u32> = lock
|
||||
.models
|
||||
@@ -377,12 +398,18 @@ fn allocate_ids(lock: &mut LockFile, key: &str, registry: &Registry, summon: boo
|
||||
.values()
|
||||
.filter_map(|ids| ids.skill_line_ability_id)
|
||||
.collect();
|
||||
let used_spell_icons: HashSet<u32> = lock
|
||||
.models
|
||||
.values()
|
||||
.filter_map(|ids| ids.spell_icon_id)
|
||||
.collect();
|
||||
let mut ids = lock.models.get(key).cloned().unwrap_or(LockedIds {
|
||||
model_data_id: next_free(registry.model_data_id_start, &used_models),
|
||||
display_info_id: next_free(registry.display_info_id_start, &used_displays),
|
||||
creature_entry: None,
|
||||
summon_spell_id: None,
|
||||
skill_line_ability_id: None,
|
||||
spell_icon_id: None,
|
||||
});
|
||||
if summon {
|
||||
if ids.creature_entry.is_none() {
|
||||
@@ -397,6 +424,9 @@ fn allocate_ids(lock: &mut LockFile, key: &str, registry: &Registry, summon: boo
|
||||
&used_skill_rows,
|
||||
));
|
||||
}
|
||||
if custom_icon && ids.spell_icon_id.is_none() {
|
||||
ids.spell_icon_id = Some(next_free(registry.spell_icon_id_start, &used_spell_icons));
|
||||
}
|
||||
}
|
||||
lock.models.insert(key.to_string(), ids.clone());
|
||||
ids
|
||||
@@ -409,6 +439,34 @@ fn validate_text(value: &str, field: &str, key: &str) -> Result<(), Box<dyn Erro
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_icon_path(project_root: &Path, folder: &Path, value: &str) -> PathBuf {
|
||||
let path = PathBuf::from(value);
|
||||
if path.is_absolute() {
|
||||
path
|
||||
} else {
|
||||
let beside_model = folder.join(&path);
|
||||
if beside_model.is_file() {
|
||||
beside_model
|
||||
} else {
|
||||
project_root.join(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn icon_file_stem(key: &str) -> String {
|
||||
let sanitized: String = key
|
||||
.chars()
|
||||
.map(|character| {
|
||||
if character.is_ascii_alphanumeric() || character == '_' {
|
||||
character
|
||||
} else {
|
||||
'_'
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
format!("MoonWell_{sanitized}")
|
||||
}
|
||||
|
||||
fn prepare_entry(
|
||||
project_root: &Path,
|
||||
spec: &EntrySpec,
|
||||
@@ -456,7 +514,24 @@ fn prepare_entry(
|
||||
file_data.push((file_data_id, format!("{archive}/{file}")));
|
||||
assets.push((source, format!("{archive}/{file}")));
|
||||
}
|
||||
let ids = allocate_ids(lock, &key, registry, summon);
|
||||
if options.icon.is_some() && !summon {
|
||||
return Err(format!("icon is only supported for pet '{}'", key).into());
|
||||
}
|
||||
let (icon_path, icon_source, icon_bytes) = if let Some(value) = options.icon.as_deref() {
|
||||
let source = resolve_icon_path(project_root, &folder, value);
|
||||
if !source.is_file() {
|
||||
return Err(format!("icon source not found: {}", source.display()).into());
|
||||
}
|
||||
let stem = icon_file_stem(&key);
|
||||
(
|
||||
Some(format!("Interface\\Icons\\{stem}")),
|
||||
Some(source.clone()),
|
||||
Some(crate::icon::convert_icon_bytes(&source)?),
|
||||
)
|
||||
} else {
|
||||
(None, None, None)
|
||||
};
|
||||
let ids = allocate_ids(lock, &key, registry, summon, icon_path.is_some());
|
||||
Ok(PreparedEntry {
|
||||
key,
|
||||
folder,
|
||||
@@ -470,6 +545,9 @@ fn prepare_entry(
|
||||
summon,
|
||||
name,
|
||||
name_ru,
|
||||
icon_path,
|
||||
icon_source,
|
||||
icon_bytes,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -565,6 +643,7 @@ fn generate_server_sql(entries: &[PreparedEntry]) -> Result<String, Box<dyn Erro
|
||||
let mut sql = String::from(
|
||||
"-- Generated by MoonWell custom creature builder.\n\
|
||||
-- Apply to acore_world, then restart ac-worldserver.\n\
|
||||
SET NAMES utf8mb4;\n\
|
||||
START TRANSACTION;\n\n",
|
||||
);
|
||||
for entry in entries.iter().filter(|entry| entry.summon) {
|
||||
@@ -590,13 +669,19 @@ fn generate_server_sql(entries: &[PreparedEntry]) -> Result<String, Box<dyn Erro
|
||||
"Призывает или отпускает спутника: {}.",
|
||||
entry.name_ru
|
||||
));
|
||||
let race_mask = entry.options.race_mask.unwrap_or(0);
|
||||
let spell_icon_id = entry.ids.spell_icon_id.unwrap_or(2686);
|
||||
|
||||
sql.push_str(&format!(
|
||||
"-- {}\n\
|
||||
INSERT INTO creature_template\n\
|
||||
(entry,name,minlevel,maxlevel,faction,speed_walk,speed_run,scale,unit_class,unit_flags,unit_flags2,type,MovementType,RegenHealth,VerifiedBuild)\n\
|
||||
VALUES ({creature},{name_en},1,1,188,1,1.14286,1,1,0,2048,12,0,1,12340)\n\
|
||||
(entry,name,minlevel,maxlevel,faction,speed_walk,speed_run,unit_class,unit_flags,unit_flags2,type,MovementType,RegenHealth,VerifiedBuild)\n\
|
||||
VALUES ({creature},{name_en},1,1,188,1,1.14286,1,0,2048,12,0,1,12340)\n\
|
||||
ON DUPLICATE KEY UPDATE name=VALUES(name),faction=VALUES(faction),type=VALUES(type),unit_flags2=VALUES(unit_flags2);\n\
|
||||
INSERT INTO creature_template_locale\n\
|
||||
(entry,locale,Name,Title,VerifiedBuild)\n\
|
||||
VALUES ({creature},'ruRU',{name_ru},'',12340)\n\
|
||||
ON DUPLICATE KEY UPDATE Name=VALUES(Name),Title=VALUES(Title),VerifiedBuild=VALUES(VerifiedBuild);\n\
|
||||
INSERT INTO creature_model_info\n\
|
||||
(DisplayID,BoundingRadius,CombatReach,Gender,DisplayID_Other_Gender,VerifiedBuild)\n\
|
||||
VALUES ({},0.25,0.5,2,0,12340)\n\
|
||||
@@ -610,12 +695,12 @@ fn generate_server_sql(entries: &[PreparedEntry]) -> Result<String, Box<dyn Erro
|
||||
sql.push_str(&format!(
|
||||
"INSERT INTO spell_dbc\n\
|
||||
(ID,Attributes,AttributesEx3,CastingTimeIndex,InterruptFlags,ProcChance,DurationIndex,RangeIndex,EquippedItemClass,Effect_1,EffectDieSides_1,EffectBasePoints_1,ImplicitTargetA_1,EffectMultipleValue_1,EffectMiscValue_1,EffectMiscValueB_1,SpellVisualID_1,SpellIconID,Name_Lang_enUS,Name_Lang_ruRU,Description_Lang_enUS,Description_Lang_ruRU,StartRecoveryCategory,StartRecoveryTime,EffectChainAmplitude_1,EffectChainAmplitude_2,EffectChainAmplitude_3,SchoolMask)\n\
|
||||
VALUES ({spell},262416,536870912,1,31,101,21,1,-1,28,1,1,32,1000,{creature},41,353,2686,{name_en},{name_ru},{description_en},{description_ru},133,1500,1,1,1,1)\n\
|
||||
ON DUPLICATE KEY UPDATE EffectMiscValue_1=VALUES(EffectMiscValue_1),Name_Lang_enUS=VALUES(Name_Lang_enUS),Name_Lang_ruRU=VALUES(Name_Lang_ruRU),Description_Lang_enUS=VALUES(Description_Lang_enUS),Description_Lang_ruRU=VALUES(Description_Lang_ruRU);\n\
|
||||
VALUES ({spell},262416,536870912,1,31,101,21,1,-1,28,1,1,32,1000,{creature},41,353,{spell_icon_id},{name_en},{name_ru},{description_en},{description_ru},133,1500,1,1,1,1)\n\
|
||||
ON DUPLICATE KEY UPDATE EffectMiscValue_1=VALUES(EffectMiscValue_1),SpellIconID=VALUES(SpellIconID),Name_Lang_enUS=VALUES(Name_Lang_enUS),Name_Lang_ruRU=VALUES(Name_Lang_ruRU),Description_Lang_enUS=VALUES(Description_Lang_enUS),Description_Lang_ruRU=VALUES(Description_Lang_ruRU);\n\
|
||||
INSERT INTO skilllineability_dbc\n\
|
||||
(ID,SkillLine,Spell,RaceMask,ClassMask,ExcludeRace,ExcludeClass,MinSkillLineRank,SupercededBySpell,AcquireMethod,TrivialSkillLineRankHigh,TrivialSkillLineRankLow,CharacterPoints_1,CharacterPoints_2)\n\
|
||||
VALUES ({skill_row},778,{spell},0,0,0,0,1,0,0,0,0,0,0)\n\
|
||||
ON DUPLICATE KEY UPDATE SkillLine=VALUES(SkillLine),Spell=VALUES(Spell),MinSkillLineRank=VALUES(MinSkillLineRank);\n\n"
|
||||
VALUES ({skill_row},778,{spell},{race_mask},0,0,0,1,0,0,0,0,0,0)\n\
|
||||
ON DUPLICATE KEY UPDATE SkillLine=VALUES(SkillLine),Spell=VALUES(Spell),RaceMask=VALUES(RaceMask),MinSkillLineRank=VALUES(MinSkillLineRank);\n\n"
|
||||
));
|
||||
}
|
||||
sql.push_str("COMMIT;\n");
|
||||
@@ -681,7 +766,9 @@ pub fn prepare(project_root: &Path) -> Result<Option<GeneratedCreatures>, Box<dy
|
||||
let mut active_creature_entries = HashSet::new();
|
||||
let mut active_spell_ids = HashSet::new();
|
||||
let mut active_skill_rows = HashSet::new();
|
||||
let mut active_spell_icon_ids = HashSet::new();
|
||||
let mut assets = Vec::new();
|
||||
let mut icons = Vec::new();
|
||||
for entry in &entries {
|
||||
if !active_model_ids.insert(entry.ids.model_data_id) {
|
||||
return Err(format!(
|
||||
@@ -713,6 +800,11 @@ pub fn prepare(project_root: &Path) -> Result<Option<GeneratedCreatures>, Box<dy
|
||||
&mut active_skill_rows,
|
||||
"skill-line row ID",
|
||||
),
|
||||
(
|
||||
entry.ids.spell_icon_id,
|
||||
&mut active_spell_icon_ids,
|
||||
"spell icon ID",
|
||||
),
|
||||
] {
|
||||
if let Some(value) = value
|
||||
&& !used.insert(value)
|
||||
@@ -741,16 +833,28 @@ pub fn prepare(project_root: &Path) -> Result<Option<GeneratedCreatures>, Box<dy
|
||||
}
|
||||
}
|
||||
assets.extend(entry.assets.clone());
|
||||
if let (Some(source), Some(bytes), Some(path)) = (
|
||||
entry.icon_source.as_ref(),
|
||||
entry.icon_bytes.as_ref(),
|
||||
entry.icon_path.as_ref(),
|
||||
) {
|
||||
icons.push((
|
||||
source.clone(),
|
||||
bytes.clone(),
|
||||
format!("{}.blp", path.replace('\\', "/")),
|
||||
));
|
||||
}
|
||||
}
|
||||
let mut csv = String::from("# FileDataID,archive path\n");
|
||||
for (id, path) in all_ids {
|
||||
csv.push_str(&format!("{id},{}\n", path.replace('/', "\\")));
|
||||
}
|
||||
let mut spell_overrides =
|
||||
String::from("# spellId\ttemplateSpellId\tcreatureEntry\tskillLineAbilityId\tname\n");
|
||||
let mut spell_overrides = String::from(
|
||||
"# spellId\ttemplateSpellId\tcreatureEntry\tskillLineAbilityId\traceMask\tspellIconId\ticonPath\tname\n",
|
||||
);
|
||||
for entry in entries.iter().filter(|entry| entry.summon) {
|
||||
spell_overrides.push_str(&format!(
|
||||
"{}\t70613\t{}\t{}\t{}\n",
|
||||
"{}\t70613\t{}\t{}\t{}\t{}\t{}\t{}\n",
|
||||
entry
|
||||
.ids
|
||||
.summon_spell_id
|
||||
@@ -763,6 +867,9 @@ pub fn prepare(project_root: &Path) -> Result<Option<GeneratedCreatures>, Box<dy
|
||||
.ids
|
||||
.skill_line_ability_id
|
||||
.ok_or("pet has no skill-line row ID")?,
|
||||
entry.options.race_mask.unwrap_or(0),
|
||||
entry.ids.spell_icon_id.unwrap_or(0),
|
||||
entry.icon_path.as_deref().unwrap_or("-"),
|
||||
entry.name_ru
|
||||
));
|
||||
}
|
||||
@@ -780,7 +887,7 @@ pub fn prepare(project_root: &Path) -> Result<Option<GeneratedCreatures>, Box<dy
|
||||
generate_server_sql(&entries)?,
|
||||
)?;
|
||||
let mut id_map = String::from(
|
||||
"key,type,modelDataID,displayInfoID,creatureEntry,summonSpellID,skillLineAbilityID\n",
|
||||
"key,type,modelDataID,displayInfoID,creatureEntry,summonSpellID,skillLineAbilityID,spellIconID\n",
|
||||
);
|
||||
for (kind, specs) in [("mount", ®istry.mounts), ("pet", ®istry.pets)] {
|
||||
for spec in specs {
|
||||
@@ -789,7 +896,7 @@ pub fn prepare(project_root: &Path) -> Result<Option<GeneratedCreatures>, Box<dy
|
||||
let key = options.key.unwrap_or(default_key(&folder)?);
|
||||
let ids = lock.models.get(&key).ok_or("lock entry disappeared")?;
|
||||
id_map.push_str(&format!(
|
||||
"{key},{kind},{},{},{},{},{}\n",
|
||||
"{key},{kind},{},{},{},{},{},{}\n",
|
||||
ids.model_data_id,
|
||||
ids.display_info_id,
|
||||
ids.creature_entry
|
||||
@@ -797,6 +904,8 @@ pub fn prepare(project_root: &Path) -> Result<Option<GeneratedCreatures>, Box<dy
|
||||
ids.summon_spell_id
|
||||
.map_or_else(String::new, |value| value.to_string()),
|
||||
ids.skill_line_ability_id
|
||||
.map_or_else(String::new, |value| value.to_string()),
|
||||
ids.spell_icon_id
|
||||
.map_or_else(String::new, |value| value.to_string())
|
||||
));
|
||||
}
|
||||
@@ -805,6 +914,7 @@ pub fn prepare(project_root: &Path) -> Result<Option<GeneratedCreatures>, Box<dy
|
||||
|
||||
Ok(Some(GeneratedCreatures {
|
||||
assets,
|
||||
icons,
|
||||
file_data_csv: csv.into_bytes(),
|
||||
creature_model_data: model_dbc,
|
||||
creature_display_info: display_dbc,
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
use image::GenericImageView;
|
||||
use image::imageops::FilterType as ImageFilterType;
|
||||
use image_blp::convert::{Blp2Format, BlpTarget, FilterType, image_to_blp};
|
||||
use image_blp::encode::encode_blp;
|
||||
use std::error::Error;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
const MAX_ICON_SIZE: u32 = 512;
|
||||
|
||||
struct EncodedIcon {
|
||||
bytes: Vec<u8>,
|
||||
source_width: u32,
|
||||
source_height: u32,
|
||||
dimension: u32,
|
||||
}
|
||||
|
||||
fn target_dimension(width: u32, height: u32) -> Result<u32, Box<dyn Error>> {
|
||||
let largest = width.max(height).max(4);
|
||||
let dimension = largest
|
||||
.checked_next_power_of_two()
|
||||
.ok_or("icon dimensions are too large")?;
|
||||
if dimension > MAX_ICON_SIZE {
|
||||
return Err(format!(
|
||||
"icon is too large: {width}x{height}; maximum converted size is {MAX_ICON_SIZE}x{MAX_ICON_SIZE}"
|
||||
)
|
||||
.into());
|
||||
}
|
||||
Ok(dimension)
|
||||
}
|
||||
|
||||
fn encode_icon(input: &Path) -> Result<EncodedIcon, Box<dyn Error>> {
|
||||
let source = image::io::Reader::open(input)?
|
||||
.with_guessed_format()?
|
||||
.decode()?;
|
||||
let (width, height) = source.dimensions();
|
||||
let dimension = target_dimension(width, height)?;
|
||||
let resized = source.resize_exact(dimension, dimension, ImageFilterType::Lanczos3);
|
||||
let blp = image_to_blp(
|
||||
resized,
|
||||
true,
|
||||
BlpTarget::Blp2(Blp2Format::Dxt1 {
|
||||
has_alpha: false,
|
||||
compress_algorithm: Default::default(),
|
||||
}),
|
||||
FilterType::Lanczos3,
|
||||
)?;
|
||||
Ok(EncodedIcon {
|
||||
bytes: encode_blp(&blp)?,
|
||||
source_width: width,
|
||||
source_height: height,
|
||||
dimension,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn convert_icon_bytes(input: &Path) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
Ok(encode_icon(input)?.bytes)
|
||||
}
|
||||
|
||||
pub fn default_output_path(input: &Path) -> PathBuf {
|
||||
input.with_extension("blp")
|
||||
}
|
||||
|
||||
pub fn convert_icon(input: &Path, output: &Path) -> Result<(), Box<dyn Error>> {
|
||||
if !input.is_file() {
|
||||
return Err(format!("icon source not found: {}", input.display()).into());
|
||||
}
|
||||
let encoded = encode_icon(input)?;
|
||||
if let Some(parent) = output.parent()
|
||||
&& !parent.as_os_str().is_empty()
|
||||
{
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
fs::write(output, encoded.bytes)?;
|
||||
println!(
|
||||
"Converted icon: {} ({}x{}) -> {} (BLP2 DXT1, {}x{}, mipmaps)",
|
||||
input.display(),
|
||||
encoded.source_width,
|
||||
encoded.source_height,
|
||||
output.display(),
|
||||
encoded.dimension,
|
||||
encoded.dimension
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn rounds_icon_dimensions_to_supported_power_of_two() {
|
||||
assert_eq!(target_dimension(56, 56).unwrap(), 64);
|
||||
assert_eq!(target_dimension(128, 64).unwrap(), 128);
|
||||
assert!(target_dimension(513, 512).is_err());
|
||||
}
|
||||
}
|
||||
+25
-1
@@ -8,6 +8,7 @@ use wow_mpq::compression::flags;
|
||||
use wow_mpq::{ArchiveBuilder, FormatVersion, ListfileOption};
|
||||
|
||||
mod creatures;
|
||||
mod icon;
|
||||
use creatures::GeneratedCreatures;
|
||||
|
||||
/// Checks if a directory name looks like a WoW locale folder (e.g. ruRU, enUS, deDE).
|
||||
@@ -48,6 +49,11 @@ fn build_mpq(
|
||||
.flat_map(|generated| generated.assets.iter())
|
||||
.filter_map(|(source, _)| source.canonicalize().ok())
|
||||
.collect();
|
||||
let generated_icon_sources: std::collections::HashSet<PathBuf> = creatures
|
||||
.into_iter()
|
||||
.flat_map(|generated| generated.icons.iter())
|
||||
.filter_map(|(source, _, _)| source.canonicalize().ok())
|
||||
.collect();
|
||||
|
||||
for entry in WalkDir::new(&base)
|
||||
.into_iter()
|
||||
@@ -55,7 +61,7 @@ fn build_mpq(
|
||||
.filter(|e| e.file_type().is_file())
|
||||
{
|
||||
let full_path = entry.path().canonicalize()?;
|
||||
if loose_sources.contains(&full_path) {
|
||||
if loose_sources.contains(&full_path) || generated_icon_sources.contains(&full_path) {
|
||||
continue;
|
||||
}
|
||||
let rel_path = match full_path.strip_prefix(&base) {
|
||||
@@ -74,6 +80,10 @@ fn build_mpq(
|
||||
}
|
||||
|
||||
if let Some(generated) = creatures {
|
||||
for (_, bytes, archive_path) in &generated.icons {
|
||||
println!(" Adding generated icon: {archive_path}");
|
||||
builder = builder.add_file_data(bytes.clone(), archive_path);
|
||||
}
|
||||
println!(" Adding generated: WXLFileData.csv");
|
||||
builder = builder.add_file_data(generated.file_data_csv.clone(), "WXLFileData.csv");
|
||||
println!(" Adding generated: WXLSpellOverrides.tsv");
|
||||
@@ -122,8 +132,22 @@ fn stage_loose_assets(
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
if args.get(1).is_some_and(|arg| arg == "icon") {
|
||||
if !(args.len() == 3 || args.len() == 4) {
|
||||
eprintln!("Usage: tool icon <input.jpg|png|tga> [output.blp]");
|
||||
std::process::exit(1);
|
||||
}
|
||||
let input = PathBuf::from(&args[2]);
|
||||
let output = args
|
||||
.get(3)
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| icon::default_output_path(&input));
|
||||
return icon::convert_icon(&input, &output);
|
||||
}
|
||||
|
||||
if args.len() != 3 {
|
||||
eprintln!("Usage: tool <src_dir> <output_dir>");
|
||||
eprintln!(" tool icon <input.jpg|png|tga> [output.blp]");
|
||||
eprintln!();
|
||||
eprintln!(" src_dir — root of source tree (must contain Data/)");
|
||||
eprintln!(" output_dir — destination root; MPQs are placed mirroring the src structure");
|
||||
|
||||
Reference in New Issue
Block a user