diff --git a/CUSTOM_CREATURES.md b/CUSTOM_CREATURES.md index 3b030c1..4c269e5 100644 --- a/CUSTOM_CREATURES.md +++ b/CUSTOM_CREATURES.md @@ -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` и diff --git a/custom-creatures.json b/custom-creatures.json index 46f46d8..3bd5519 100644 --- a/custom-creatures.json +++ b/custom-creatures.json @@ -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 } ] } diff --git a/custom-creatures.lock.json b/custom-creatures.lock.json index 502398f..b9a64c4 100644 --- a/custom-creatures.lock.json +++ b/custom-creatures.lock.json @@ -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 } } } diff --git a/modules/moonwell/host/CustomSpellProvider.cpp b/modules/moonwell/host/CustomSpellProvider.cpp index dc78ba1..174218a 100644 --- a/modules/moonwell/host/CustomSpellProvider.cpp +++ b/modules/moonwell/host/CustomSpellProvider.cpp @@ -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 g_spellDbc; std::vector g_skillDbc; + std::vector 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 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(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(kDescriptionPrefixUtf8)) + item.name + "."; const uint32_t descriptionOffset = stringSize + static_cast(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& base, const std::vector& overrides, + std::vector& out) + { + uint32_t count = 0, stringSize = 0; + size_t recordsEnd = 0; + if (!ValidateWdbc(base, kSpellIconFields, kSpellIconRecordSize, count, stringSize, recordsEnd)) + return false; + + std::vector records; + std::vector 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(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(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(records.size() / kSpellIconRecordSize)); + WriteU32(out.data() + 16, stringSize + static_cast(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 overrideBytes, spellBase, skillBase; + std::vector overrideBytes, spellBase, skillBase, spellIconBase; if (root.empty() || !store.Mount(root) || !store.ReadAll(kOverridesPath, overrideBytes)) return; std::vector 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& 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; } diff --git a/run.ps1 b/run.ps1 index 0182c92..fe30b87 100644 --- a/run.ps1 +++ b/run.ps1 @@ -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 diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000..b6d0ba9 Binary files /dev/null and b/screenshot.png differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/armorreflect4.blp b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/armorreflect4.blp new file mode 100644 index 0000000..e66a83a Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/armorreflect4.blp differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne.m2 b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne.m2 new file mode 100644 index 0000000..8b7ccd3 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne.m2 differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne.manifest.json b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne.manifest.json new file mode 100644 index 0000000..16e81b2 --- /dev/null +++ b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne.manifest.json @@ -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 + } + ] +} diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne00.skin b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne00.skin new file mode 100644 index 0000000..f51bb82 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne00.skin differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0060-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0060-00.anim new file mode 100644 index 0000000..21a03db Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0060-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0061-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0061-00.anim new file mode 100644 index 0000000..0ec8ace Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0061-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0062-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0062-00.anim new file mode 100644 index 0000000..c560b4a Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0062-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0067-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0067-00.anim new file mode 100644 index 0000000..ddb1a9a Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0067-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0069-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0069-00.anim new file mode 100644 index 0000000..67750d4 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0069-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0069-01.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0069-01.anim new file mode 100644 index 0000000..3e16e97 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0069-01.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0069-02.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0069-02.anim new file mode 100644 index 0000000..8055c8f Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0069-02.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0069-03.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0069-03.anim new file mode 100644 index 0000000..7f39ffa Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0069-03.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0070-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0070-00.anim new file mode 100644 index 0000000..f9bbc0a Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0070-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0074-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0074-00.anim new file mode 100644 index 0000000..089623a Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0074-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0075-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0075-00.anim new file mode 100644 index 0000000..5a5eebe Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0075-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0079-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0079-00.anim new file mode 100644 index 0000000..6da1eba Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0079-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0096-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0096-00.anim new file mode 100644 index 0000000..8ff51f8 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0096-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0097-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0097-00.anim new file mode 100644 index 0000000..06d7873 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0097-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0098-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0098-00.anim new file mode 100644 index 0000000..ccb79c5 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0098-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0099-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0099-00.anim new file mode 100644 index 0000000..b772462 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0099-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0100-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0100-00.anim new file mode 100644 index 0000000..7309d88 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0100-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0101-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0101-00.anim new file mode 100644 index 0000000..04d6798 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0101-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0123-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0123-00.anim new file mode 100644 index 0000000..711b3ce Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0123-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0128-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0128-00.anim new file mode 100644 index 0000000..263f049 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0128-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0129-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0129-00.anim new file mode 100644 index 0000000..ee4898d Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0129-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0133-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0133-00.anim new file mode 100644 index 0000000..582fcc8 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0133-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0134-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0134-00.anim new file mode 100644 index 0000000..bc2f890 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkin_ne0134-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkinne.blp b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkinne.blp new file mode 100644 index 0000000..f8d80bb Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/babymoonkinne.blp differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/inv_misc_petmoonkinne.jpg b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/inv_misc_petmoonkinne.jpg new file mode 100644 index 0000000..e000724 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Alliance/inv_misc_petmoonkinne.jpg differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta.m2 b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta.m2 new file mode 100644 index 0000000..99d2594 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta.m2 differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta.manifest.json b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta.manifest.json new file mode 100644 index 0000000..2e33355 --- /dev/null +++ b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta.manifest.json @@ -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 + } + ] +} diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta00.skin b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta00.skin new file mode 100644 index 0000000..86ebff9 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta00.skin differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0060-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0060-00.anim new file mode 100644 index 0000000..ea59dc1 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0060-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0061-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0061-00.anim new file mode 100644 index 0000000..3b792a9 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0061-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0062-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0062-00.anim new file mode 100644 index 0000000..08237e8 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0062-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0067-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0067-00.anim new file mode 100644 index 0000000..fe95673 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0067-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0069-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0069-00.anim new file mode 100644 index 0000000..59af5aa Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0069-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0069-01.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0069-01.anim new file mode 100644 index 0000000..1b06b8c Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0069-01.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0069-02.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0069-02.anim new file mode 100644 index 0000000..dafb94c Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0069-02.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0069-03.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0069-03.anim new file mode 100644 index 0000000..7f39ffa Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0069-03.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0070-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0070-00.anim new file mode 100644 index 0000000..d4bdc5f Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0070-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0074-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0074-00.anim new file mode 100644 index 0000000..089623a Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0074-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0075-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0075-00.anim new file mode 100644 index 0000000..5a5eebe Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0075-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0079-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0079-00.anim new file mode 100644 index 0000000..46c2437 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0079-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0096-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0096-00.anim new file mode 100644 index 0000000..8ff51f8 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0096-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0097-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0097-00.anim new file mode 100644 index 0000000..ee36a4f Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0097-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0098-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0098-00.anim new file mode 100644 index 0000000..ccb79c5 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0098-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0099-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0099-00.anim new file mode 100644 index 0000000..b772462 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0099-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0100-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0100-00.anim new file mode 100644 index 0000000..7309d88 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0100-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0101-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0101-00.anim new file mode 100644 index 0000000..30c8316 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0101-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0123-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0123-00.anim new file mode 100644 index 0000000..711b3ce Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0123-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0128-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0128-00.anim new file mode 100644 index 0000000..263f049 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0128-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0129-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0129-00.anim new file mode 100644 index 0000000..ee4898d Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0129-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0133-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0133-00.anim new file mode 100644 index 0000000..582fcc8 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0133-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0134-00.anim b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0134-00.anim new file mode 100644 index 0000000..bc2f890 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkin_ta0134-00.anim differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkinta.blp b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkinta.blp new file mode 100644 index 0000000..fe59ad7 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/babymoonkinta.blp differ diff --git a/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/inv_misc_petmoonkinta.jpg b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/inv_misc_petmoonkinta.jpg new file mode 100644 index 0000000..3943dc7 Binary files /dev/null and b/src/Data/patch-Z/Creature/MoonkinHatchling_Horde/inv_misc_petmoonkinta.jpg differ diff --git a/src/Data/patch-Z/item/objectcomponents/shield/armorreflect4.blp b/src/Data/patch-Z/item/objectcomponents/shield/armorreflect4.blp new file mode 100644 index 0000000..c8d9d1a Binary files /dev/null and b/src/Data/patch-Z/item/objectcomponents/shield/armorreflect4.blp differ diff --git a/tool/Cargo.lock b/tool/Cargo.lock index 7d4fd0d..c93ddff 100644 --- a/tool/Cargo.lock +++ b/tool/Cargo.lock @@ -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", +] diff --git a/tool/Cargo.toml b/tool/Cargo.toml index 96c7237..15a7368 100644 --- a/tool/Cargo.toml +++ b/tool/Cargo.toml @@ -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" diff --git a/tool/src/creatures.rs b/tool/src/creatures.rs index 167c9d2..609e2bd 100644 --- a/tool/src/creatures.rs +++ b/tool/src/creatures.rs @@ -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, @@ -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), } #[derive(Debug, Default, Deserialize)] @@ -74,6 +79,8 @@ pub struct EntryOptions { pub flags: Option, pub name: Option, pub name_ru: Option, + pub race_mask: Option, + pub icon: Option, } 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, #[serde(default, skip_serializing_if = "Option::is_none")] skill_line_ability_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + spell_icon_id: Option, } #[derive(Debug)] @@ -134,10 +145,14 @@ struct PreparedEntry { summon: bool, name: String, name_ru: String, + icon_path: Option, + icon_source: Option, + icon_bytes: Option>, } pub struct GeneratedCreatures { pub assets: Vec<(PathBuf, String)>, + pub icons: Vec<(PathBuf, Vec, String)>, pub file_data_csv: Vec, pub creature_model_data: Vec, pub creature_display_info: Vec, @@ -355,7 +370,13 @@ fn next_free(start: u32, used: &HashSet) -> 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 = lock.models.values().map(|ids| ids.model_data_id).collect(); let used_displays: HashSet = 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 = 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 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 Result Result Result, Box Result, Box Result, Box Result, Box Result, Box Result, Box Result, Box Result, Box, + source_width: u32, + source_height: u32, + dimension: u32, +} + +fn target_dimension(width: u32, height: u32) -> Result> { + 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> { + 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, Box> { + 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> { + 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()); + } +} diff --git a/tool/src/main.rs b/tool/src/main.rs index d20ebe8..f4f6523 100644 --- a/tool/src/main.rs +++ b/tool/src/main.rs @@ -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 = 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> { let args: Vec = env::args().collect(); + if args.get(1).is_some_and(|arg| arg == "icon") { + if !(args.len() == 3 || args.len() == 4) { + eprintln!("Usage: tool icon [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 "); + eprintln!(" tool icon [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");