feat(Scripting): Created two perl/lua scripting hooks for merchants (#5083)
Build / Linux (push) Has been cancelled
Build / Windows (push) Has been cancelled

This commit is contained in:
Dan
2026-05-13 03:18:36 -04:00
committed by GitHub
parent ef6dfe0469
commit ca704c7f88
9 changed files with 148 additions and 16 deletions
+39 -16
View File
@@ -14378,9 +14378,8 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app)
sizeof(Merchant_Purchase_Struct), app->size); sizeof(Merchant_Purchase_Struct), app->size);
return; return;
} }
RDTSC_Timer t1(true);
Merchant_Purchase_Struct* mp = (Merchant_Purchase_Struct*)app->pBuffer; Merchant_Purchase_Struct* mp = (Merchant_Purchase_Struct*)app->pBuffer;
Mob* vendor = entity_list.GetMob(mp->npcid); Mob* vendor = entity_list.GetMob(mp->npcid);
if (vendor == 0 || !vendor->IsNPC() || vendor->GetClass() != Class::Merchant) 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) if (DistanceSquared(m_Position, vendor->GetPosition()) > USE_NPC_RANGE2)
return; return;
uint32 price = 0;
uint32 itemid = GetItemIDAt(mp->itemslot); uint32 itemid = GetItemIDAt(mp->itemslot);
if (itemid == 0) if (itemid == 0)
return; return;
const EQ::ItemData* item = database.GetItem(itemid); const EQ::ItemData* item = database.GetItem(itemid);
EQ::ItemInstance* inst = GetInv().GetItem(mp->itemslot); EQ::ItemInstance* inst = GetInv().GetItem(mp->itemslot);
if (!item || !inst) { if (!item || !inst) {
Message(Chat::Red, "You seemed to have misplaced that item.."); Message(Chat::Red, "You seem to have misplaced that item..");
return; return;
} }
if (mp->quantity > 1)
{ if (!item->NoDrop) {
return;
}
if (mp->quantity > 1) {
if ((inst->GetCharges() < 0) || (mp->quantity > (uint32)inst->GetCharges())) if ((inst->GetCharges() < 0) || (mp->quantity > (uint32)inst->GetCharges()))
return; return;
} }
if (!item->NoDrop) { // Check for veto from script
//Message(Chat::Red,"%s tells you, 'LOL NOPE'", vendor->GetName()); if (parse->PlayerHasQuestSub(EVENT_MERCHANT_PRESELL)) {
return; 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; uint32 cost_quantity = inst->IsCharged() ? 1 : mp->quantity;
if (inst->IsCharged()) uint32 price = 0;
uint32 cost_quantity = 1;
uint32 i;
if (RuleB(Merchant, UsePriceMod)) { 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); price = (uint32)(item->Price * i) * Client::CalcPriceMod(vendor, true);
// Don't use SellCostMod if using UseClassicPriceMod // Don't use SellCostMod if using UseClassicPriceMod
@@ -14436,7 +14451,7 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app)
} }
} }
else { 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 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) { if (price > 4000000000) {
cost_quantity = i; cost_quantity = i;
@@ -14448,6 +14463,7 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app)
AddMoneyToPP(price); AddMoneyToPP(price);
// Update merchant stock and refresh client
if (inst->IsStackable() || inst->IsCharged()) if (inst->IsStackable() || inst->IsCharged())
{ {
unsigned int i_quan = inst->GetCharges(); unsigned int i_quan = inst->GetCharges();
@@ -14561,6 +14577,8 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app)
QueuePacket(outapp); QueuePacket(outapp);
safe_delete(outapp); safe_delete(outapp);
SendMoneyUpdate(); SendMoneyUpdate();
RDTSC_Timer t1(true);
t1.start(); t1.start();
Save(1); Save(1);
t1.stop(); t1.stop();
@@ -14679,6 +14697,11 @@ void Client::Handle_OP_ShopRequest(const EQApplicationPacket *app)
if ((tabs_to_display & Parcel) == Parcel) { if ((tabs_to_display & Parcel) == Parcel) {
SendBulkParcels(); SendBulkParcels();
} }
if (parse->PlayerHasQuestSub(EVENT_MERCHANT_OPEN)) {
std::vector<std::any> extra_pointers = { tmp };
parse->EventPlayer(EVENT_MERCHANT_OPEN, this, "", 0, &extra_pointers);
}
} }
return; return;
+29
View File
@@ -163,8 +163,10 @@ const char* QuestEventSubroutines[_LargestEventID] = {
"EVENT_LANGUAGE_SKILL_UP", "EVENT_LANGUAGE_SKILL_UP",
"EVENT_ALT_CURRENCY_MERCHANT_BUY", "EVENT_ALT_CURRENCY_MERCHANT_BUY",
"EVENT_ALT_CURRENCY_MERCHANT_SELL", "EVENT_ALT_CURRENCY_MERCHANT_SELL",
"EVENT_MERCHANT_OPEN",
"EVENT_MERCHANT_BUY", "EVENT_MERCHANT_BUY",
"EVENT_MERCHANT_SELL", "EVENT_MERCHANT_SELL",
"EVENT_MERCHANT_PRESELL",
"EVENT_INSPECT", "EVENT_INSPECT",
"EVENT_TASK_BEFORE_UPDATE", "EVENT_TASK_BEFORE_UPDATE",
"EVENT_AA_BUY", "EVENT_AA_BUY",
@@ -2289,6 +2291,33 @@ void PerlembParser::ExportEventVariables(
break; 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: { case EVENT_AA_BUY: {
Seperator sep(data); Seperator sep(data);
ExportVar(package_name.c_str(), "aa_cost", sep.arg[0]); ExportVar(package_name.c_str(), "aa_cost", sep.arg[0]);
+2
View File
@@ -116,8 +116,10 @@ enum QuestEventID {
EVENT_LANGUAGE_SKILL_UP, EVENT_LANGUAGE_SKILL_UP,
EVENT_ALT_CURRENCY_MERCHANT_BUY, EVENT_ALT_CURRENCY_MERCHANT_BUY,
EVENT_ALT_CURRENCY_MERCHANT_SELL, EVENT_ALT_CURRENCY_MERCHANT_SELL,
EVENT_MERCHANT_OPEN,
EVENT_MERCHANT_BUY, EVENT_MERCHANT_BUY,
EVENT_MERCHANT_SELL, EVENT_MERCHANT_SELL,
EVENT_MERCHANT_PRESELL,
EVENT_INSPECT, EVENT_INSPECT,
EVENT_TASK_BEFORE_UPDATE, EVENT_TASK_BEFORE_UPDATE,
EVENT_AA_BUY, EVENT_AA_BUY,
+2
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("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_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("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_buy", static_cast<int>(EVENT_MERCHANT_BUY)),
luabind::value("merchant_sell", static_cast<int>(EVENT_MERCHANT_SELL)), 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("inspect", static_cast<int>(EVENT_INSPECT)),
luabind::value("task_before_update", static_cast<int>(EVENT_TASK_BEFORE_UPDATE)), luabind::value("task_before_update", static_cast<int>(EVENT_TASK_BEFORE_UPDATE)),
luabind::value("aa_buy", static_cast<int>(EVENT_AA_BUY)), luabind::value("aa_buy", static_cast<int>(EVENT_AA_BUY)),
+6
View File
@@ -158,6 +158,11 @@ uint32 Lua_ItemInst::GetItemScriptID() {
return self->GetItemScriptID(); return self->GetItemScriptID();
} }
uint8 Lua_ItemInst::GetItemType() {
Lua_Safe_Call_Int();
return self->GetItemType();
}
int Lua_ItemInst::GetCharges() { int Lua_ItemInst::GetCharges() {
Lua_Safe_Call_Int(); Lua_Safe_Call_Int();
return self->GetCharges(); return self->GetCharges();
@@ -497,6 +502,7 @@ luabind::scope lua_register_iteminst() {
.def("GetItemID", (uint32(Lua_ItemInst::*)(int))&Lua_ItemInst::GetItemID) .def("GetItemID", (uint32(Lua_ItemInst::*)(int))&Lua_ItemInst::GetItemID)
.def("GetItemLink", (std::string(Lua_ItemInst::*)(void))&Lua_ItemInst::GetItemLink) .def("GetItemLink", (std::string(Lua_ItemInst::*)(void))&Lua_ItemInst::GetItemLink)
.def("GetItemScriptID", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetItemScriptID) .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("GetMaxEvolveLvl", (int(Lua_ItemInst::*)(void))&Lua_ItemInst::GetMaxEvolveLvl)
.def("GetName", (std::string(Lua_ItemInst::*)(void))&Lua_ItemInst::GetName) .def("GetName", (std::string(Lua_ItemInst::*)(void))&Lua_ItemInst::GetName)
.def("GetSerialNumber", (int(Lua_ItemInst::*)(void))&Lua_ItemInst::GetSerialNumber) .def("GetSerialNumber", (int(Lua_ItemInst::*)(void))&Lua_ItemInst::GetSerialNumber)
+1
View File
@@ -71,6 +71,7 @@ public:
bool IsAmmo(); bool IsAmmo();
uint32 GetID(); uint32 GetID();
uint32 GetItemScriptID(); uint32 GetItemScriptID();
uint8 GetItemType();
int GetCharges(); int GetCharges();
void SetCharges(int charges); void SetCharges(int charges);
uint32 GetPrice(); uint32 GetPrice();
+4
View File
@@ -160,8 +160,10 @@ const char *LuaEvents[_LargestEventID] = {
"event_language_skill_up", "event_language_skill_up",
"event_alt_currency_merchant_buy", "event_alt_currency_merchant_buy",
"event_alt_currency_merchant_sell", "event_alt_currency_merchant_sell",
"event_merchant_open",
"event_merchant_buy", "event_merchant_buy",
"event_merchant_sell", "event_merchant_sell",
"event_merchant_presell",
"event_inspect", "event_inspect",
"event_task_before_update", "event_task_before_update",
"event_aa_buy", "event_aa_buy",
@@ -335,8 +337,10 @@ LuaParser::LuaParser() {
PlayerArgumentDispatch[EVENT_LANGUAGE_SKILL_UP] = handle_player_language_skill_up; 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_BUY] = handle_player_alt_currency_merchant;
PlayerArgumentDispatch[EVENT_ALT_CURRENCY_MERCHANT_SELL] = 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_BUY] = handle_player_merchant;
PlayerArgumentDispatch[EVENT_MERCHANT_SELL] = 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_INSPECT] = handle_player_inspect;
PlayerArgumentDispatch[EVENT_AA_BUY] = handle_player_aa_buy; PlayerArgumentDispatch[EVENT_AA_BUY] = handle_player_aa_buy;
PlayerArgumentDispatch[EVENT_AA_GAIN] = handle_player_aa_gain; PlayerArgumentDispatch[EVENT_AA_GAIN] = handle_player_aa_gain;
+47
View File
@@ -2340,6 +2340,53 @@ void handle_player_merchant(
lua_setfield(L, -2, "item_cost"); 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( void handle_player_augment_insert(
QuestInterface *parse, QuestInterface *parse,
lua_State* L, lua_State* L,
+18
View File
@@ -671,6 +671,24 @@ void handle_player_merchant(
std::vector<std::any> *extra_pointers 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( void handle_player_inspect(
QuestInterface *parse, QuestInterface *parse,
lua_State* L, lua_State* L,