[Bot] Add EVENT_TRADE Support to Bots. (#2560)

* [Bot] Add EVENT_TRADE Support to Bots.

* Fixed issue with duplicate items after Event Trade

* Update logic

* Add CalcBotStats call after Bot Trade Event

* Fix Lua EVENT_TRADE.

* Formatting.

* More formatting.

Co-authored-by: Kinglykrab <kinglykrab@gmail.com>
This commit is contained in:
Aeadoin 2022-11-25 15:09:08 -05:00 committed by GitHub
parent 45c4fe55f0
commit 99052aec8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 242 additions and 99 deletions

View File

@ -4571,11 +4571,11 @@ void Bot::PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client*
using namespace EQ;
struct ClientTrade {
const ItemInstance* trade_item_instance;
ItemInstance* trade_item_instance;
int16 from_client_slot;
int16 to_bot_slot;
ClientTrade(const ItemInstance* item, int16 from) : trade_item_instance(item), from_client_slot(from), to_bot_slot(invslot::SLOT_INVALID) { }
ClientTrade(ItemInstance* item, int16 from) : trade_item_instance(item), from_client_slot(from), to_bot_slot(invslot::SLOT_INVALID) { }
};
struct ClientReturn {
@ -4639,11 +4639,19 @@ void Bot::PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client*
}
std::list<ClientTrade> client_trade;
std::list<ClientTrade> event_trade;
std::list<ClientReturn> client_return;
bool trade_event_exists = false;
if (parse->BotHasQuestSub(EVENT_TRADE)) {
// There is a EVENT_TRADE, we will let the Event handle returning of items.
trade_event_exists = true;
}
// pre-checks for incoming illegal transfers
EQ::InventoryProfile& user_inv = client->GetInv();
for (int16 trade_index = begin_slot_id; trade_index <= end_slot_id; ++trade_index) {
auto trade_instance = client->GetInv()[trade_index];
auto trade_instance = user_inv.GetItem(trade_index);
if (!trade_instance) {
continue;
}
@ -4675,37 +4683,61 @@ void Bot::PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client*
}
if (trade_instance->IsStackable() && trade_instance->GetCharges() < trade_instance->GetItem()->StackSize) { // temp until partial stacks are implemented
client->Message(
Chat::Yellow,
fmt::format(
"{} is only a partially stacked item, the trade has been cancelled!",
item_link
).c_str()
);
client->ResetTrade();
return;
if (trade_event_exists) {
event_trade.push_back(ClientTrade(trade_instance, trade_index));
continue;
}
else {
client->Message(
Chat::Yellow,
fmt::format(
"{} is only a partially stacked item, the trade has been cancelled!",
item_link
).c_str()
);
client->ResetTrade();
return;
}
}
if (CheckLoreConflict(trade_instance->GetItem())) {
client->Message(
Chat::Yellow,
fmt::format(
"This bot already has {}, the trade has been cancelled!",
item_link
).c_str()
);
client->ResetTrade();
return;
if (trade_event_exists) {
event_trade.push_back(ClientTrade(trade_instance, trade_index));
continue;
}
else {
client->Message(
Chat::Yellow,
fmt::format(
"This bot already has {}, the trade has been cancelled!",
item_link
).c_str()
);
client->ResetTrade();
return;
}
}
if (!trade_instance->IsType(item::ItemClassCommon)) {
client_return.push_back(ClientReturn(trade_instance, trade_index));
continue;
if (trade_event_exists) {
event_trade.push_back(ClientTrade(trade_instance, trade_index));
continue;
}
else {
client->ResetTrade();
return;
}
}
if (!trade_instance->IsEquipable(GetBaseRace(), GetClass()) || (GetLevel() < trade_instance->GetItem()->ReqLevel)) { // deity checks will be handled within IsEquipable()
client_return.push_back(ClientReturn(trade_instance, trade_index));
continue;
if (trade_event_exists) {
event_trade.push_back(ClientTrade(trade_instance, trade_index));
continue;
}
else {
client->ResetTrade();
return;
}
}
client_trade.push_back(ClientTrade(trade_instance, trade_index));
@ -4713,7 +4745,9 @@ void Bot::PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client*
// check for incoming lore hacks
for (auto& trade_iterator : client_trade) {
if (!trade_iterator.trade_item_instance->GetItem()->LoreFlag) {
auto trade_instance = trade_iterator.trade_item_instance;
auto trade_index = trade_iterator.from_client_slot;
if (!trade_instance->GetItem()->LoreFlag) {
continue;
}
@ -4726,14 +4760,14 @@ void Bot::PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client*
continue;
}
if (trade_iterator.trade_item_instance->GetItem()->LoreGroup == -1 && check_iterator.trade_item_instance->GetItem()->ID == trade_iterator.trade_item_instance->GetItem()->ID) {
if (trade_instance->GetItem()->LoreGroup == -1 && check_iterator.trade_item_instance->GetItem()->ID == trade_instance->GetItem()->ID) {
LogError("Bot::PerformTradeWithClient trade hack detected by {} with {}.", client->GetCleanName(), GetCleanName());
client->Message(Chat::White, "Trade hack detected, the trade has been cancelled.");
client->ResetTrade();
return;
}
if ((trade_iterator.trade_item_instance->GetItem()->LoreGroup > 0) && (check_iterator.trade_item_instance->GetItem()->LoreGroup == trade_iterator.trade_item_instance->GetItem()->LoreGroup)) {
if ((trade_instance->GetItem()->LoreGroup > 0) && (check_iterator.trade_item_instance->GetItem()->LoreGroup == trade_instance->GetItem()->LoreGroup)) {
LogError("Bot::PerformTradeWithClient trade hack detected by {} with {}.", client->GetCleanName(), GetCleanName());
client->Message(Chat::White, "Trade hack detected, the trade has been cancelled.");
client->ResetTrade();
@ -4833,12 +4867,19 @@ void Bot::PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client*
}
}
// move unassignable items from trade list to return list
// move unassignable items from trade list to event list
for (std::list<ClientTrade>::iterator trade_iterator = client_trade.begin(); trade_iterator != client_trade.end();) {
if (trade_iterator->to_bot_slot == invslot::SLOT_INVALID) {
client_return.push_back(ClientReturn(trade_iterator->trade_item_instance, trade_iterator->from_client_slot));
trade_iterator = client_trade.erase(trade_iterator);
continue;
if (trade_event_exists) {
event_trade.push_back(ClientTrade(trade_iterator->trade_item_instance, trade_iterator->from_client_slot));
trade_iterator = client_trade.erase(trade_iterator);
continue;
}
else {
client_return.push_back(ClientReturn(trade_iterator->trade_item_instance, trade_iterator->from_client_slot));
trade_iterator = client_trade.erase(trade_iterator);
continue;
}
}
++trade_iterator;
}
@ -5035,6 +5076,24 @@ void Bot::PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client*
if (accepted_count) {
CalcBotStats(client->GetBotOption(Client::booStatsUpdate));
}
if (event_trade.size()) {
// Get Traded Items
EQ::ItemInstance* insts[8] = { 0 };
EQ::InventoryProfile& user_inv = client->GetInv();
for (int i = EQ::invslot::TRADE_BEGIN; i <= EQ::invslot::TRADE_END; ++i) {
insts[i - EQ::invslot::TRADE_BEGIN] = user_inv.GetItem(i);
client->DeleteItemInInventory(i);
}
// copy to be filtered by task updates, null trade slots preserved for quest event arg
std::vector<EQ::ItemInstance*> items(insts, insts + std::size(insts));
// Check if EVENT_TRADE accepts any items
std::vector<std::any> item_list(items.begin(), items.end());
parse->EventBot(EVENT_TRADE, this, client, "", 0, &item_list);
CalcBotStats(false);
}
}
bool Bot::Death(Mob *killerMob, int64 damage, uint16 spell_id, EQ::skills::SkillType attack_skill) {

View File

@ -1116,12 +1116,12 @@ void PerlembParser::GetQuestTypes(
if (
event == EVENT_SPELL_EFFECT_CLIENT ||
event == EVENT_SPELL_EFFECT_NPC ||
#ifdef BOTS
#ifdef BOTS
event == EVENT_SPELL_EFFECT_BOT ||
#endif
event == EVENT_SPELL_EFFECT_BUFF_TIC_CLIENT ||
event == EVENT_SPELL_EFFECT_BUFF_TIC_NPC ||
#ifdef BOTS
#ifdef BOTS
event == EVENT_SPELL_EFFECT_BUFF_TIC_BOT ||
#endif
event == EVENT_SPELL_FADE ||
@ -1241,7 +1241,7 @@ void PerlembParser::ExportQGlobals(
if (
!isPlayerQuest &&
!isGlobalPlayerQuest &&
!isBotQuest &&
!isBotQuest &&
!isGlobalBotQuest &&
!isItemQuest &&
!isSpellQuest
@ -1427,7 +1427,7 @@ void PerlembParser::ExportMobVariables(
if (
!isPlayerQuest &&
!isGlobalPlayerQuest &&
!isBotQuest &&
!isBotQuest &&
!isGlobalBotQuest &&
!isItemQuest &&
!isSpellQuest
@ -1525,56 +1525,55 @@ void PerlembParser::ExportEventVariables(
case EVENT_TRADE: {
if (extra_pointers) {
size_t sz = extra_pointers->size();
size_t sz = extra_pointers->size();
for (size_t i = 0; i < sz; ++i) {
EQ::ItemInstance *inst = std::any_cast<EQ::ItemInstance *>(extra_pointers->at(i));
auto* inst = std::any_cast<EQ::ItemInstance *>(extra_pointers->at(i));
const uint32 item_id = inst ? inst->GetItem()->ID : 0;
const int16 item_charges = inst ? inst->GetCharges() : 0;
const auto is_attuned = inst ? inst->IsAttuned() : false;
std::string var_name = "item";
var_name += std::to_string(i + 1);
auto var_name = fmt::format("item{}", i + 1);
ExportVar(package_name.c_str(), var_name.c_str(), item_id);
auto temp_var_name = fmt::format("{}_charges", var_name);
ExportVar(package_name.c_str(), temp_var_name.c_str(), item_charges);
temp_var_name = fmt::format("{}_attuned", var_name);
ExportVar(package_name.c_str(), temp_var_name.c_str(), is_attuned);
temp_var_name = fmt::format("{}_inst", var_name);
if (inst) {
ExportVar(package_name.c_str(), var_name.c_str(), inst->GetItem()->ID);
std::string temp_var_name = var_name;
temp_var_name += "_charges";
ExportVar(package_name.c_str(), temp_var_name.c_str(), inst->GetCharges());
temp_var_name = var_name;
temp_var_name += "_attuned";
ExportVar(package_name.c_str(), temp_var_name.c_str(), inst->IsAttuned());
temp_var_name = var_name;
temp_var_name += "_inst";
ExportVar(package_name.c_str(), temp_var_name.c_str(), "QuestItem", inst);
}
else {
ExportVar(package_name.c_str(), var_name.c_str(), 0);
std::string temp_var_name = var_name;
temp_var_name += "_charges";
ExportVar(package_name.c_str(), temp_var_name.c_str(), 0);
temp_var_name = var_name;
temp_var_name += "_attuned";
ExportVar(package_name.c_str(), temp_var_name.c_str(), 0);
temp_var_name = var_name;
temp_var_name += "_inst";
} else {
ExportVar(package_name.c_str(), temp_var_name.c_str(), 0);
}
}
}
ExportVar(package_name.c_str(), "copper", GetVar("copper." + std::string(itoa(objid))).c_str());
ExportVar(package_name.c_str(), "silver", GetVar("silver." + std::string(itoa(objid))).c_str());
ExportVar(package_name.c_str(), "gold", GetVar("gold." + std::string(itoa(objid))).c_str());
ExportVar(package_name.c_str(), "platinum", GetVar("platinum." + std::string(itoa(objid))).c_str());
std::string hashname = package_name + std::string("::itemcount");
perl->eval(std::string("%").append(hashname).append(" = ();").c_str());
perl->eval(std::string("++$").append(hashname).append("{$").append(package_name).append("::item1};").c_str());
perl->eval(std::string("++$").append(hashname).append("{$").append(package_name).append("::item2};").c_str());
perl->eval(std::string("++$").append(hashname).append("{$").append(package_name).append("::item3};").c_str());
perl->eval(std::string("++$").append(hashname).append("{$").append(package_name).append("::item4};").c_str());
auto unique_id = npcmob->GetNPCTypeID();
if (npcmob->IsBot()) {
unique_id = npcmob->CastToBot()->GetBotID();
}
ExportVar(package_name.c_str(), "copper", GetVar(fmt::format("copper.{}", unique_id)).c_str());
ExportVar(package_name.c_str(), "silver", GetVar(fmt::format("silver.{}", unique_id)).c_str());
ExportVar(package_name.c_str(), "gold", GetVar(fmt::format("gold.{}", unique_id)).c_str());
ExportVar(package_name.c_str(), "platinum", GetVar(fmt::format("platinum.{}", unique_id)).c_str());
auto hash_name = fmt::format("{}::itemcount", package_name);
perl->eval(fmt::format("%{} = ();", hash_name).c_str());
perl->eval(fmt::format("++${}{{${}::item1}};", hash_name, package_name).c_str());
perl->eval(fmt::format("++${}{{${}::item2}};", hash_name, package_name).c_str());
perl->eval(fmt::format("++${}{{${}::item3}};", hash_name, package_name).c_str());
perl->eval(fmt::format("++${}{{${}::item4}};", hash_name, package_name).c_str());
if (npcmob->IsBot()) {
perl->eval(fmt::format("++${}{{${}::item5}};", hash_name, package_name).c_str());
perl->eval(fmt::format("++${}{{${}::item6}};", hash_name, package_name).c_str());
perl->eval(fmt::format("++${}{{${}::item7}};", hash_name, package_name).c_str());
perl->eval(fmt::format("++${}{{${}::item8}};", hash_name, package_name).c_str());
}
break;
}
@ -1744,7 +1743,7 @@ void PerlembParser::ExportEventVariables(
break;
}
#ifdef BOTS
case EVENT_SPELL_EFFECT_BUFF_TIC_BOT:
#endif

View File

@ -294,6 +294,7 @@ LuaParser::LuaParser() {
BotArgumentDispatch[EVENT_SLAY] = handle_bot_slay;
BotArgumentDispatch[EVENT_TARGET_CHANGE] = handle_bot_target_change;
BotArgumentDispatch[EVENT_TIMER] = handle_bot_timer;
BotArgumentDispatch[EVENT_TRADE] = handle_bot_trade;
BotArgumentDispatch[EVENT_USE_SKILL] = handle_bot_use_skill;
#endif

View File

@ -49,16 +49,15 @@ void handle_npc_event_trade(QuestInterface *parse, lua_State* L, NPC* npc, Mob *
luabind::adl::object l_client_o = luabind::adl::object(L, l_client);
l_client_o.push(L);
lua_setfield(L, -2, "other");
lua_createtable(L, 0, 0);
std::stringstream ident;
ident << npc->GetNPCTypeID();
if(extra_pointers) {
const auto npc_id = npc->GetNPCTypeID();
if (extra_pointers) {
size_t sz = extra_pointers->size();
for(size_t i = 0; i < sz; ++i) {
std::string prefix = "item" + std::to_string(i + 1);
EQ::ItemInstance *inst = std::any_cast<EQ::ItemInstance*>(extra_pointers->at(i));
for (size_t i = 0; i < sz; ++i) {
auto prefix = fmt::format("item{}", i + 1);
auto* inst = std::any_cast<EQ::ItemInstance*>(extra_pointers->at(i));
Lua_ItemInst l_inst = inst;
luabind::adl::object l_inst_o = luabind::adl::object(L, l_inst);
@ -68,17 +67,30 @@ void handle_npc_event_trade(QuestInterface *parse, lua_State* L, NPC* npc, Mob *
}
}
lua_pushinteger(L, std::stoul(parse->GetVar("platinum." + ident.str())));
auto money_string = fmt::format("platinum.{}", npc_id);
uint32 money_value = !parse->GetVar(money_string).empty() ? std::stoul(parse->GetVar(money_string)) : 0;
lua_pushinteger(L, money_value);
lua_setfield(L, -2, "platinum");
lua_pushinteger(L, std::stoul(parse->GetVar("gold." + ident.str())));
money_string = fmt::format("gold.{}", npc_id);
money_value = !parse->GetVar(money_string).empty() ? std::stoul(parse->GetVar(money_string)) : 0;
lua_pushinteger(L, money_value);
lua_setfield(L, -2, "gold");
lua_pushinteger(L, std::stoul(parse->GetVar("silver." + ident.str())));
money_string = fmt::format("silver.{}", npc_id);
money_value = !parse->GetVar(money_string).empty() ? std::stoul(parse->GetVar(money_string)) : 0;
lua_pushinteger(L, money_value);
lua_setfield(L, -2, "silver");
lua_pushinteger(L, std::stoul(parse->GetVar("copper." + ident.str())));
money_string = fmt::format("copper.{}", npc_id);
money_value = !parse->GetVar(money_string).empty() ? std::stoul(parse->GetVar(money_string)) : 0;
lua_pushinteger(L, money_value);
lua_setfield(L, -2, "copper");
lua_setfield(L, -2, "trade");
}
@ -262,12 +274,12 @@ void handle_npc_loot_zone(QuestInterface *parse, lua_State* L, NPC* npc, Mob *in
luabind::adl::object l_client_o = luabind::adl::object(L, l_client);
l_client_o.push(L);
lua_setfield(L, -2, "other");
Lua_ItemInst l_item(std::any_cast<EQ::ItemInstance*>(extra_pointers->at(0)));
luabind::adl::object l_item_o = luabind::adl::object(L, l_item);
l_item_o.push(L);
lua_setfield(L, -2, "item");
Lua_Corpse l_corpse(std::any_cast<Corpse*>(extra_pointers->at(1)));
luabind::adl::object l_corpse_o = luabind::adl::object(L, l_corpse);
l_corpse_o.push(L);
@ -420,10 +432,10 @@ void handle_player_cast(QuestInterface *parse, lua_State* L, Client* client, std
}
lua_setfield(L, -2, "spell");
lua_pushinteger(L, std::stoi(sep.arg[1]));
lua_setfield(L, -2, "caster_id");
lua_pushinteger(L, std::stoi(sep.arg[2]));
lua_setfield(L, -2, "caster_level");
}
@ -534,7 +546,7 @@ void handle_player_combine(QuestInterface *parse, lua_State* L, Client* client,
lua_setfield(L, -2, "recipe_id");
lua_pushstring(L, data.c_str());
lua_setfield(L, -2, "recipe_name");
lua_setfield(L, -2, "recipe_name");
}
void handle_player_feign(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data,
@ -650,7 +662,7 @@ void handle_player_quest_combine(QuestInterface* parse, lua_State* L, Client* cl
lua_pushinteger(L, std::stoi(data));
lua_setfield(L, -2, "container_slot");
}
void handle_player_consider(QuestInterface* parse, lua_State* L, Client* client, std::string data, uint32 extra_data, std::vector<std::any>* extra_pointers) {
lua_pushinteger(L, std::stoi(data));
lua_setfield(L, -2, "entity_id");
@ -817,7 +829,7 @@ void handle_spell_event(QuestInterface *parse, lua_State* L, Mob* mob, Client* c
lua_pushinteger(L, std::stoi(sep.arg[3]));
lua_setfield(L, -2, "buff_slot");
Lua_Spell l_spell(spell_id);
luabind::adl::object l_spell_o = luabind::adl::object(L, l_spell);
l_spell_o.push(L);
@ -853,7 +865,7 @@ void handle_player_equip_item(QuestInterface *parse, lua_State* L, Client* clien
lua_pushnumber(L, std::stoi(sep.arg[1]));
lua_setfield(L, -2, "slot_id");
Lua_ItemInst l_item(extra_data);
luabind::adl::object l_item_o = luabind::adl::object(L, l_item);
l_item_o.push(L);
@ -995,10 +1007,10 @@ void handle_bot_cast(
}
lua_setfield(L, -2, "spell");
lua_pushinteger(L, std::stoi(sep.arg[1]));
lua_setfield(L, -2, "caster_id");
lua_pushinteger(L, std::stoi(sep.arg[2]));
lua_setfield(L, -2, "caster_level");
}
@ -1153,6 +1165,64 @@ void handle_bot_timer(
lua_setfield(L, -2, "timer");
}
void handle_bot_trade(
QuestInterface *parse,
lua_State* L,
Bot* bot,
Mob *init,
std::string data,
uint32 extra_data,
std::vector<std::any> *extra_pointers
) {
Lua_Client l_client(reinterpret_cast<Client*>(init));
luabind::adl::object l_client_o = luabind::adl::object(L, l_client);
l_client_o.push(L);
lua_setfield(L, -2, "other");
lua_createtable(L, 0, 0);
const auto bot_id = bot->GetBotID();
if (extra_pointers) {
size_t sz = extra_pointers->size();
for (size_t i = 0; i < sz; ++i) {
auto prefix = fmt::format("item{}", i + 1);
auto* inst = std::any_cast<EQ::ItemInstance*>(extra_pointers->at(i));
Lua_ItemInst l_inst = inst;
luabind::adl::object l_inst_o = luabind::adl::object(L, l_inst);
l_inst_o.push(L);
lua_setfield(L, -2, prefix.c_str());
}
}
auto money_string = fmt::format("platinum.{}", bot_id);
uint32 money_value = !parse->GetVar(money_string).empty() ? std::stoul(parse->GetVar(money_string)) : 0;
lua_pushinteger(L, money_value);
lua_setfield(L, -2, "platinum");
money_string = fmt::format("gold.{}", bot_id);
money_value = !parse->GetVar(money_string).empty() ? std::stoul(parse->GetVar(money_string)) : 0;
lua_pushinteger(L, money_value);
lua_setfield(L, -2, "gold");
money_string = fmt::format("silver.{}", bot_id);
money_value = !parse->GetVar(money_string).empty() ? std::stoul(parse->GetVar(money_string)) : 0;
lua_pushinteger(L, money_value);
lua_setfield(L, -2, "silver");
money_string = fmt::format("copper.{}", bot_id);
money_value = !parse->GetVar(money_string).empty() ? std::stoul(parse->GetVar(money_string)) : 0;
lua_pushinteger(L, money_value);
lua_setfield(L, -2, "copper");
lua_setfield(L, -2, "trade");
}
void handle_bot_use_skill(
QuestInterface *parse,
lua_State* L,

View File

@ -276,6 +276,16 @@ void handle_bot_timer(
std::vector<std::any> *extra_pointers
);
void handle_bot_trade(
QuestInterface *parse,
lua_State* L,
Bot* bot,
Mob* init,
std::string data,
uint32 extra_data,
std::vector<std::any> *extra_pointers
);
void handle_bot_use_skill(
QuestInterface *parse,
lua_State* L,

View File

@ -1234,6 +1234,10 @@ bool QuestParserCollection::BotHasQuestSubGlobal(QuestEventID evt) {
return false;
}
bool QuestParserCollection::BotHasQuestSub(QuestEventID evt) {
return BotHasQuestSubLocal(evt) || BotHasQuestSubGlobal(evt);
}
QuestInterface *QuestParserCollection::GetQIByBotQuest(std::string &filename) {
if (!zone || !zone->IsLoaded()) {
return nullptr;