feat(Scripting): Created two perl/lua scripting hooks for merchants (#5083)
Some checks are pending
Build / Linux (push) Waiting to run
Build / Windows (push) Waiting to run

This commit is contained in:
Dan 2026-05-13 03:18:36 -04:00 committed by GitHub
parent ef6dfe0469
commit ca704c7f88
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 148 additions and 16 deletions

View File

@ -14378,9 +14378,8 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app)
sizeof(Merchant_Purchase_Struct), app->size);
return;
}
RDTSC_Timer t1(true);
Merchant_Purchase_Struct* mp = (Merchant_Purchase_Struct*)app->pBuffer;
Mob* vendor = entity_list.GetMob(mp->npcid);
if (vendor == 0 || !vendor->IsNPC() || vendor->GetClass() != Class::Merchant)
@ -14390,35 +14389,51 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app)
if (DistanceSquared(m_Position, vendor->GetPosition()) > USE_NPC_RANGE2)
return;
uint32 price = 0;
uint32 itemid = GetItemIDAt(mp->itemslot);
if (itemid == 0)
return;
const EQ::ItemData* item = database.GetItem(itemid);
EQ::ItemInstance* inst = GetInv().GetItem(mp->itemslot);
if (!item || !inst) {
Message(Chat::Red, "You seemed to have misplaced that item..");
Message(Chat::Red, "You seem to have misplaced that item..");
return;
}
if (mp->quantity > 1)
{
if (!item->NoDrop) {
return;
}
if (mp->quantity > 1) {
if ((inst->GetCharges() < 0) || (mp->quantity > (uint32)inst->GetCharges()))
return;
}
if (!item->NoDrop) {
//Message(Chat::Red,"%s tells you, 'LOL NOPE'", vendor->GetName());
return;
// Check for veto from script
if (parse->PlayerHasQuestSub(EVENT_MERCHANT_PRESELL)) {
std::string export_string = fmt::format("{} {} {}", mp->itemslot, itemid, inst->GetItemType());
std::vector<std::any> extra_pointers = { vendor, inst };
int result = parse->EventPlayer(EVENT_MERCHANT_PRESELL, this, export_string, 0, &extra_pointers);
// CANCEL: If a script returns -1 for this event, the sale wil be cancelled. Sends a dummy packet sent to satisfy the client
if (result == -1) {
auto outapp = new EQApplicationPacket(OP_ShopPlayerSell, sizeof(Merchant_Purchase_Struct));
Merchant_Purchase_Struct* mco = (Merchant_Purchase_Struct*)outapp->pBuffer;
mco->npcid = vendor->GetID();
mco->itemslot = -1; // Critical or the client will remove the item visually
mco->quantity = 0;
mco->price = 0;
QueuePacket(outapp);
safe_delete(outapp);
return;
}
}
uint32 cost_quantity = mp->quantity;
if (inst->IsCharged())
uint32 cost_quantity = 1;
uint32 i;
uint32 cost_quantity = inst->IsCharged() ? 1 : mp->quantity;
uint32 price = 0;
if (RuleB(Merchant, UsePriceMod)) {
for (i = 1; i <= cost_quantity; i++) {
for (uint32 i = 1; i <= cost_quantity; i++) {
price = (uint32)(item->Price * i) * Client::CalcPriceMod(vendor, true);
// Don't use SellCostMod if using UseClassicPriceMod
@ -14436,7 +14451,7 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app)
}
}
else {
for (i = 1; i <= cost_quantity; i++) {
for (uint32 i = 1; i <= cost_quantity; i++) {
price = (uint32)((item->Price * i)*(RuleR(Merchant, BuyCostMod)) + 0.5); // need to round up, because client does it automatically when displaying price
if (price > 4000000000) {
cost_quantity = i;
@ -14448,6 +14463,7 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app)
AddMoneyToPP(price);
// Update merchant stock and refresh client
if (inst->IsStackable() || inst->IsCharged())
{
unsigned int i_quan = inst->GetCharges();
@ -14561,6 +14577,8 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app)
QueuePacket(outapp);
safe_delete(outapp);
SendMoneyUpdate();
RDTSC_Timer t1(true);
t1.start();
Save(1);
t1.stop();
@ -14679,6 +14697,11 @@ void Client::Handle_OP_ShopRequest(const EQApplicationPacket *app)
if ((tabs_to_display & Parcel) == Parcel) {
SendBulkParcels();
}
if (parse->PlayerHasQuestSub(EVENT_MERCHANT_OPEN)) {
std::vector<std::any> extra_pointers = { tmp };
parse->EventPlayer(EVENT_MERCHANT_OPEN, this, "", 0, &extra_pointers);
}
}
return;

View File

@ -163,8 +163,10 @@ const char* QuestEventSubroutines[_LargestEventID] = {
"EVENT_LANGUAGE_SKILL_UP",
"EVENT_ALT_CURRENCY_MERCHANT_BUY",
"EVENT_ALT_CURRENCY_MERCHANT_SELL",
"EVENT_MERCHANT_OPEN",
"EVENT_MERCHANT_BUY",
"EVENT_MERCHANT_SELL",
"EVENT_MERCHANT_PRESELL",
"EVENT_INSPECT",
"EVENT_TASK_BEFORE_UPDATE",
"EVENT_AA_BUY",
@ -2289,6 +2291,33 @@ void PerlembParser::ExportEventVariables(
break;
}
case EVENT_MERCHANT_OPEN: {
if (!extra_pointers || extra_pointers->size() < 1) break;
auto mob_ptr = std::any_cast<Mob*>(extra_pointers->at(0));
if (!mob_ptr) break;
ExportVar(package_name.c_str(), "other", "Mob", mob_ptr);
break;
}
case EVENT_MERCHANT_PRESELL: {
Seperator sep(data);
ExportVar(package_name.c_str(), "slot_id", sep.arg[0]);
ExportVar(package_name.c_str(), "item_id", sep.arg[1]);
ExportVar(package_name.c_str(), "item_type", sep.arg[2]);
if (!extra_pointers || extra_pointers->size() < 2) break;
auto mob_ptr = std::any_cast<Mob*>(extra_pointers->at(0));
auto inst_ptr = std::any_cast<EQ::ItemInstance*>(extra_pointers->at(1));
if (!mob_ptr || !inst_ptr) break;
ExportVar(package_name.c_str(), "other", "Mob", mob_ptr);
ExportVar(package_name.c_str(), "item", "ItemInstance", inst_ptr);
break;
}
case EVENT_AA_BUY: {
Seperator sep(data);
ExportVar(package_name.c_str(), "aa_cost", sep.arg[0]);

View File

@ -116,8 +116,10 @@ enum QuestEventID {
EVENT_LANGUAGE_SKILL_UP,
EVENT_ALT_CURRENCY_MERCHANT_BUY,
EVENT_ALT_CURRENCY_MERCHANT_SELL,
EVENT_MERCHANT_OPEN,
EVENT_MERCHANT_BUY,
EVENT_MERCHANT_SELL,
EVENT_MERCHANT_PRESELL,
EVENT_INSPECT,
EVENT_TASK_BEFORE_UPDATE,
EVENT_AA_BUY,

View File

@ -6968,8 +6968,10 @@ luabind::scope lua_register_events() {
luabind::value("language_skill_up", static_cast<int>(EVENT_LANGUAGE_SKILL_UP)),
luabind::value("alt_currency_merchant_buy", static_cast<int>(EVENT_ALT_CURRENCY_MERCHANT_BUY)),
luabind::value("alt_currency_merchant_sell", static_cast<int>(EVENT_ALT_CURRENCY_MERCHANT_SELL)),
luabind::value("merchant_open", static_cast<int>(EVENT_MERCHANT_OPEN)),
luabind::value("merchant_buy", static_cast<int>(EVENT_MERCHANT_BUY)),
luabind::value("merchant_sell", static_cast<int>(EVENT_MERCHANT_SELL)),
luabind::value("merchant_presell", static_cast<int>(EVENT_MERCHANT_PRESELL)),
luabind::value("inspect", static_cast<int>(EVENT_INSPECT)),
luabind::value("task_before_update", static_cast<int>(EVENT_TASK_BEFORE_UPDATE)),
luabind::value("aa_buy", static_cast<int>(EVENT_AA_BUY)),

View File

@ -158,6 +158,11 @@ uint32 Lua_ItemInst::GetItemScriptID() {
return self->GetItemScriptID();
}
uint8 Lua_ItemInst::GetItemType() {
Lua_Safe_Call_Int();
return self->GetItemType();
}
int Lua_ItemInst::GetCharges() {
Lua_Safe_Call_Int();
return self->GetCharges();
@ -497,6 +502,7 @@ luabind::scope lua_register_iteminst() {
.def("GetItemID", (uint32(Lua_ItemInst::*)(int))&Lua_ItemInst::GetItemID)
.def("GetItemLink", (std::string(Lua_ItemInst::*)(void))&Lua_ItemInst::GetItemLink)
.def("GetItemScriptID", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetItemScriptID)
.def("GetItemType", (uint8(Lua_ItemInst::*)(void)) & Lua_ItemInst::GetItemType)
.def("GetMaxEvolveLvl", (int(Lua_ItemInst::*)(void))&Lua_ItemInst::GetMaxEvolveLvl)
.def("GetName", (std::string(Lua_ItemInst::*)(void))&Lua_ItemInst::GetName)
.def("GetSerialNumber", (int(Lua_ItemInst::*)(void))&Lua_ItemInst::GetSerialNumber)

View File

@ -71,6 +71,7 @@ public:
bool IsAmmo();
uint32 GetID();
uint32 GetItemScriptID();
uint8 GetItemType();
int GetCharges();
void SetCharges(int charges);
uint32 GetPrice();

View File

@ -160,8 +160,10 @@ const char *LuaEvents[_LargestEventID] = {
"event_language_skill_up",
"event_alt_currency_merchant_buy",
"event_alt_currency_merchant_sell",
"event_merchant_open",
"event_merchant_buy",
"event_merchant_sell",
"event_merchant_presell",
"event_inspect",
"event_task_before_update",
"event_aa_buy",
@ -335,8 +337,10 @@ LuaParser::LuaParser() {
PlayerArgumentDispatch[EVENT_LANGUAGE_SKILL_UP] = handle_player_language_skill_up;
PlayerArgumentDispatch[EVENT_ALT_CURRENCY_MERCHANT_BUY] = handle_player_alt_currency_merchant;
PlayerArgumentDispatch[EVENT_ALT_CURRENCY_MERCHANT_SELL] = handle_player_alt_currency_merchant;
PlayerArgumentDispatch[EVENT_MERCHANT_OPEN] = handle_player_merchant_open;
PlayerArgumentDispatch[EVENT_MERCHANT_BUY] = handle_player_merchant;
PlayerArgumentDispatch[EVENT_MERCHANT_SELL] = handle_player_merchant;
PlayerArgumentDispatch[EVENT_MERCHANT_PRESELL] = handle_player_merchant_presell;
PlayerArgumentDispatch[EVENT_INSPECT] = handle_player_inspect;
PlayerArgumentDispatch[EVENT_AA_BUY] = handle_player_aa_buy;
PlayerArgumentDispatch[EVENT_AA_GAIN] = handle_player_aa_gain;

View File

@ -2340,6 +2340,53 @@ void handle_player_merchant(
lua_setfield(L, -2, "item_cost");
}
void handle_player_merchant_open(
QuestInterface* parse,
lua_State* L,
Client* client,
std::string data,
uint32 extra_data,
std::vector<std::any>* extra_pointers
) {
if (!extra_pointers || extra_pointers->size() < 1) return;
auto mob_ptr = std::any_cast<Mob*>(extra_pointers->at(0));
if (!mob_ptr) return;
Lua_Mob l_mob(mob_ptr);
luabind::adl::object l_mob_o = luabind::adl::object(L, l_mob);
l_mob_o.push(L);
lua_setfield(L, -2, "other");
}
void handle_player_merchant_presell(
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::ToInt(sep.arg[0])); lua_setfield(L, -2, "slot_id");
lua_pushinteger(L, Strings::ToInt(sep.arg[1])); lua_setfield(L, -2, "item_id");
lua_pushinteger(L, Strings::ToInt(sep.arg[2])); lua_setfield(L, -2, "item_type");
if (!extra_pointers || extra_pointers->size() < 2) return;
auto mob_ptr = std::any_cast<Mob*>(extra_pointers->at(0));
auto inst_ptr = std::any_cast<EQ::ItemInstance*>(extra_pointers->at(1));
if (!mob_ptr || !inst_ptr) return;
Lua_Mob l_mob(mob_ptr);
luabind::adl::object(L, l_mob).push(L);
lua_setfield(L, -2, "other");
Lua_ItemInst l_iteminst(inst_ptr);
luabind::adl::object(L, l_iteminst).push(L);
lua_setfield(L, -2, "item");
}
void handle_player_augment_insert(
QuestInterface *parse,
lua_State* L,

View File

@ -671,6 +671,24 @@ void handle_player_merchant(
std::vector<std::any> *extra_pointers
);
void handle_player_merchant_open(
QuestInterface* parse,
lua_State* L,
Client* client,
std::string data,
uint32 extra_data,
std::vector<std::any>* extra_pointers
);
void handle_player_merchant_presell(
QuestInterface* parse,
lua_State* L,
Client* client,
std::string data,
uint32 extra_data,
std::vector<std::any>* extra_pointers
);
void handle_player_inspect(
QuestInterface *parse,
lua_State* L,