diff --git a/common/emu_constants.h b/common/emu_constants.h index 6edba139d..83056cfb2 100644 --- a/common/emu_constants.h +++ b/common/emu_constants.h @@ -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*/ diff --git a/common/shareddb.cpp b/common/shareddb.cpp index f853dbb59..ac7a3a173 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -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(Strings::ToInt(row[1])); + b.language = e.language; + b.text = e.txtfile; - return txtout; + return b; } // Create appropriate EQ::ItemInstance class diff --git a/common/shareddb.h b/common/shareddb.h index c4b322e94..67c684206 100644 --- a/common/shareddb.h +++ b/common/shareddb.h @@ -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 diff --git a/zone/client.cpp b/zone/client.cpp index 9ff5947f4..b8a60580c 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -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(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 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::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])); } } diff --git a/zone/client.h b/zone/client.h index bb421c233..98bd7b15c 100644 --- a/zone/client.h +++ b/zone/client.h @@ -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 { diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 57ad5fe2c..76f72ab31 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -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) diff --git a/zone/embparser.cpp b/zone/embparser.cpp index 3dbd8b7ce..7b89674b0 100644 --- a/zone/embparser.cpp +++ b/zone/embparser.cpp @@ -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(extra_pointers->at(0)).c_str()); + ExportVar(package_name.c_str(), "can_cast", std::any_cast(extra_pointers->at(1))); + ExportVar(package_name.c_str(), "can_scribe", std::any_cast(extra_pointers->at(2))); + ExportVar(package_name.c_str(), "slot_id", std::any_cast(extra_pointers->at(3))); + ExportVar(package_name.c_str(), "target_id", std::any_cast(extra_pointers->at(4))); + ExportVar(package_name.c_str(), "type", std::any_cast(extra_pointers->at(5))); + ExportVar( + package_name.c_str(), + "item", + "QuestItem", + std::any_cast(extra_pointers->at(6)) + ); + } + + break; + } + default: { break; } diff --git a/zone/event_codes.h b/zone/event_codes.h index ec4fbef0a..aeee13393 100644 --- a/zone/event_codes.h +++ b/zone/event_codes.h @@ -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, diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index d3a893488..74ed3a721 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -6907,7 +6907,8 @@ luabind::scope lua_register_events() { luabind::value("entity_variable_delete", static_cast(EVENT_ENTITY_VARIABLE_DELETE)), luabind::value("entity_variable_set", static_cast(EVENT_ENTITY_VARIABLE_SET)), luabind::value("entity_variable_update", static_cast(EVENT_ENTITY_VARIABLE_UPDATE)), - luabind::value("aa_loss", static_cast(EVENT_AA_LOSS)) + luabind::value("aa_loss", static_cast(EVENT_AA_LOSS)), + luabind::value("read", static_cast(EVENT_READ_ITEM)) )]; } diff --git a/zone/lua_parser.cpp b/zone/lua_parser.cpp index 493acf0a3..be1e5155b 100644 --- a/zone/lua_parser.cpp +++ b/zone/lua_parser.cpp @@ -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; diff --git a/zone/lua_parser_events.cpp b/zone/lua_parser_events.cpp index 77cff0872..f0186056c 100644 --- a/zone/lua_parser_events.cpp +++ b/zone/lua_parser_events.cpp @@ -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 *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(extra_pointers->at(0)).c_str()); + lua_setfield(L, -2, "book_text"); + + lua_pushboolean(L, std::any_cast(extra_pointers->at(1))); + lua_setfield(L, -2, "can_cast"); + + lua_pushboolean(L, std::any_cast(extra_pointers->at(2))); + lua_setfield(L, -2, "can_scribe"); + + lua_pushinteger(L, std::any_cast(extra_pointers->at(3))); + lua_setfield(L, -2, "slot_id"); + + lua_pushinteger(L, std::any_cast(extra_pointers->at(4))); + lua_setfield(L, -2, "target_id"); + + lua_pushinteger(L, std::any_cast(extra_pointers->at(5))); + lua_setfield(L, -2, "type"); + + Lua_ItemInst l_item(std::any_cast(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, diff --git a/zone/lua_parser_events.h b/zone/lua_parser_events.h index d1d176f1f..526046f91 100644 --- a/zone/lua_parser_events.h +++ b/zone/lua_parser_events.h @@ -855,6 +855,15 @@ void handle_player_spell_blocked( std::vector *extra_pointers ); +void handle_player_read_item( + QuestInterface *parse, + lua_State* L, + Client* client, + std::string data, + uint32 extra_data, + std::vector *extra_pointers +); + // Item void handle_item_click( QuestInterface *parse,