[Quest API] Add EVENT_READ_ITEM to Perl/Lua (#4497)

* [Quest API] Add EVENT_READ_ITEM to Perl/Lua

* Add item_id export

* Add item export.

* Update client.cpp
This commit is contained in:
Alex King 2024-10-08 18:25:14 -04:00 committed by GitHub
parent 8568cf7d49
commit e3198edb86
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 182 additions and 76 deletions

View File

@ -756,4 +756,10 @@ namespace PCNPCOnlyFlagType {
constexpr int NPC = 2; constexpr int NPC = 2;
} }
namespace BookType {
constexpr uint8 Scroll = 0;
constexpr uint8 Book = 1;
constexpr uint8 ItemInfo = 2;
}
#endif /*COMMON_EMU_CONSTANTS_H*/ #endif /*COMMON_EMU_CONSTANTS_H*/

View File

@ -47,6 +47,7 @@
#include "repositories/character_corpses_repository.h" #include "repositories/character_corpses_repository.h"
#include "repositories/skill_caps_repository.h" #include "repositories/skill_caps_repository.h"
#include "repositories/inventory_repository.h" #include "repositories/inventory_repository.h"
#include "repositories/books_repository.h"
namespace ItemField namespace ItemField
{ {
@ -1391,30 +1392,28 @@ const EQ::ItemData* SharedDatabase::IterateItems(uint32* id) const
return nullptr; return nullptr;
} }
std::string SharedDatabase::GetBook(const char *txtfile, int16 *language) Book_Struct SharedDatabase::GetBook(const std::string& text_file)
{ {
char txtfile2[20]; const auto& l = BooksRepository::GetWhere(
std::string txtout; *this,
strcpy(txtfile2, txtfile); fmt::format(
"`name` = '{}'",
Strings::Escape(text_file)
)
);
const std::string query = StringFormat("SELECT txtfile, language FROM books WHERE name = '%s'", txtfile2); Book_Struct b;
auto results = QueryDatabase(query);
if (!results.Success()) { if (l.empty()) {
txtout.assign(" ",1); return b;
return txtout;
} }
if (results.RowCount() == 0) { const auto& e = l.front();
LogError("No book to send, ({})", txtfile);
txtout.assign(" ",1);
return txtout;
}
auto& row = results.begin(); b.language = e.language;
txtout.assign(row[0],strlen(row[0])); b.text = e.txtfile;
*language = static_cast<int16>(Strings::ToInt(row[1]));
return txtout; return b;
} }
// Create appropriate EQ::ItemInstance class // Create appropriate EQ::ItemInstance class

View File

