diff --git a/common/shareddb.cpp b/common/shareddb.cpp index a368357b2..65333fdce 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -1854,9 +1854,9 @@ void SharedDatabase::LoadSpells(void *data, int max_spells) { sp[tempid].min_range = Strings::ToFloat(row[231]); sp[tempid].no_remove = Strings::ToBool(row[232]); sp[tempid].damage_shield_type = 0; - } + } - LoadDamageShieldTypes(sp, max_spells); + LoadDamageShieldTypes(sp, max_spells); } void SharedDatabase::LoadCharacterInspectMessage(uint32 character_id, InspectMessage_Struct* message) { diff --git a/zone/embparser.cpp b/zone/embparser.cpp index 29f83b539..8a02c4a7d 100644 --- a/zone/embparser.cpp +++ b/zone/embparser.cpp @@ -202,6 +202,7 @@ const char* QuestEventSubroutines[_LargestEventID] = { "EVENT_ENTITY_VARIABLE_SET", "EVENT_ENTITY_VARIABLE_UPDATE", "EVENT_AA_LOSS", + "EVENT_SPELL_BLOCKED", // Add new events before these or Lua crashes "EVENT_SPELL_EFFECT_BOT", @@ -1937,6 +1938,25 @@ void PerlembParser::ExportEventVariables( break; } + case EVENT_SPELL_BLOCKED: { + Seperator sep(data); + const uint32 blocking_spell_id = Strings::ToUnsignedInt(sep.arg[0]); + const uint32 cast_spell_id = Strings::ToUnsignedInt(sep.arg[1]); + + ExportVar(package_name.c_str(), "blocking_spell_id", blocking_spell_id); + ExportVar(package_name.c_str(), "cast_spell_id", cast_spell_id); + + if (IsValidSpell(blocking_spell_id)) { + ExportVar(package_name.c_str(), "blocking_spell", "Spell", (void*) &spells[blocking_spell_id]); + } + + if (IsValidSpell(cast_spell_id)) { + ExportVar(package_name.c_str(), "cast_spell", "Spell", (void*) &spells[cast_spell_id]); + } + + break; + } + //tradeskill events case EVENT_COMBINE_SUCCESS: case EVENT_COMBINE_FAILURE: { diff --git a/zone/event_codes.h b/zone/event_codes.h index 5542f7fa7..ec4fbef0a 100644 --- a/zone/event_codes.h +++ b/zone/event_codes.h @@ -143,6 +143,7 @@ typedef enum { EVENT_ENTITY_VARIABLE_SET, EVENT_ENTITY_VARIABLE_UPDATE, EVENT_AA_LOSS, + EVENT_SPELL_BLOCKED, // Add new events before these or Lua crashes EVENT_SPELL_EFFECT_BOT, diff --git a/zone/lua_parser.cpp b/zone/lua_parser.cpp index f5c284fd0..608e3c5fe 100644 --- a/zone/lua_parser.cpp +++ b/zone/lua_parser.cpp @@ -184,6 +184,7 @@ const char *LuaEvents[_LargestEventID] = { "event_entity_variable_set", "event_entity_variable_update", "event_aa_loss" + "event_spell_blocked" }; extern Zone *zone; @@ -257,6 +258,7 @@ LuaParser::LuaParser() { NPCArgumentDispatch[EVENT_ENTITY_VARIABLE_DELETE] = handle_npc_entity_variable; NPCArgumentDispatch[EVENT_ENTITY_VARIABLE_SET] = handle_npc_entity_variable; NPCArgumentDispatch[EVENT_ENTITY_VARIABLE_UPDATE] = handle_npc_entity_variable; + NPCArgumentDispatch[EVENT_SPELL_BLOCKED] = handle_npc_spell_blocked; PlayerArgumentDispatch[EVENT_SAY] = handle_player_say; PlayerArgumentDispatch[EVENT_ENVIRONMENTAL_DAMAGE] = handle_player_environmental_damage; @@ -345,6 +347,7 @@ LuaParser::LuaParser() { PlayerArgumentDispatch[EVENT_ENTITY_VARIABLE_SET] = handle_player_entity_variable; PlayerArgumentDispatch[EVENT_ENTITY_VARIABLE_UPDATE] = handle_player_entity_variable; PlayerArgumentDispatch[EVENT_AA_LOSS] = handle_player_aa_loss; + PlayerArgumentDispatch[EVENT_SPELL_BLOCKED] = handle_player_spell_blocked; ItemArgumentDispatch[EVENT_ITEM_CLICK] = handle_item_click; ItemArgumentDispatch[EVENT_ITEM_CLICK_CAST] = handle_item_click; @@ -399,6 +402,7 @@ LuaParser::LuaParser() { BotArgumentDispatch[EVENT_ENTITY_VARIABLE_DELETE] = handle_bot_entity_variable; BotArgumentDispatch[EVENT_ENTITY_VARIABLE_SET] = handle_bot_entity_variable; BotArgumentDispatch[EVENT_ENTITY_VARIABLE_UPDATE] = handle_bot_entity_variable; + BotArgumentDispatch[EVENT_SPELL_BLOCKED] = handle_bot_spell_blocked; #endif L = nullptr; diff --git a/zone/lua_parser_events.cpp b/zone/lua_parser_events.cpp index 73265c769..77cff0872 100644 --- a/zone/lua_parser_events.cpp +++ b/zone/lua_parser_events.cpp @@ -626,6 +626,39 @@ void handle_npc_entity_variable( } } +void handle_npc_spell_blocked( + QuestInterface *parse, + lua_State* L, + NPC* npc, + Mob *init, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +) +{ + Seperator sep(data.c_str()); + + lua_pushinteger(L, Strings::ToUnsignedInt(sep.arg[0])); + lua_setfield(L, -2, "blocking_spell_id"); + + lua_pushinteger(L, Strings::ToUnsignedInt(sep.arg[1])); + lua_setfield(L, -2, "cast_spell_id"); + + const uint32 blocking_spell_id = Strings::ToUnsignedInt(sep.arg[0]); + + Lua_Spell l_spell_one(IsValidSpell(blocking_spell_id) ? &spells[blocking_spell_id] : nullptr); + luabind::adl::object l_spell_one_o = luabind::adl::object(L, l_spell_one); + l_spell_one_o.push(L); + lua_setfield(L, -2, "blocking_spell"); + + const uint32 cast_spell_id = Strings::ToUnsignedInt(sep.arg[0]); + + Lua_Spell l_spell_two(IsValidSpell(cast_spell_id) ? &spells[cast_spell_id] : nullptr); + luabind::adl::object l_spell_two_o = luabind::adl::object(L, l_spell_two); + l_spell_two_o.push(L); + lua_setfield(L, -2, "cast_spell"); +} + // Player void handle_player_say( QuestInterface *parse, @@ -1674,11 +1707,44 @@ void handle_player_aa_loss( std::string data, uint32 extra_data, std::vector *extra_pointers -) { +) +{ lua_pushinteger(L, Strings::ToInt(data)); lua_setfield(L, -2, "aa_lost"); } +void handle_player_spell_blocked( + QuestInterface *parse, + lua_State* L, + Client* client, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +) +{ + Seperator sep(data.c_str()); + + lua_pushinteger(L, Strings::ToUnsignedInt(sep.arg[0])); + lua_setfield(L, -2, "blocking_spell_id"); + + lua_pushinteger(L, Strings::ToUnsignedInt(sep.arg[1])); + lua_setfield(L, -2, "cast_spell_id"); + + const uint32 blocking_spell_id = Strings::ToUnsignedInt(sep.arg[0]); + + Lua_Spell l_spell_one(IsValidSpell(blocking_spell_id) ? &spells[blocking_spell_id] : nullptr); + luabind::adl::object l_spell_one_o = luabind::adl::object(L, l_spell_one); + l_spell_one_o.push(L); + lua_setfield(L, -2, "blocking_spell"); + + const uint32 cast_spell_id = Strings::ToUnsignedInt(sep.arg[0]); + + Lua_Spell l_spell_two(IsValidSpell(cast_spell_id) ? &spells[cast_spell_id] : nullptr); + luabind::adl::object l_spell_two_o = luabind::adl::object(L, l_spell_two); + l_spell_two_o.push(L); + lua_setfield(L, -2, "cast_spell"); +} + // Item void handle_item_click( QuestInterface *parse, @@ -2690,4 +2756,37 @@ void handle_bot_entity_variable( } } +void handle_bot_spell_blocked( + QuestInterface *parse, + lua_State* L, + Bot* bot, + Mob *init, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +) +{ + Seperator sep(data.c_str()); + + lua_pushinteger(L, Strings::ToUnsignedInt(sep.arg[0])); + lua_setfield(L, -2, "blocking_spell_id"); + + lua_pushinteger(L, Strings::ToUnsignedInt(sep.arg[1])); + lua_setfield(L, -2, "cast_spell_id"); + + const uint32 blocking_spell_id = Strings::ToUnsignedInt(sep.arg[0]); + + Lua_Spell l_spell_one(IsValidSpell(blocking_spell_id) ? &spells[blocking_spell_id] : nullptr); + luabind::adl::object l_spell_one_o = luabind::adl::object(L, l_spell_one); + l_spell_one_o.push(L); + lua_setfield(L, -2, "blocking_spell"); + + const uint32 cast_spell_id = Strings::ToUnsignedInt(sep.arg[0]); + + Lua_Spell l_spell_two(IsValidSpell(cast_spell_id) ? &spells[cast_spell_id] : nullptr); + luabind::adl::object l_spell_two_o = luabind::adl::object(L, l_spell_two); + l_spell_two_o.push(L); + lua_setfield(L, -2, "cast_spell"); +} + #endif diff --git a/zone/lua_parser_events.h b/zone/lua_parser_events.h index 9d4457ed6..d1d176f1f 100644 --- a/zone/lua_parser_events.h +++ b/zone/lua_parser_events.h @@ -250,6 +250,16 @@ void handle_npc_entity_variable( std::vector *extra_pointers ); +void handle_npc_spell_blocked( + QuestInterface *parse, + lua_State* L, + NPC* npc, + Mob *init, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +); + // Player void handle_player_say( QuestInterface *parse, @@ -836,6 +846,15 @@ void handle_player_aa_loss( std::vector *extra_pointers ); +void handle_player_spell_blocked( + QuestInterface *parse, + lua_State* L, + Client* client, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +); + // Item void handle_item_click( QuestInterface *parse, @@ -1230,5 +1249,15 @@ void handle_bot_entity_variable( std::vector *extra_pointers ); +void handle_bot_spell_blocked( + QuestInterface *parse, + lua_State* L, + Bot* bot, + Mob* init, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +); + #endif #endif diff --git a/zone/spells.cpp b/zone/spells.cpp index 6e5a95dac..b7b848046 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -3066,7 +3066,17 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, int blocked_effect, blocked_below_value, blocked_slot; int overwrite_effect, overwrite_below_value, overwrite_slot; - LogSpells("Check Stacking on old [{}] ([{}]) @ lvl [{}] (by [{}]) vs. new [{}] ([{}]) @ lvl [{}] (by [{}])", sp1.name, spellid1, caster_level1, (caster1==nullptr)?"Nobody":caster1->GetName(), sp2.name, spellid2, caster_level2, (caster2==nullptr)?"Nobody":caster2->GetName()); + LogSpells( + "Check Stacking on old [{}] ([{}]) @ lvl [{}] (by [{}]) vs. new [{}] ([{}]) @ lvl [{}] (by [{}])", + sp1.name, + spellid1, + caster_level1, + !caster1 ? "Nobody" : caster1->GetName(), + sp2.name, + spellid2, + caster_level2, + !caster2 ? "Nobody" : caster2->GetName() + ); if (IsResurrectionEffects(spellid1)) { return 0; @@ -3534,28 +3544,103 @@ int Mob::AddBuff(Mob *caster, uint16 spell_id, int duration, int32 level_overrid if (IsValidSpell(curbuf.spellid)) { // there's a buff in this slot - ret = CheckStackConflict(curbuf.spellid, curbuf.casterlevel, spell_id, - caster_level, entity_list.GetMobID(curbuf.casterid), caster, buffslot); - if (ret == -1) { // stop the spell - LogSpells("Adding buff [{}] failed: stacking prevented by spell [{}] in slot [{}] with caster level [{}]", - spell_id, curbuf.spellid, buffslot, curbuf.casterlevel); - if (caster && caster->IsClient() && RuleB(Client, UseLiveBlockedMessage)) { - if (caster->GetClass() != Class::Bard) { - caster->Message(Chat::Red, "Your %s did not take hold on %s. (Blocked by %s.)", spells[spell_id].name, GetName(), spells[curbuf.spellid].name); + ret = CheckStackConflict( + curbuf.spellid, + curbuf.casterlevel, + spell_id, + caster_level, + entity_list.GetMobID(curbuf.casterid), + caster, + buffslot + ); + + if (ret == -1) { // stop the spell + LogSpells( + "Adding buff [{}] failed: stacking prevented by spell [{}] in slot [{}] with caster level [{}]", + spell_id, + curbuf.spellid, + buffslot, + curbuf.casterlevel + ); + + if (caster) { + if (caster->IsClient() && RuleB(Client, UseLiveBlockedMessage) && caster->GetClass() != Class::Bard) { + caster->Message( + Chat::Red, + fmt::format( + "Your {} did not take hold on {}. (Blocked by {}.)", + spells[spell_id].name, + GetName(), + spells[curbuf.spellid].name + ).c_str() + ); + } + + const bool caster_has_block_event = ( + (caster->IsBot() && parse->BotHasQuestSub(EVENT_SPELL_BLOCKED)) || + (caster->IsClient() && parse->PlayerHasQuestSub(EVENT_SPELL_BLOCKED)) || + (caster->IsNPC() && parse->HasQuestSub(caster->GetNPCTypeID(), EVENT_SPELL_BLOCKED)) + ); + + const bool cast_on_has_block_event = ( + (IsBot() && parse->BotHasQuestSub(EVENT_SPELL_BLOCKED)) || + (IsClient() && parse->PlayerHasQuestSub(EVENT_SPELL_BLOCKED)) || + (IsNPC() && parse->HasQuestSub(GetNPCTypeID(), EVENT_SPELL_BLOCKED)) + ); + + if (caster_has_block_event || cast_on_has_block_event) { + const std::string& export_string = fmt::format( + "{} {}", + curbuf.spellid, + spell_id + ); + + if (caster_has_block_event) { + if (caster->IsBot()) { + parse->EventBot(EVENT_SPELL_BLOCKED, caster->CastToBot(), this, export_string, 0); + } else if (caster->IsClient()) { + parse->EventPlayer(EVENT_SPELL_BLOCKED, caster->CastToClient(), export_string, 0); + } else if (caster->IsNPC()) { + parse->EventNPC(EVENT_SPELL_BLOCKED, caster->CastToNPC(), this, export_string, 0); + } + } + + if (cast_on_has_block_event && caster != this) { + if (IsBot()) { + parse->EventBot(EVENT_SPELL_BLOCKED, CastToBot(), caster, export_string, 0); + } else if (IsClient()) { + parse->EventPlayer(EVENT_SPELL_BLOCKED, CastToClient(), export_string, 0); + } else if (IsNPC()) { + parse->EventNPC(EVENT_SPELL_BLOCKED, CastToNPC(), caster, export_string, 0); + } + } } } + return -1; - } - if (ret == 1) { // set a flag to indicate that there will be overwriting - LogSpells("Adding buff [{}] will overwrite spell [{}] in slot [{}] with caster level [{}]", - spell_id, curbuf.spellid, buffslot, curbuf.casterlevel); + } else if (ret == 1 && !will_overwrite) { + // set a flag to indicate that there will be overwriting + LogSpells( + "Adding buff [{}] will overwrite spell [{}] in slot [{}] with caster level [{}]", + spell_id, + curbuf.spellid, + buffslot, + curbuf.casterlevel + ); + // If this is the first buff it would override, use its slot will_overwrite = true; overwrite_slots.push_back(buffslot); - } - if (ret == 2) { //ResurrectionEffectBlock handling to move potential overwrites to a new buff slock while keeping Res Sickness - LogSpells("Adding buff [{}] will overwrite spell [{}] in slot [{}] with caster level [{}], but ResurrectionEffectBlock is set to 2. Attempting to move [{}] to an empty buff slot.", - spell_id, curbuf.spellid, buffslot, curbuf.casterlevel, spell_id); + } else if (ret == 2) { + //ResurrectionEffectBlock handling to move potential overwrites to a new buff slock while keeping Res Sickness + LogSpells( + "Adding buff [{}] will overwrite spell [{}] in slot [{}] with caster level [{}], but ResurrectionEffectBlock is set to 2. Attempting to move [{}] to an empty buff slot.", + spell_id, + curbuf.spellid, + buffslot, + curbuf.casterlevel, + spell_id + ); } } else { if (emptyslot == -1) {