mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-16 05:11:29 +00:00
[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:
parent
6e5bf4b941
commit
7230714cbc
@ -63,6 +63,10 @@ extern volatile bool RunLoops;
|
|||||||
#include "../common/expedition_lockout_timer.h"
|
#include "../common/expedition_lockout_timer.h"
|
||||||
#include "cheat_manager.h"
|
#include "cheat_manager.h"
|
||||||
|
|
||||||
|
#include "../common/repositories/character_spells_repository.h"
|
||||||
|
#include "../common/repositories/character_disciplines_repository.h"
|
||||||
|
|
||||||
|
|
||||||
extern QueryServ* QServ;
|
extern QueryServ* QServ;
|
||||||
extern EntityList entity_list;
|
extern EntityList entity_list;
|
||||||
extern Zone* zone;
|
extern Zone* zone;
|
||||||
@ -5587,7 +5591,7 @@ void Client::UpdateLDoNWinLoss(uint32 theme_id, bool win, bool remove) {
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
database.UpdateAdventureStatsEntry(CharacterID(), theme_id, win, remove);
|
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->window = 0xFF;
|
||||||
out->type = book_type;
|
out->type = book_type;
|
||||||
out->invslot = 0;
|
out->invslot = 0;
|
||||||
|
|
||||||
memcpy(out->booktext, book_text.c_str(), length);
|
memcpy(out->booktext, book_text.c_str(), length);
|
||||||
|
|
||||||
if (book_language > 0 && book_language < MAX_PP_LANGUAGE) {
|
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);
|
SendItemPacket(EQ::invslot::slotCursor, summoned_bag, ItemPacketLimbo);
|
||||||
safe_delete(summoned_bag);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -799,10 +799,15 @@ public:
|
|||||||
std::vector<int> GetMemmedSpells();
|
std::vector<int> GetMemmedSpells();
|
||||||
std::vector<int> GetScribeableSpells(uint8 min_level = 1, uint8 max_level = 0);
|
std::vector<int> GetScribeableSpells(uint8 min_level = 1, uint8 max_level = 0);
|
||||||
std::vector<int> GetScribedSpells();
|
std::vector<int> GetScribedSpells();
|
||||||
void ScribeSpell(uint16 spell_id, int slot, bool update_client = true);
|
// defer save used when bulk saving
|
||||||
void UnscribeSpell(int slot, bool update_client = true);
|
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 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 UntrainDiscAll(bool update_client = true);
|
||||||
void UntrainDiscBySpellID(uint16 spell_id, bool update_client = true);
|
void UntrainDiscBySpellID(uint16 spell_id, bool update_client = true);
|
||||||
bool SpellGlobalCheck(uint16 spell_id, uint32 char_id);
|
bool SpellGlobalCheck(uint16 spell_id, uint32 char_id);
|
||||||
|
|||||||
@ -2048,7 +2048,7 @@ void Client::Handle_OP_AdventureMerchantPurchase(const EQApplicationPacket *app)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
} else if (aps->Type == DiscordMerchant) {
|
} else if (aps->Type == DiscordMerchant) {
|
||||||
if (GetPVPPoints() < item_cost) {
|
if (GetPVPPoints() < item_cost) {
|
||||||
@ -14791,8 +14791,8 @@ void Client::Handle_OP_Translocate(const EQApplicationPacket *app)
|
|||||||
zone->GetZoneID() == PendingTranslocateData.zone_id &&
|
zone->GetZoneID() == PendingTranslocateData.zone_id &&
|
||||||
zone->GetInstanceID() == PendingTranslocateData.instance_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
|
// 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
|
// 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
|
// to the bind coords it has from the PlayerProfile, but with the X and Y reversed. I suspect they are
|
||||||
|
|||||||
@ -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
|
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;
|
++count;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -7939,14 +7939,18 @@ void command_scribespells(Client *c, const Seperator *sep)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
t->Message(Chat::White, "Successfully scribed %i spells.", count);
|
t->Message(Chat::White, "Successfully scribed %i spells.", count);
|
||||||
if (t != c)
|
if (t != c) {
|
||||||
c->Message(Chat::White, "Successfully scribed %i spells for %s.", count, t->GetName());
|
c->Message(Chat::White, "Successfully scribed %i spells for %s.", count, t->GetName());
|
||||||
|
}
|
||||||
|
|
||||||
|
t->SaveSpells();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
t->Message(Chat::White, "No spells scribed.");
|
t->Message(Chat::White, "No spells scribed.");
|
||||||
if (t != c)
|
if (t != c) {
|
||||||
c->Message(Chat::White, "No spells scribed for %s.", t->GetName());
|
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 = c->GetTarget()->CastToClient();
|
||||||
|
|
||||||
t->UntrainDiscAll();
|
t->UntrainDiscAll();
|
||||||
|
t->Message(Chat::Yellow, "All disciplines removed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
void command_wpinfo(Client *c, const Seperator *sep)
|
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) {
|
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());
|
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);
|
std::string query = fmt::format("UPDATE npc_types SET gender = {} WHERE id = {}", gender_id, npc_id);
|
||||||
content_db.QueryDatabase(query);
|
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);
|
std::string query = fmt::format("UPDATE npc_types SET ammo_idfile = {} WHERE id = {}", atoi(sep->arg[2]), npc_id);
|
||||||
content_db.QueryDatabase(query);
|
content_db.QueryDatabase(query);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcasecmp(sep->arg[1], "weapon") == 0) {
|
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());
|
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);
|
content_db.QueryDatabase(query);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcasecmp(sep->arg[1], "accuracy") == 0) {
|
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());
|
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);
|
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);
|
content_db.QueryDatabase(query);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcasecmp(sep->arg[1], "armtexture") == 0) {
|
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());
|
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);
|
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 = 1;
|
||||||
animation_name = "Sitting";
|
animation_name = "Sitting";
|
||||||
} else if(strcasecmp(sep->arg[2], "crouch") == 0 || atoi(sep->arg[2]) == 2) { // Crouch
|
} else if(strcasecmp(sep->arg[2], "crouch") == 0 || atoi(sep->arg[2]) == 2) { // Crouch
|
||||||
animation = 2;
|
animation = 2;
|
||||||
animation_name = "Crouching";
|
animation_name = "Crouching";
|
||||||
} else if(strcasecmp(sep->arg[2], "dead") == 0 || atoi(sep->arg[2]) == 3) { // Dead
|
} else if(strcasecmp(sep->arg[2], "dead") == 0 || atoi(sep->arg[2]) == 3) { // Dead
|
||||||
animation = 3;
|
animation = 3;
|
||||||
@ -10999,7 +11004,6 @@ void command_traindisc(Client *c, const Seperator *sep)
|
|||||||
}
|
}
|
||||||
else if (t->GetPP().disciplines.values[r] == 0) {
|
else if (t->GetPP().disciplines.values[r] == 0) {
|
||||||
t->GetPP().disciplines.values[r] = spell_id_;
|
t->GetPP().disciplines.values[r] = spell_id_;
|
||||||
database.SaveCharacterDisc(t->CharacterID(), r, spell_id_);
|
|
||||||
change = true;
|
change = true;
|
||||||
t->Message(Chat::White, "You have learned a new discipline!");
|
t->Message(Chat::White, "You have learned a new discipline!");
|
||||||
++count; // success counter
|
++count; // success counter
|
||||||
@ -11011,17 +11015,22 @@ void command_traindisc(Client *c, const Seperator *sep)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (change)
|
if (change) {
|
||||||
t->SendDisciplineUpdate();
|
t->SendDisciplineUpdate();
|
||||||
|
t->SaveDisciplines();
|
||||||
|
}
|
||||||
|
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
t->Message(Chat::White, "Successfully trained %u disciplines.", count);
|
t->Message(Chat::White, "Successfully trained %u disciplines.", count);
|
||||||
if (t != c)
|
if (t != c) {
|
||||||
c->Message(Chat::White, "Successfully trained %u disciplines for %s.", count, t->GetName());
|
c->Message(Chat::White, "Successfully trained %u disciplines for %s.", count, t->GetName());
|
||||||
} else {
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
t->Message(Chat::White, "No disciplines trained.");
|
t->Message(Chat::White, "No disciplines trained.");
|
||||||
if (t != c)
|
if (t != c) {
|
||||||
c->Message(Chat::White, "No disciplines trained for %s.", t->GetName());
|
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]");
|
c->Message(Chat::White, "Command Syntax: #dye help | #dye [slot] [red] [green] [blue] [use_tint]");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8 slot = 0;
|
uint8 slot = 0;
|
||||||
uint8 red = 255;
|
uint8 red = 255;
|
||||||
uint8 green = 255;
|
uint8 green = 255;
|
||||||
@ -14902,7 +14911,7 @@ void command_dye(Client *c, const Seperator *sep)
|
|||||||
std::vector<std::string> slot_messages;
|
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, "Command Syntax: #dye help | #dye [slot] [red] [green] [blue] [use_tint]");
|
||||||
c->Message(Chat::White, "Red, Green, and Blue go from 0 to 255.");
|
c->Message(Chat::White, "Red, Green, and Blue go from 0 to 255.");
|
||||||
|
|
||||||
for (const auto& slot : dye_slots) {
|
for (const auto& slot : dye_slots) {
|
||||||
slot_messages.push_back(fmt::format("({}) {}", slot_id, slot));
|
slot_messages.push_back(fmt::format("({}) {}", slot_id, slot));
|
||||||
slot_id++;
|
slot_id++;
|
||||||
|
|||||||
@ -180,7 +180,7 @@ int32 Mob::GetActReflectedSpellDamage(int32 spell_id, int32 value, int effective
|
|||||||
value = int(static_cast<float>(value) * CastToNPC()->GetSpellScale() / 100.0f);
|
value = int(static_cast<float>(value) * CastToNPC()->GetSpellScale() / 100.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int32 base_spell_dmg = value;
|
int32 base_spell_dmg = value;
|
||||||
|
|
||||||
value = value * effectiveness / 100;
|
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
|
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) {
|
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
|
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) {
|
if (target) {
|
||||||
value += value * target->GetHealRate() / 100; //SPA 120 modifies value after Focus Applied but before critical
|
value += value * target->GetHealRate() / 100; //SPA 120 modifies value after Focus Applied but before critical
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Apply critical hit modifier
|
Apply critical hit modifier
|
||||||
*/
|
*/
|
||||||
@ -363,7 +363,7 @@ int32 Mob::GetActSpellHealing(uint16 spell_id, int32 value, Mob* target) {
|
|||||||
if (target) {
|
if (target) {
|
||||||
value += target->GetFocusEffect(focusFcHealAmtIncoming, spell_id); //SPA 394 Add after critical
|
value += target->GetFocusEffect(focusFcHealAmtIncoming, spell_id); //SPA 394 Add after critical
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsNPC() && CastToNPC()->GetHealScale()) {
|
if (IsNPC() && CastToNPC()->GetHealScale()) {
|
||||||
value = int(static_cast<float>(value) * CastToNPC()->GetHealScale() / 100.0f);
|
value = int(static_cast<float>(value) * CastToNPC()->GetHealScale() / 100.0f);
|
||||||
}
|
}
|
||||||
@ -619,13 +619,14 @@ bool Client::MemorizeSpellFromItem(uint32 item_id) {
|
|||||||
return false;
|
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)) {
|
if (!HasSpellScribed(spell_id)) {
|
||||||
auto next_slot = GetNextAvailableSpellBookSlot();
|
auto next_slot = GetNextAvailableSpellBookSlot();
|
||||||
if (next_slot != -1) {
|
if (next_slot != -1) {
|
||||||
ScribeSpell(spell_id, next_slot);
|
ScribeSpell(spell_id, next_slot);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
Message(
|
Message(
|
||||||
Chat::Red,
|
Chat::Red,
|
||||||
"Unable to scribe spell %s (%i) to spellbook: no more spell book slots available.",
|
"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);
|
SummonItem(item_id);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
Message(Chat::Red, "You already know this spell.");
|
Message(Chat::Red, "You already know this spell.");
|
||||||
SummonItem(item_id);
|
SummonItem(item_id);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Message(Chat::Red, "You have learned too many spells and can learn no more.");
|
Message(Chat::Red, "You have learned too many spells and can learn no more.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1130,7 +1130,8 @@ uint16 QuestManager::scribespells(uint8 max_level, uint8 min_level) {
|
|||||||
if (initiator->HasSpellScribed(spell_id))
|
if (initiator->HasSpellScribed(spell_id))
|
||||||
continue;
|
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);
|
book_slot = initiator->GetNextAvailableSpellBookSlot(book_slot);
|
||||||
spells_learned++;
|
spells_learned++;
|
||||||
}
|
}
|
||||||
@ -1139,6 +1140,9 @@ uint16 QuestManager::scribespells(uint8 max_level, uint8 min_level) {
|
|||||||
if (spells_learned > 0) {
|
if (spells_learned > 0) {
|
||||||
std::string spell_message = (spells_learned == 1 ? "a new spell" : fmt::format("{} new spells", spells_learned));
|
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());
|
initiator->Message(Chat::White, fmt::format("You have learned {}!", spell_message).c_str());
|
||||||
|
|
||||||
|
// bulk insert spells
|
||||||
|
initiator->SaveSpells();
|
||||||
}
|
}
|
||||||
return spells_learned;
|
return spells_learned;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -445,7 +445,7 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot,
|
|||||||
) {
|
) {
|
||||||
cast_failed = false;
|
cast_failed = false;
|
||||||
}
|
}
|
||||||
} else if (spell_target->IsRaidGrouped()) {
|
} else if (spell_target->IsRaidGrouped()) {
|
||||||
Raid *target_raid = spell_target->GetRaid();
|
Raid *target_raid = spell_target->GetRaid();
|
||||||
Raid *my_raid = GetRaid();
|
Raid *my_raid = GetRaid();
|
||||||
if (
|
if (
|
||||||
@ -3874,15 +3874,15 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, int reflect_effectivenes
|
|||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
Reflect
|
Reflect
|
||||||
base= % Chance to Reflect
|
base= % Chance to Reflect
|
||||||
Limit= Resist Modifier (+Value for decrease chance to resist)
|
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)
|
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.
|
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.
|
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)
|
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.
|
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.
|
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) &&
|
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(
|
entity_list.AddHealAggro(
|
||||||
spelltar, this,
|
spelltar, this,
|
||||||
CheckHealAggroAmount(spell_id, spelltar, (spelltar->GetMaxHP() - spelltar->GetHP())));
|
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;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(update_client)
|
if (update_client) {
|
||||||
{
|
if (m_pp.spell_book[slot] != 0xFFFFFFFF) {
|
||||||
if(m_pp.spell_book[slot] != 0xFFFFFFFF)
|
UnscribeSpell(slot, update_client, defer_save);
|
||||||
UnscribeSpell(slot, update_client);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_pp.spell_book[slot] = spell_id;
|
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);
|
LogSpells("Spell [{}] scribed into spell book slot [{}]", spell_id, slot);
|
||||||
|
|
||||||
if(update_client)
|
if (update_client) {
|
||||||
{
|
|
||||||
MemorizeSpell(slot, spell_id, memSpellScribing);
|
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;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
LogSpells("Spell [{}] erased from spell book slot [{}]", m_pp.spell_book[slot], slot);
|
LogSpells("Spell [{}] erased from spell book slot [{}]", m_pp.spell_book[slot], slot);
|
||||||
m_pp.spell_book[slot] = 0xFFFFFFFF;
|
m_pp.spell_book[slot] = 0xFFFFFFFF;
|
||||||
|
|
||||||
database.DeleteCharacterSpell(this->CharacterID(), m_pp.spell_book[slot], slot);
|
if (!defer_save) {
|
||||||
if(update_client && slot < EQ::spells::DynamicLookup(ClientVersion(), GetGM())->SpellbookSize)
|
database.DeleteCharacterSpell(this->CharacterID(), m_pp.spell_book[slot], slot);
|
||||||
{
|
}
|
||||||
auto outapp = new EQApplicationPacket(OP_DeleteSpell, sizeof(DeleteSpell_Struct));
|
|
||||||
DeleteSpell_Struct* del = (DeleteSpell_Struct*)outapp->pBuffer;
|
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->spell_slot = slot;
|
||||||
del->success = 1;
|
del->success = 1;
|
||||||
QueuePacket(outapp);
|
QueuePacket(outapp);
|
||||||
safe_delete(outapp);
|
safe_delete(outapp);
|
||||||
}
|
}
|
||||||
@ -5357,37 +5364,44 @@ void Client::UnscribeSpell(int slot, bool update_client)
|
|||||||
|
|
||||||
void Client::UnscribeSpellAll(bool update_client)
|
void Client::UnscribeSpellAll(bool update_client)
|
||||||
{
|
{
|
||||||
for(int i = 0; i < EQ::spells::SPELLBOOK_SIZE; i++)
|
for (int i = 0; i < EQ::spells::SPELLBOOK_SIZE; i++) {
|
||||||
{
|
if (m_pp.spell_book[i] != 0xFFFFFFFF) {
|
||||||
if(m_pp.spell_book[i] != 0xFFFFFFFF)
|
UnscribeSpell(i, update_client, true);
|
||||||
UnscribeSpell(i, update_client);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
LogSpells("Discipline [{}] untrained from slot [{}]", m_pp.disciplines.values[slot], slot);
|
LogSpells("Discipline [{}] untrained from slot [{}]", m_pp.disciplines.values[slot], slot);
|
||||||
m_pp.disciplines.values[slot] = 0;
|
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();
|
SendDisciplineUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::UntrainDiscAll(bool update_client)
|
void Client::UntrainDiscAll(bool update_client)
|
||||||
{
|
{
|
||||||
int i;
|
for (int i = 0; i < MAX_PP_DISCIPLINES; i++) {
|
||||||
|
if (m_pp.disciplines.values[i] != 0) {
|
||||||
for(i = 0; i < MAX_PP_DISCIPLINES; i++)
|
UntrainDisc(i, update_client, true);
|
||||||
{
|
}
|
||||||
if(m_pp.disciplines.values[i] != 0)
|
|
||||||
UntrainDisc(i, update_client);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bulk delete / save
|
||||||
|
SaveDisciplines();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::UntrainDiscBySpellID(uint16 spell_id, bool update_client)
|
void Client::UntrainDiscBySpellID(uint16 spell_id, bool update_client)
|
||||||
@ -6244,3 +6258,4 @@ bool Client::IsLinkedSpellReuseTimerReady(uint32 timer_id)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user