mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 21:01:29 +00:00
[Tradeskills] Implement learning recipes from books (#4170)
This uses the book scribe button to learn recipes in SoF+ clients. For Titanium clients the button is not available so a workaround will need to be added later. Note live gives no feedback when scribing books (at least those tested).
This commit is contained in:
parent
96830b4a19
commit
5bfd8f5da2
@ -59,6 +59,24 @@ public:
|
||||
return NewEntity();
|
||||
}
|
||||
|
||||
// insert with ON DUPLICATE KEY UPDATE to leave rows that exist unchanged
|
||||
static int InsertUpdateMany(Database& db, const std::vector<CharRecipeList>& entries)
|
||||
{
|
||||
std::vector<std::string> values;
|
||||
values.reserve(entries.size());
|
||||
|
||||
for (const auto& e: entries)
|
||||
{
|
||||
values.emplace_back(fmt::format("({},{},{})", e.char_id, e.recipe_id, e.madecount));
|
||||
}
|
||||
|
||||
auto results = db.QueryDatabase(fmt::format(
|
||||
"INSERT INTO {0} (char_id, recipe_id, madecount) VALUES {1} ON DUPLICATE KEY UPDATE {2}={2}",
|
||||
TableName(), fmt::join(values, ","), PrimaryKey()));
|
||||
|
||||
return results.Success() ? results.RowsAffected() : 0;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif //EQEMU_CHAR_RECIPE_LIST_REPOSITORY_H
|
||||
|
||||
@ -68,6 +68,7 @@ extern volatile bool RunLoops;
|
||||
#include "../common/repositories/discovered_items_repository.h"
|
||||
#include "../common/repositories/inventory_repository.h"
|
||||
#include "../common/repositories/keyring_repository.h"
|
||||
#include "../common/repositories/tradeskill_recipe_repository.h"
|
||||
#include "../common/events/player_events.h"
|
||||
#include "../common/events/player_event_logs.h"
|
||||
#include "dialogue_window.h"
|
||||
@ -2294,29 +2295,23 @@ void Client::ReadBook(BookRequest_Struct *book) {
|
||||
|
||||
BookText_Struct *out = (BookText_Struct *) outapp->pBuffer;
|
||||
out->window = book->window;
|
||||
|
||||
|
||||
if (ClientVersion() >= EQ::versions::ClientVersion::SoF) {
|
||||
// SoF+ need to look up book type for the output message.
|
||||
const EQ::ItemInstance *inst = nullptr;
|
||||
|
||||
if (book->invslot <= EQ::invbag::GENERAL_BAGS_END)
|
||||
{
|
||||
inst = m_inv[book->invslot];
|
||||
}
|
||||
|
||||
if(inst)
|
||||
out->type = inst->GetItem()->Book;
|
||||
else
|
||||
out->type = book->type;
|
||||
}
|
||||
else {
|
||||
out->type = book->type;
|
||||
}
|
||||
out->type = book->type;
|
||||
out->invslot = book->invslot;
|
||||
out->target_id = book->target_id;
|
||||
out->can_cast = 0; // todo: implement
|
||||
out->can_scribe = 0; // todo: implement
|
||||
out->can_scribe = false;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(out->booktext, booktxt2.c_str(), length);
|
||||
|
||||
|
||||
@ -350,6 +350,7 @@ public:
|
||||
int GetRecipeMadeCount(uint32 recipe_id);
|
||||
bool HasRecipeLearned(uint32 recipe_id);
|
||||
bool CanIncreaseTradeskill(EQ::skills::SkillType tradeskill);
|
||||
void ScribeRecipes(uint32_t item_id) const;
|
||||
|
||||
bool GetRevoked() const { return revoked; }
|
||||
void SetRevoked(bool rev) { revoked = rev; }
|
||||
|
||||
@ -4168,10 +4168,11 @@ void Client::Handle_OP_BookButton(const EQApplicationPacket* app)
|
||||
BookButton_Struct* book = reinterpret_cast<BookButton_Struct*>(app->pBuffer);
|
||||
|
||||
const EQ::ItemInstance* const inst = GetInv().GetItem(book->invslot);
|
||||
if (inst && inst->GetItem()->Book)
|
||||
if (inst && inst->GetItem())
|
||||
{
|
||||
// todo: if scribe book learn recipes and delete book from inventory
|
||||
// todo: if cast book use its spell on target and delete book from inventory (unless reusable?)
|
||||
// todo: cast spell button (unknown if anything on live uses this)
|
||||
ScribeRecipes(inst->GetItem()->ID);
|
||||
DeleteItemInInventory(book->invslot, 1, true);
|
||||
}
|
||||
|
||||
EQApplicationPacket outapp(OP_FinishWindow, 0);
|
||||
|
||||
@ -34,6 +34,7 @@
|
||||
#include "zonedb.h"
|
||||
#include "worldserver.h"
|
||||
#include "../common/repositories/char_recipe_list_repository.h"
|
||||
#include "../common/repositories/criteria/content_filter_criteria.h"
|
||||
#include "../common/repositories/tradeskill_recipe_repository.h"
|
||||
#include "../common/repositories/tradeskill_recipe_entries_repository.h"
|
||||
|
||||
@ -1892,4 +1893,31 @@ bool Client::CheckTradeskillLoreConflict(int32 recipe_id)
|
||||
return false;
|
||||
}
|
||||
|
||||
void Client::ScribeRecipes(uint32_t item_id) const
|
||||
{
|
||||
if (item_id == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto recipes = TradeskillRecipeRepository::GetWhere(content_db, fmt::format(
|
||||
"learned_by_item_id = {} {}", item_id, ContentFilterCriteria::apply()));
|
||||
|
||||
std::vector<CharRecipeListRepository::CharRecipeList> learned;
|
||||
learned.reserve(recipes.size());
|
||||
|
||||
for (const auto& recipe : recipes)
|
||||
{
|
||||
auto entry = CharRecipeListRepository::NewEntity();
|
||||
entry.char_id = static_cast<int32_t>(CharacterID());
|
||||
entry.recipe_id = recipe.id;
|
||||
learned.push_back(entry);
|
||||
}
|
||||
|
||||
if (!learned.empty())
|
||||
{
|
||||
// avoid replacing madecount for recipes the client already has
|
||||
int rows = CharRecipeListRepository::InsertUpdateMany(database, learned);
|
||||
LogTradeskills("Client [{}] scribed [{}] recipes from [{}]", CharacterID(), rows, item_id);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user