From 260a48be1e7ab1e309ac01f1db944a4199d326e4 Mon Sep 17 00:00:00 2001 From: Emagi Date: Thu, 26 Jun 2025 18:27:35 -0400 Subject: [PATCH] Fix #38 address issues with food/drink cross zone Custom spells now survive cross zone with their modified spell stats saved to the database for reloading later. The design is such that a spell can be custom defined for its spell stats, but a character/player cannot have more than one of that spell (eg. food can share a spell id since only one food can be applied to you, but you can't have many spells apply using the same custom spell id). CREATE TABLE character_custom_spell_dataindex ( charid INT UNSIGNED NOT NULL, spell_id INT UNSIGNED NOT NULL, idx INT UNSIGNED NOT NULL, type ENUM('int', 'float', 'bool', 'string') NOT NULL, value1 TEXT, value2 TEXT, PRIMARY KEY (charid, spell_id, idx) ); CREATE TABLE character_custom_spell_display ( charid INT UNSIGNED NOT NULL, spell_id INT UNSIGNED NOT NULL, idx INT UNSIGNED NOT NULL, field VARCHAR(64) NOT NULL, value TEXT, PRIMARY KEY (charid, spell_id, idx, field) ); CREATE TABLE character_custom_spell_data ( charid INT UNSIGNED NOT NULL, spell_id INT UNSIGNED NOT NULL, field VARCHAR(64) NOT NULL, type ENUM('int', 'float', 'bool', 'string') NOT NULL, value TEXT NOT NULL, PRIMARY KEY (charid, spell_id, field) ); --- source/WorldServer/Entity.cpp | 13 +- source/WorldServer/Entity.h | 9 +- source/WorldServer/LuaFunctions.cpp | 46 ++++--- source/WorldServer/LuaInterface.cpp | 180 +++++++++++++++++++++++++++ source/WorldServer/LuaInterface.h | 73 +++++++++++ source/WorldServer/Player.cpp | 125 ++++++++++++++++++- source/WorldServer/Player.h | 7 +- source/WorldServer/Spells.cpp | 7 +- source/WorldServer/Spells.h | 8 ++ source/WorldServer/WorldDatabase.cpp | 113 +++++++++++++++-- source/WorldServer/WorldDatabase.h | 3 + 11 files changed, 551 insertions(+), 33 deletions(-) diff --git a/source/WorldServer/Entity.cpp b/source/WorldServer/Entity.cpp index d883f8d..536d946 100644 --- a/source/WorldServer/Entity.cpp +++ b/source/WorldServer/Entity.cpp @@ -127,6 +127,7 @@ void Entity::DeleteSpellEffects(bool removeClient) if (IsPlayer()) GetInfoStruct()->maintained_effects[i].icon = 0xFFFF; GetInfoStruct()->maintained_effects[i].spell_id = 0xFFFFFFFF; + GetInfoStruct()->maintained_effects[i].inherited_spell_id = 0; GetInfoStruct()->maintained_effects[i].spell = nullptr; } } @@ -139,6 +140,7 @@ void Entity::DeleteSpellEffects(bool removeClient) } } GetInfoStruct()->spell_effects[i].spell_id = 0xFFFFFFFF; + GetInfoStruct()->spell_effects[i].inherited_spell_id = 0; GetInfoStruct()->spell_effects[i].icon = 0; GetInfoStruct()->spell_effects[i].icon_backdrop = 0; GetInfoStruct()->spell_effects[i].tier = 0; @@ -1177,6 +1179,7 @@ void Entity::RemoveMaintainedSpell(LuaSpell* luaspell){ if (found) { memset(&GetInfoStruct()->maintained_effects[29], 0, sizeof(MaintainedEffects)); GetInfoStruct()->maintained_effects[29].spell_id = 0xFFFFFFFF; + GetInfoStruct()->maintained_effects[29].inherited_spell_id = 0; GetInfoStruct()->maintained_effects[29].icon = 0xFFFF; GetInfoStruct()->maintained_effects[29].spell = nullptr; } @@ -1198,6 +1201,7 @@ void Entity::RemoveSpellEffect(LuaSpell* spell) { GetZone()->GetSpellProcess()->RemoveTargetFromSpell(spell, this); memset(&GetInfoStruct()->spell_effects[44], 0, sizeof(SpellEffects)); GetInfoStruct()->spell_effects[44].spell_id = 0xFFFFFFFF; + GetInfoStruct()->spell_effects[44].inherited_spell_id = 0; GetInfoStruct()->spell_effects[44].spell = nullptr; changed = true; info_changed = true; @@ -1227,12 +1231,12 @@ MaintainedEffects* Entity::GetFreeMaintainedSpellSlot(){ return ret; } -MaintainedEffects* Entity::GetMaintainedSpell(int32 spell_id){ +MaintainedEffects* Entity::GetMaintainedSpell(int32 spell_id, bool on_char_load){ MaintainedEffects* ret = 0; InfoStruct* info = GetInfoStruct(); MMaintainedSpells.readlock(__FUNCTION__, __LINE__); for (int i = 0; imaintained_effects[i].spell_id == spell_id){ + if (info->maintained_effects[i].spell_id == spell_id || (on_char_load && info->maintained_effects[i].inherited_spell_id == id)){ ret = &info->maintained_effects[i]; break; } @@ -1256,12 +1260,12 @@ SpellEffects* Entity::GetFreeSpellEffectSlot(){ return ret; } -SpellEffects* Entity::GetSpellEffect(int32 id, Entity* caster) { +SpellEffects* Entity::GetSpellEffect(int32 id, Entity* caster, bool on_char_load) { SpellEffects* ret = 0; InfoStruct* info = GetInfoStruct(); MSpellEffects.readlock(__FUNCTION__, __LINE__); for(int i = 0; i < 45; i++) { - if(info->spell_effects[i].spell_id == id) { + if(info->spell_effects[i].spell_id == id || (on_char_load && info->maintained_effects[i].inherited_spell_id == id)) { if (!caster || info->spell_effects[i].caster == caster){ ret = &info->spell_effects[i]; break; @@ -2743,6 +2747,7 @@ void Entity::AddDetrimentalSpell(LuaSpell* luaspell, int32 override_expire_times new_det.det_type = data->det_type; new_det.incurable = data->incurable; new_det.spell_id = spell->GetSpellID(); + new_det.inherited_spell_id = data->inherited_spell_id; new_det.control_effect = data->control_effect_type; new_det.total_time = spell->GetSpellDuration()/10; diff --git a/source/WorldServer/Entity.h b/source/WorldServer/Entity.h index 831d848..9d1a434 100644 --- a/source/WorldServer/Entity.h +++ b/source/WorldServer/Entity.h @@ -55,6 +55,7 @@ struct MaintainedEffects{ int32 target; int8 target_type; int32 spell_id; + int32 inherited_spell_id; int32 slot_pos; int16 icon; int16 icon_backdrop; @@ -67,6 +68,7 @@ struct MaintainedEffects{ struct SpellEffects{ int32 spell_id; + int32 inherited_spell_id; Entity* caster; float total_time; int32 expire_timestamp; @@ -78,6 +80,7 @@ struct SpellEffects{ struct DetrimentalEffects { int32 spell_id; + int32 inherited_spell_id; Entity* caster; int32 expire_timestamp; int16 icon; @@ -1067,6 +1070,7 @@ struct InfoStruct{ for(int i=0;i<45;i++){ if(i<30){ maintained_effects[i].spell_id = 0xFFFFFFFF; + maintained_effects[i].inherited_spell_id = 0; if (spawn->IsPlayer()) maintained_effects[i].icon = 0xFFFF; @@ -1074,6 +1078,7 @@ struct InfoStruct{ } spell_effects[i].icon = 0; spell_effects[i].spell_id = 0xFFFFFFFF; + spell_effects[i].inherited_spell_id = 0; spell_effects[i].icon_backdrop = 0; spell_effects[i].tier = 0; spell_effects[i].total_time = 0.0f; @@ -1425,7 +1430,7 @@ public: virtual void AddSkillBonus(int32 spell_id, int32 skill_id, float value); void AddDetrimentalSpell(LuaSpell* spell, int32 override_expire_timestamp = 0); DetrimentalEffects* GetDetrimentalEffect(int32 spell_id, Entity* caster); - virtual MaintainedEffects* GetMaintainedSpell(int32 spell_id); + virtual MaintainedEffects* GetMaintainedSpell(int32 spell_id, bool on_char_load = false); void RemoveDetrimentalSpell(LuaSpell* spell); void SetDeity(int8 new_deity){ deity = new_deity; @@ -1489,7 +1494,7 @@ public: void DoRegenUpdate(); MaintainedEffects* GetFreeMaintainedSpellSlot(); SpellEffects* GetFreeSpellEffectSlot(); - SpellEffects* GetSpellEffect(int32 id, Entity* caster = 0); + SpellEffects* GetSpellEffect(int32 id, Entity* caster = 0, bool on_char_load = false); SpellEffects* GetSpellEffectBySpellType(int8 spell_type); SpellEffects* GetSpellEffectWithLinkedTimer(int32 id, int32 linked_timer = 0, sint32 type_group_spell_id = 0, Entity* caster = 0); LuaSpell* HasLinkedTimerID(LuaSpell* spell, Spawn* target = nullptr, bool stackWithOtherPlayers = true); diff --git a/source/WorldServer/LuaFunctions.cpp b/source/WorldServer/LuaFunctions.cpp index 2a6fa60..c576e08 100644 --- a/source/WorldServer/LuaFunctions.cpp +++ b/source/WorldServer/LuaFunctions.cpp @@ -12436,12 +12436,16 @@ int EQ2Emu_lua_SetSpellData(lua_State* state) { boost::to_lower(field); - bool valSet = false; + bool setVal = false; - spell->spell->SetSpellData(state, field, fieldArg); + setVal = spell->spell->SetSpellData(state, field, fieldArg); lua_interface->ResetFunctionStack(state); - - return valSet; + if(setVal) { + spell->MarkFieldModified(field); + } + lua_interface->SetBooleanValue(state, setVal); + + return 1; } int EQ2Emu_lua_SetSpellDataIndex(lua_State* state) { @@ -12509,8 +12513,14 @@ int EQ2Emu_lua_SetSpellDataIndex(lua_State* state) { } lua_interface->ResetFunctionStack(state); + + lua_interface->SetBooleanValue(state, setVal); - return setVal; + if(setVal) { + data->needs_db_save = true; + } + + return 1; } @@ -12607,22 +12617,30 @@ int EQ2Emu_lua_SetSpellDisplayEffect(lua_State* state) { return 0; } + bool setVal = true; // do we need to lock? eh probably not this should only be used before use of the custom spell SpellDisplayEffect* effect = spell->spell->effects[idx]; - if (field == "description") + if (field == "description") { effect->description = string(lua_interface->GetStringValue(state, 4)); - else if (field == "bullet") - effect->subbullet = lua_interface->GetInt8Value(state, 4); - else if (field == "percentage") - effect->percentage = lua_interface->GetInt8Value(state, 4); - else { // no match - lua_interface->ResetFunctionStack(state); - return 0; + effect->needs_db_save = true; } - + else if (field == "bullet") { + effect->subbullet = lua_interface->GetInt8Value(state, 4); + effect->needs_db_save = true; + } + else if (field == "percentage") { + effect->percentage = lua_interface->GetInt8Value(state, 4); + effect->needs_db_save = true; + } + else { + setVal = false; + } + lua_interface->ResetFunctionStack(state); + lua_interface->SetBooleanValue(state, setVal); + return 1; } diff --git a/source/WorldServer/LuaInterface.cpp b/source/WorldServer/LuaInterface.cpp index 4a00734..faf1c14 100644 --- a/source/WorldServer/LuaInterface.cpp +++ b/source/WorldServer/LuaInterface.cpp @@ -39,6 +39,149 @@ extern WorldDatabase database; extern ZoneList zone_list; +const std::unordered_map> SpellDataFieldAccessors = { + {"spell_book_type", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->spell_book_type); }}}, + {"icon", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->icon); }}}, + {"icon_heroic_op", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->icon_heroic_op); }}}, + {"icon_backdrop", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->icon_backdrop); }}}, + {"type", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->type); }}}, + {"class_skill", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->class_skill); }}}, + {"min_class_skill_req", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->min_class_skill_req); }}}, + {"mastery_skill", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->mastery_skill); }}}, + {"ts_loc_index", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->ts_loc_index); }}}, + {"num_levels", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->num_levels); }}}, + {"tier", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->tier); }}}, + {"hp_req", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->hp_req); }}}, + {"hp_upkeep", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->hp_upkeep); }}}, + {"power_req", {SpellFieldType::Float, [](SpellData* d) { return std::to_string(d->power_req); }}}, + {"power_by_level", {SpellFieldType::Boolean, [](SpellData* d) { return d->power_by_level ? "1" : "0"; }}}, + {"power_upkeep", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->power_upkeep); }}}, + {"savagery_req", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->savagery_req); }}}, + {"savagery_upkeep", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->savagery_upkeep); }}}, + {"dissonance_req", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->dissonance_req); }}}, + {"dissonance_upkeep", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->dissonance_upkeep); }}}, + {"target_type", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->target_type); }}}, + {"cast_time", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->cast_time); }}}, + {"recovery", {SpellFieldType::Float, [](SpellData* d) { return std::to_string(d->recovery); }}}, + {"recast", {SpellFieldType::Float, [](SpellData* d) { return std::to_string(d->recast); }}}, + {"linked_timer", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->linked_timer); }}}, + {"radius", {SpellFieldType::Float, [](SpellData* d) { return std::to_string(d->radius); }}}, + {"max_aoe_targets", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->max_aoe_targets); }}}, + {"friendly_spell", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->friendly_spell); }}}, + {"req_concentration", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->req_concentration); }}}, + {"range", {SpellFieldType::Float, [](SpellData* d) { return std::to_string(d->range); }}}, + {"duration1", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->duration1); }}}, + {"duration2", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->duration2); }}}, + {"resistibility", {SpellFieldType::Float, [](SpellData* d) { return std::to_string(d->resistibility); }}}, + {"duration_until_cancel", {SpellFieldType::Boolean, [](SpellData* d) { return d->duration_until_cancel ? "1" : "0"; }}}, + {"power_req_percent", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->power_req_percent); }}}, + {"hp_req_percent", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->hp_req_percent); }}}, + {"savagery_req_percent", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->savagery_req_percent); }}}, + {"dissonance_req_percent", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->dissonance_req_percent); }}}, + {"name", {SpellFieldType::String, [](SpellData* d) { return d->name.data; }}}, + {"description", {SpellFieldType::String, [](SpellData* d) { return d->description.data; }}}, + {"success_message", {SpellFieldType::String, [](SpellData* d) { return d->success_message; }}}, + {"fade_message", {SpellFieldType::String, [](SpellData* d) { return d->fade_message; }}}, + {"fade_message_others", {SpellFieldType::String, [](SpellData* d) { return d->fade_message_others; }}}, + {"cast_type", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->cast_type).c_str(); }}}, + {"lua_script", {SpellFieldType::String, [](SpellData* d) { return d->lua_script; }}}, + {"interruptable", {SpellFieldType::Boolean, [](SpellData* d) { return d->interruptable ? "1" : "0"; }}}, + {"spell_visual", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->spell_visual); }}}, + {"effect_message", {SpellFieldType::String, [](SpellData* d) { return d->effect_message; }}}, + {"min_range", {SpellFieldType::Float, [](SpellData* d) { return std::to_string(d->min_range); }}}, + {"can_effect_raid", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->can_effect_raid); }}}, + {"affect_only_group_members", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->affect_only_group_members); }}}, + {"group_spell", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->group_spell); }}}, + {"hit_bonus", {SpellFieldType::Float, [](SpellData* d) { return std::to_string(d->hit_bonus); }}}, + {"display_spell_tier", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->display_spell_tier); }}}, + {"is_active", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->is_active); }}}, + {"det_type", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->det_type); }}}, + {"incurable", {SpellFieldType::Boolean, [](SpellData* d) { return d->incurable ? "1" : "0"; }}}, + {"control_effect_type", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->control_effect_type); }}}, + {"casting_flags", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->casting_flags); }}}, + {"cast_while_moving", {SpellFieldType::Boolean, [](SpellData* d) { return d->cast_while_moving ? "1" : "0"; }}}, + {"persist_through_death", {SpellFieldType::Boolean, [](SpellData* d) { return d->persist_through_death ? "1" : "0"; }}}, + {"not_maintained", {SpellFieldType::Boolean, [](SpellData* d) { return d->not_maintained ? "1" : "0"; }}}, + {"is_aa", {SpellFieldType::Boolean, [](SpellData* d) { return d->is_aa ? "1" : "0"; }}}, + {"savage_bar", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->savage_bar); }}}, + {"savage_bar_slot", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->savage_bar_slot); }}}, + {"soe_spell_crc", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->soe_spell_crc); }}}, + {"spell_type", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->spell_type); }}}, + {"spell_name_crc", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->spell_name_crc); }}}, + {"type_group_spell_id", {SpellFieldType::Integer, [](SpellData* d) { return std::to_string(d->type_group_spell_id); }}}, + {"can_fizzle", {SpellFieldType::Boolean, [](SpellData* d) { return d->can_fizzle ? "1" : "0"; }}} +}; + +const std::unordered_map> SpellFieldGenericSetters = { + { "spell_book_type", [](Spell* spell, const std::string& val) { spell->GetSpellData()->spell_book_type = static_cast(std::stoi(val)); } }, + { "icon", [](Spell* spell, const std::string& val) { spell->GetSpellData()->icon = static_cast(std::stoi(val)); } }, + { "icon_heroic_op", [](Spell* spell, const std::string& val) { spell->GetSpellData()->icon_heroic_op = static_cast(std::stoi(val)); } }, + { "icon_backdrop", [](Spell* spell, const std::string& val) { spell->GetSpellData()->icon_backdrop = static_cast(std::stoi(val)); } }, + { "type", [](Spell* spell, const std::string& val) { spell->GetSpellData()->type = static_cast(std::stoi(val)); } }, + { "class_skill", [](Spell* spell, const std::string& val) { spell->GetSpellData()->class_skill = static_cast(std::stoi(val)); } }, + { "min_class_skill_req", [](Spell* spell, const std::string& val) { spell->GetSpellData()->min_class_skill_req = static_cast(std::stoi(val)); } }, + { "mastery_skill", [](Spell* spell, const std::string& val) { spell->GetSpellData()->mastery_skill = static_cast(std::stoi(val)); } }, + { "ts_loc_index", [](Spell* spell, const std::string& val) { spell->GetSpellData()->ts_loc_index = static_cast(std::stoi(val)); } }, + { "num_levels", [](Spell* spell, const std::string& val) { spell->GetSpellData()->num_levels = static_cast(std::stoi(val)); } }, + { "tier", [](Spell* spell, const std::string& val) { spell->GetSpellData()->tier = static_cast(std::stoi(val)); } }, + { "hp_req", [](Spell* spell, const std::string& val) { spell->GetSpellData()->hp_req = static_cast(std::stoi(val)); } }, + { "hp_upkeep", [](Spell* spell, const std::string& val) { spell->GetSpellData()->hp_upkeep = static_cast(std::stoi(val)); } }, + { "power_req", [](Spell* spell, const std::string& val) { spell->GetSpellData()->power_req = std::stof(val); } }, + { "power_by_level", [](Spell* spell, const std::string& val) { spell->GetSpellData()->power_by_level = (val == "1" || val == "true"); } }, + { "power_upkeep", [](Spell* spell, const std::string& val) { spell->GetSpellData()->power_upkeep = static_cast(std::stoi(val)); } }, + { "savagery_req", [](Spell* spell, const std::string& val) { spell->GetSpellData()->savagery_req = static_cast(std::stoi(val)); } }, + { "savagery_upkeep", [](Spell* spell, const std::string& val) { spell->GetSpellData()->savagery_upkeep = static_cast(std::stoi(val)); } }, + { "dissonance_req", [](Spell* spell, const std::string& val) { spell->GetSpellData()->dissonance_req = static_cast(std::stoi(val)); } }, + { "dissonance_upkeep", [](Spell* spell, const std::string& val) { spell->GetSpellData()->dissonance_upkeep = static_cast(std::stoi(val)); } }, + { "target_type", [](Spell* spell, const std::string& val) { spell->GetSpellData()->target_type = static_cast(std::stoi(val)); } }, + { "cast_time", [](Spell* spell, const std::string& val) { spell->GetSpellData()->cast_time = static_cast(std::stoi(val)); } }, + { "recovery", [](Spell* spell, const std::string& val) { spell->GetSpellData()->recovery = std::stof(val); } }, + { "recast", [](Spell* spell, const std::string& val) { spell->GetSpellData()->recast = std::stof(val); } }, + { "linked_timer", [](Spell* spell, const std::string& val) { spell->GetSpellData()->linked_timer = static_cast(std::stoi(val)); } }, + { "radius", [](Spell* spell, const std::string& val) { spell->GetSpellData()->radius = std::stof(val); } }, + { "max_aoe_targets", [](Spell* spell, const std::string& val) { spell->GetSpellData()->max_aoe_targets = static_cast(std::stoi(val)); } }, + { "friendly_spell", [](Spell* spell, const std::string& val) { spell->GetSpellData()->friendly_spell = static_cast(std::stoi(val)); } }, + { "req_concentration", [](Spell* spell, const std::string& val) { spell->GetSpellData()->req_concentration = static_cast(std::stoi(val)); } }, + { "range", [](Spell* spell, const std::string& val) { spell->GetSpellData()->range = std::stof(val); } }, + { "duration1", [](Spell* spell, const std::string& val) { spell->GetSpellData()->duration1 = static_cast(std::stoi(val)); } }, + { "duration2", [](Spell* spell, const std::string& val) { spell->GetSpellData()->duration2 = static_cast(std::stoi(val)); } }, + { "resistibility", [](Spell* spell, const std::string& val) { spell->GetSpellData()->resistibility = std::stof(val); } }, + { "duration_until_cancel", [](Spell* spell, const std::string& val) { spell->GetSpellData()->duration_until_cancel = (val == "1" || val == "true"); } }, + { "power_req_percent", [](Spell* spell, const std::string& val) { spell->GetSpellData()->power_req_percent = static_cast(std::stoi(val)); } }, + { "hp_req_percent", [](Spell* spell, const std::string& val) { spell->GetSpellData()->hp_req_percent = static_cast(std::stoi(val)); } }, + { "savagery_req_percent", [](Spell* spell, const std::string& val) { spell->GetSpellData()->savagery_req_percent = static_cast(std::stoi(val)); } }, + { "dissonance_req_percent", [](Spell* spell, const std::string& val) { spell->GetSpellData()->dissonance_req_percent = static_cast(std::stoi(val)); } }, + { "name", [](Spell* spell, const std::string& val) { spell->GetSpellData()->name.data = val; } }, + { "description", [](Spell* spell, const std::string& val) { spell->GetSpellData()->description.data = val; } }, + { "success_message", [](Spell* spell, const std::string& val) { spell->GetSpellData()->success_message = val; } }, + { "fade_message", [](Spell* spell, const std::string& val) { spell->GetSpellData()->fade_message = val; } }, + { "fade_message_others", [](Spell* spell, const std::string& val) { spell->GetSpellData()->fade_message_others = val; } }, + { "cast_type", [](Spell* spell, const std::string& val) { spell->GetSpellData()->cast_type = static_cast(std::stoi(val)); } }, + { "call_frequency", [](Spell* spell, const std::string& val) { spell->GetSpellData()->call_frequency = static_cast(std::stoi(val)); } }, + { "interruptable", [](Spell* spell, const std::string& val) { spell->GetSpellData()->interruptable = (val == "1" || val == "true"); } }, + { "spell_visual", [](Spell* spell, const std::string& val) { spell->GetSpellData()->spell_visual = static_cast(std::stoi(val)); } }, + { "effect_message", [](Spell* spell, const std::string& val) { spell->GetSpellData()->effect_message = val; } }, + { "min_range", [](Spell* spell, const std::string& val) { spell->GetSpellData()->min_range = std::stof(val); } }, + { "can_effect_raid", [](Spell* spell, const std::string& val) { spell->GetSpellData()->can_effect_raid = static_cast(std::stoi(val)); } }, + { "affect_only_group_members", [](Spell* spell, const std::string& val) { spell->GetSpellData()->affect_only_group_members = static_cast(std::stoi(val)); } }, + { "group_spell", [](Spell* spell, const std::string& val) { spell->GetSpellData()->group_spell = static_cast(std::stoi(val)); } }, + { "hit_bonus", [](Spell* spell, const std::string& val) { spell->GetSpellData()->hit_bonus = std::stof(val); } }, + { "display_spell_tier", [](Spell* spell, const std::string& val) { spell->GetSpellData()->display_spell_tier = static_cast(std::stoi(val)); } }, + { "is_active", [](Spell* spell, const std::string& val) { spell->GetSpellData()->is_active = static_cast(std::stoi(val)); } }, + { "det_type", [](Spell* spell, const std::string& val) { spell->GetSpellData()->det_type = static_cast(std::stoi(val)); } }, + { "incurable", [](Spell* spell, const std::string& val) { spell->GetSpellData()->incurable = (val == "1" || val == "true"); } }, + { "control_effect_type", [](Spell* spell, const std::string& val) { spell->GetSpellData()->control_effect_type = static_cast(std::stoi(val)); } }, + { "casting_flags", [](Spell* spell, const std::string& val) { spell->GetSpellData()->casting_flags = static_cast(std::stoi(val)); } }, + { "cast_while_moving", [](Spell* spell, const std::string& val) { spell->GetSpellData()->cast_while_moving = (val == "1" || val == "true"); } }, + { "persist_through_death", [](Spell* spell, const std::string& val) { spell->GetSpellData()->persist_through_death = (val == "1" || val == "true"); } }, + { "not_maintained", [](Spell* spell, const std::string& val) { spell->GetSpellData()->not_maintained = (val == "1" || val == "true"); } }, + { "is_aa", [](Spell* spell, const std::string& val) { spell->GetSpellData()->is_aa = (val == "1" || val == "true"); } }, + { "savage_bar", [](Spell* spell, const std::string& val) { spell->GetSpellData()->savage_bar = static_cast(std::stoi(val)); } }, + { "spell_type", [](Spell* spell, const std::string& val) { spell->GetSpellData()->spell_type = static_cast(std::stoi(val)); } }, + { "type_group_spell_id", [](Spell* spell, const std::string& val) { spell->GetSpellData()->type_group_spell_id = static_cast(std::stoi(val)); } }, + { "can_fizzle", [](Spell* spell, const std::string& val) { spell->GetSpellData()->can_fizzle = (val == "1" || val == "true"); } }, +}; + LuaInterface::LuaInterface() { shutting_down = false; lua_system_reloading = false; @@ -3066,4 +3209,41 @@ LUASpellWrapper::LUASpellWrapper() { bool LUASpellWrapper::IsSpell() { return true; +} + +bool LuaSpell::SetSpellDataIndex(int idx, const std::string& value, const std::string& value2) { + if (!spell || spell->lua_data.size() <= idx) + return false; + + LUAData* data = spell->lua_data[idx]; + if (!data) + return false; + + bool setVal = true; + + switch (data->type) { + case 0: // int + int + data->int_value = std::stoi(value); + data->int_value2 = std::stoi(value2); + break; + case 1: // float + float + data->float_value = std::stof(value); + data->float_value2 = std::stof(value2); + break; + case 2: // bool + data->bool_value = (value == "1" || value == "true"); + break; + case 3: // string + string + data->string_value = value; + data->string_value2 = value2; + break; + default: + setVal = false; + break; + } + + if (setVal) + data->needs_db_save = true; + + return setVal; } \ No newline at end of file diff --git a/source/WorldServer/LuaInterface.h b/source/WorldServer/LuaInterface.h index 0148937..51d2892 100644 --- a/source/WorldServer/LuaInterface.h +++ b/source/WorldServer/LuaInterface.h @@ -22,6 +22,7 @@ #include #include +#include #include "Spawn.h" #include "Spells.h" @@ -73,6 +74,18 @@ struct OptionWindowOption { #define EFFECT_FLAG_FEAR_IMMUNE 2097152 #define EFFECT_FLAG_SAFEFALL 4194304 +enum class SpellFieldType { + Integer, + Float, + Boolean, + String +}; + +using SpellFieldGetter = std::function; +extern const std::unordered_map> SpellFieldGenericSetters; +extern const std::unordered_map> SpellDataFieldAccessors; +extern std::unordered_map> SpellDataFieldSetters; + struct LuaSpell{ Entity* caster; int32 initial_caster_char_id; @@ -108,6 +121,9 @@ struct LuaSpell{ ZoneServer* zone; int16 initial_caster_level; + std::unordered_set modified_fields; + mutable std::shared_mutex spell_modify_mutex; + void AddTarget(int32 target_id) { std::unique_lock lock(targets_mutex); targets.push_back(target_id); @@ -212,6 +228,63 @@ struct LuaSpell{ char_id_targets.clear(); } + void MarkFieldModified(const std::string& field) { + std::unique_lock lock(spell_modify_mutex); + modified_fields.insert(field); + } + + bool IsFieldModified(const std::string& field) const { + std::shared_lock lock(spell_modify_mutex); + return modified_fields.find(field) != modified_fields.end(); + } + + void ClearFieldModifications() { + std::unique_lock lock(spell_modify_mutex); + modified_fields.clear(); + } + + std::unordered_set GetModifiedFieldsCopy() const { + std::shared_lock lock(spell_modify_mutex); + return modified_fields; // safe shallow copy + } + + bool SetSpellDataGeneric(const std::string& field, int value) { + return SetSpellDataGeneric(field, std::to_string(value)); + } + + bool SetSpellDataGeneric(const std::string& field, float value) { + return SetSpellDataGeneric(field, std::to_string(value)); + } + + bool SetSpellDataGeneric(const std::string& field, bool value) { + return SetSpellDataGeneric(field, value ? "1" : "0"); + } + + bool SetSpellDataGeneric(const std::string& field, const std::string& value) { + auto it = SpellFieldGenericSetters.find(field); + if (it == SpellFieldGenericSetters.end()) + return false; + + if (!spell) + return false; + + it->second(spell, value); + return true; + } + + bool SetSpellDataIndex(int idx, const std::string& value, const std::string& value2 = ""); + + bool SetSpellDataIndex(int idx, int value, int value2) { + return SetSpellDataIndex(idx, std::to_string(value), std::to_string(value2)); + } + + bool SetSpellDataIndex(int idx, float value, float value2) { + return SetSpellDataIndex(idx, std::to_string(value), std::to_string(value2)); + } + + bool SetSpellDataIndex(int idx, bool value) { + return SetSpellDataIndex(idx, value ? "1" : "0"); + } }; enum class LuaArgType { SINT64, INT64, SINT, INT, FLOAT, STRING, BOOL, SPAWN, ZONE, SKILL, ITEM, QUEST, SPELL /* etc */ }; diff --git a/source/WorldServer/Player.cpp b/source/WorldServer/Player.cpp index a0e2d25..cdf716e 100644 --- a/source/WorldServer/Player.cpp +++ b/source/WorldServer/Player.cpp @@ -3284,10 +3284,12 @@ PlayerInfo::PlayerInfo(Player* in_player){ for(int i=0;i<45;i++){ if(i<30){ info_struct->maintained_effects[i].spell_id = 0xFFFFFFFF; + info_struct->maintained_effects[i].inherited_spell_id = 0; info_struct->maintained_effects[i].icon = 0xFFFF; info_struct->maintained_effects[i].spell = nullptr; } - info_struct->spell_effects[i].spell_id = 0xFFFFFFFF; + info_struct->spell_effects[i].spell_id = 0xFFFFFFFF; + info_struct->spell_effects[i].inherited_spell_id = 0; info_struct->spell_effects[i].icon = 0; info_struct->spell_effects[i].icon_backdrop = 0; info_struct->spell_effects[i].tier = 0; @@ -3324,12 +3326,12 @@ MaintainedEffects* Player::GetFreeMaintainedSpellSlot(){ return ret; } -MaintainedEffects* Player::GetMaintainedSpell(int32 id){ +MaintainedEffects* Player::GetMaintainedSpell(int32 id, bool on_char_load){ MaintainedEffects* ret = 0; InfoStruct* info = GetInfoStruct(); GetMaintainedMutex()->readlock(__FUNCTION__, __LINE__); for(int i=0;imaintained_effects[i].spell_id == id){ + if(info->maintained_effects[i].spell_id == id || (on_char_load && info->maintained_effects[i].inherited_spell_id == id)){ ret = &info->maintained_effects[i]; break; } @@ -3560,6 +3562,7 @@ void Player::RemoveMaintainedSpell(LuaSpell* luaspell){ if (found) { memset(&GetInfoStruct()->maintained_effects[29], 0, sizeof(MaintainedEffects)); GetInfoStruct()->maintained_effects[29].spell_id = 0xFFFFFFFF; + GetInfoStruct()->maintained_effects[29].inherited_spell_id = 0; GetInfoStruct()->maintained_effects[29].icon = 0xFFFF; GetInfoStruct()->maintained_effects[29].spell = nullptr; charsheet_changed = true; @@ -3580,6 +3583,7 @@ void Player::RemoveSpellEffect(LuaSpell* spell){ if (found) { memset(&GetInfoStruct()->spell_effects[44], 0, sizeof(SpellEffects)); GetInfoStruct()->spell_effects[44].spell_id = 0xFFFFFFFF; + GetInfoStruct()->spell_effects[44].inherited_spell_id = 0; GetInfoStruct()->spell_effects[44].spell = nullptr; changed = true; info_changed = true; @@ -7331,6 +7335,110 @@ NPC* Player::InstantiateSpiritShard(float origX, float origY, float origZ, float return npc; } +void Player::SaveCustomSpellFields(LuaSpell* luaspell) { + if (!luaspell || !luaspell->spell || !luaspell->spell->IsCopiedSpell()) + return; + + auto spell_data = luaspell->spell->GetSpellData(); + std::unordered_set modified_fields = luaspell->GetModifiedFieldsCopy(); + + Query savedEffects; + for (const std::string& field : modified_fields) { + auto it = SpellDataFieldAccessors.find(field); + if (it == SpellDataFieldAccessors.end()) + continue; + + const auto& [type, getter] = it->second; + std::string value = getter(spell_data); + + std::string type_str; + switch (type) { + case SpellFieldType::Integer: type_str = "int"; break; + case SpellFieldType::Float: type_str = "float"; break; + case SpellFieldType::Boolean: type_str = "bool"; break; + case SpellFieldType::String: type_str = "string"; break; + default: continue; + } + + savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_INSERT, "INSERT INTO character_custom_spell_data (charid, spell_id, field, type, value) VALUES (%u, %u, '%s', '%s', '%s')", + GetCharacterID(), + luaspell->spell->GetSpellData()->inherited_spell_id, + database.getSafeEscapeString(field.c_str()).c_str(), + type_str.c_str(), + database.getSafeEscapeString(value.c_str()).c_str()); + } +} + + +void Player::SaveCustomSpellDataIndex(LuaSpell* luaspell) { + if (!luaspell || !luaspell->spell || !luaspell->spell->IsCopiedSpell()) + return; + + auto& vec = luaspell->spell->lua_data; + + Query savedEffects; + for (int i = 0; i < vec.size(); ++i) { + LUAData* data = vec[i]; + if (!data || !data->needs_db_save) + continue; + + std::string value1, value2, type; + switch (data->type) { + case 0: + value1 = std::to_string(data->int_value); + value2 = std::to_string(data->int_value2); + type = "int"; + break; + case 1: + value1 = std::to_string(data->float_value); + value2 = std::to_string(data->float_value2); + type = "float"; + break; + case 2: + value1 = data->bool_value ? "1" : "0"; + type = "bool"; + break; + case 3: + value1 = database.getSafeEscapeString(data->string_value.c_str()); + value2 = database.getSafeEscapeString(data->string_value2.c_str()); + type = "string"; + break; + default: + continue; + } + + savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_INSERT, "INSERT INTO character_custom_spell_dataindex (charid, spell_id, idx, type, value1, value2) VALUES (%u, %u, %d, '%s', '%s', '%s')", GetCharacterID(), + luaspell->spell->GetSpellData()->inherited_spell_id, + i, + type.c_str(), value1.c_str(), value2.c_str()); + } +} + +void Player::SaveCustomSpellEffectsDisplay(LuaSpell* luaspell) { + if (!luaspell || !luaspell->spell || !luaspell->spell->IsCopiedSpell()) + return; + + auto& vec = luaspell->spell->effects; + + Query savedEffects; + for (int i = 0; i < vec.size(); ++i) { + SpellDisplayEffect* eff = vec[i]; + if (!eff || !eff->needs_db_save) + continue; + + std::string charid = std::to_string(GetCharacterID()); + + savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_INSERT, "INSERT INTO character_custom_spell_display (charid, spell_id, idx, field, value) VALUES (%u, %u, %d, 'description', '%s')", + GetCharacterID(), luaspell->spell->GetSpellData()->inherited_spell_id, i, + database.getSafeEscapeString(eff->description.c_str()).c_str()); + + savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_INSERT, "INSERT INTO character_custom_spell_display (charid, spell_id, idx, field, value) VALUES (%u, %u, %d, 'bullet', '%d')", + GetCharacterID(), luaspell->spell->GetSpellData()->inherited_spell_id, i, eff->subbullet); + + savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_INSERT, "INSERT INTO character_custom_spell_display (charid, spell_id, idx, field, value) VALUES (%u, %u, %d, 'percentage', '%d')", + GetCharacterID(), luaspell->spell->GetSpellData()->inherited_spell_id, i, eff->percentage); + } +} void Player::SaveSpellEffects() { if(stop_save_spell_effects) @@ -7346,6 +7454,9 @@ void Player::SaveSpellEffects() Query savedEffects; savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_spell_effects where charid=%u", GetCharacterID()); savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_spell_effect_targets where caster_char_id=%u", GetCharacterID()); + savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_custom_spell_dataindex where charid=%u", GetCharacterID()); + savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_custom_spell_display where charid=%u", GetCharacterID()); + savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_custom_spell_data where charid=%u", GetCharacterID()); InfoStruct* info = GetInfoStruct(); MSpellEffects.readlock(__FUNCTION__, __LINE__); MMaintainedSpells.readlock(__FUNCTION__, __LINE__); @@ -7376,6 +7487,10 @@ void Player::SaveSpellEffects() info->spell_effects[i].total_time, timestamp, database.getSafeEscapeString(info->spell_effects[i].spell->file_name.c_str()).c_str(), info->spell_effects[i].spell->spell->IsCopiedSpell(), GetCharacterID(), info->spell_effects[i].spell->damage_remaining, info->spell_effects[i].spell->effect_bitmask, info->spell_effects[i].spell->num_triggers, info->spell_effects[i].spell->had_triggers, info->spell_effects[i].spell->cancel_after_all_triggers, info->spell_effects[i].spell->crit, info->spell_effects[i].spell->last_spellattack_hit, info->spell_effects[i].spell->interrupted, info->spell_effects[i].spell->resisted, info->spell_effects[i].spell->has_damaged, (info->maintained_effects[i].expire_timestamp) == 0xFFFFFFFF ? "" : database.getSafeEscapeString(spellProcess->SpellScriptTimerCustomFunction(info->spell_effects[i].spell).c_str()).c_str(), info->spell_effects[i].spell->initial_caster_level); + + SaveCustomSpellFields(info->spell_effects[i].spell); + SaveCustomSpellDataIndex(info->spell_effects[i].spell); + SaveCustomSpellEffectsDisplay(info->spell_effects[i].spell); } if (i < NUM_MAINTAINED_EFFECTS && info->maintained_effects[i].spell_id != 0xFFFFFFFF){ Spawn* spawn = GetZone()->GetSpawnByID(info->maintained_effects[i].spell->initial_target); @@ -7404,6 +7519,10 @@ void Player::SaveSpellEffects() info->maintained_effects[i].spell->damage_remaining, info->maintained_effects[i].spell->effect_bitmask, info->maintained_effects[i].spell->num_triggers, info->maintained_effects[i].spell->had_triggers, info->maintained_effects[i].spell->cancel_after_all_triggers, info->maintained_effects[i].spell->crit, info->maintained_effects[i].spell->last_spellattack_hit, info->maintained_effects[i].spell->interrupted, info->maintained_effects[i].spell->resisted, info->maintained_effects[i].spell->has_damaged, (info->maintained_effects[i].expire_timestamp) == 0xFFFFFFFF ? "" : database.getSafeEscapeString(spellProcess->SpellScriptTimerCustomFunction(info->maintained_effects[i].spell).c_str()).c_str(), info->maintained_effects[i].spell->initial_caster_level); + SaveCustomSpellFields(info->maintained_effects[i].spell); + SaveCustomSpellDataIndex(info->maintained_effects[i].spell); + SaveCustomSpellEffectsDisplay(info->maintained_effects[i].spell); + std::string insertTargets = string("insert into character_spell_effect_targets (caster_char_id, target_char_id, target_type, db_effect_type, spell_id, effect_slot, slot_pos) values "); bool firstTarget = true; map targetsInserted; diff --git a/source/WorldServer/Player.h b/source/WorldServer/Player.h index d89b02d..b0324f0 100644 --- a/source/WorldServer/Player.h +++ b/source/WorldServer/Player.h @@ -589,7 +589,7 @@ public: EQ2Packet* MoveInventoryItem(sint32 to_bag_id, int16 from_index, int8 new_slot, int8 charges, int8 appearance_type, bool* item_deleted, int16 version = 1); bool IsPlayer(){ return true; } MaintainedEffects* GetFreeMaintainedSpellSlot(); - MaintainedEffects* GetMaintainedSpell(int32 id); + MaintainedEffects* GetMaintainedSpell(int32 id, bool on_char_load = false); MaintainedEffects* GetMaintainedSpellBySlot(int8 slot); MaintainedEffects* GetMaintainedSpells(); SpellEffects* GetFreeSpellEffectSlot(); @@ -1050,7 +1050,10 @@ public: void DismissAllPets(); void SaveSpellEffects(); - + void SaveCustomSpellFields(LuaSpell* luaspell); + void SaveCustomSpellDataIndex(LuaSpell* luaspell); + void SaveCustomSpellEffectsDisplay(LuaSpell* luaspell); + void SetSaveSpellEffects(bool val) { stop_save_spell_effects = val; } AppearanceData SavedApp; CharFeatures SavedFeatures; diff --git a/source/WorldServer/Spells.cpp b/source/WorldServer/Spells.cpp index a1d2ea4..3127f0c 100644 --- a/source/WorldServer/Spells.cpp +++ b/source/WorldServer/Spells.cpp @@ -222,7 +222,7 @@ void Spell::AddSpellLuaData(int8 type, int int_value, int int_value2, float floa data->string_value = string_value; data->string_value2 = string_value2; data->string_helper = helper; - + data->needs_db_save = false; lua_data.push_back(data); } @@ -237,6 +237,7 @@ void Spell::AddSpellLuaDataInt(int value, int value2, string helper) { data->float_value2 = 0; data->bool_value = false; data->string_helper = helper; + data->needs_db_save = false; lua_data.push_back(data); } @@ -252,6 +253,7 @@ void Spell::AddSpellLuaDataFloat(float value, float value2, string helper) { data->float_value2 = value2; data->bool_value = false; data->string_helper = helper; + data->needs_db_save = false; lua_data.push_back(data); } @@ -265,6 +267,7 @@ void Spell::AddSpellLuaDataBool(bool value, string helper) { data->float_value = 0; data->bool_value = value; data->string_helper = helper; + data->needs_db_save = false; lua_data.push_back(data); } @@ -282,6 +285,7 @@ void Spell::AddSpellLuaDataString(string value, string value2,string helper) { data->string_value = value; data->string_value2 = value2; data->string_helper = helper; + data->needs_db_save = false; lua_data.push_back(data); } @@ -1154,6 +1158,7 @@ void Spell::AddSpellEffect(int8 percentage, int8 subbullet, string description){ effect->description = description; effect->subbullet = subbullet; effect->percentage = percentage; + effect->needs_db_save = false; effects.push_back(effect); } diff --git a/source/WorldServer/Spells.h b/source/WorldServer/Spells.h index 8a86cf4..73fcdcc 100644 --- a/source/WorldServer/Spells.h +++ b/source/WorldServer/Spells.h @@ -205,6 +205,7 @@ struct LUAData{ sint32 int_value2; float float_value2; string string_helper; + bool needs_db_save; }; struct SpellScriptTimer { LuaSpell* spell; @@ -224,6 +225,7 @@ struct SpellDisplayEffect{ int8 percentage; int8 subbullet; string description; + bool needs_db_save; }; enum GivenByType { @@ -352,6 +354,12 @@ public: int16 GetSavageryRequired(Spawn* spawn); int16 GetDissonanceRequired(Spawn* spawn); SpellData* GetSpellData(); + SpellDisplayEffect* GetSpellDisplayEffectSafe(int index) const { + if (index < 0 || index >= effects.size()) + return nullptr; + return effects[index]; + } + bool GetSpellData(lua_State* state, std::string field); bool SetSpellData(lua_State* state, std::string field, int8 fieldArg); bool ScribeAllowed(Player* player); diff --git a/source/WorldServer/WorldDatabase.cpp b/source/WorldServer/WorldDatabase.cpp index 3332138..f2eefcf 100644 --- a/source/WorldServer/WorldDatabase.cpp +++ b/source/WorldServer/WorldDatabase.cpp @@ -5283,6 +5283,11 @@ bool WorldDatabase::DeleteCharacter(int32 account_id, int32 character_id){ query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_titles where char_id = %u", character_id); query.AddQueryAsync(character_id, this, Q_DELETE, "delete from char_colors where char_id = %u", character_id); query.AddQueryAsync(character_id, this, Q_DELETE, "delete from statistics where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_spell_effects where charid=%u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_spell_effect_targets where caster_char_id=%u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_custom_spell_dataindex where charid=%u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_custom_spell_display where charid=%u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_custom_spell_data where charid=%u", character_id); return true; } @@ -7970,6 +7975,94 @@ int32 WorldDatabase::CreateSpiritShard(const char* name, int32 level, int8 race, return query.GetLastInsertedID(); } +void WorldDatabase::LoadCustomSpellData(Client* client, LuaSpell* luaspell) { + if (!luaspell || !luaspell->spell || !luaspell->spell->GetSpellData() || !luaspell->spell->IsCopiedSpell()) + return; + + auto spell_data = luaspell->spell->GetSpellData(); + + DatabaseResult result; + if (!database_new.Select(&result, "SELECT field, type, value FROM character_custom_spell_data WHERE charid = %u AND spell_id = %u", + client->GetPlayer()->GetCharacterID(), luaspell->spell->GetSpellData()->inherited_spell_id)) + return; + + while (result.Next()) { + std::string field = result.GetStringStr("field"); + std::string type = result.GetStringStr("type"); + std::string value = result.GetStringStr("value"); + + if (type == "int") + luaspell->SetSpellDataGeneric(field, atoi(value.c_str())); + else if (type == "float") + luaspell->SetSpellDataGeneric(field, static_cast(atof(value.c_str()))); + else if (type == "bool") + luaspell->SetSpellDataGeneric(field, value == "1"); + else if (type == "string") + luaspell->SetSpellDataGeneric(field, value); + + luaspell->MarkFieldModified(field); + } +} + +void WorldDatabase::LoadCustomSpellDataIndex(Client* client, LuaSpell* luaspell) { + if (!luaspell || !luaspell->spell || !luaspell->spell->GetSpellData() || !luaspell->spell->IsCopiedSpell()) + return; + + DatabaseResult result; + if (!database_new.Select(&result, "SELECT idx, type, value1, value2 FROM character_custom_spell_dataindex WHERE charid = %u AND spell_id = %u", + client->GetPlayer()->GetCharacterID(), luaspell->spell->GetSpellData()->inherited_spell_id)) + return; + + while (result.Next()) { + int idx = result.GetInt32Str("idx"); + std::string type = result.GetStringStr("type"); + std::string v1 = result.GetStringStr("value1"); + std::string v2 = result.GetStringStr("value2"); + + if (type == "int") + luaspell->SetSpellDataIndex(idx, atoi(v1.c_str()), atoi(v2.c_str())); + else if (type == "float") + luaspell->SetSpellDataIndex(idx, static_cast(atof(v1.c_str())), static_cast(atof(v2.c_str()))); + else if (type == "bool") + luaspell->SetSpellDataIndex(idx, v1 == "1"); + else if (type == "string") + luaspell->SetSpellDataIndex(idx, v1, v2); + } +} + +void WorldDatabase::LoadCustomSpellDisplayEffects(Client* client, LuaSpell* luaspell) { + if (!luaspell || !luaspell->spell || !luaspell->spell->GetSpellData() || !luaspell->spell->IsCopiedSpell()) + return; + + DatabaseResult result; + if (!database_new.Select(&result, "SELECT idx, field, value FROM character_custom_spell_display WHERE charid = %u AND spell_id = %u", + client->GetPlayer()->GetCharacterID(), luaspell->spell->GetSpellData()->inherited_spell_id)) + return; + + while (result.Next()) { + int idx = result.GetInt32Str("idx"); + std::string field = result.GetStringStr("field"); + std::string value = result.GetStringStr("value"); + + SpellDisplayEffect* effect = luaspell->spell->GetSpellDisplayEffectSafe(idx); + if (!effect) + continue; + + if (field == "description") { + effect->description = value; + effect->needs_db_save = true; + } + else if (field == "bullet") { + effect->subbullet = atoi(value.c_str()); + effect->needs_db_save = true; + } + else if (field == "percentage") { + effect->percentage = atoi(value.c_str()); + effect->needs_db_save = true; + } + } +} + void WorldDatabase::LoadCharacterSpellEffects(int32 char_id, Client* client, int8 db_spell_type) { SpellProcess* spellProcess = client->GetCurrentZone()->GetSpellProcess(); @@ -8049,7 +8142,7 @@ void WorldDatabase::LoadCharacterSpellEffects(int32 char_id, Client* client, int bool isExistingLuaSpell = false; MaintainedEffects* effect = nullptr; Client* tmpCaster = nullptr; - if(caster_char_id == player->GetCharacterID() && (target_char_id == 0xFFFFFFFF || target_char_id == player->GetCharacterID()) && (effect = player->GetMaintainedSpell(spell_id)) != nullptr) + if(caster_char_id == player->GetCharacterID() && (target_char_id == 0xFFFFFFFF || target_char_id == player->GetCharacterID()) && (effect = player->GetMaintainedSpell(spell_id, true)) != nullptr) { safe_delete(lua_spell); lua_spell = effect->spell; @@ -8059,9 +8152,9 @@ void WorldDatabase::LoadCharacterSpellEffects(int32 char_id, Client* client, int isExistingLuaSpell = true; } else if ( caster_char_id != player->GetCharacterID() && (tmpCaster = zone_list.GetClientByCharID(caster_char_id)) != nullptr - && tmpCaster->GetPlayer() && (effect = tmpCaster->GetPlayer()->GetMaintainedSpell(spell_id)) != nullptr) + && tmpCaster->GetPlayer() && (effect = tmpCaster->GetPlayer()->GetMaintainedSpell(spell_id, true)) != nullptr) { - if(effect->spell && effect->spell_id == spell_id) + if(effect->spell && (effect->spell_id == spell_id || effect->inherited_spell_id == spell_id)) { safe_delete(lua_spell); if(tmpCaster->GetCurrentZone() == player->GetZone()) @@ -8155,6 +8248,10 @@ void WorldDatabase::LoadCharacterSpellEffects(int32 char_id, Client* client, int //lua_spell->num_calls ?? //if(target_char_id == player->GetCharacterID()) // lua_spell->targets.push_back(player->GetID()); + LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCustomSpell: %s (%u) lua_spell caster %s (%u), caster char id: %u. IsCopiedSpell: %u", lua_spell->spell->GetName(), lua_spell->spell->GetSpellID(), lua_spell->caster ? lua_spell->caster->GetName() : "", lua_spell->caster ? lua_spell->caster->GetID() : 0, caster_char_id, lua_spell->spell->IsCopiedSpell()); + LoadCustomSpellData(client, lua_spell); + LoadCustomSpellDataIndex(client, lua_spell); + LoadCustomSpellDisplayEffects(client, lua_spell); if(db_spell_type == DB_TYPE_SPELLEFFECTS) { @@ -8212,7 +8309,8 @@ void WorldDatabase::LoadCharacterSpellEffects(int32 char_id, Client* client, int info->spell_effects[effect_slot].expire_timestamp = Timer::GetCurrentTime2() + expire_timestamp; info->spell_effects[effect_slot].icon = icon; info->spell_effects[effect_slot].icon_backdrop = icon_backdrop; - info->spell_effects[effect_slot].spell_id = spell_id; + info->spell_effects[effect_slot].spell_id = lua_spell->spell->GetSpellID(); + info->spell_effects[effect_slot].inherited_spell_id = lua_spell->spell->GetSpellData()->inherited_spell_id; info->spell_effects[effect_slot].tier = tier; info->spell_effects[effect_slot].total_time = total_time; info->spell_effects[effect_slot].spell = lua_spell; @@ -8315,7 +8413,8 @@ void WorldDatabase::LoadCharacterSpellEffects(int32 char_id, Client* client, int info->maintained_effects[effect_slot].expire_timestamp = Timer::GetCurrentTime2() + expire_timestamp; info->maintained_effects[effect_slot].icon = icon; info->maintained_effects[effect_slot].icon_backdrop = icon_backdrop; - info->maintained_effects[effect_slot].spell_id = spell_id; + info->maintained_effects[effect_slot].spell_id = lua_spell->spell->GetSpellID(); + info->maintained_effects[effect_slot].inherited_spell_id = lua_spell->spell->GetSpellData()->inherited_spell_id; info->maintained_effects[effect_slot].tier = tier; info->maintained_effects[effect_slot].total_time = total_time; info->maintained_effects[effect_slot].spell = lua_spell; @@ -8387,7 +8486,7 @@ void WorldDatabase::LoadCharacterSpellEffects(int32 char_id, Client* client, int Client* tmpCaster = nullptr; MaintainedEffects* effect = nullptr; if (caster_char_id != player->GetCharacterID() && (tmpCaster = zone_list.GetClientByCharID(caster_char_id)) != nullptr && (cross_zone_target_buff || - tmpCaster->GetCurrentZone() == player->GetZone()) && tmpCaster->GetPlayer() && (effect = tmpCaster->GetPlayer()->GetMaintainedSpell(in_spell_id)) != nullptr) + tmpCaster->GetCurrentZone() == player->GetZone()) && tmpCaster->GetPlayer() && (effect = tmpCaster->GetPlayer()->GetMaintainedSpell(in_spell_id, true)) != nullptr) { if(prev_target_type > 0) { @@ -8399,7 +8498,7 @@ void WorldDatabase::LoadCharacterSpellEffects(int32 char_id, Client* client, int restoreSpells.insert(make_pair(effect->spell, player->GetCharmedPet())); } } - else if(!player->GetSpellEffect(effect->spell_id, tmpCaster->GetPlayer())) + else if(!player->GetSpellEffect(effect->spell_id, tmpCaster->GetPlayer(), true)) { if(effect->spell->initial_target_char_id == player->GetCharacterID()) effect->spell->initial_target = player->GetID(); diff --git a/source/WorldServer/WorldDatabase.h b/source/WorldServer/WorldDatabase.h index 40c4742..ab53169 100644 --- a/source/WorldServer/WorldDatabase.h +++ b/source/WorldServer/WorldDatabase.h @@ -656,6 +656,9 @@ public: float x, float y, float z, float heading, int32 gridid, int32 charid, int32 zoneid, int32 instanceid); bool DeleteSpiritShard(int32 id); + void LoadCustomSpellData(Client* client, LuaSpell* luaspell); + void LoadCustomSpellDataIndex(Client* client, LuaSpell* luaspell); + void LoadCustomSpellDisplayEffects(Client* client, LuaSpell* luaspell); void LoadCharacterSpellEffects(int32 char_id, Client *client, int8 db_spell_type); int32 GetMysqlExpCurve(int level);