[Feature] Change #scribespells to be aware of spellgroups & ranks (#2501)

* Change #scribespells to be aware of spellgroups & ranks

* Formatting

* Fix Formatting, and change stored return  data type to match function return type.

* Compact If Statements

* Implemented SQL Query to reduce number of iterations required.

* Cleaned up Query, and improved performance

* Cleaned up SQL Queries

* Formatting

* Indenting fix.

* Update client.cpp

* Fix Formatting in spells.cpp

* Fix ValueWithin.

Co-authored-by: Kinglykrab <kinglykrab@gmail.com>
Co-authored-by: Kinglykrab <89047260+Kinglykrab@users.noreply.github.com>
This commit is contained in:
Aeadoin 2022-11-06 18:01:58 -05:00 committed by GitHub
parent e01ac39887
commit 33b95c42c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 65 additions and 15 deletions

View File

@ -10513,6 +10513,8 @@ std::vector<int> Client::GetMemmedSpells() {
std::vector<int> Client::GetScribeableSpells(uint8 min_level, uint8 max_level) {
std::vector<int> scribeable_spells;
std::unordered_map<uint32, std::vector<uint16>> spell_group_cache = LoadSpellGroupCache(min_level, max_level);
for (uint16 spell_id = 0; spell_id < SPDAT_RECORDS; ++spell_id) {
bool scribeable = true;
if (!IsValidSpell(spell_id)) {
@ -10547,13 +10549,33 @@ std::vector<int> Client::GetScribeableSpells(uint8 min_level, uint8 max_level) {
continue;
}
if (RuleB(Spells, EnableSpellGlobals) && !SpellGlobalCheck(spell_id, CharacterID())) {
if (
RuleB(Spells, EnableSpellGlobals) &&
!SpellGlobalCheck(spell_id, CharacterID())
) {
scribeable = false;
} else if (RuleB(Spells, EnableSpellBuckets) && !SpellBucketCheck(spell_id, CharacterID())) {
} else if (
RuleB(Spells, EnableSpellBuckets) &&
!SpellBucketCheck(spell_id, CharacterID())
) {
scribeable = false;
}
if (scribeable) {
if (spells[spell_id].spell_group) {
const auto& g = spell_group_cache.find(spells[spell_id].spell_group);
if (g != spell_group_cache.end()) {
for (const auto& s : g->second) {
if (
EQ::ValueWithin(spells[s].classes[m_pp.class_ - 1], min_level, max_level) &&
s == spell_id &&
scribeable
) {
scribeable_spells.push_back(spell_id);
}
continue;
}
}
} else if (scribeable) {
scribeable_spells.push_back(spell_id);
}
}
@ -10562,7 +10584,7 @@ std::vector<int> Client::GetScribeableSpells(uint8 min_level, uint8 max_level) {
std::vector<int> Client::GetScribedSpells() {
std::vector<int> scribed_spells;
for(int index = 0; index < EQ::spells::SPELLBOOK_SIZE; index++) {
for (int index = 0; index < EQ::spells::SPELLBOOK_SIZE; index++) {
if (IsValidSpell(m_pp.spell_book[index])) {
scribed_spells.push_back(m_pp.spell_book[index]);
}

View File

@ -1062,6 +1062,7 @@ public:
inline uint32 GetSpellByBookSlot(int book_slot) { return m_pp.spell_book[book_slot]; }
inline bool HasSpellScribed(int spellid) { return FindSpellBookSlotBySpellID(spellid) != -1; }
uint32 GetHighestScribedSpellinSpellGroup(uint32 spell_group);
std::unordered_map<uint32, std::vector<uint16>> LoadSpellGroupCache(uint8 min_level, uint8 max_level);
uint16 GetMaxSkillAfterSpecializationRules(EQ::skills::SkillType skillid, uint16 maxSkill);
void SendPopupToClient(const char *Title, const char *Text, uint32 PopupID = 0, uint32 Buttons = 0, uint32 Duration = 0);
void SendFullPopup(const char *Title, const char *Text, uint32 PopupID = 0, uint32 NegativeID = 0, uint32 Buttons = 0, uint32 Duration = 0, const char *ButtonName0 = 0, const char *ButtonName1 = 0, uint32 SoundControls = 0);

View File

@ -182,7 +182,7 @@ bool Mob::CastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot,
}
//Goal of Spells:UseSpellImpliedTargeting is to replicate the EQ2 feature where spells will 'pass through' invalid targets to target's target to try to find a valid target.
if (RuleB(Spells,UseSpellImpliedTargeting) && IsClient()) {
if (RuleB(Spells,UseSpellImpliedTargeting) && IsClient()) {
Mob* spell_target = entity_list.GetMobID(target_id);
if (spell_target) {
Mob* targets_target = spell_target->GetTarget();
@ -202,7 +202,7 @@ bool Mob::CastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot,
}
}
}
}
}
if (!DoCastingChecksOnCaster(spell_id, slot) ||
!DoCastingChecksZoneRestrictions(true, spell_id) ||
@ -2278,7 +2278,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, in
}
}
//Guard Assist Code
//Guard Assist Code
if (RuleB(Character, PVPEnableGuardFactionAssist) && spell_target && IsDetrimentalSpell(spell_id) && spell_target != this) {
if (IsClient() && spell_target->IsClient()|| (HasOwner() && GetOwner()->IsClient() && spell_target->IsClient())) {
auto& mob_list = entity_list.GetCloseMobList(spell_target);
@ -2591,8 +2591,8 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, in
case Beam:
{
BeamDirectional(spell_id, resist_adjust);
break;
}
break;
}
case TargetRing:
{
@ -2885,7 +2885,7 @@ int CalcBuffDuration_formula(int level, int formula, int duration)
temp = 10 * (level + 10);
break;
case 50: // Permanent. Cancelled by casting/combat for perm invis, non-lev zones for lev, curing poison/curse
// counters, etc.
// counters, etc.
return -1;
case 51: // Permanent. Cancelled when out of range of aura.
return -4;
@ -3108,7 +3108,7 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2,
continue;
if (IsBardOnlyStackEffect(effect1) && GetSpellLevel(spellid1, BARD) != 255 &&
GetSpellLevel(spellid2, BARD) != 255)
GetSpellLevel(spellid2, BARD) != 255)
continue;
// big ol' list according to the client, wasn't that nice!
@ -3541,7 +3541,7 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, int reflect_effectivenes
// other AE spells this is redundant, oh well
// 1 = PCs, 2 = NPCs
if (spells[spell_id].pcnpc_only_flag && spells[spell_id].target_type != ST_AETargetHateList &&
spells[spell_id].target_type != ST_HateList) {
spells[spell_id].target_type != ST_HateList) {
if (spells[spell_id].pcnpc_only_flag == 1 && !spelltar->IsClient() && !spelltar->IsMerc() && !spelltar->IsBot())
return false;
else if (spells[spell_id].pcnpc_only_flag == 2 && (spelltar->IsClient() || spelltar->IsMerc() || spelltar->IsBot()))
@ -5520,6 +5520,33 @@ uint32 Client::GetHighestScribedSpellinSpellGroup(uint32 spell_group)
return highest_spell_id;
}
std::unordered_map<uint32, std::vector<uint16>> Client::LoadSpellGroupCache(uint8 min_level, uint8 max_level) {
std::unordered_map<uint32, std::vector<uint16>> spell_group_cache;
const auto query = fmt::format(
"SELECT a.spellgroup, a.id, a.rank "
"FROM spells_new a "
"INNER JOIN ("
"SELECT spellgroup, MAX(rank) rank "
"FROM spells_new "
"GROUP BY spellgroup) "
"b ON a.spellgroup = b.spellgroup AND a.rank = b.rank "
"WHERE a.spellgroup IN (SELECT DISTINCT spellgroup FROM spells_new WHERE spellgroup != 0 and classes{} BETWEEN {} AND {}) ORDER BY rank DESC",
m_pp.class_, min_level, max_level
);
auto results = database.QueryDatabase(query);
if (!results.Success() || !results.RowCount()) {
return spell_group_cache;
}
for (auto row : results) {
spell_group_cache[std::stoul(row[0])].push_back(static_cast<uint16>(std::stoul(row[1])));
}
return spell_group_cache;
}
bool Client::SpellGlobalCheck(uint16 spell_id, uint32 character_id) {
std::string query = fmt::format(
"SELECT qglobal, value FROM spell_globals WHERE spellid = {}",
@ -6340,7 +6367,7 @@ void Mob::BeamDirectional(uint16 spell_id, int16 resist_adjust)
while (iter != targets_in_range.end()) {
if (!(*iter) || (beneficial_targets && ((*iter)->IsNPC() && !(*iter)->IsPetOwnerClient())) ||
(*iter)->BehindMob(this, (*iter)->GetX(), (*iter)->GetY())) {
(*iter)->BehindMob(this, (*iter)->GetX(), (*iter)->GetY())) {
++iter;
continue;
}
@ -6429,7 +6456,7 @@ void Mob::ConeDirectional(uint16 spell_id, int16 resist_adjust)
}
float heading_to_target =
(CalculateHeadingToTarget((*iter)->GetX(), (*iter)->GetY()) * 360.0f / 512.0f);
(CalculateHeadingToTarget((*iter)->GetX(), (*iter)->GetY()) * 360.0f / 512.0f);
while (heading_to_target < 0.0f)
heading_to_target += 360.0f;
@ -6473,7 +6500,7 @@ void Mob::ConeDirectional(uint16 spell_id, int16 resist_adjust)
if (angle_start > angle_end) {
if ((heading_to_target >= angle_start && heading_to_target <= 360.0f) ||
(heading_to_target >= 0.0f && heading_to_target <= angle_end)) {
(heading_to_target >= 0.0f && heading_to_target <= angle_end)) {
if (CheckLosFN((*iter)) || spells[spell_id].npc_no_los) {
(*iter)->CalcSpellPowerDistanceMod(spell_id, 0, this);
SpellOnTarget(spell_id, (*iter), 0, true, resist_adjust);