[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;
}
namespace BookType {
constexpr uint8 Scroll = 0;
constexpr uint8 Book = 1;
constexpr uint8 ItemInfo = 2;
}
#endif /*COMMON_EMU_CONSTANTS_H*/

View File

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

View File

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

View File

@ -2299,52 +2299,67 @@ void Client::SetGM(bool toggle) {
UpdateWho();
}
void Client::ReadBook(BookRequest_Struct *book) {
int16 book_language=0;
char *txtfile = book->txtfile;
void Client::ReadBook(BookRequest_Struct* book)
{
const std::string& text_file = book->txtfile;
if(txtfile[0] == '0' && txtfile[1] == '\0') {
//invalid book... coming up on non-book items.
if (text_file.empty()) {
return;
}
std::string booktxt2 = content_db.GetBook(txtfile, &book_language);
int length = booktxt2.length();
auto b = content_db.GetBook(text_file);
if (booktxt2[0] != '\0') {
#if EQDEBUG >= 6
LogInfo("Client::ReadBook() textfile:[{}] Text:[{}]", txtfile, booktxt2.c_str());
#endif
auto outapp = new EQApplicationPacket(OP_ReadBook, length + sizeof(BookText_Struct));
if (!b.text.empty()) {
auto outapp = new EQApplicationPacket(OP_ReadBook, b.text.size() + sizeof(BookText_Struct));
auto inst = const_cast<EQ::ItemInstance*>(m_inv[book->invslot]);
BookText_Struct *out = (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;
auto t = (BookText_Struct*) outapp->pBuffer;
if (ClientVersion() >= EQ::versions::ClientVersion::SoF && book->invslot <= EQ::invbag::GENERAL_BAGS_END)
{
const EQ::ItemInstance* inst = m_inv[book->invslot];
if (inst && inst->GetItem())
{
auto recipe = TradeskillRecipeRepository::GetWhere(content_db,
fmt::format("learned_by_item_id = {} LIMIT 1", inst->GetItem()->ID));
out->type = inst->GetItem()->Book;
out->can_scribe = !recipe.empty();
t->window = book->window;
t->type = book->type;
t->invslot = book->invslot;
t->target_id = book->target_id;
t->can_cast = 0; // todo: implement
t->can_scribe = false;
if (ClientVersion() >= EQ::versions::ClientVersion::SoF && book->invslot <= EQ::invbag::GENERAL_BAGS_END) {
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 (m_pp.languages[book_language] < Language::MaxValue) {
GarbleMessage(out->booktext, (Language::MaxValue - m_pp.languages[book_language]));
if (EQ::ValueWithin(b.language, Language::CommonTongue, Language::Unknown27)) {
if (m_pp.languages[b.language] < Language::MaxValue) {
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);
safe_delete(outapp);
}
@ -10916,23 +10931,24 @@ void Client::SetIPExemption(int exemption_amount)
void Client::ReadBookByName(std::string book_name, uint8 book_type)
{
int16 book_language = 0;
std::string book_text = content_db.GetBook(book_name.c_str(), &book_language);
int length = book_text.length();
auto b = content_db.GetBook(book_name);
if (book_text[0] != '\0') {
LogDebug("Client::ReadBookByName() Book Name: [{}] Text: [{}]", book_name, book_text.c_str());
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;
if (!b.text.empty()) {
LogDebug("Book Name: [{}] Text: [{}]", book_name, b.text);
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)) {
if (m_pp.languages[book_language] < Language::MaxValue) {
GarbleMessage(out->booktext, (Language::MaxValue - m_pp.languages[book_language]));
auto o = (BookText_Struct *) outapp->pBuffer;
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;
};
// do not ask what all these mean because I have no idea!
// named from the client's CEverQuest::GetInnateDesc, they're missing some
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));
return;
}
BookRequest_Struct* book = (BookRequest_Struct*)app->pBuffer;
ReadBook(book);
if (ClientVersion() >= EQ::versions::ClientVersion::SoF)
{
EQApplicationPacket EndOfBook(OP_FinishWindow, 0);
QueuePacket(&EndOfBook);
auto b = (BookRequest_Struct*) app->pBuffer;
ReadBook(b);
if (ClientVersion() >= EQ::versions::ClientVersion::SoF) {
EQApplicationPacket end_of_book(OP_FinishWindow, 0);
QueuePacket(&end_of_book);
}
return;
}
void Client::Handle_OP_RecipeAutoCombine(const EQApplicationPacket *app)

View File

@ -203,6 +203,7 @@ const char* QuestEventSubroutines[_LargestEventID] = {
"EVENT_ENTITY_VARIABLE_UPDATE",
"EVENT_AA_LOSS",
"EVENT_SPELL_BLOCKED",
"EVENT_READ_ITEM",
// Add new events before these or Lua crashes
"EVENT_SPELL_EFFECT_BOT",
@ -2488,6 +2489,28 @@ void PerlembParser::ExportEventVariables(
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: {
break;
}

View File

@ -144,6 +144,7 @@ typedef enum {
EVENT_ENTITY_VARIABLE_UPDATE,
EVENT_AA_LOSS,
EVENT_SPELL_BLOCKED,
EVENT_READ_ITEM,
// Add new events before these or Lua crashes
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_set", static_cast<int>(EVENT_ENTITY_VARIABLE_SET)),
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_update",
"event_aa_loss",
"event_spell_blocked"
"event_spell_blocked",
"event_read_item"
};
extern Zone *zone;
@ -348,6 +349,7 @@ LuaParser::LuaParser() {
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;
PlayerArgumentDispatch[EVENT_READ_ITEM] = handle_player_read_item;
ItemArgumentDispatch[EVENT_ITEM_CLICK] = 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");
}
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
void handle_item_click(
QuestInterface *parse,

View File

@ -855,6 +855,15 @@ void handle_player_spell_blocked(
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
void handle_item_click(
QuestInterface *parse,