[Spells/Disciplines] Bulk Train / Scribe (#1640)

* Bulk scribe spells

* Add bulk disc training

* Remove bulk from non bulk method

* PR adjustments
This commit is contained in:
Chris Miles 2021-10-27 20:45:27 -05:00 committed by GitHub
parent 6e5bf4b941
commit 7230714cbc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 157 additions and 75 deletions

View File

@ -63,6 +63,10 @@ extern volatile bool RunLoops;
#include "../common/expedition_lockout_timer.h"
#include "cheat_manager.h"
#include "../common/repositories/character_spells_repository.h"
#include "../common/repositories/character_disciplines_repository.h"
extern QueryServ* QServ;
extern EntityList entity_list;
extern Zone* zone;
@ -5587,7 +5591,7 @@ void Client::UpdateLDoNWinLoss(uint32 theme_id, bool win, bool remove) {
break;
default:
return;
}
}
database.UpdateAdventureStatsEntry(CharacterID(), theme_id, win, remove);
}
@ -10550,7 +10554,7 @@ void Client::ReadBookByName(std::string book_name, uint8 book_type)
out->window = 0xFF;
out->type = book_type;
out->invslot = 0;
memcpy(out->booktext, book_text.c_str(), length);
if (book_language > 0 && book_language < MAX_PP_LANGUAGE) {
@ -10673,3 +10677,45 @@ void Client::SummonBaggedItems(uint32 bag_item_id, const std::vector<ServerLootI
SendItemPacket(EQ::invslot::slotCursor, summoned_bag, ItemPacketLimbo);
safe_delete(summoned_bag);
}
void Client::SaveSpells()
{
std::vector<CharacterSpellsRepository::CharacterSpells> character_spells = {};
for (int index = 0; index < EQ::spells::SPELLBOOK_SIZE; index++) {
if (IsValidSpell(m_pp.spell_book[index])) {
auto spell = CharacterSpellsRepository::NewEntity();
spell.id = CharacterID();
spell.slot_id = index;
spell.spell_id = m_pp.spell_book[index];
character_spells.emplace_back(spell);
}
}
CharacterSpellsRepository::DeleteWhere(database, fmt::format("id = {}", CharacterID()));
if (!character_spells.empty()) {
CharacterSpellsRepository::InsertMany(database, character_spells);
}
}
void Client::SaveDisciplines()
{
std::vector<CharacterDisciplinesRepository::CharacterDisciplines> character_discs = {};
for (int index = 0; index < MAX_PP_DISCIPLINES; index++) {
if (IsValidSpell(m_pp.disciplines.values[index])) {
auto discipline = CharacterDisciplinesRepository::NewEntity();
discipline.id = CharacterID();
discipline.slot_id = index;
discipline.disc_id = m_pp.disciplines.values[index];
character_discs.emplace_back(discipline);
}
}
CharacterDisciplinesRepository::DeleteWhere(database, fmt::format("id = {}", CharacterID()));
if (!character_discs.empty()) {
CharacterDisciplinesRepository::InsertMany(database, character_discs);
}
}

View File

@ -799,10 +799,15 @@ public:
std::vector<int> GetMemmedSpells();
std::vector<int> GetScribeableSpells(uint8 min_level = 1, uint8 max_level = 0);
std::vector<int> GetScribedSpells();
void ScribeSpell(uint16 spell_id, int slot, bool update_client = true);
void UnscribeSpell(int slot, bool update_client = true);
// defer save used when bulk saving
void ScribeSpell(uint16 spell_id, int slot, bool update_client = true, bool defer_save = false);
void SaveSpells();
void SaveDisciplines();
// defer save used when bulk saving
void UnscribeSpell(int slot, bool update_client = true, bool defer_save = false);
void UnscribeSpellAll(bool update_client = true);
void UntrainDisc(int slot, bool update_client = true);
void UntrainDisc(int slot, bool update_client = true, bool defer_save = false);
void UntrainDiscAll(bool update_client = true);
void UntrainDiscBySpellID(uint16 spell_id, bool update_client = true);
bool SpellGlobalCheck(uint16 spell_id, uint32 char_id);

View File

@ -2048,7 +2048,7 @@ void Client::Handle_OP_AdventureMerchantPurchase(const EQApplicationPacket *app)
}
}
}
} else if (aps->Type == DiscordMerchant) {
if (GetPVPPoints() < item_cost) {
@ -14791,8 +14791,8 @@ void Client::Handle_OP_Translocate(const EQApplicationPacket *app)
zone->GetZoneID() == PendingTranslocateData.zone_id &&
zone->GetInstanceID() == PendingTranslocateData.instance_id
);
if (parse->EventSpell(EVENT_SPELL_EFFECT_TRANSLOCATE_COMPLETE, nullptr, this, spell_id, "", 0) == 0) {
if (parse->EventSpell(EVENT_SPELL_EFFECT_TRANSLOCATE_COMPLETE, nullptr, this, spell_id, "", 0) == 0) {
// If the spell has a translocate to bind effect, AND we are already in the zone the client
// is bound in, use the GoToBind method. If we send OP_Translocate in this case, the client moves itself
// to the bind coords it has from the PlayerProfile, but with the X and Y reversed. I suspect they are

View File

@ -7928,7 +7928,7 @@ void command_scribespells(Client *c, const Seperator *sep)
}
if (!IsDiscipline(spell_id_) && !t->HasSpellScribed(spell_id)) { // isn't a discipline & we don't already have it scribed
t->ScribeSpell(spell_id_, book_slot);
t->ScribeSpell(spell_id_, book_slot, true, true);
++count;
}
@ -7939,14 +7939,18 @@ void command_scribespells(Client *c, const Seperator *sep)
}
if (count > 0) {
t->Message(Chat::White, "Successfully scribed %i spells.", count);
if (t != c)
c->Message(Chat::White, "Successfully scribed %i spells for %s.", count, t->GetName());
t->Message(Chat::White, "Successfully scribed %i spells.", count);
if (t != c) {
c->Message(Chat::White, "Successfully scribed %i spells for %s.", count, t->GetName());
}
t->SaveSpells();
}
else {
t->Message(Chat::White, "No spells scribed.");
if (t != c)
c->Message(Chat::White, "No spells scribed for %s.", t->GetName());
if (t != c) {
c->Message(Chat::White, "No spells scribed for %s.", t->GetName());
}
}
}
@ -8058,6 +8062,7 @@ void command_untraindiscs(Client *c, const Seperator *sep) {
t = c->GetTarget()->CastToClient();
t->UntrainDiscAll();
t->Message(Chat::Yellow, "All disciplines removed.");
}
void command_wpinfo(Client *c, const Seperator *sep)
@ -9275,7 +9280,7 @@ void command_npcedit(Client *c, const Seperator *sep)
}
if (strcasecmp(sep->arg[1], "gender") == 0) {
auto gender_id = atoi(sep->arg[2]);
auto gender_id = atoi(sep->arg[2]);
c->Message(Chat::Yellow, fmt::format("NPC ID {} is now a {} ({}).", npc_id, gender_id, GetGenderName(gender_id)).c_str());
std::string query = fmt::format("UPDATE npc_types SET gender = {} WHERE id = {}", gender_id, npc_id);
content_db.QueryDatabase(query);
@ -9461,7 +9466,7 @@ void command_npcedit(Client *c, const Seperator *sep)
std::string query = fmt::format("UPDATE npc_types SET ammo_idfile = {} WHERE id = {}", atoi(sep->arg[2]), npc_id);
content_db.QueryDatabase(query);
return;
}
}
if (strcasecmp(sep->arg[1], "weapon") == 0) {
c->Message(Chat::Yellow, fmt::format("NPC ID {} will have Model {} set to their Primary and Model {} set to their Secondary on repop.", npc_id, atoi(sep->arg[2]), atoi(sep->arg[3])).c_str());
@ -9679,7 +9684,7 @@ void command_npcedit(Client *c, const Seperator *sep)
content_db.QueryDatabase(query);
return;
}
if (strcasecmp(sep->arg[1], "accuracy") == 0) {
c->Message(Chat::Yellow, fmt::format("NPC ID {} now has {} Accuracy.", npc_id, atoi(sep->arg[2])).c_str());
std::string query = fmt::format("UPDATE npc_types SET accuracy = {} WHERE id = {}", atoi(sep->arg[2]), npc_id);
@ -9749,7 +9754,7 @@ void command_npcedit(Client *c, const Seperator *sep)
content_db.QueryDatabase(query);
return;
}
if (strcasecmp(sep->arg[1], "armtexture") == 0) {
c->Message(Chat::Yellow, fmt::format("NPC ID {} is now using Arm Texture {}.", npc_id, atoi(sep->arg[2])).c_str());
std::string query = fmt::format("UPDATE npc_types SET armtexture = {} WHERE id = {}", atoi(sep->arg[2]), npc_id);
@ -9934,7 +9939,7 @@ void command_npcedit(Client *c, const Seperator *sep)
animation = 1;
animation_name = "Sitting";
} else if(strcasecmp(sep->arg[2], "crouch") == 0 || atoi(sep->arg[2]) == 2) { // Crouch
animation = 2;
animation = 2;
animation_name = "Crouching";
} else if(strcasecmp(sep->arg[2], "dead") == 0 || atoi(sep->arg[2]) == 3) { // Dead
animation = 3;
@ -10999,7 +11004,6 @@ void command_traindisc(Client *c, const Seperator *sep)
}
else if (t->GetPP().disciplines.values[r] == 0) {
t->GetPP().disciplines.values[r] = spell_id_;
database.SaveCharacterDisc(t->CharacterID(), r, spell_id_);
change = true;
t->Message(Chat::White, "You have learned a new discipline!");
++count; // success counter
@ -11011,17 +11015,22 @@ void command_traindisc(Client *c, const Seperator *sep)
}
}
if (change)
if (change) {
t->SendDisciplineUpdate();
t->SaveDisciplines();
}
if (count > 0) {
t->Message(Chat::White, "Successfully trained %u disciplines.", count);
if (t != c)
c->Message(Chat::White, "Successfully trained %u disciplines for %s.", count, t->GetName());
} else {
t->Message(Chat::White, "Successfully trained %u disciplines.", count);
if (t != c) {
c->Message(Chat::White, "Successfully trained %u disciplines for %s.", count, t->GetName());
}
}
else {
t->Message(Chat::White, "No disciplines trained.");
if (t != c)
c->Message(Chat::White, "No disciplines trained for %s.", t->GetName());
if (t != c) {
c->Message(Chat::White, "No disciplines trained for %s.", t->GetName());
}
}
}
@ -14880,7 +14889,7 @@ void command_dye(Client *c, const Seperator *sep)
c->Message(Chat::White, "Command Syntax: #dye help | #dye [slot] [red] [green] [blue] [use_tint]");
return;
}
uint8 slot = 0;
uint8 red = 255;
uint8 green = 255;
@ -14902,7 +14911,7 @@ void command_dye(Client *c, const Seperator *sep)
std::vector<std::string> slot_messages;
c->Message(Chat::White, "Command Syntax: #dye help | #dye [slot] [red] [green] [blue] [use_tint]");
c->Message(Chat::White, "Red, Green, and Blue go from 0 to 255.");
for (const auto& slot : dye_slots) {
slot_messages.push_back(fmt::format("({}) {}", slot_id, slot));
slot_id++;

View File

@ -180,7 +180,7 @@ int32 Mob::GetActReflectedSpellDamage(int32 spell_id, int32 value, int effective
value = int(static_cast<float>(value) * CastToNPC()->GetSpellScale() / 100.0f);
}
}
int32 base_spell_dmg = value;
value = value * effectiveness / 100;
@ -343,7 +343,7 @@ int32 Mob::GetActSpellHealing(uint16 spell_id, int32 value, Mob* target) {
}
value += GetFocusEffect(focusFcHealAmtCrit, spell_id); //SPA 396 Add before critical
if (!spells[spell_id].no_heal_damage_item_mod && itembonuses.HealAmt && spells[spell_id].classes[(GetClass() % 17) - 1] >= GetLevel() - 5) {
value += GetExtraSpellAmt(spell_id, itembonuses.HealAmt, base_value); //Item Heal Amt Add before critical
}
@ -351,7 +351,7 @@ int32 Mob::GetActSpellHealing(uint16 spell_id, int32 value, Mob* target) {
if (target) {
value += value * target->GetHealRate() / 100; //SPA 120 modifies value after Focus Applied but before critical
}
/*
Apply critical hit modifier
*/
@ -363,7 +363,7 @@ int32 Mob::GetActSpellHealing(uint16 spell_id, int32 value, Mob* target) {
if (target) {
value += target->GetFocusEffect(focusFcHealAmtIncoming, spell_id); //SPA 394 Add after critical
}
if (IsNPC() && CastToNPC()->GetHealScale()) {
value = int(static_cast<float>(value) * CastToNPC()->GetHealScale() / 100.0f);
}
@ -619,13 +619,14 @@ bool Client::MemorizeSpellFromItem(uint32 item_id) {
return false;
}
for(int index = 0; index < EQ::spells::SPELLBOOK_SIZE; index++) {
for (int index = 0; index < EQ::spells::SPELLBOOK_SIZE; index++) {
if (!HasSpellScribed(spell_id)) {
auto next_slot = GetNextAvailableSpellBookSlot();
if (next_slot != -1) {
ScribeSpell(spell_id, next_slot);
return true;
} else {
}
else {
Message(
Chat::Red,
"Unable to scribe spell %s (%i) to spellbook: no more spell book slots available.",
@ -635,12 +636,14 @@ bool Client::MemorizeSpellFromItem(uint32 item_id) {
SummonItem(item_id);
return false;
}
} else {
}
else {
Message(Chat::Red, "You already know this spell.");
SummonItem(item_id);
return false;
}
}
Message(Chat::Red, "You have learned too many spells and can learn no more.");
return false;
}

View File

@ -1130,7 +1130,8 @@ uint16 QuestManager::scribespells(uint8 max_level, uint8 min_level) {
if (initiator->HasSpellScribed(spell_id))
continue;
initiator->ScribeSpell(spell_id, book_slot);
// defer saving per spell and bulk save at the end
initiator->ScribeSpell(spell_id, book_slot, true, true);
book_slot = initiator->GetNextAvailableSpellBookSlot(book_slot);
spells_learned++;
}
@ -1139,6 +1140,9 @@ uint16 QuestManager::scribespells(uint8 max_level, uint8 min_level) {
if (spells_learned > 0) {
std::string spell_message = (spells_learned == 1 ? "a new spell" : fmt::format("{} new spells", spells_learned));
initiator->Message(Chat::White, fmt::format("You have learned {}!", spell_message).c_str());
// bulk insert spells
initiator->SaveSpells();
}
return spells_learned;
}

View File

@ -445,7 +445,7 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot,
) {
cast_failed = false;
}
} else if (spell_target->IsRaidGrouped()) {
} else if (spell_target->IsRaidGrouped()) {
Raid *target_raid = spell_target->GetRaid();
Raid *my_raid = GetRaid();
if (
@ -3874,15 +3874,15 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, int reflect_effectivenes
}
/*
Reflect
base= % Chance to Reflect
Limit= Resist Modifier (+Value for decrease chance to resist)
base= % Chance to Reflect
Limit= Resist Modifier (+Value for decrease chance to resist)
Max= % of base spell damage (this is the base before any formula or focus is applied)
On live any type of detrimental spell can be reflected as long as the Reflectable spell field is set, this includes AOE.
The 'caster' of the reflected spell is owner of the reflect effect. Caster's focus effects are NOT applied to reflected spell.
reflect_effectiveness is applied to damage spells, a value of 100 is no change to base damage. Other values change by percent. (50=50% of damage)
we this variable to both check if a spell being applied is from a reflection and for the damage modifier.
There are a few spells in database that are not detrimental that have Reflectable field set, however from testing, they do not actually reflect.
*/
if(spells[spell_id].reflectable && !reflect_effectiveness && spelltar && this != spelltar && IsDetrimentalSpell(spell_id) &&
@ -4059,7 +4059,7 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, int reflect_effectivenes
}
}
}
entity_list.AddHealAggro(
spelltar, this,
CheckHealAggroAmount(spell_id, spelltar, (spelltar->GetMaxHP() - spelltar->GetHP())));
@ -5314,42 +5314,49 @@ int Client::MemmedCount() {
}
void Client::ScribeSpell(uint16 spell_id, int slot, bool update_client)
void Client::ScribeSpell(uint16 spell_id, int slot, bool update_client, bool defer_save)
{
if(slot >= EQ::spells::SPELLBOOK_SIZE || slot < 0)
if (slot >= EQ::spells::SPELLBOOK_SIZE || slot < 0) {
return;
}
if(update_client)
{
if(m_pp.spell_book[slot] != 0xFFFFFFFF)
UnscribeSpell(slot, update_client);
if (update_client) {
if (m_pp.spell_book[slot] != 0xFFFFFFFF) {
UnscribeSpell(slot, update_client, defer_save);
}
}
m_pp.spell_book[slot] = spell_id;
database.SaveCharacterSpell(this->CharacterID(), spell_id, slot);
// defer save if we're bulk saving elsewhere
if (!defer_save) {
database.SaveCharacterSpell(this->CharacterID(), spell_id, slot);
}
LogSpells("Spell [{}] scribed into spell book slot [{}]", spell_id, slot);
if(update_client)
{
if (update_client) {
MemorizeSpell(slot, spell_id, memSpellScribing);
}
}
void Client::UnscribeSpell(int slot, bool update_client)
void Client::UnscribeSpell(int slot, bool update_client, bool defer_save)
{
if(slot >= EQ::spells::SPELLBOOK_SIZE || slot < 0)
if (slot >= EQ::spells::SPELLBOOK_SIZE || slot < 0) {
return;
}
LogSpells("Spell [{}] erased from spell book slot [{}]", m_pp.spell_book[slot], slot);
m_pp.spell_book[slot] = 0xFFFFFFFF;
database.DeleteCharacterSpell(this->CharacterID(), m_pp.spell_book[slot], slot);
if(update_client && slot < EQ::spells::DynamicLookup(ClientVersion(), GetGM())->SpellbookSize)
{
auto outapp = new EQApplicationPacket(OP_DeleteSpell, sizeof(DeleteSpell_Struct));
DeleteSpell_Struct* del = (DeleteSpell_Struct*)outapp->pBuffer;
if (!defer_save) {
database.DeleteCharacterSpell(this->CharacterID(), m_pp.spell_book[slot], slot);
}
if (update_client && slot < EQ::spells::DynamicLookup(ClientVersion(), GetGM())->SpellbookSize) {
auto outapp = new EQApplicationPacket(OP_DeleteSpell, sizeof(DeleteSpell_Struct));
DeleteSpell_Struct *del = (DeleteSpell_Struct *) outapp->pBuffer;
del->spell_slot = slot;
del->success = 1;
del->success = 1;
QueuePacket(outapp);
safe_delete(outapp);
}
@ -5357,37 +5364,44 @@ void Client::UnscribeSpell(int slot, bool update_client)
void Client::UnscribeSpellAll(bool update_client)
{
for(int i = 0; i < EQ::spells::SPELLBOOK_SIZE; i++)
{
if(m_pp.spell_book[i] != 0xFFFFFFFF)
UnscribeSpell(i, update_client);
for (int i = 0; i < EQ::spells::SPELLBOOK_SIZE; i++) {
if (m_pp.spell_book[i] != 0xFFFFFFFF) {
UnscribeSpell(i, update_client, true);
}
}
// bulk save at end (this will only delete)
SaveSpells();
}
void Client::UntrainDisc(int slot, bool update_client)
void Client::UntrainDisc(int slot, bool update_client, bool defer_save)
{
if(slot >= MAX_PP_DISCIPLINES || slot < 0)
if (slot >= MAX_PP_DISCIPLINES || slot < 0) {
return;
}
LogSpells("Discipline [{}] untrained from slot [{}]", m_pp.disciplines.values[slot], slot);
m_pp.disciplines.values[slot] = 0;
database.DeleteCharacterDisc(this->CharacterID(), slot);
if(update_client)
{
if (!defer_save) {
database.DeleteCharacterDisc(this->CharacterID(), slot);
}
if (update_client) {
SendDisciplineUpdate();
}
}
void Client::UntrainDiscAll(bool update_client)
{
int i;
for(i = 0; i < MAX_PP_DISCIPLINES; i++)
{
if(m_pp.disciplines.values[i] != 0)
UntrainDisc(i, update_client);
for (int i = 0; i < MAX_PP_DISCIPLINES; i++) {
if (m_pp.disciplines.values[i] != 0) {
UntrainDisc(i, update_client, true);
}
}
// bulk delete / save
SaveDisciplines();
}
void Client::UntrainDiscBySpellID(uint16 spell_id, bool update_client)
@ -6244,3 +6258,4 @@ bool Client::IsLinkedSpellReuseTimerReady(uint32 timer_id)
}