Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c905d0558e |
@@ -51,6 +51,8 @@
|
|||||||
"key": "my_mount",
|
"key": "my_mount",
|
||||||
"name": "My Mount",
|
"name": "My Mount",
|
||||||
"name_ru": "Мой транспорт",
|
"name_ru": "Мой транспорт",
|
||||||
|
"race_mask": 1101,
|
||||||
|
"icon": "my_icon.jpg",
|
||||||
"archive": "Creature/MyMount",
|
"archive": "Creature/MyMount",
|
||||||
"model_file": "my_mount.m2",
|
"model_file": "my_mount.m2",
|
||||||
"template_display_id": 2404,
|
"template_display_id": 2404,
|
||||||
@@ -67,6 +69,23 @@
|
|||||||
компаньон-кот с display ID `5448`. Шаблон задаёт физические размеры, звук и прочие поля DBC;
|
компаньон-кот с 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` и
|
Для каждого элемента `pets` сборщик автоматически выдаёт `creatureEntry`, `summonSpellID` и
|
||||||
|
|||||||
+20
-1
@@ -5,6 +5,7 @@
|
|||||||
"creature_entry_id_start": 5000000,
|
"creature_entry_id_start": 5000000,
|
||||||
"summon_spell_id_start": 110000,
|
"summon_spell_id_start": 110000,
|
||||||
"skill_line_ability_id_start": 30000,
|
"skill_line_ability_id_start": 30000,
|
||||||
|
"spell_icon_id_start": 50000,
|
||||||
"defaults": {
|
"defaults": {
|
||||||
"mount_template_display_id": 2404,
|
"mount_template_display_id": 2404,
|
||||||
"pet_template_display_id": 5448
|
"pet_template_display_id": 5448
|
||||||
@@ -15,7 +16,25 @@
|
|||||||
"folder": "src/Data/patch-Z/Creature/Catslime",
|
"folder": "src/Data/patch-Z/Creature/Catslime",
|
||||||
"name": "Cat Slime",
|
"name": "Cat Slime",
|
||||||
"name_ru": "Котослизень",
|
"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,
|
"creature_entry": 5000000,
|
||||||
"summon_spell_id": 110000,
|
"summon_spell_id": 110000,
|
||||||
"skill_line_ability_id": 30000
|
"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 kOverridesPath = "WXLSpellOverrides.tsv";
|
||||||
constexpr std::string_view kSpellPath = "DBFilesClient\\Spell.dbc";
|
constexpr std::string_view kSpellPath = "DBFilesClient\\Spell.dbc";
|
||||||
constexpr std::string_view kSkillPath = "DBFilesClient\\SkillLineAbility.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 kSpellFields = 234;
|
||||||
constexpr uint32_t kSpellRecordSize = kSpellFields * 4;
|
constexpr uint32_t kSpellRecordSize = kSpellFields * 4;
|
||||||
constexpr uint32_t kSkillFields = 14;
|
constexpr uint32_t kSkillFields = 14;
|
||||||
constexpr uint32_t kSkillRecordSize = kSkillFields * 4;
|
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
|
struct Override
|
||||||
{
|
{
|
||||||
@@ -33,6 +38,9 @@ namespace moonwell::host
|
|||||||
uint32_t templateSpellId = 0;
|
uint32_t templateSpellId = 0;
|
||||||
uint32_t creatureEntry = 0;
|
uint32_t creatureEntry = 0;
|
||||||
uint32_t skillLineAbilityId = 0;
|
uint32_t skillLineAbilityId = 0;
|
||||||
|
uint32_t raceMask = 0;
|
||||||
|
uint32_t spellIconId = 0;
|
||||||
|
std::string iconPath;
|
||||||
std::string name;
|
std::string name;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -40,6 +48,7 @@ namespace moonwell::host
|
|||||||
bool g_ready = false;
|
bool g_ready = false;
|
||||||
std::vector<uint8_t> g_spellDbc;
|
std::vector<uint8_t> g_spellDbc;
|
||||||
std::vector<uint8_t> g_skillDbc;
|
std::vector<uint8_t> g_skillDbc;
|
||||||
|
std::vector<uint8_t> g_spellIconDbc;
|
||||||
|
|
||||||
uint32_t ReadU32(const uint8_t* data)
|
uint32_t ReadU32(const uint8_t* data)
|
||||||
{
|
{
|
||||||
@@ -93,26 +102,50 @@ namespace moonwell::host
|
|||||||
lineStart = lineEnd + 1;
|
lineStart = lineEnd + 1;
|
||||||
if (line.empty() || line.front() == '#') continue;
|
if (line.empty() || line.front() == '#') continue;
|
||||||
|
|
||||||
std::string_view fields[5];
|
std::vector<std::string_view> fields;
|
||||||
size_t fieldStart = 0;
|
size_t fieldStart = 0;
|
||||||
bool valid = true;
|
while (true)
|
||||||
for (size_t i = 0; i < 4; ++i)
|
|
||||||
{
|
{
|
||||||
const size_t tab = line.find('\t', fieldStart);
|
const size_t tab = line.find('\t', fieldStart);
|
||||||
if (tab == std::string_view::npos) { valid = false; break; }
|
if (tab == std::string_view::npos)
|
||||||
fields[i] = line.substr(fieldStart, tab - fieldStart);
|
{
|
||||||
|
fields.push_back(Trim(line.substr(fieldStart)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
fields.push_back(Trim(line.substr(fieldStart, tab - fieldStart)));
|
||||||
fieldStart = tab + 1;
|
fieldStart = tab + 1;
|
||||||
}
|
}
|
||||||
if (!valid) return false;
|
|
||||||
fields[4] = Trim(line.substr(fieldStart));
|
|
||||||
|
|
||||||
Override item;
|
Override item;
|
||||||
if (!ParseU32(fields[0], item.spellId) ||
|
if (fields.size() == 5)
|
||||||
!ParseU32(fields[1], item.templateSpellId) ||
|
{
|
||||||
!ParseU32(fields[2], item.creatureEntry) ||
|
if (!ParseU32(fields[0], item.spellId) ||
|
||||||
!ParseU32(fields[3], item.skillLineAbilityId) || fields[4].empty())
|
!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;
|
return false;
|
||||||
item.name.assign(fields[4]);
|
}
|
||||||
out.push_back(std::move(item));
|
out.push_back(std::move(item));
|
||||||
}
|
}
|
||||||
return !out.empty();
|
return !out.empty();
|
||||||
@@ -170,11 +203,13 @@ namespace moonwell::host
|
|||||||
uint8_t* row = records.data() + recordStart;
|
uint8_t* row = records.data() + recordStart;
|
||||||
WriteU32(row, item.spellId);
|
WriteU32(row, item.spellId);
|
||||||
WriteU32(row + 110 * 4, item.creatureEntry);
|
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());
|
const uint32_t nameOffset = stringSize + static_cast<uint32_t>(strings.size());
|
||||||
strings.insert(strings.end(), item.name.begin(), item.name.end());
|
strings.insert(strings.end(), item.name.begin(), item.name.end());
|
||||||
strings.push_back(0);
|
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());
|
const uint32_t descriptionOffset = stringSize + static_cast<uint32_t>(strings.size());
|
||||||
strings.insert(strings.end(), description.begin(), description.end());
|
strings.insert(strings.end(), description.begin(), description.end());
|
||||||
strings.push_back(0);
|
strings.push_back(0);
|
||||||
@@ -213,6 +248,7 @@ namespace moonwell::host
|
|||||||
uint8_t* row = records.data() + recordStart;
|
uint8_t* row = records.data() + recordStart;
|
||||||
WriteU32(row, item.skillLineAbilityId);
|
WriteU32(row, item.skillLineAbilityId);
|
||||||
WriteU32(row + 8, item.spellId);
|
WriteU32(row + 8, item.spellId);
|
||||||
|
WriteU32(row + 12, item.raceMask);
|
||||||
}
|
}
|
||||||
|
|
||||||
out.reserve(base.size() + records.size());
|
out.reserve(base.size() + records.size());
|
||||||
@@ -224,19 +260,63 @@ namespace moonwell::host
|
|||||||
return true;
|
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()
|
void Load()
|
||||||
{
|
{
|
||||||
const std::string root = wxl::host::ClientRoot();
|
const std::string root = wxl::host::ClientRoot();
|
||||||
wxl::host::mpq::MpqStore store;
|
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))
|
if (root.empty() || !store.Mount(root) || !store.ReadAll(kOverridesPath, overrideBytes))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::vector<Override> overrides;
|
std::vector<Override> overrides;
|
||||||
if (!ParseOverrides(overrideBytes, overrides) ||
|
if (!ParseOverrides(overrideBytes, overrides) ||
|
||||||
!store.ReadAll(kSpellPath, spellBase) || !store.ReadAll(kSkillPath, skillBase) ||
|
!store.ReadAll(kSpellPath, spellBase) || !store.ReadAll(kSkillPath, skillBase) ||
|
||||||
|
!store.ReadAll(kSpellIconPath, spellIconBase) ||
|
||||||
!BuildSpellDbc(spellBase, overrides, g_spellDbc) ||
|
!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");
|
WLOG_ERROR("moonwell-spells: failed to build custom companion DBCs");
|
||||||
return;
|
return;
|
||||||
@@ -247,10 +327,20 @@ namespace moonwell::host
|
|||||||
|
|
||||||
bool Provide(std::string_view name, std::vector<uint8_t>& out)
|
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);
|
std::call_once(g_loadOnce, &Load);
|
||||||
if (!g_ready) return false;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -110,11 +110,19 @@ function Stop-WowRuntime {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Ensure tool exists
|
# --- Always rebuild the tool so generated DBC/SQL formats match the current sources.
|
||||||
if (!(Test-Path $TOOL)) {
|
$cargo = Get-Command cargo -ErrorAction SilentlyContinue
|
||||||
Write-Host "Building Rust tool..."
|
if (-not $cargo) {
|
||||||
Push-Location (Join-Path $ROOT "tool")
|
throw "Cargo was not found; the MPQ/DBC builder cannot be refreshed."
|
||||||
cargo build --release
|
}
|
||||||
|
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
|
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
|
# --- Build/package/deploy WarcraftXL before the dist sync. The packaged files
|
||||||
# are uploaded by upload_to_s3.py and consumed by the launcher manifest.
|
# are uploaded by upload_to_s3.py and consumed by the launcher manifest.
|
||||||
Stop-WowRuntime -ClientPath $WOW_HOME
|
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..."
|
Write-Host "Building and packaging WarcraftXL..."
|
||||||
& $WXL_BUILD_SCRIPT -Configuration Release -ClientPath $WOW_HOME `
|
& $WXL_BUILD_SCRIPT -Configuration Release -ClientPath $WOW_HOME `
|
||||||
-PackagePath $DIST_DIR -Deploy
|
-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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
|
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]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.11.0"
|
version = "2.11.0"
|
||||||
@@ -103,6 +115,12 @@ version = "3.20.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck"
|
||||||
|
version = "1.25.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@@ -170,6 +188,12 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "color_quant"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorchoice"
|
name = "colorchoice"
|
||||||
version = "1.0.5"
|
version = "1.0.5"
|
||||||
@@ -253,6 +277,12 @@ version = "0.8.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crunchy"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
@@ -313,12 +343,38 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"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]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
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]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.1.9"
|
version = "1.1.9"
|
||||||
@@ -381,6 +437,27 @@ dependencies = [
|
|||||||
"wasip3",
|
"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]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.15.5"
|
version = "0.15.5"
|
||||||
@@ -416,6 +493,39 @@ version = "2.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
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]]
|
[[package]]
|
||||||
name = "implode"
|
name = "implode"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -459,6 +569,15 @@ version = "1.0.17"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
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]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.91"
|
version = "0.3.91"
|
||||||
@@ -484,6 +603,12 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lebe"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libbz2-rs-sys"
|
name = "libbz2-rs-sys"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -558,6 +683,12 @@ version = "2.8.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minimal-lexical"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.8.9"
|
version = "0.8.9"
|
||||||
@@ -568,6 +699,16 @@ dependencies = [
|
|||||||
"simd-adler32",
|
"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]]
|
[[package]]
|
||||||
name = "num-bigint"
|
name = "num-bigint"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
@@ -594,6 +735,16 @@ dependencies = [
|
|||||||
"zeroize",
|
"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]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.46"
|
version = "0.1.46"
|
||||||
@@ -665,6 +816,12 @@ dependencies = [
|
|||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paste"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pem-rfc7468"
|
name = "pem-rfc7468"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@@ -703,7 +860,20 @@ checksum = "5a7ff566c5cb263cf6a9f3b8b8ccbeaf236c0b3516c184134c062863258b3789"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"indicatif",
|
"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]]
|
[[package]]
|
||||||
@@ -740,6 +910,38 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"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]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.45"
|
version = "1.0.45"
|
||||||
@@ -819,6 +1021,15 @@ dependencies = [
|
|||||||
"getrandom 0.3.4",
|
"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]]
|
[[package]]
|
||||||
name = "rayon"
|
name = "rayon"
|
||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
@@ -839,13 +1050,19 @@ dependencies = [
|
|||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "reborrow"
|
||||||
|
version = "0.5.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.5.18"
|
version = "0.5.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.11.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -874,7 +1091,7 @@ version = "1.1.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.11.0",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
@@ -989,6 +1206,9 @@ name = "spin"
|
|||||||
version = "0.9.8"
|
version = "0.9.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||||
|
dependencies = [
|
||||||
|
"lock_api",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spki"
|
name = "spki"
|
||||||
@@ -1036,13 +1256,43 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"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]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.18"
|
version = "2.0.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||||
dependencies = [
|
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]]
|
[[package]]
|
||||||
@@ -1056,10 +1306,23 @@ dependencies = [
|
|||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "tool"
|
name = "tool"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"image",
|
||||||
|
"image-blp",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
@@ -1209,7 +1472,7 @@ version = "0.244.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.11.0",
|
||||||
"hashbrown 0.15.5",
|
"hashbrown 0.15.5",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"semver",
|
"semver",
|
||||||
@@ -1225,6 +1488,12 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "weezl"
|
||||||
|
version = "0.1.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi-util"
|
name = "winapi-util"
|
||||||
version = "0.1.11"
|
version = "0.1.11"
|
||||||
@@ -1380,7 +1649,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags",
|
"bitflags 2.11.0",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -1437,7 +1706,7 @@ dependencies = [
|
|||||||
"rsa",
|
"rsa",
|
||||||
"sha1",
|
"sha1",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror",
|
"thiserror 2.0.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1471,3 +1740,12 @@ name = "zmij"
|
|||||||
version = "1.0.21"
|
version = "1.0.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
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"
|
wow-mpq = "0.6.4"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
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,
|
pub summon_spell_id_start: u32,
|
||||||
#[serde(default = "default_skill_line_ability_id")]
|
#[serde(default = "default_skill_line_ability_id")]
|
||||||
pub skill_line_ability_id_start: u32,
|
pub skill_line_ability_id_start: u32,
|
||||||
|
#[serde(default = "default_spell_icon_id")]
|
||||||
|
pub spell_icon_id_start: u32,
|
||||||
pub defaults: Defaults,
|
pub defaults: Defaults,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub mounts: Vec<EntrySpec>,
|
pub mounts: Vec<EntrySpec>,
|
||||||
@@ -45,6 +47,9 @@ fn default_summon_spell_id() -> u32 {
|
|||||||
fn default_skill_line_ability_id() -> u32 {
|
fn default_skill_line_ability_id() -> u32 {
|
||||||
30_000
|
30_000
|
||||||
}
|
}
|
||||||
|
fn default_spell_icon_id() -> u32 {
|
||||||
|
50_000
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Defaults {
|
pub struct Defaults {
|
||||||
@@ -56,7 +61,7 @@ pub struct Defaults {
|
|||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum EntrySpec {
|
pub enum EntrySpec {
|
||||||
Folder(String),
|
Folder(String),
|
||||||
Detailed(EntryOptions),
|
Detailed(Box<EntryOptions>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
#[derive(Debug, Default, Deserialize)]
|
||||||
@@ -74,6 +79,8 @@ pub struct EntryOptions {
|
|||||||
pub flags: Option<u32>,
|
pub flags: Option<u32>,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub name_ru: Option<String>,
|
pub name_ru: Option<String>,
|
||||||
|
pub race_mask: Option<u32>,
|
||||||
|
pub icon: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EntrySpec {
|
impl EntrySpec {
|
||||||
@@ -97,6 +104,8 @@ impl EntrySpec {
|
|||||||
flags: options.flags,
|
flags: options.flags,
|
||||||
name: options.name.clone(),
|
name: options.name.clone(),
|
||||||
name_ru: options.name_ru.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>,
|
summon_spell_id: Option<u32>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
skill_line_ability_id: Option<u32>,
|
skill_line_ability_id: Option<u32>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
spell_icon_id: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -134,10 +145,14 @@ struct PreparedEntry {
|
|||||||
summon: bool,
|
summon: bool,
|
||||||
name: String,
|
name: String,
|
||||||
name_ru: String,
|
name_ru: String,
|
||||||
|
icon_path: Option<String>,
|
||||||
|
icon_source: Option<PathBuf>,
|
||||||
|
icon_bytes: Option<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GeneratedCreatures {
|
pub struct GeneratedCreatures {
|
||||||
pub assets: Vec<(PathBuf, String)>,
|
pub assets: Vec<(PathBuf, String)>,
|
||||||
|
pub icons: Vec<(PathBuf, Vec<u8>, String)>,
|
||||||
pub file_data_csv: Vec<u8>,
|
pub file_data_csv: Vec<u8>,
|
||||||
pub creature_model_data: Vec<u8>,
|
pub creature_model_data: Vec<u8>,
|
||||||
pub creature_display_info: Vec<u8>,
|
pub creature_display_info: Vec<u8>,
|
||||||
@@ -355,7 +370,13 @@ fn next_free(start: u32, used: &HashSet<u32>) -> u32 {
|
|||||||
value
|
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_models: HashSet<u32> = lock.models.values().map(|ids| ids.model_data_id).collect();
|
||||||
let used_displays: HashSet<u32> = lock
|
let used_displays: HashSet<u32> = lock
|
||||||
.models
|
.models
|
||||||
@@ -377,12 +398,18 @@ fn allocate_ids(lock: &mut LockFile, key: &str, registry: &Registry, summon: boo
|
|||||||
.values()
|
.values()
|
||||||
.filter_map(|ids| ids.skill_line_ability_id)
|
.filter_map(|ids| ids.skill_line_ability_id)
|
||||||
.collect();
|
.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 {
|
let mut ids = lock.models.get(key).cloned().unwrap_or(LockedIds {
|
||||||
model_data_id: next_free(registry.model_data_id_start, &used_models),
|
model_data_id: next_free(registry.model_data_id_start, &used_models),
|
||||||
display_info_id: next_free(registry.display_info_id_start, &used_displays),
|
display_info_id: next_free(registry.display_info_id_start, &used_displays),
|
||||||
creature_entry: None,
|
creature_entry: None,
|
||||||
summon_spell_id: None,
|
summon_spell_id: None,
|
||||||
skill_line_ability_id: None,
|
skill_line_ability_id: None,
|
||||||
|
spell_icon_id: None,
|
||||||
});
|
});
|
||||||
if summon {
|
if summon {
|
||||||
if ids.creature_entry.is_none() {
|
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,
|
&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());
|
lock.models.insert(key.to_string(), ids.clone());
|
||||||
ids
|
ids
|
||||||
@@ -409,6 +439,34 @@ fn validate_text(value: &str, field: &str, key: &str) -> Result<(), Box<dyn Erro
|
|||||||
Ok(())
|
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(
|
fn prepare_entry(
|
||||||
project_root: &Path,
|
project_root: &Path,
|
||||||
spec: &EntrySpec,
|
spec: &EntrySpec,
|
||||||
@@ -456,7 +514,24 @@ fn prepare_entry(
|
|||||||
file_data.push((file_data_id, format!("{archive}/{file}")));
|
file_data.push((file_data_id, format!("{archive}/{file}")));
|
||||||
assets.push((source, 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 {
|
Ok(PreparedEntry {
|
||||||
key,
|
key,
|
||||||
folder,
|
folder,
|
||||||
@@ -470,6 +545,9 @@ fn prepare_entry(
|
|||||||
summon,
|
summon,
|
||||||
name,
|
name,
|
||||||
name_ru,
|
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(
|
let mut sql = String::from(
|
||||||
"-- Generated by MoonWell custom creature builder.\n\
|
"-- Generated by MoonWell custom creature builder.\n\
|
||||||
-- Apply to acore_world, then restart ac-worldserver.\n\
|
-- Apply to acore_world, then restart ac-worldserver.\n\
|
||||||
|
SET NAMES utf8mb4;\n\
|
||||||
START TRANSACTION;\n\n",
|
START TRANSACTION;\n\n",
|
||||||
);
|
);
|
||||||
for entry in entries.iter().filter(|entry| entry.summon) {
|
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
|
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!(
|
sql.push_str(&format!(
|
||||||
"-- {}\n\
|
"-- {}\n\
|
||||||
INSERT INTO creature_template\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\
|
(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,1,0,2048,12,0,1,12340)\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\
|
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\
|
INSERT INTO creature_model_info\n\
|
||||||
(DisplayID,BoundingRadius,CombatReach,Gender,DisplayID_Other_Gender,VerifiedBuild)\n\
|
(DisplayID,BoundingRadius,CombatReach,Gender,DisplayID_Other_Gender,VerifiedBuild)\n\
|
||||||
VALUES ({},0.25,0.5,2,0,12340)\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!(
|
sql.push_str(&format!(
|
||||||
"INSERT INTO spell_dbc\n\
|
"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\
|
(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\
|
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),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\
|
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\
|
INSERT INTO skilllineability_dbc\n\
|
||||||
(ID,SkillLine,Spell,RaceMask,ClassMask,ExcludeRace,ExcludeClass,MinSkillLineRank,SupercededBySpell,AcquireMethod,TrivialSkillLineRankHigh,TrivialSkillLineRankLow,CharacterPoints_1,CharacterPoints_2)\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\
|
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),MinSkillLineRank=VALUES(MinSkillLineRank);\n\n"
|
ON DUPLICATE KEY UPDATE SkillLine=VALUES(SkillLine),Spell=VALUES(Spell),RaceMask=VALUES(RaceMask),MinSkillLineRank=VALUES(MinSkillLineRank);\n\n"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
sql.push_str("COMMIT;\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_creature_entries = HashSet::new();
|
||||||
let mut active_spell_ids = HashSet::new();
|
let mut active_spell_ids = HashSet::new();
|
||||||
let mut active_skill_rows = HashSet::new();
|
let mut active_skill_rows = HashSet::new();
|
||||||
|
let mut active_spell_icon_ids = HashSet::new();
|
||||||
let mut assets = Vec::new();
|
let mut assets = Vec::new();
|
||||||
|
let mut icons = Vec::new();
|
||||||
for entry in &entries {
|
for entry in &entries {
|
||||||
if !active_model_ids.insert(entry.ids.model_data_id) {
|
if !active_model_ids.insert(entry.ids.model_data_id) {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
@@ -713,6 +800,11 @@ pub fn prepare(project_root: &Path) -> Result<Option<GeneratedCreatures>, Box<dy
|
|||||||
&mut active_skill_rows,
|
&mut active_skill_rows,
|
||||||
"skill-line row ID",
|
"skill-line row ID",
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
entry.ids.spell_icon_id,
|
||||||
|
&mut active_spell_icon_ids,
|
||||||
|
"spell icon ID",
|
||||||
|
),
|
||||||
] {
|
] {
|
||||||
if let Some(value) = value
|
if let Some(value) = value
|
||||||
&& !used.insert(value)
|
&& !used.insert(value)
|
||||||
@@ -741,16 +833,28 @@ pub fn prepare(project_root: &Path) -> Result<Option<GeneratedCreatures>, Box<dy
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
assets.extend(entry.assets.clone());
|
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");
|
let mut csv = String::from("# FileDataID,archive path\n");
|
||||||
for (id, path) in all_ids {
|
for (id, path) in all_ids {
|
||||||
csv.push_str(&format!("{id},{}\n", path.replace('/', "\\")));
|
csv.push_str(&format!("{id},{}\n", path.replace('/', "\\")));
|
||||||
}
|
}
|
||||||
let mut spell_overrides =
|
let mut spell_overrides = String::from(
|
||||||
String::from("# spellId\ttemplateSpellId\tcreatureEntry\tskillLineAbilityId\tname\n");
|
"# spellId\ttemplateSpellId\tcreatureEntry\tskillLineAbilityId\traceMask\tspellIconId\ticonPath\tname\n",
|
||||||
|
);
|
||||||
for entry in entries.iter().filter(|entry| entry.summon) {
|
for entry in entries.iter().filter(|entry| entry.summon) {
|
||||||
spell_overrides.push_str(&format!(
|
spell_overrides.push_str(&format!(
|
||||||
"{}\t70613\t{}\t{}\t{}\n",
|
"{}\t70613\t{}\t{}\t{}\t{}\t{}\t{}\n",
|
||||||
entry
|
entry
|
||||||
.ids
|
.ids
|
||||||
.summon_spell_id
|
.summon_spell_id
|
||||||
@@ -763,6 +867,9 @@ pub fn prepare(project_root: &Path) -> Result<Option<GeneratedCreatures>, Box<dy
|
|||||||
.ids
|
.ids
|
||||||
.skill_line_ability_id
|
.skill_line_ability_id
|
||||||
.ok_or("pet has no skill-line row 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
|
entry.name_ru
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -780,7 +887,7 @@ pub fn prepare(project_root: &Path) -> Result<Option<GeneratedCreatures>, Box<dy
|
|||||||
generate_server_sql(&entries)?,
|
generate_server_sql(&entries)?,
|
||||||
)?;
|
)?;
|
||||||
let mut id_map = String::from(
|
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 (kind, specs) in [("mount", ®istry.mounts), ("pet", ®istry.pets)] {
|
||||||
for spec in specs {
|
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 key = options.key.unwrap_or(default_key(&folder)?);
|
||||||
let ids = lock.models.get(&key).ok_or("lock entry disappeared")?;
|
let ids = lock.models.get(&key).ok_or("lock entry disappeared")?;
|
||||||
id_map.push_str(&format!(
|
id_map.push_str(&format!(
|
||||||
"{key},{kind},{},{},{},{},{}\n",
|
"{key},{kind},{},{},{},{},{},{}\n",
|
||||||
ids.model_data_id,
|
ids.model_data_id,
|
||||||
ids.display_info_id,
|
ids.display_info_id,
|
||||||
ids.creature_entry
|
ids.creature_entry
|
||||||
@@ -797,6 +904,8 @@ pub fn prepare(project_root: &Path) -> Result<Option<GeneratedCreatures>, Box<dy
|
|||||||
ids.summon_spell_id
|
ids.summon_spell_id
|
||||||
.map_or_else(String::new, |value| value.to_string()),
|
.map_or_else(String::new, |value| value.to_string()),
|
||||||
ids.skill_line_ability_id
|
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())
|
.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 {
|
Ok(Some(GeneratedCreatures {
|
||||||
assets,
|
assets,
|
||||||
|
icons,
|
||||||
file_data_csv: csv.into_bytes(),
|
file_data_csv: csv.into_bytes(),
|
||||||
creature_model_data: model_dbc,
|
creature_model_data: model_dbc,
|
||||||
creature_display_info: display_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};
|
use wow_mpq::{ArchiveBuilder, FormatVersion, ListfileOption};
|
||||||
|
|
||||||
mod creatures;
|
mod creatures;
|
||||||
|
mod icon;
|
||||||
use creatures::GeneratedCreatures;
|
use creatures::GeneratedCreatures;
|
||||||
|
|
||||||
/// Checks if a directory name looks like a WoW locale folder (e.g. ruRU, enUS, deDE).
|
/// 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())
|
.flat_map(|generated| generated.assets.iter())
|
||||||
.filter_map(|(source, _)| source.canonicalize().ok())
|
.filter_map(|(source, _)| source.canonicalize().ok())
|
||||||
.collect();
|
.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)
|
for entry in WalkDir::new(&base)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -55,7 +61,7 @@ fn build_mpq(
|
|||||||
.filter(|e| e.file_type().is_file())
|
.filter(|e| e.file_type().is_file())
|
||||||
{
|
{
|
||||||
let full_path = entry.path().canonicalize()?;
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
let rel_path = match full_path.strip_prefix(&base) {
|
let rel_path = match full_path.strip_prefix(&base) {
|
||||||
@@ -74,6 +80,10 @@ fn build_mpq(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(generated) = creatures {
|
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");
|
println!(" Adding generated: WXLFileData.csv");
|
||||||
builder = builder.add_file_data(generated.file_data_csv.clone(), "WXLFileData.csv");
|
builder = builder.add_file_data(generated.file_data_csv.clone(), "WXLFileData.csv");
|
||||||
println!(" Adding generated: WXLSpellOverrides.tsv");
|
println!(" Adding generated: WXLSpellOverrides.tsv");
|
||||||
@@ -122,8 +132,22 @@ fn stage_loose_assets(
|
|||||||
fn main() -> Result<(), Box<dyn Error>> {
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
let args: Vec<String> = env::args().collect();
|
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 {
|
if args.len() != 3 {
|
||||||
eprintln!("Usage: tool <src_dir> <output_dir>");
|
eprintln!("Usage: tool <src_dir> <output_dir>");
|
||||||
|
eprintln!(" tool icon <input.jpg|png|tga> [output.blp]");
|
||||||
eprintln!();
|
eprintln!();
|
||||||
eprintln!(" src_dir — root of source tree (must contain Data/)");
|
eprintln!(" src_dir — root of source tree (must contain Data/)");
|
||||||
eprintln!(" output_dir — destination root; MPQs are placed mirroring the src structure");
|
eprintln!(" output_dir — destination root; MPQs are placed mirroring the src structure");
|
||||||
|
|||||||
Reference in New Issue
Block a user