[Quest API] Add Spell Blocked Event to Perl/Lua (#4217)

* [Spells] Add Unblockable Spell Table and Spell Blocked Event

- Add `EVENT_SPELL_BLOCKED`, exports `$blocking_spell_id`, `$cast_spell_id`, `$blocking_spell`, and `$cast_spell`.

- Add `event_spell_blocked`, exports `e.blocking_spell_id`, `e.cast_spell_id`, `e.blocking_spell`, and `e.cast_spell`.

- Adds `spells_unblockable` table with a `spell_id` and `is_unblockable` column.
- This table will need to be populated based on known spells that should be unblockable.

- This event will allow operators to perform events when spells are blocked.

* Cleanup

* Cleanup

* Update spells.cpp

* Remove unused repositories.

* Finalize

* Update lua_parser_events.cpp
This commit is contained in:
Alex King 2024-03-31 23:58:30 -04:00 committed by GitHub
parent 048aad437b
commit d77966797e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 258 additions and 20 deletions

View File

@ -1854,9 +1854,9 @@ void SharedDatabase::LoadSpells(void *data, int max_spells) {
sp[tempid].min_range = Strings::ToFloat(row[231]); sp[tempid].min_range = Strings::ToFloat(row[231]);
sp[tempid].no_remove = Strings::ToBool(row[232]); sp[tempid].no_remove = Strings::ToBool(row[232]);
sp[tempid].damage_shield_type = 0; sp[tempid].damage_shield_type = 0;
} }
LoadDamageShieldTypes(sp, max_spells); LoadDamageShieldTypes(sp, max_spells);
} }
void SharedDatabase::LoadCharacterInspectMessage(uint32 character_id, InspectMessage_Struct* message) { void SharedDatabase::LoadCharacterInspectMessage(uint32 character_id, InspectMessage_Struct* message) {

View File

@ -202,6 +202,7 @@ const char* QuestEventSubroutines[_LargestEventID] = {
"EVENT_ENTITY_VARIABLE_SET", "EVENT_ENTITY_VARIABLE_SET",
"EVENT_ENTITY_VARIABLE_UPDATE", "EVENT_ENTITY_VARIABLE_UPDATE",
"EVENT_AA_LOSS", "EVENT_AA_LOSS",
"EVENT_SPELL_BLOCKED",
// Add new events before these or Lua crashes // Add new events before these or Lua crashes
"EVENT_SPELL_EFFECT_BOT", "EVENT_SPELL_EFFECT_BOT",
@ -1937,6 +1938,25 @@ void PerlembParser::ExportEventVariables(
break; 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 //tradeskill events
case EVENT_COMBINE_SUCCESS: case EVENT_COMBINE_SUCCESS:
case EVENT_COMBINE_FAILURE: { case EVENT_COMBINE_FAILURE: {

View File

@ -143,6 +143,7 @@ typedef enum {
EVENT_ENTITY_VARIABLE_SET, EVENT_ENTITY_VARIABLE_SET,
EVENT_ENTITY_VARIABLE_UPDATE, EVENT_ENTITY_VARIABLE_UPDATE,
EVENT_AA_LOSS, EVENT_AA_LOSS,
EVENT_SPELL_BLOCKED,
// Add new events before these or Lua crashes // Add new events before these or Lua crashes
EVENT_SPELL_EFFECT_BOT, EVENT_SPELL_EFFECT_BOT,

View File

@ -184,6 +184,7 @@ const char *LuaEvents[_LargestEventID] = {
"event_entity_variable_set", "event_entity_variable_set",
"event_entity_variable_update", "event_entity_variable_update",
"event_aa_loss" "event_aa_loss"
"event_spell_blocked"
}; };
extern Zone *zone; extern Zone *zone;
@ -257,6 +258,7 @@ LuaParser::LuaParser() {
NPCArgumentDispatch[EVENT_ENTITY_VARIABLE_DELETE] = handle_npc_entity_variable; NPCArgumentDispatch[EVENT_ENTITY_VARIABLE_DELETE] = handle_npc_entity_variable;
NPCArgumentDispatch[EVENT_ENTITY_VARIABLE_SET] = handle_npc_entity_variable; NPCArgumentDispatch[EVENT_ENTITY_VARIABLE_SET] = handle_npc_entity_variable;
NPCArgumentDispatch[EVENT_ENTITY_VARIABLE_UPDATE] = 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_SAY] = handle_player_say;
PlayerArgumentDispatch[EVENT_ENVIRONMENTAL_DAMAGE] = handle_player_environmental_damage; 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_SET] = handle_player_entity_variable;
PlayerArgumentDispatch[EVENT_ENTITY_VARIABLE_UPDATE] = handle_player_entity_variable; PlayerArgumentDispatch[EVENT_ENTITY_VARIABLE_UPDATE] = handle_player_entity_variable;
PlayerArgumentDispatch[EVENT_AA_LOSS] = handle_player_aa_loss; 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] = handle_item_click;
ItemArgumentDispatch[EVENT_ITEM_CLICK_CAST] = 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_DELETE] = handle_bot_entity_variable;
BotArgumentDispatch[EVENT_ENTITY_VARIABLE_SET] = handle_bot_entity_variable; BotArgumentDispatch[EVENT_ENTITY_VARIABLE_SET] = handle_bot_entity_variable;
BotArgumentDispatch[EVENT_ENTITY_VARIABLE_UPDATE] = handle_bot_entity_variable; BotArgumentDispatch[EVENT_ENTITY_VARIABLE_UPDATE] = handle_bot_entity_variable;
BotArgumentDispatch[EVENT_SPELL_BLOCKED] = handle_bot_spell_blocked;
#endif #endif
L = nullptr; L = nullptr;

View File

@ -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<std::any> *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 // Player
void handle_player_say( void handle_player_say(
QuestInterface *parse, QuestInterface *parse,
@ -1674,11 +1707,44 @@ void handle_player_aa_loss(
std::string data, std::string data,
uint32 extra_data, uint32 extra_data,
std::vector<std::any> *extra_pointers std::vector<std::any> *extra_pointers
) { )
{
lua_pushinteger(L, Strings::ToInt(data)); lua_pushinteger(L, Strings::ToInt(data));
lua_setfield(L, -2, "aa_lost"); 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<std::any> *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 // Item
void handle_item_click( void handle_item_click(
QuestInterface *parse, 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<std::any> *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 #endif

View File

@ -250,6 +250,16 @@ void handle_npc_entity_variable(
std::vector<std::any> *extra_pointers std::vector<std::any> *extra_pointers
); );
void handle_npc_spell_blocked(
QuestInterface *parse,
lua_State* L,
NPC* npc,
Mob *init,
std::string data,
uint32 extra_data,
std::vector<std::any> *extra_pointers
);
// Player // Player
void handle_player_say( void handle_player_say(
QuestInterface *parse, QuestInterface *parse,
@ -836,6 +846,15 @@ void handle_player_aa_loss(
std::vector<std::any> *extra_pointers std::vector<std::any> *extra_pointers
); );
void handle_player_spell_blocked(
QuestInterface *parse,
lua_State* L,
Client* client,
std::string data,
uint32 extra_data,
std::vector<std::any> *extra_pointers
);
// Item // Item
void handle_item_click( void handle_item_click(
QuestInterface *parse, QuestInterface *parse,
@ -1230,5 +1249,15 @@ void handle_bot_entity_variable(
std::vector<std::any> *extra_pointers std::vector<std::any> *extra_pointers
); );
void handle_bot_spell_blocked(
QuestInterface *parse,
lua_State* L,
Bot* bot,
Mob* init,
std::string data,
uint32 extra_data,
std::vector<std::any> *extra_pointers
);
#endif #endif
#endif #endif

View File

@ -3066,7 +3066,17 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2,
int blocked_effect, blocked_below_value, blocked_slot; int blocked_effect, blocked_below_value, blocked_slot;
int overwrite_effect, overwrite_below_value, overwrite_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)) { if (IsResurrectionEffects(spellid1)) {
return 0; return 0;
@ -3534,28 +3544,103 @@ int Mob::AddBuff(Mob *caster, uint16 spell_id, int duration, int32 level_overrid
if (IsValidSpell(curbuf.spellid)) { if (IsValidSpell(curbuf.spellid)) {
// there's a buff in this slot // there's a buff in this slot
ret = CheckStackConflict(curbuf.spellid, curbuf.casterlevel, spell_id, ret = CheckStackConflict(
caster_level, entity_list.GetMobID(curbuf.casterid), caster, buffslot); curbuf.spellid,
if (ret == -1) { // stop the spell curbuf.casterlevel,
LogSpells("Adding buff [{}] failed: stacking prevented by spell [{}] in slot [{}] with caster level [{}]", spell_id,
spell_id, curbuf.spellid, buffslot, curbuf.casterlevel); caster_level,
if (caster && caster->IsClient() && RuleB(Client, UseLiveBlockedMessage)) { entity_list.GetMobID(curbuf.casterid),
if (caster->GetClass() != Class::Bard) { caster,
caster->Message(Chat::Red, "Your %s did not take hold on %s. (Blocked by %s.)", spells[spell_id].name, GetName(), spells[curbuf.spellid].name); 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; return -1;
} } else if (ret == 1 && !will_overwrite) {
if (ret == 1) { // set a flag to indicate that there will be overwriting // set a flag to indicate that there will be overwriting
LogSpells("Adding buff [{}] will overwrite spell [{}] in slot [{}] with caster level [{}]", LogSpells(
spell_id, curbuf.spellid, buffslot, curbuf.casterlevel); "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 // If this is the first buff it would override, use its slot
will_overwrite = true; will_overwrite = true;
overwrite_slots.push_back(buffslot); overwrite_slots.push_back(buffslot);
} } else if (ret == 2) {
if (ret == 2) { //ResurrectionEffectBlock handling to move potential overwrites to a new buff slock while keeping Res Sickness //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.", LogSpells(
spell_id, curbuf.spellid, buffslot, curbuf.casterlevel, spell_id); "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 { } else {
if (emptyslot == -1) { if (emptyslot == -1) {