mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-11 19:43:52 +00:00
feat(pipeline): WCRA crafting recipe catalog (133rd open format)
Novel replacement for the implicit recipe expansion vanilla WoW
carried in SpellReagents.dbc + Spell.dbc effect-24 (CREATE_ITEM)
+ per-trade-skill SkillLineAbility rows. Each WCRA entry binds
one trade-skill recipe spell to its variable-length reagent
list (itemId+count pairs, vanilla cap 8, format cap 32),
produced-item id + count, the trade skill it belongs to, the
minimum skill level to cast, and the source item that teaches
the recipe.
Three presets seeded with canonical vanilla item/spell IDs:
--gen-cra-alchemy 4 potions (Minor/Lesser Healing+Mana,
Greater Healing, Major Mana) using
herb itemIds 2447/765/2450/3357/etc
and Empty Vial / Crystal Vial
--gen-cra-engineering 3 recipes including Target Dummy with
5 reagents (variable reagent count
demonstration); learnedFromItemId
references the recipe blueprint
--gen-cra-blacksmithing 3 recipes covering low/mid/high tiers
(skill 1 / 50 / 235) including Heavy
Mithril Helm with 4 different bar/ore
reagents
Validator catches: id+name+spellId+tradeSkillId+producedItemId+
producedCount required, no duplicate recipeIds, no duplicate
spellIds (cast-handler conflict — two recipes responding to the
same cast), no zero-itemId/zero-count reagents, no duplicate
reagent itemIds within a single recipe (should be merged into
single entry with summed count), no self-reagent (recipe
consuming its own produced item is a perpetual-motion bug).
Warns on requiredSkillLevel > 450 (above WotLK cap) and empty
reagent list (free-to-craft is unusual but allowed for some
alchemy transmutes).
Format count 132 -> 133. CLI flag count 1391 -> 1398.
This commit is contained in:
parent
a4ac12dbeb
commit
7df59b1d80
10 changed files with 794 additions and 0 deletions
295
src/pipeline/wowee_crafting_recipes.cpp
Normal file
295
src/pipeline/wowee_crafting_recipes.cpp
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
#include "pipeline/wowee_crafting_recipes.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
|
||||
namespace wowee {
|
||||
namespace pipeline {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char kMagic[4] = {'W', 'C', 'R', 'A'};
|
||||
constexpr uint32_t kVersion = 1;
|
||||
|
||||
template <typename T>
|
||||
void writePOD(std::ofstream& os, const T& v) {
|
||||
os.write(reinterpret_cast<const char*>(&v), sizeof(T));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool readPOD(std::ifstream& is, T& v) {
|
||||
is.read(reinterpret_cast<char*>(&v), sizeof(T));
|
||||
return is.gcount() == static_cast<std::streamsize>(sizeof(T));
|
||||
}
|
||||
|
||||
void writeStr(std::ofstream& os, const std::string& s) {
|
||||
uint32_t n = static_cast<uint32_t>(s.size());
|
||||
writePOD(os, n);
|
||||
if (n > 0) os.write(s.data(), n);
|
||||
}
|
||||
|
||||
bool readStr(std::ifstream& is, std::string& s) {
|
||||
uint32_t n = 0;
|
||||
if (!readPOD(is, n)) return false;
|
||||
if (n > (1u << 20)) return false;
|
||||
s.resize(n);
|
||||
if (n > 0) {
|
||||
is.read(s.data(), n);
|
||||
if (is.gcount() != static_cast<std::streamsize>(n)) {
|
||||
s.clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string normalizePath(std::string base) {
|
||||
if (base.size() < 5 || base.substr(base.size() - 5) != ".wcra") {
|
||||
base += ".wcra";
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const WoweeCraftingRecipes::Entry*
|
||||
WoweeCraftingRecipes::findById(uint32_t recipeId) const {
|
||||
for (const auto& e : entries)
|
||||
if (e.recipeId == recipeId) return &e;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const WoweeCraftingRecipes::Entry*
|
||||
WoweeCraftingRecipes::findBySpellId(uint32_t spellId) const {
|
||||
for (const auto& e : entries)
|
||||
if (e.spellId == spellId) return &e;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<const WoweeCraftingRecipes::Entry*>
|
||||
WoweeCraftingRecipes::findByTradeSkill(uint16_t tradeSkillId) const {
|
||||
std::vector<const Entry*> out;
|
||||
for (const auto& e : entries)
|
||||
if (e.tradeSkillId == tradeSkillId) out.push_back(&e);
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<const WoweeCraftingRecipes::Entry*>
|
||||
WoweeCraftingRecipes::findByProducedItem(uint32_t itemId) const {
|
||||
std::vector<const Entry*> out;
|
||||
for (const auto& e : entries)
|
||||
if (e.producedItemId == itemId) out.push_back(&e);
|
||||
return out;
|
||||
}
|
||||
|
||||
bool WoweeCraftingRecipesLoader::save(
|
||||
const WoweeCraftingRecipes& cat,
|
||||
const std::string& basePath) {
|
||||
std::ofstream os(normalizePath(basePath), std::ios::binary);
|
||||
if (!os) return false;
|
||||
os.write(kMagic, 4);
|
||||
writePOD(os, kVersion);
|
||||
writeStr(os, cat.name);
|
||||
uint32_t entryCount = static_cast<uint32_t>(cat.entries.size());
|
||||
writePOD(os, entryCount);
|
||||
for (const auto& e : cat.entries) {
|
||||
writePOD(os, e.recipeId);
|
||||
writePOD(os, e.spellId);
|
||||
writeStr(os, e.name);
|
||||
writePOD(os, e.tradeSkillId);
|
||||
writePOD(os, e.requiredSkillLevel);
|
||||
writePOD(os, e.producedItemId);
|
||||
writePOD(os, e.producedCount);
|
||||
writePOD(os, e.categoryId);
|
||||
writePOD(os, e.learnedFromItemId);
|
||||
uint32_t reagentCount =
|
||||
static_cast<uint32_t>(e.reagents.size());
|
||||
writePOD(os, reagentCount);
|
||||
for (const auto& r : e.reagents) {
|
||||
writePOD(os, r.itemId);
|
||||
writePOD(os, r.count);
|
||||
}
|
||||
}
|
||||
return os.good();
|
||||
}
|
||||
|
||||
WoweeCraftingRecipes WoweeCraftingRecipesLoader::load(
|
||||
const std::string& basePath) {
|
||||
WoweeCraftingRecipes out;
|
||||
std::ifstream is(normalizePath(basePath), std::ios::binary);
|
||||
if (!is) return out;
|
||||
char magic[4];
|
||||
is.read(magic, 4);
|
||||
if (std::memcmp(magic, kMagic, 4) != 0) return out;
|
||||
uint32_t version = 0;
|
||||
if (!readPOD(is, version) || version != kVersion) return out;
|
||||
if (!readStr(is, out.name)) return out;
|
||||
uint32_t entryCount = 0;
|
||||
if (!readPOD(is, entryCount)) return out;
|
||||
if (entryCount > (1u << 20)) return out;
|
||||
out.entries.resize(entryCount);
|
||||
for (auto& e : out.entries) {
|
||||
if (!readPOD(is, e.recipeId) ||
|
||||
!readPOD(is, e.spellId)) {
|
||||
out.entries.clear(); return out;
|
||||
}
|
||||
if (!readStr(is, e.name)) {
|
||||
out.entries.clear(); return out;
|
||||
}
|
||||
if (!readPOD(is, e.tradeSkillId) ||
|
||||
!readPOD(is, e.requiredSkillLevel) ||
|
||||
!readPOD(is, e.producedItemId) ||
|
||||
!readPOD(is, e.producedCount) ||
|
||||
!readPOD(is, e.categoryId) ||
|
||||
!readPOD(is, e.learnedFromItemId)) {
|
||||
out.entries.clear(); return out;
|
||||
}
|
||||
uint32_t reagentCount = 0;
|
||||
if (!readPOD(is, reagentCount)) {
|
||||
out.entries.clear(); return out;
|
||||
}
|
||||
// Sanity cap — no recipe should have more than
|
||||
// 32 reagents; vanilla cap is 8.
|
||||
if (reagentCount > 32) {
|
||||
out.entries.clear(); return out;
|
||||
}
|
||||
e.reagents.resize(reagentCount);
|
||||
for (auto& r : e.reagents) {
|
||||
if (!readPOD(is, r.itemId) ||
|
||||
!readPOD(is, r.count)) {
|
||||
out.entries.clear(); return out;
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
bool WoweeCraftingRecipesLoader::exists(
|
||||
const std::string& basePath) {
|
||||
std::ifstream is(normalizePath(basePath), std::ios::binary);
|
||||
return is.good();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Vanilla trade-skill IDs from SkillLine.dbc:
|
||||
// Alchemy=171, Blacksmithing=164,
|
||||
// Engineering=202, Enchanting=333,
|
||||
// LeatherWorking=165, Tailoring=197,
|
||||
// Cooking=185, FirstAid=129.
|
||||
constexpr uint16_t kAlchemy = 171;
|
||||
constexpr uint16_t kEngineering = 202;
|
||||
constexpr uint16_t kBlacksmithing = 164;
|
||||
|
||||
WoweeCraftingRecipes::Entry makeRecipe(
|
||||
uint32_t recipeId, uint32_t spellId, const char* name,
|
||||
uint16_t tradeSkillId, uint16_t requiredSkill,
|
||||
uint32_t producedItemId, uint16_t producedCount,
|
||||
uint16_t categoryId, uint32_t learnedFromItemId,
|
||||
std::vector<WoweeCraftingRecipes::Reagent> reagents) {
|
||||
WoweeCraftingRecipes::Entry e;
|
||||
e.recipeId = recipeId; e.spellId = spellId;
|
||||
e.name = name;
|
||||
e.tradeSkillId = tradeSkillId;
|
||||
e.requiredSkillLevel = requiredSkill;
|
||||
e.producedItemId = producedItemId;
|
||||
e.producedCount = producedCount;
|
||||
e.categoryId = categoryId;
|
||||
e.learnedFromItemId = learnedFromItemId;
|
||||
e.reagents = std::move(reagents);
|
||||
return e;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
WoweeCraftingRecipes WoweeCraftingRecipesLoader::makeAlchemyPotions(
|
||||
const std::string& catalogName) {
|
||||
using R = WoweeCraftingRecipes;
|
||||
WoweeCraftingRecipes c;
|
||||
c.name = catalogName;
|
||||
// Vanilla Alchemy potions. Reagent itemIds are
|
||||
// canonical: Peacebloom=2447, Silverleaf=765,
|
||||
// Briarthorn=2450, Mageroyal=785,
|
||||
// Bruiseweed=2453, Stranglekelp=3820,
|
||||
// Liferoot=3357, Goldthorn=3821, Khadgar's
|
||||
// Whisker=3358, Empty Vial=3371.
|
||||
c.entries.push_back(makeRecipe(
|
||||
1, 2330, "Minor Healing Potion", kAlchemy, 1,
|
||||
118, 1, 1, 0,
|
||||
{{2447, 1}, {765, 1}, {3371, 1}}));
|
||||
// Lesser Mana Potion: Mageroyal + Stranglekelp.
|
||||
c.entries.push_back(makeRecipe(
|
||||
2, 2331, "Lesser Mana Potion", kAlchemy, 100,
|
||||
3385, 1, 1, 0,
|
||||
{{785, 1}, {3820, 1}, {3371, 1}}));
|
||||
// Greater Healing Potion: Liferoot + Khadgar's
|
||||
// Whisker.
|
||||
c.entries.push_back(makeRecipe(
|
||||
3, 11457, "Greater Healing Potion", kAlchemy, 155,
|
||||
3928, 1, 1, 0,
|
||||
{{3357, 1}, {3358, 1}, {3371, 1}}));
|
||||
// Major Mana Potion: Sungrass=8838 + Blindweed
|
||||
// =8839 + Crystal Vial=8766 (uses larger vial).
|
||||
c.entries.push_back(makeRecipe(
|
||||
4, 17580, "Major Mana Potion", kAlchemy, 295,
|
||||
13444, 1, 1, 0,
|
||||
{{8838, 3}, {8839, 3}, {8766, 1}}));
|
||||
return c;
|
||||
}
|
||||
|
||||
WoweeCraftingRecipes WoweeCraftingRecipesLoader::makeEngineering(
|
||||
const std::string& catalogName) {
|
||||
using R = WoweeCraftingRecipes;
|
||||
WoweeCraftingRecipes c;
|
||||
c.name = catalogName;
|
||||
// Rough Blasting Powder: 1 Rough Stone (2835)
|
||||
// for 1 powder. Lowest-skill engineering recipe.
|
||||
c.entries.push_back(makeRecipe(
|
||||
10, 3918, "Rough Blasting Powder", kEngineering, 1,
|
||||
4357, 1, 1, 0,
|
||||
{{2835, 1}}));
|
||||
// Mechanical Squirrel Box: rough copper-bar
|
||||
// recipe — 4 reagents.
|
||||
c.entries.push_back(makeRecipe(
|
||||
11, 4413, "Mechanical Squirrel Box", kEngineering, 75,
|
||||
4401, 1, 1, 0,
|
||||
{{2840, 2}, {4399, 1}, {2589, 1}, {4357, 1}}));
|
||||
// Target Dummy: 5 reagents — demonstrates
|
||||
// variable reagent count within the recipe
|
||||
// catalog. Blueprint is itemId 4406.
|
||||
c.entries.push_back(makeRecipe(
|
||||
12, 4079, "Target Dummy", kEngineering, 75,
|
||||
2092, 1, 1, 4406,
|
||||
{{2840, 4}, {4361, 2}, {2997, 2}, {2589, 4}, {4357, 1}}));
|
||||
return c;
|
||||
}
|
||||
|
||||
WoweeCraftingRecipes WoweeCraftingRecipesLoader::makeBlacksmithing(
|
||||
const std::string& catalogName) {
|
||||
using R = WoweeCraftingRecipes;
|
||||
WoweeCraftingRecipes c;
|
||||
c.name = catalogName;
|
||||
// Rough Sharpening Stone: 1 Rough Stone -> 1
|
||||
// sharpening stone. Skill 1 (default).
|
||||
c.entries.push_back(makeRecipe(
|
||||
20, 2660, "Rough Sharpening Stone", kBlacksmithing, 1,
|
||||
2862, 1, 1, 0,
|
||||
{{2835, 1}}));
|
||||
// Coarse Grinding Stone: 2 Coarse Stone (2836).
|
||||
// Skill 50.
|
||||
c.entries.push_back(makeRecipe(
|
||||
21, 3326, "Coarse Grinding Stone", kBlacksmithing, 50,
|
||||
3486, 1, 1, 0,
|
||||
{{2836, 2}}));
|
||||
// Heavy Mithril Helm: high-skill plate piece
|
||||
// requiring multiple bar types. Skill 235.
|
||||
c.entries.push_back(makeRecipe(
|
||||
22, 9938, "Heavy Mithril Helm", kBlacksmithing, 235,
|
||||
7909, 1, 2, 11163,
|
||||
{{3860, 8}, {3859, 1}, {3864, 4}, {3866, 2}}));
|
||||
return c;
|
||||
}
|
||||
|
||||
} // namespace pipeline
|
||||
} // namespace wowee
|
||||
Loading…
Add table
Add a link
Reference in a new issue