@ -41,8 +41,7 @@ struct NPCFactionList;
struct FactionAssociations; struct FactionAssociations;
namespace EQ namespace EQ {
{
struct ItemData; struct ItemData;
class ItemInstance; class ItemInstance;
@ -50,6 +49,12 @@ namespace EQ
class MemoryMappedFile; class MemoryMappedFile;
} }
struct Book_Struct
{
uint8 language;
std::string text;
};
/* /*
This object is inherited by world and zone's DB object, This object is inherited by world and zone's DB object,
and is mainly here to facilitate shared memory, and other and is mainly here to facilitate shared memory, and other
@ -114,7 +119,7 @@ public:
int admin int admin
); );
std::string GetBook(const char *txtfile, int16 *language); Book_Struct GetBook(const std::string& text_file);
/** /**
* items * items

View File

@ -2299,52 +2299,67 @@ void Client::SetGM(bool toggle) {
UpdateWho(); UpdateWho();
} }
void Client::ReadBook(BookRequest_Struct *book) { void Client::ReadBook(BookRequest_Struct* book)
int16 book_language=0; {
char *txtfile = book->txtfile; const std::string& text_file = book->txtfile;
if(txtfile[0] == '0' && txtfile[1] == '\0') { if (text_file.empty()) {
//invalid book... coming up on non-book items.
return; return;
} }
std::string booktxt2 = content_db.GetBook(txtfile, &book_language); auto b = content_db.GetBook(text_file);
int length = booktxt2.length();
if (booktxt2[0] != '\0') { if (!b.text.empty()) {
#if EQDEBUG >= 6 auto outapp = new EQApplicationPacket(OP_ReadBook, b.text.size() + sizeof(BookText_Struct));
LogInfo("Client::ReadBook() textfile:[{}] Text:[{}]", txtfile, booktxt2.c_str()); auto inst = const_cast<EQ::ItemInstance*>(m_inv[book->invslot]);
#endif
auto outapp = new EQApplicationPacket(OP_ReadBook, length + sizeof(BookText_Struct));
BookText_Struct *out = (BookText_Struct *) outapp->pBuffer; auto t = (BookText_Struct*) outapp->pBuffer;
out->window = book->window;
out->type = book->type;
out->invslot = book->invslot;
out->target_id = book->target_id;
out->can_cast = 0; // todo: implement
out->can_scribe = false;
if (ClientVersion() >= EQ::versions::ClientVersion::SoF && book->invslot <= EQ::invbag::GENERAL_BAGS_END) t->window = book->window;
{ t->type = book->type;
const EQ::ItemInstance* inst = m_inv[book->invslot]; t->invslot = book->invslot;
if (inst && inst->GetItem()) t->target_id = book->target_id;
{ t->can_cast = 0; // todo: implement
auto recipe = TradeskillRecipeRepository::GetWhere(content_db, t->can_scribe = false;
fmt::format("learned_by_item_id = {} LIMIT 1", inst->GetItem()->ID));
out->type = inst->GetItem()->Book; if (ClientVersion() >= EQ::versions::ClientVersion::SoF && book->invslot <= EQ::invbag::GENERAL_BAGS_END) {
out->can_scribe = !recipe.empty(); if (inst && inst->GetItem()) {
auto recipe = TradeskillRecipeRepository::GetWhere(
content_db,
fmt::format(
"learned_by_item_id = {} LIMIT 1",
inst->GetItem()->ID
)
);
t->type = inst->GetItem()->Book;
t->can_scribe = !recipe.empty();
} }
} }
memcpy(out->booktext, booktxt2.c_str(), length); memcpy(t->booktext, b.text.c_str(), b.text.size());
if (EQ::ValueWithin(book_language, Language::CommonTongue, Language::Unknown27)) { if (EQ::ValueWithin(b.language, Language::CommonTongue, Language::Unknown27)) {
if (m_pp.languages[book_language] < Language::MaxValue) { if (m_pp.languages[b.language] < Language::MaxValue) {
GarbleMessage(out->booktext, (Language::MaxValue - m_pp.languages[book_language])); GarbleMessage(t->booktext, (Language::MaxValue - m_pp.languages[b.language]));
} }
} }
// Send only books and scrolls to this event
if (parse->PlayerHasQuestSub(EVENT_READ_ITEM) && t->type != BookType::ItemInfo) {
std::vector<std::any> args = {
b.text,
t->can_cast,
t->can_scribe,
t->invslot,
t->target_id,
t->type,
inst
};
parse->EventPlayer(EVENT_READ_ITEM, this, book->txtfile, inst ? inst->GetID() : 0, &args);
}
QueuePacket(outapp); QueuePacket(outapp);
safe_delete(outapp); safe_delete(outapp);
} }
@ -10916,23 +10931,24 @@ void Client::SetIPExemption(int exemption_amount)
void Client::ReadBookByName(std::string book_name, uint8 book_type) void Client::ReadBookByName(std::string book_name, uint8 book_type)
{ {
int16 book_language = 0; auto b = content_db.GetBook(book_name);
std::string book_text = content_db.GetBook(book_name.c_str(), &book_language);
int length = book_text.length();
if (book_text[0] != '\0') { if (!b.text.empty()) {
LogDebug("Client::ReadBookByName() Book Name: [{}] Text: [{}]", book_name, book_text.c_str()); LogDebug("Book Name: [{}] Text: [{}]", book_name, b.text);
auto outapp = new EQApplicationPacket(OP_ReadBook, length + sizeof(BookText_Struct));
BookText_Struct *out = (BookText_Struct *) outapp->pBuffer;
out->window = 0xFF;
out->type = book_type;
out->invslot = 0;
memcpy(out->booktext, book_text.c_str(), length); auto outapp = new EQApplicationPacket(OP_ReadBook, b.text.size() + sizeof(BookText_Struct));
if (EQ::ValueWithin(book_language, Language::CommonTongue, Language::Unknown27)) { auto o = (BookText_Struct *) outapp->pBuffer;
if (m_pp.languages[book_language] < Language::MaxValue) {
GarbleMessage(out->booktext, (Language::MaxValue - m_pp.languages[book_language])); o->window = std::numeric_limits<uint8>::max();
o->type = book_type;
o->invslot = 0;
memcpy(o->booktext, b.text.c_str(), b.text.size());
if (EQ::ValueWithin(b.language, Language::CommonTongue, Language::Unknown27)) {
if (m_pp.languages[b.language] < Language::MaxValue) {
GarbleMessage(o->booktext, (Language::MaxValue - m_pp.languages[b.language]));
} }
} }

View File

@ -196,6 +196,7 @@ struct RespawnOption
float heading; float heading;
}; };
// do not ask what all these mean because I have no idea! // do not ask what all these mean because I have no idea!
// named from the client's CEverQuest::GetInnateDesc, they're missing some // named from the client's CEverQuest::GetInnateDesc, they're missing some
enum eInnateSkill { enum eInnateSkill {

View File

@ -13046,14 +13046,14 @@ void Client::Handle_OP_ReadBook(const EQApplicationPacket *app)
LogError("Wrong size: OP_ReadBook, size=[{}], expected [{}]", app->size, sizeof(BookRequest_Struct)); LogError("Wrong size: OP_ReadBook, size=[{}], expected [{}]", app->size, sizeof(BookRequest_Struct));
return; return;
} }
BookRequest_Struct* book = (BookRequest_Struct*)app->pBuffer;
ReadBook(book); auto b = (BookRequest_Struct*) app->pBuffer;
if (ClientVersion() >= EQ::versions::ClientVersion::SoF) ReadBook(b);
{
EQApplicationPacket EndOfBook(OP_FinishWindow, 0); if (ClientVersion() >= EQ::versions::ClientVersion::SoF) {
QueuePacket(&EndOfBook); EQApplicationPacket end_of_book(OP_FinishWindow, 0);
QueuePacket(&end_of_book);
} }
return;
} }
void Client::Handle_OP_RecipeAutoCombine(const EQApplicationPacket *app) void Client::Handle_OP_RecipeAutoCombine(const EQApplicationPacket *app)

View File

@ -203,6 +203,7 @@ const char* QuestEventSubroutines[_LargestEventID] = {
"EVENT_ENTITY_VARIABLE_UPDATE", "EVENT_ENTITY_VARIABLE_UPDATE",
"EVENT_AA_LOSS", "EVENT_AA_LOSS",
"EVENT_SPELL_BLOCKED", "EVENT_SPELL_BLOCKED",
"EVENT_READ_ITEM",
// Add new events before these or Lua crashes // Add new events before these or Lua crashes
"EVENT_SPELL_EFFECT_BOT", "EVENT_SPELL_EFFECT_BOT",
@ -2488,6 +2489,28 @@ void PerlembParser::ExportEventVariables(
break; break;
} }
case EVENT_READ_ITEM: {;
ExportVar(package_name.c_str(), "item_id", extra_data);
ExportVar(package_name.c_str(), "text_file", data);
if (extra_pointers && extra_pointers->size() == 7) {
ExportVar(package_name.c_str(), "book_text", std::any_cast<std::string>(extra_pointers->at(0)).c_str());
ExportVar(package_name.c_str(), "can_cast", std::any_cast<int8>(extra_pointers->at(1)));
ExportVar(package_name.c_str(), "can_scribe", std::any_cast<int8>(extra_pointers->at(2)));
ExportVar(package_name.c_str(), "slot_id", std::any_cast<int16>(extra_pointers->at(3)));
ExportVar(package_name.c_str(), "target_id", std::any_cast<int>(extra_pointers->at(4)));
ExportVar(package_name.c_str(), "type", std::any_cast<uint8>(extra_pointers->at(5)));
ExportVar(
package_name.c_str(),
"item",
"QuestItem",
std::any_cast<EQ::ItemInstance*>(extra_pointers->at(6))
);
}
break;
}
default: { default: {
break; break;
} }

View File

@ -144,6 +144,7 @@ typedef enum {
EVENT_ENTITY_VARIABLE_UPDATE, EVENT_ENTITY_VARIABLE_UPDATE,
EVENT_AA_LOSS, EVENT_AA_LOSS,
EVENT_SPELL_BLOCKED, EVENT_SPELL_BLOCKED,
EVENT_READ_ITEM,
// 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

@ -6907,7 +6907,8 @@ luabind::scope lua_register_events() {
luabind::value("entity_variable_delete", static_cast<int>(EVENT_ENTITY_VARIABLE_DELETE)), luabind::value("entity_variable_delete", static_cast<int>(EVENT_ENTITY_VARIABLE_DELETE)),
luabind::value("entity_variable_set", static_cast<int>(EVENT_ENTITY_VARIABLE_SET)), luabind::value("entity_variable_set", static_cast<int>(EVENT_ENTITY_VARIABLE_SET)),
luabind::value("entity_variable_update", static_cast<int>(EVENT_ENTITY_VARIABLE_UPDATE)), luabind::value("entity_variable_update", static_cast<int>(EVENT_ENTITY_VARIABLE_UPDATE)),
luabind::value("aa_loss", static_cast<int>(EVENT_AA_LOSS)) luabind::value("aa_loss", static_cast<int>(EVENT_AA_LOSS)),
luabind::value("read", static_cast<int>(EVENT_READ_ITEM))
)]; )];
} }

View File

@ -184,7 +184,8 @@ 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" "event_spell_blocked",
"event_read_item"
}; };
extern Zone *zone; extern Zone *zone;
@ -348,6 +349,7 @@ LuaParser::LuaParser() {
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; PlayerArgumentDispatch[EVENT_SPELL_BLOCKED] = handle_player_spell_blocked;
PlayerArgumentDispatch[EVENT_READ_ITEM] = handle_player_read_item;
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;

View File

@ -1745,6 +1745,49 @@ void handle_player_spell_blocked(
lua_setfield(L, -2, "cast_spell"); lua_setfield(L, -2, "cast_spell");
} }
void handle_player_read_item(
QuestInterface *parse,
lua_State* L,
Client* client,
std::string data,
uint32 extra_data,
std::vector<std::any> *extra_pointers
)
{
lua_pushstring(L, data.c_str());
lua_setfield(L, -2, "text_file");
lua_pushinteger(L, extra_data);
lua_setfield(L, -2, "item_id");
if (extra_pointers) {
if (extra_pointers->size() == 7) {
lua_pushstring(L, std::any_cast<std::string>(extra_pointers->at(0)).c_str());
lua_setfield(L, -2, "book_text");
lua_pushboolean(L, std::any_cast<int8>(extra_pointers->at(1)));
lua_setfield(L, -2, "can_cast");
lua_pushboolean(L, std::any_cast<int8>(extra_pointers->at(2)));
lua_setfield(L, -2, "can_scribe");
lua_pushinteger(L, std::any_cast<int16>(extra_pointers->at(3)));
lua_setfield(L, -2, "slot_id");
lua_pushinteger(L, std::any_cast<int>(extra_pointers->at(4)));
lua_setfield(L, -2, "target_id");
lua_pushinteger(L, std::any_cast<uint8>(extra_pointers->at(5)));
lua_setfield(L, -2, "type");
Lua_ItemInst l_item(std::any_cast<EQ::ItemInstance*>(extra_pointers->at(6)));
luabind::adl::object l_item_o = luabind::adl::object(L, l_item);
l_item_o.push(L);
lua_setfield(L, -2, "item");
}
}
}
// Item // Item
void handle_item_click( void handle_item_click(
QuestInterface *parse, QuestInterface *parse,

View File

@ -855,6 +855,15 @@ void handle_player_spell_blocked(
std::vector<std::any> *extra_pointers std::vector<std::any> *extra_pointers
); );
void handle_player_read_item(
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,