Core/Professions: Export missing game symbols and wire crafting procs

This commit is contained in:
luis
2026-09-22 10:49:20 -03:00
parent bed3e0ff04
commit 2f83245d87
12 changed files with 704 additions and 18 deletions
+283 -8
View File
@@ -17,6 +17,7 @@
#include "Crafting.h"
#include "Containers.h"
#include "Log.h"
#include "DB2Stores.h"
#include "DB2Structure.h"
#include "Item.h"
@@ -28,7 +29,10 @@
#include "Spell.h"
#include "SpellInfo.h"
#include "SpellMgr.h"
#include "CraftingPacketsCommon.h"
#include "Random.h"
#include "SpellPackets.h"
#include "UpdateFields.h"
#include <cmath>
#include <unordered_set>
@@ -111,6 +115,47 @@ namespace
return false;
}
struct CraftingQualityMatch
{
int32 CraftingQualityID = 0;
uint32 Tier = 0;
float NextPercentage = 0.0f;
};
CraftingQualityMatch MatchCraftingQuality(int32 craftingDifficultyID, float skillPercent)
{
CraftingQualityMatch match;
float bestPercentage = -1.0f;
bool foundNext = false;
for (CraftingDifficultyQualityEntry const* row : sCraftingDifficultyQualityStore)
{
if (row->CraftingDifficultyID != craftingDifficultyID)
continue;
if (row->QualityPercentage <= skillPercent && row->QualityPercentage >= bestPercentage)
{
bestPercentage = row->QualityPercentage;
match.CraftingQualityID = row->CraftingQualityID;
if (CraftingQualityEntry const* quality = sCraftingQualityStore.LookupEntry(row->CraftingQualityID))
match.Tier = uint32(std::max(0, quality->QualityTier));
else
match.Tier = 0;
}
if (row->QualityPercentage > skillPercent && (!foundNext || row->QualityPercentage < match.NextPercentage))
{
match.NextPercentage = row->QualityPercentage;
foundNext = true;
}
}
if (!foundNext)
match.NextPercentage = 0.0f;
return match;
}
}
SpellCastResult Crafting::DoCraft(uint32 craftingDataId)
@@ -241,22 +286,188 @@ SpellCastResult Crafting::DoCraft(uint32 craftingDataId)
}
uint32 skillLevel = _player->GetSkillValue(GetSkillIdForSpell(_spell->GetSpellInfo()->Id));
uint32 reagentSkill = 0;
float baseDifficulty = float(_craftingData->CraftingDifficulty);
if (CraftingDifficultyEntry const* craftingDifficultyEntry = sCraftingDifficultyStore.LookupEntry(_craftingData->CraftingDifficultyID))
CraftingDifficultyEntry const* craftingDifficultyEntry = sCraftingDifficultyStore.LookupEntry(_craftingData->CraftingDifficultyID);
if (craftingDifficultyEntry)
{
float bonusPercent = _recraft ? craftingDifficultyEntry->ReCraftSkillBonusPercent : craftingDifficultyEntry->CraftSkillBonusPercent;
if (totalWeight > 0.0f && bonusPercent > 0.0f)
{
float maximumReagentBonus = std::round(baseDifficulty * bonusPercent / 100.0f);
skillLevel += uint32(std::max(0L, std::lround(maximumReagentBonus * weightedEffect / totalWeight)));
reagentSkill = uint32(std::max(0L, std::lround(maximumReagentBonus * weightedEffect / totalWeight)));
skillLevel += reagentSkill;
}
}
uint32 difficultyPercent = 100;
if (difficulty > 0.0f)
difficultyPercent = uint32(std::max(0L, std::lround(float(skillLevel) / difficulty * 100.0f)));
SkillLineAbilityEntry const* skillAbility = nullptr;
SkillLineAbilityMapBounds abilityBounds = sSpellMgr->GetSkillLineAbilityMapBounds(_spell->GetSpellInfo()->Id);
for (SkillLineAbilityMap::const_iterator ability = abilityBounds.first; ability != abilityBounds.second; ++ability)
{
if (!skillAbility)
skillAbility = ability->second;
if (ability->second->SkillupSkillLineID)
{
skillAbility = ability->second;
break;
}
}
uint32 qualityTier = sDB2Manager.GetCraftingQualityTierByDifficultyPercent(_craftingData->CraftingDifficultyID, difficultyPercent);
ProfessionEffectTotals traitEffects;
auto visitSkillLineChain = [](uint32 skillLineId, auto&& visitor)
{
for (uint32 step = 0; skillLineId && step < 8; ++step)
{
if (visitor(skillLineId))
return true;
SkillLineEntry const* skillLine = sSkillLineStore.LookupEntry(skillLineId);
uint32 parent = skillLine ? skillLine->ParentSkillLineID : 0;
if (!parent || parent == skillLineId)
break;
skillLineId = parent;
}
return false;
};
if (skillAbility)
{
std::unordered_set<uint32> skillLines;
auto addChain = [&](uint32 skillLineId)
{
visitSkillLineChain(skillLineId, [&](uint32 line)
{
return !skillLines.insert(line).second;
});
};
addChain(skillAbility->SkillLine);
addChain(skillAbility->SkillupSkillLineID);
auto applyTraitEntry = [&](UF::TraitEntry const& entry)
{
int32 rank = entry.Rank + entry.GrantedRanks + entry.BonusRanks;
if (rank <= 0 || entry.TraitNodeEntryID <= 0)
return;
TraitNodeEntryEntry const* nodeEntry = sTraitNodeEntryStore.LookupEntry(entry.TraitNodeEntryID);
if (!nodeEntry || nodeEntry->TraitDefinitionID <= 0)
return;
sDB2Manager.AccumulateProfessionTraitEffects(nodeEntry->TraitDefinitionID, rank, traitEffects);
};
for (auto const& [_, traitConfig] : _player->m_activePlayerData->TraitConfigs)
{
if (TraitConfigType(*traitConfig.value.Type) != TraitConfigType::Profession)
continue;
int32 configSkillLine = traitConfig.value.SkillLineID;
if (configSkillLine <= 0 || !skillLines.contains(uint32(configSkillLine)))
continue;
for (UF::TraitEntry const& entry : traitConfig.value.Entries)
applyTraitEntry(entry);
for (UF::TraitSubTreeCache const& subTree : traitConfig.value.SubTrees)
for (UF::TraitEntry const& entry : subTree.Entries)
applyTraitEntry(entry);
}
}
if (traitEffects.Skill > 0.0f)
skillLevel += uint32(std::lround(traitEffects.Skill));
difficulty += traitEffects.DifficultyDelta;
if (difficulty < 1.0f)
difficulty = 1.0f;
// Client profession UI scale shared by Dragonflight, The War Within and Midnight: 1100 rating is 100%.
// This is not a spell id.
constexpr float ProfessionRatingScale = 1100.0f;
bool inspired = false;
float critBonusSkill = 0.0f;
if (traitEffects.InspirationRating > 0.0f && roll_chance(traitEffects.InspirationRating / ProfessionRatingScale * 100.0f))
{
inspired = true;
float bonusPercent = _craftingData->InspirationSkillBonusPercent;
if (bonusPercent <= 0.0f && craftingDifficultyEntry)
bonusPercent = craftingDifficultyEntry->InspirationSkillBonusPercent;
critBonusSkill = difficulty * bonusPercent / 100.0f + traitEffects.InspirationBonusSkill;
if (critBonusSkill > 0.0f)
skillLevel += uint32(std::lround(critBonusSkill));
}
auto skillPercent = [&]()
{
return float(skillLevel) / difficulty * 100.0f;
};
uint32 concentrationCurrency = 0;
if (skillAbility)
{
auto findCurrency = [&](uint32 skillLineId)
{
return visitSkillLineChain(skillLineId, [&](uint32 line)
{
concentrationCurrency = sDB2Manager.GetConcentrationCurrencyForSkillLine(line);
return concentrationCurrency != 0;
});
};
if (!findCurrency(skillAbility->SkillLine))
findCurrency(skillAbility->SkillupSkillLineID);
}
int32 concentrationSpent = 0;
bool concentrationApplied = false;
CraftingQualityMatch quality = MatchCraftingQuality(_craftingData->CraftingDifficultyID, skillPercent());
if (_spell->m_targets.ApplyConcentration && quality.NextPercentage > skillPercent())
{
bool haveCurves = craftingDifficultyEntry
&& craftingDifficultyEntry->ConcentrationSkillCurveID > 0
&& craftingDifficultyEntry->ConcentrationDifficultyCurveID > 0;
float skillFactor = 0.0f;
float difficultyFactor = 0.0f;
if (haveCurves)
{
skillFactor = sDB2Manager.GetCurveValueAt(uint32(craftingDifficultyEntry->ConcentrationSkillCurveID), skillPercent());
difficultyFactor = sDB2Manager.GetCurveValueAt(uint32(craftingDifficultyEntry->ConcentrationDifficultyCurveID), difficulty);
}
float reduction = traitEffects.ConcentrationCostReductionPercent;
if (reduction < 0.0f)
reduction = 0.0f;
else if (reduction > 100.0f)
reduction = 100.0f;
int32 cost = 0;
if (skillFactor > 0.0f && difficultyFactor > 0.0f)
cost = int32(std::lround(skillFactor * difficultyFactor * (1.0f - reduction / 100.0f)));
if (!haveCurves || !concentrationCurrency || cost <= 0)
{
TC_LOG_DEBUG("spells", "Spell {} crafted without concentration because the profession currency or cost curve is missing.", _spell->GetSpellInfo()->Id);
}
else
{
uint32 reserved = 0;
if (auto reservedCurrency = usedCurrencies.find(concentrationCurrency); reservedCurrency != usedCurrencies.end())
reserved = reservedCurrency->second;
if (!_player->HasCurrency(concentrationCurrency, reserved + uint32(cost)))
return SpellCastResult::SPELL_FAILED_NOT_ENOUGH_CURRENCY;
skillLevel = std::max(skillLevel, uint32(std::ceil(difficulty * quality.NextPercentage / 100.0f)));
concentrationSpent = cost;
concentrationApplied = true;
quality = MatchCraftingQuality(_craftingData->CraftingDifficultyID, skillPercent());
}
}
uint32 qualityTier = quality.Tier;
uint32 craftedItemId = GetCraftedItemIdForQuality(qualityTier);
if (!craftedItemId)
return SpellCastResult::SPELL_FAILED_ERROR;
@@ -278,9 +489,46 @@ SpellCastResult Crafting::DoCraft(uint32 craftingDataId)
statsChosenByReagent = true;
}
uint32 craftCount = 1;
uint32 multicraftExtra = 0;
if (itemTemplate && itemTemplate->GetMaxStackSize() > 1 && traitEffects.MulticraftRating > 0.0f
&& roll_chance(traitEffects.MulticraftRating / ProfessionRatingScale * 100.0f))
{
float minExtra = 0.0f;
float maxExtra = 0.0f;
bool haveRange = false;
if (skillAbility)
{
auto findRange = [&](uint32 skillLineId)
{
return visitSkillLineChain(skillLineId, [&](uint32 line)
{
haveRange = sDB2Manager.GetMulticraftExtraQuantityRange(line, minExtra, maxExtra);
return haveRange;
});
};
if (!findRange(skillAbility->SkillLine))
findRange(skillAbility->SkillupSkillLineID);
}
if (haveRange)
{
maxExtra += traitEffects.MulticraftExtraPercent;
if (maxExtra < minExtra)
maxExtra = minExtra;
float rolled = frand(minExtra, maxExtra);
multicraftExtra = std::max<uint32>(1, uint32(std::lround(float(craftCount) * rolled / 100.0f)));
}
else
multicraftExtra = 1;
}
uint32 totalCount = craftCount + multicraftExtra;
ItemPosCountVec dest;
uint32 noSpace = 0;
InventoryResult msg = _player->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, craftedItemId, 1, &noSpace);
InventoryResult msg = _player->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, craftedItemId, totalCount, &noSpace);
if (msg != EQUIP_ERR_OK)
{
_player->SendEquipError(msg, nullptr, nullptr, craftedItemId);
@@ -296,13 +544,40 @@ SpellCastResult Crafting::DoCraft(uint32 craftingDataId)
if (createdItem->GetTemplate()->HasSignature())
createdItem->SetCreator(_player->GetGUID());
if (concentrationApplied)
usedCurrencies[concentrationCurrency] += uint32(concentrationSpent);
for (auto const& [itemId, itemCount] : usedItems)
_player->DestroyItemCount(itemId, itemCount, true);
for (auto const& [currencyId, currencyCount] : usedCurrencies)
_player->ModifyCurrency(currencyId, -int32(currencyCount), CurrencyGainSource::Spell, CurrencyDestroyReason::Spell);
_player->SendNewItem(createdItem, 1, true, true);
float progress = float(skillLevel) / difficulty;
if (progress < 0.0f)
progress = 0.0f;
else if (progress > 1.0f)
progress = 1.0f;
WorldPackets::Crafting::CraftingData craftingData;
craftingData.CraftingQualityID = quality.CraftingQualityID;
craftingData.QualityProgress = progress;
craftingData.SkillLineAbilityID = skillAbility ? int32(skillAbility->ID) : 0;
craftingData.CraftingDataID = int32(_craftingData->ID);
craftingData.Multicraft = int32(multicraftExtra);
craftingData.SkillFromReagents = int32(reagentSkill);
craftingData.Skill = int32(skillLevel);
craftingData.CritBonusSkill = inspired ? int32(std::lround(critBonusSkill)) : 0;
craftingData.IsCrit = inspired;
craftingData.IsRecraft = _recraft;
craftingData.ItemGUID = createdItem->GetGUID();
craftingData.Quantity = int32(totalCount);
craftingData.NewItem.Initialize(createdItem);
craftingData.ConcentrationCurrencyID = concentrationApplied ? int32(concentrationCurrency) : 0;
craftingData.ConcentrationSpent = concentrationSpent;
craftingData.ApplyConcentration = concentrationApplied;
_player->SendNewItem(createdItem, totalCount, true, true, false, 0, &craftingData);
_player->UpdateCraftSkill(_spell->GetSpellInfo());
return SpellCastResult::SPELL_CAST_OK;
}
+385
View File
@@ -38,6 +38,11 @@
#include <cctype>
#include <cmath>
#include <limits>
//WowCommunity
#include "DB2FileSystemSource.h"
#include <cstring>
#include <type_traits>
//WowCommunity
DB2Storage<AchievementEntry> sAchievementStore("Achievement.db2", &AchievementLoadInfo::Instance);
DB2Storage<Achievement_CategoryEntry> sAchievementCategoryStore("Achievement_Category.db2", &AchievementCategoryLoadInfo::Instance);
@@ -727,6 +732,322 @@ namespace
std::unordered_map<uint32, std::set<ItemSparseEntry const*>> _ItemSparseByMCRReagentItem;
std::unordered_map<uint32, std::vector<uint32>> _CraftingDataItemIDByCraftingData;
std::unordered_map<uint32, CraftingReagentQualityEntry const*> _CraftingReagentQualityByItem;
//WowCommunity
struct ProfessionEffectValue
{
int32 Type = 0;
float Amount = 0.0f;
};
std::unordered_map<uint32, std::vector<ProfessionEffectValue>> _professionEffectsByTraitDefinition;
std::unordered_map<uint32, uint32> _concentrationCurrencyBySkillLine;
std::unordered_map<uint32, std::pair<float, float>> _multicraftExtraPercentBySkillLine;
// Client files only. These tables have no hotfix statement, so a missing or
// mismatched db2 must not be added to the fatal load error list.
void LoadProfessionProcTables(std::string const& path)
{
#pragma pack(push, 1)
struct ProfessionRecord
{
uint32 ID;
int32 SkillLineID;
int32 ProfessionEnumValue;
float ResourcefulEffectLow;
float ResourcefulEffectHigh;
float MinCraftExtraQtyPct;
float MaxCraftExtraQtyPct;
float GatherTimeIncreasePct;
int32 ActionTypeEnumValue;
float RecraftModSkillGainPct;
int32 PublicOrderCapacityCurrencyID;
};
struct ProfessionEffectRecord
{
uint32 ID;
int32 ProfessionEffectTypeEnumID;
float Amount;
int32 ModifiedCraftingReagentSlotID;
};
struct ProfessionTraitRecord
{
uint32 ID;
int32 TraitDefinitionID;
};
struct ProfessionTraitXEffectRecord
{
uint32 ID;
int32 ProfessionTraitID;
int32 ProfessionEffectID;
int32 Unused;
};
struct ProfessionExpansionRecord
{
uint32 ID;
int32 SkillLineID;
int32 RecraftSpellID;
int32 ConcentrationCurrencyID;
int32 ExpansionID;
int32 SpecResetSpellID;
int32 SpecResetCurrencyID;
int32 ProfessionID;
};
#pragma pack(pop)
static_assert(sizeof(ProfessionRecord) == 44);
static_assert(sizeof(ProfessionEffectRecord) == 16);
static_assert(sizeof(ProfessionTraitRecord) == 8);
static_assert(sizeof(ProfessionTraitXEffectRecord) == 16);
static_assert(sizeof(ProfessionExpansionRecord) == 32);
// Layout hashes are the 12.1.0.69875 WoWDBDefs LAYOUT ids.
static const DB2MetaField ProfessionFields[11] =
{
{.Type = FT_INT, .ArraySize = 1, .IsSigned = false },
{.Type = FT_INT, .ArraySize = 1, .IsSigned = true },
{.Type = FT_INT, .ArraySize = 1, .IsSigned = true },
{.Type = FT_FLOAT, .ArraySize = 1, .IsSigned = false },
{.Type = FT_FLOAT, .ArraySize = 1, .IsSigned = false },
{.Type = FT_FLOAT, .ArraySize = 1, .IsSigned = false },
{.Type = FT_FLOAT, .ArraySize = 1, .IsSigned = false },
{.Type = FT_FLOAT, .ArraySize = 1, .IsSigned = false },
{.Type = FT_INT, .ArraySize = 1, .IsSigned = true },
{.Type = FT_FLOAT, .ArraySize = 1, .IsSigned = false },
{.Type = FT_INT, .ArraySize = 1, .IsSigned = true },
};
static const DB2Meta ProfessionMeta =
{
.FileDataId = 4508544,
.IndexField = 0,
.ParentIndexField = -1,
.FieldCount = 11,
.FileFieldCount = 11,
.LayoutHash = 0x86FE926D,
.Fields = ProfessionFields
};
static const DB2FieldMeta ProfessionLoadFields[11] =
{
{.IsSigned = false, .Type = FT_INT, .Name = "ID" },
{.IsSigned = true, .Type = FT_INT, .Name = "SkillLineID" },
{.IsSigned = true, .Type = FT_INT, .Name = "ProfessionEnumValue" },
{.IsSigned = false, .Type = FT_FLOAT, .Name = "ResourcefulEffectLow" },
{.IsSigned = false, .Type = FT_FLOAT, .Name = "ResourcefulEffectHigh" },
{.IsSigned = false, .Type = FT_FLOAT, .Name = "MinCraftExtraQtyPct" },
{.IsSigned = false, .Type = FT_FLOAT, .Name = "MaxCraftExtraQtyPct" },
{.IsSigned = false, .Type = FT_FLOAT, .Name = "GatherTimeIncreasePct" },
{.IsSigned = true, .Type = FT_INT, .Name = "ActionTypeEnumValue" },
{.IsSigned = false, .Type = FT_FLOAT, .Name = "RecraftModSkillGainPct" },
{.IsSigned = true, .Type = FT_INT, .Name = "PublicOrderCapacityCurrencyID" },
};
static const DB2FileLoadInfo ProfessionLoadInfo{ ProfessionLoadFields, 11, &ProfessionMeta };
static const DB2MetaField ProfessionEffectFields[3] =
{
{.Type = FT_INT, .ArraySize = 1, .IsSigned = true },
{.Type = FT_FLOAT, .ArraySize = 1, .IsSigned = false },
{.Type = FT_INT, .ArraySize = 1, .IsSigned = true },
};
static const DB2Meta ProfessionEffectMeta =
{
.FileDataId = 4505297,
.IndexField = -1,
.ParentIndexField = -1,
.FieldCount = 3,
.FileFieldCount = 3,
.LayoutHash = 0x04C16FA9,
.Fields = ProfessionEffectFields
};
static const DB2FieldMeta ProfessionEffectLoadFields[4] =
{
{.IsSigned = false, .Type = FT_INT, .Name = "ID" },
{.IsSigned = true, .Type = FT_INT, .Name = "ProfessionEffectTypeEnumID" },
{.IsSigned = false, .Type = FT_FLOAT, .Name = "Amount" },
{.IsSigned = true, .Type = FT_INT, .Name = "ModifiedCraftingReagentSlotID" },
};
static const DB2FileLoadInfo ProfessionEffectLoadInfo{ ProfessionEffectLoadFields, 4, &ProfessionEffectMeta };
static const DB2MetaField ProfessionTraitFields[2] =
{
{.Type = FT_INT, .ArraySize = 1, .IsSigned = false },
{.Type = FT_INT, .ArraySize = 1, .IsSigned = true },
};
static const DB2Meta ProfessionTraitMeta =
{
.FileDataId = 4505298,
.IndexField = 0,
.ParentIndexField = -1,
.FieldCount = 2,
.FileFieldCount = 2,
.LayoutHash = 0x306455FE,
.Fields = ProfessionTraitFields
};
static const DB2FieldMeta ProfessionTraitLoadFields[2] =
{
{.IsSigned = false, .Type = FT_INT, .Name = "ID" },
{.IsSigned = true, .Type = FT_INT, .Name = "TraitDefinitionID" },
};
static const DB2FileLoadInfo ProfessionTraitLoadInfo{ ProfessionTraitLoadFields, 2, &ProfessionTraitMeta };
static const DB2MetaField ProfessionTraitXEffectFields[4] =
{
{.Type = FT_INT, .ArraySize = 1, .IsSigned = false },
{.Type = FT_INT, .ArraySize = 1, .IsSigned = true },
{.Type = FT_INT, .ArraySize = 1, .IsSigned = true },
{.Type = FT_INT, .ArraySize = 1, .IsSigned = true },
};
static const DB2Meta ProfessionTraitXEffectMeta =
{
.FileDataId = 4505494,
.IndexField = 0,
.ParentIndexField = 1,
.FieldCount = 4,
.FileFieldCount = 4,
.LayoutHash = 0x97A0A286,
.Fields = ProfessionTraitXEffectFields
};
static const DB2FieldMeta ProfessionTraitXEffectLoadFields[4] =
{
{.IsSigned = false, .Type = FT_INT, .Name = "ID" },
{.IsSigned = true, .Type = FT_INT, .Name = "ProfessionTraitID" },
{.IsSigned = true, .Type = FT_INT, .Name = "ProfessionEffectID" },
{.IsSigned = true, .Type = FT_INT, .Name = "Field_10_0_0_44649_003" },
};
static const DB2FileLoadInfo ProfessionTraitXEffectLoadInfo{ ProfessionTraitXEffectLoadFields, 4, &ProfessionTraitXEffectMeta };
static const DB2MetaField ProfessionExpansionFields[8] =
{
{.Type = FT_INT, .ArraySize = 1, .IsSigned = false },
{.Type = FT_INT, .ArraySize = 1, .IsSigned = true },
{.Type = FT_INT, .ArraySize = 1, .IsSigned = true },
{.Type = FT_INT, .ArraySize = 1, .IsSigned = true },
{.Type = FT_INT, .ArraySize = 1, .IsSigned = true },
{.Type = FT_INT, .ArraySize = 1, .IsSigned = true },
{.Type = FT_INT, .ArraySize = 1, .IsSigned = true },
{.Type = FT_INT, .ArraySize = 1, .IsSigned = true },
};
static const DB2Meta ProfessionExpansionMeta =
{
.FileDataId = 5662322,
.IndexField = 0,
.ParentIndexField = 7,
.FieldCount = 8,
.FileFieldCount = 7,
.LayoutHash = 0x6DABCB98,
.Fields = ProfessionExpansionFields
};
static const DB2FieldMeta ProfessionExpansionLoadFields[8] =
{
{.IsSigned = false, .Type = FT_INT, .Name = "ID" },
{.IsSigned = true, .Type = FT_INT, .Name = "SkillLineID" },
{.IsSigned = true, .Type = FT_INT, .Name = "RecraftSpellID" },
{.IsSigned = true, .Type = FT_INT, .Name = "ConcentrationCurrencyID" },
{.IsSigned = true, .Type = FT_INT, .Name = "ExpansionID" },
{.IsSigned = true, .Type = FT_INT, .Name = "SpecResetSpellID" },
{.IsSigned = true, .Type = FT_INT, .Name = "SpecResetCurrencyID" },
{.IsSigned = true, .Type = FT_INT, .Name = "ProfessionID" },
};
static const DB2FileLoadInfo ProfessionExpansionLoadInfo{ ProfessionExpansionLoadFields, 8, &ProfessionExpansionMeta };
auto loadRecords = [&](char const* fileName, DB2FileLoadInfo const* loadInfo, auto& out)
{
using Record = typename std::decay_t<decltype(out)>::value_type;
try
{
DB2FileLoader loader;
DB2FileSystemSource source(path + fileName);
loader.Load(&source, loadInfo);
uint32 indexSize = 0;
char** index = nullptr;
char* data = loader.AutoProduceData(indexSize, index);
loader.AutoProduceRecordCopies(indexSize, index, data);
if (loadInfo->Meta->GetRecordSize() != sizeof(Record))
{
delete[] data;
delete[] index;
TC_LOG_ERROR("server.loading", "Profession table {} record size {} does not match C++ size {}", fileName, loadInfo->Meta->GetRecordSize(), sizeof(Record));
return;
}
for (uint32 i = 0; i < indexSize; ++i)
{
if (!index[i])
continue;
Record record;
std::memcpy(&record, index[i], sizeof(Record));
out.push_back(record);
}
delete[] data;
delete[] index;
TC_LOG_INFO("server.loading", ">> Loaded {} rows from {}", out.size(), fileName);
}
catch (std::exception const& error)
{
TC_LOG_ERROR("server.loading", "Profession table {} was not loaded ({}). Concentration, inspiration and multicraft stay off for data from this file.", fileName, error.what());
}
};
std::vector<ProfessionRecord> professions;
std::vector<ProfessionEffectRecord> effects;
std::vector<ProfessionTraitRecord> traits;
std::vector<ProfessionTraitXEffectRecord> links;
std::vector<ProfessionExpansionRecord> expansions;
loadRecords("Profession.db2", &ProfessionLoadInfo, professions);
loadRecords("ProfessionEffect.db2", &ProfessionEffectLoadInfo, effects);
loadRecords("ProfessionTrait.db2", &ProfessionTraitLoadInfo, traits);
loadRecords("ProfessionTraitXEffect.db2", &ProfessionTraitXEffectLoadInfo, links);
loadRecords("ProfessionExpansion.db2", &ProfessionExpansionLoadInfo, expansions);
if (professions.empty() || effects.empty() || traits.empty() || links.empty() || expansions.empty())
{
TC_LOG_ERROR("server.loading", "Profession proc tables are incomplete. Concentration, inspiration and multicraft stay disabled.");
return;
}
std::unordered_map<uint32, ProfessionEffectValue> effectsById;
for (ProfessionEffectRecord const& effect : effects)
effectsById[effect.ID] = { effect.ProfessionEffectTypeEnumID, effect.Amount };
std::unordered_map<uint32, uint32> traitDefinitionById;
for (ProfessionTraitRecord const& trait : traits)
if (trait.TraitDefinitionID > 0)
traitDefinitionById[trait.ID] = uint32(trait.TraitDefinitionID);
for (ProfessionTraitXEffectRecord const& link : links)
{
if (link.ProfessionTraitID <= 0 || link.ProfessionEffectID <= 0)
continue;
auto trait = traitDefinitionById.find(uint32(link.ProfessionTraitID));
auto effect = effectsById.find(uint32(link.ProfessionEffectID));
if (trait == traitDefinitionById.end() || effect == effectsById.end())
continue;
_professionEffectsByTraitDefinition[trait->second].push_back(effect->second);
}
for (ProfessionRecord const& profession : professions)
{
if (profession.SkillLineID <= 0)
continue;
if (profession.MinCraftExtraQtyPct > 0.0f || profession.MaxCraftExtraQtyPct > 0.0f)
_multicraftExtraPercentBySkillLine[uint32(profession.SkillLineID)] = { profession.MinCraftExtraQtyPct, profession.MaxCraftExtraQtyPct };
}
for (ProfessionExpansionRecord const& expansion : expansions)
if (expansion.SkillLineID > 0 && expansion.ConcentrationCurrencyID > 0)
_concentrationCurrencyBySkillLine[uint32(expansion.SkillLineID)] = uint32(expansion.ConcentrationCurrencyID);
}
//WowCommunity
}
static void LoadDB2(std::bitset<TOTAL_LOCALES>& availableDb2Locales, std::vector<std::string>& errlist, StorageMap& stores, DB2StorageBase* storage, std::string const& db2Path,
@@ -1378,6 +1699,7 @@ uint32 DB2Manager::LoadStores(std::string const& dataPath, LocaleConstant defaul
LOAD_DB2(sItemLogicalCostStore);
LOAD_DB2(sTrophyStore);
LOAD_DB2(sManagedWorldStateBuffStore);
LoadProfessionProcTables(db2Path + localeNames[defaultLocale] + '/');
//WowCommunity
// error checks
@@ -3971,6 +4293,69 @@ uint32 DB2Manager::GetCraftingQualityTierByDifficultyPercent(int32 craftingDiffi
return 0;
}
void DB2Manager::AccumulateProfessionTraitEffects(int32 traitDefinitionId, int32 rank, ProfessionEffectTotals& totals) const
{
if (traitDefinitionId <= 0 || rank <= 0)
return;
auto itr = _professionEffectsByTraitDefinition.find(uint32(traitDefinitionId));
if (itr == _professionEffectsByTraitDefinition.end())
return;
// ProfessionEffectType.EnumID, build 12.1.0.69875. Amount is per rank.
for (ProfessionEffectValue const& effect : itr->second)
{
float value = effect.Amount * float(rank);
switch (effect.Type)
{
case 0: // Skill
totals.Skill += value;
break;
case 1: // Inspiration rating
totals.InspirationRating += value;
break;
case 7: // Multicraft rating
totals.MulticraftRating += value;
break;
case 18: // additional items crafted with Multicraft, percent
totals.MulticraftExtraPercent += value;
break;
case 20: // bonus Crafting Skill from Inspiration
totals.InspirationBonusSkill += value;
break;
case 22: // Decrease Difficulty
totals.DifficultyDelta -= value;
break;
case 23: // Increase Difficulty
totals.DifficultyDelta += value;
break;
case 27: // reduce concentration cost, percent
totals.ConcentrationCostReductionPercent += value;
break;
default:
break;
}
}
}
uint32 DB2Manager::GetConcentrationCurrencyForSkillLine(uint32 skillLineId) const
{
auto itr = _concentrationCurrencyBySkillLine.find(skillLineId);
return itr != _concentrationCurrencyBySkillLine.end() ? itr->second : 0;
}
bool DB2Manager::GetMulticraftExtraQuantityRange(uint32 skillLineId, float& minPercent, float& maxPercent) const
{
auto itr = _multicraftExtraPercentBySkillLine.find(skillLineId);
if (itr == _multicraftExtraPercentBySkillLine.end())
return false;
minPercent = itr->second.first;
maxPercent = itr->second.second;
return true;
}
uint32 DB2Manager::GetConduitForItem(uint32 itemId) const
{
auto itr = _conduitsByItem.find(itemId);
+17
View File
@@ -541,6 +541,20 @@ TC_GAME_API extern TaxiPathNodesByPath sTaxiPathNod
static bool Compare(structure const* left, structure const* right); \
};
// Sum of ProfessionEffect rows reached through the player's profession trait config.
// Amounts are already multiplied by rank. Effect types are ProfessionEffectType.EnumID.
struct ProfessionEffectTotals
{
float Skill = 0.0f;
float InspirationRating = 0.0f;
float MulticraftRating = 0.0f;
float MulticraftExtraPercent = 0.0f;
float InspirationBonusSkill = 0.0f;
float ConcentrationCostReductionPercent = 0.0f;
float DifficultyDelta = 0.0f;
};
class TC_GAME_API DB2Manager
{
public:
@@ -737,6 +751,9 @@ public:
std::vector<uint32> GetCraftingDataItemIDByCraftingData(uint32 craftingDataID);
CraftingReagentQualityEntry const* GetCraftingReagentQualityByItem(uint32 itemID);
uint32 GetCraftingQualityTierByDifficultyPercent(int32 craftingDifficultyID, uint32 difficultyPercent);
void AccumulateProfessionTraitEffects(int32 traitDefinitionId, int32 rank, ProfessionEffectTotals& totals) const;
uint32 GetConcentrationCurrencyForSkillLine(uint32 skillLineId) const;
bool GetMulticraftExtraQuantityRange(uint32 skillLineId, float& minPercent, float& maxPercent) const;
std::vector<RenownRewardsEntry const*> const* GetRenownRewards(int32 covenantId, int32 level) const;
std::vector<BountyEntry const*> const* GetBountiesForBountySet(int32 bountySetId) const;
@@ -21,7 +21,7 @@
#include "ObjectGuid.h"
#include <atomic>
class ObjectGuidGenerator
class TC_GAME_API ObjectGuidGenerator
{
public:
explicit ObjectGuidGenerator(HighGuid high, ObjectGuid::LowType start = UI64LIT(1)) : _nextGuid(start), _high(high) { }
+3 -1
View File
@@ -14021,7 +14021,7 @@ void Player::SendItemPassives()
SendDirectMessage(sendItemPassives.Write());
}
void Player::SendNewItem(Item* item, uint32 quantity, bool pushed, bool created, bool broadcast /*= false*/, uint32 dungeonEncounterId /*= 0*/)
void Player::SendNewItem(Item* item, uint32 quantity, bool pushed, bool created, bool broadcast /*= false*/, uint32 dungeonEncounterId /*= 0*/, WorldPackets::Crafting::CraftingData const* craftingData /*= nullptr*/)
{
if (!item) // prevent crash
return;
@@ -14047,6 +14047,8 @@ void Player::SendNewItem(Item* item, uint32 quantity, bool pushed, bool created,
packet.BattlePetLevel = item->GetModifier(ITEM_MODIFIER_BATTLE_PET_LEVEL);
packet.ItemGUID = item->GetGUID();
if (craftingData)
packet.CraftingData = *craftingData;
packet.Pushed = pushed;
packet.ChatNotifyType = WorldPackets::Item::ItemPushResult::DISPLAY_TYPE_NORMAL;
+1 -1
View File
@@ -1724,7 +1724,7 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player>
bool IsUseEquipedWeapon(bool mainhand) const;
bool IsTwoHandUsed() const;
bool IsUsingTwoHandedWeaponInOneHand() const;
void SendNewItem(Item* item, uint32 quantity, bool pushed, bool created, bool broadcast = false, uint32 dungeonEncounterId = 0);
void SendNewItem(Item* item, uint32 quantity, bool pushed, bool created, bool broadcast = false, uint32 dungeonEncounterId = 0, WorldPackets::Crafting::CraftingData const* craftingData = nullptr);
bool BuyItemFromVendorSlot(ObjectGuid vendorguid, uint32 vendorslot, uint32 item, uint32 count, uint8 bag, uint8 slot);
Optional<SellResult> CanSellItemToVendor(Item const* item, uint32 amount) const;
Optional<SellResult> SellItemToVendor(Item* item, uint32 amount);
+2
View File
@@ -153,6 +153,8 @@ SpellCastTargets::SpellCastTargets(Unit* caster, WorldPackets::Spells::SpellCast
ModifiedCraftingReagents[uint32(reagent.Slot)].push_back(reagent);
}
ApplyConcentration = (spellCastRequest.CraftingCastFlags & 1) != 0;
Update(caster);
}
+1
View File
@@ -452,6 +452,7 @@ public:
//WowCommunity
std::unordered_map<uint32 /*SpellSlot*/, std::vector<WorldPackets::Spells::SpellCraftingReagent>> ModifiedCraftingReagents;
bool ApplyConcentration = false; // SpellCastRequest.CraftingCastFlags bit 0 (value 1)
//WowCommunity
private:
@@ -91,7 +91,7 @@ struct boss_anubzekt : public BossAI
me->GetRandomNearPosition(10.0f), TEMPSUMMON_CORPSE_TIMED_DESPAWN, 5s))
{
DoCastSelf(SPELL_SUMMON_WEBMAGE, true);
webmage->SetInCombatWithZone();
webmage->AI()->DoZoneInCombat();
}
events.Repeat(IsMythicPlus() ? 30s : 45s);
break;
@@ -187,12 +187,12 @@ struct npc_starved_crawler : public ScriptedAI
AttackStart(target);
}
void UpdateAI(uint32 diff) override
void UpdateAI(uint32 /*diff*/) override
{
if (!UpdateVictim())
return;
DoMeleeAttackIfReady();
me->DoMeleeAttackIfReady();
}
};
@@ -39,6 +39,11 @@ enum Misc
SINGULARITY_ENERGY = 100
};
enum BloodworkerEvents
{
EVENT_BLACK_BLOOD = 1
};
struct boss_kikatal : public BossAI
{
boss_kikatal(Creature* creature) : BossAI(creature, DATA_KIKATAL_THE_HARVESTER) { }
@@ -76,7 +81,7 @@ struct boss_kikatal : public BossAI
if (Creature* bloodworker = me->SummonCreature(NPC_BLOODWORKER, pos, TEMPSUMMON_CORPSE_TIMED_DESPAWN, IsMythicPlus() ? 30s : 70s))
{
DoCastSelf(SPELL_CALL_DRONES, true);
bloodworker->SetInCombatWithZone();
bloodworker->AI()->DoZoneInCombat();
}
events.Repeat(IsMythicPlus() ? 20s : 35s);
break;
@@ -14,7 +14,6 @@ static constexpr ObjectData creatureData[] =
{ BOSS_AVANOXX, DATA_AVANOXX },
{ BOSS_ANUBZEKT, DATA_ANUBZEKT },
{ BOSS_KIKATAL_THE_HARVESTER, DATA_KIKATAL_THE_HARVESTER },
{ 0, 0 } // END
};
static constexpr DungeonEncounterData const encounters[] =
@@ -35,7 +34,7 @@ public:
{
SetHeaders(DataHeader);
SetBossNumber(EncounterCount);
LoadObjectData(creatureData, nullptr);
LoadObjectData(creatureData, {});
LoadDungeonEncounterData(encounters);
}
@@ -69,7 +68,7 @@ public:
private:
bool faction_group = HORDE;
Team faction_group = HORDE;
};
InstanceScript* GetInstanceScript(InstanceMap* map) const override