mirror of
https://gitlab.com/Scribble/gdlenhanced.git
synced 2026-08-17 14:23:04 -04:00
501 lines
13 KiB
C++
501 lines
13 KiB
C++
|
|
#include <StdAfx.h>
|
|
#include "Vendor.h"
|
|
#include "WeenieFactory.h"
|
|
#include "World.h"
|
|
#include "Player.h"
|
|
#include "EmoteManager.h"
|
|
#include "SpellcastingManager.h"
|
|
|
|
CVendorItem::CVendorItem()
|
|
{
|
|
}
|
|
|
|
CVendorItem::~CVendorItem()
|
|
{
|
|
g_pWorld->EnsureRemoved(weenie);
|
|
SafeDelete(weenie);
|
|
}
|
|
|
|
CVendor::CVendor()
|
|
{
|
|
m_Qualities.m_WeenieType = Vendor_WeenieType;
|
|
}
|
|
|
|
CVendor::~CVendor()
|
|
{
|
|
ResetItems();
|
|
}
|
|
|
|
void CVendor::Tick()
|
|
{
|
|
if (m_EmoteManager)
|
|
m_EmoteManager->Tick();
|
|
|
|
if (m_VendorCycleTime <= Timer::cur_time)
|
|
{
|
|
CheckRange();
|
|
m_VendorCycleTime = (Timer::cur_time + 3);
|
|
}
|
|
|
|
}
|
|
|
|
void CVendor::ResetItems()
|
|
{
|
|
for (auto item : m_Items)
|
|
{
|
|
delete item;
|
|
}
|
|
|
|
m_Items.clear();
|
|
}
|
|
|
|
void CVendor::AddVendorItem(uint32_t wcid, int ptid, float shade, int amount)
|
|
{
|
|
|
|
if (wcid == 1)
|
|
return;
|
|
|
|
CWeenieObject *weenie = g_pWeenieFactory->CreateWeenieByClassID(wcid);
|
|
|
|
if (!weenie)
|
|
return;
|
|
|
|
if (ptid)
|
|
weenie->m_Qualities.SetInt(PALETTE_TEMPLATE_INT, ptid);
|
|
|
|
if (shade > 0.0)
|
|
weenie->m_Qualities.SetFloat(SHADE_FLOAT, shade);
|
|
|
|
if (!g_pWorld->CreateEntity(weenie, false))
|
|
return;
|
|
|
|
CVendorItem *item = new CVendorItem();
|
|
item->weenie = weenie;
|
|
item->amount = amount;
|
|
m_Items.push_back(item);
|
|
}
|
|
|
|
void CVendor::AddVendorItem(uint32_t wcid, int amount)
|
|
{
|
|
if (wcid == 1)
|
|
return;
|
|
|
|
CWeenieObject *weenie = g_pWeenieFactory->CreateWeenieByClassID(wcid);
|
|
|
|
if (!weenie)
|
|
return;
|
|
|
|
if (!g_pWorld->CreateEntity(weenie, false))
|
|
return;
|
|
|
|
CVendorItem *item = new CVendorItem();
|
|
item->weenie = weenie;
|
|
item->amount = amount;
|
|
m_Items.push_back(item);
|
|
}
|
|
|
|
CVendorItem *CVendor::FindVendorItem(uint32_t item_id)
|
|
{
|
|
for (auto item : m_Items)
|
|
{
|
|
if (item->weenie->GetID() == item_id)
|
|
return item;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int CVendor::TrySellItemsToPlayer(CPlayerWeenie *buyer, const std::list<ItemProfile *> &desiredItems)
|
|
{
|
|
const uint32_t MAX_COIN_PURCHASE = 2000000000; // // limit to purchases less than 2 billion pyreal
|
|
|
|
// using WERROR_NO_OBJECT as a generic error, change later if it matters
|
|
if (desiredItems.size() >= 100)
|
|
return WERROR_NO_OBJECT;
|
|
|
|
int currencyid = W_COINSTACK_CLASS;
|
|
if (profile.trade_id)
|
|
currencyid = profile.trade_id;
|
|
int currencyamount = buyer->RecalculateCoinAmount(currencyid);
|
|
|
|
// check cost of items: Pyreal, Slots, PackSlots
|
|
UINT64 totalCost = 0;
|
|
uint32_t totalSlotsRequired = 0;
|
|
uint32_t totalPackSlotsRequired = 0;
|
|
for (auto desiredItem : desiredItems)
|
|
{
|
|
CVendorItem *vendorItem = FindVendorItem(desiredItem->iid);
|
|
if (!vendorItem)
|
|
return WERROR_NO_OBJECT;
|
|
if (vendorItem->amount >= 0 && vendorItem->amount < desiredItem->amount)
|
|
return WERROR_NO_OBJECT;
|
|
if (desiredItem->amount == 0)
|
|
continue;
|
|
|
|
int maxStackSize = vendorItem->weenie->InqIntQuality(MAX_STACK_SIZE_INT, 1);
|
|
if (maxStackSize < 1)
|
|
maxStackSize = 1;
|
|
|
|
if (vendorItem->weenie->RequiresPackSlot())
|
|
{
|
|
totalPackSlotsRequired++;
|
|
}
|
|
else {
|
|
totalSlotsRequired += (desiredItem->amount / maxStackSize);
|
|
if (desiredItem->amount % maxStackSize > 0)
|
|
totalSlotsRequired++;
|
|
}
|
|
|
|
|
|
if (vendorItem->weenie->InqIntQuality(ITEM_TYPE_INT, TYPE_UNDEF) == TYPE_PROMISSORY_NOTE)
|
|
totalCost += (uint32_t)round((vendorItem->weenie->GetValue() * 1.15) * desiredItem->amount);
|
|
else
|
|
totalCost += (uint32_t)round((vendorItem->weenie->GetValue() * profile.sell_price) * desiredItem->amount);
|
|
|
|
}
|
|
|
|
if (totalCost >= MAX_COIN_PURCHASE)
|
|
return WERROR_NO_OBJECT; // limit to purchases less than 2 billion pyreal
|
|
|
|
if (currencyamount < totalCost)
|
|
{
|
|
buyer->SendText("You don't have enough money.", LTT_DEFAULT);
|
|
return WERROR_NO_OBJECT;
|
|
}
|
|
|
|
if (buyer->Container_GetNumFreeMainPackSlots() < totalSlotsRequired)
|
|
{
|
|
buyer->SendText("There's not enough room for the items in your inventory.", LTT_DEFAULT);
|
|
return WERROR_NO_OBJECT;
|
|
}
|
|
|
|
if ((buyer->GetContainersCapacity() - buyer->m_Packs.size()) < totalPackSlotsRequired)
|
|
{
|
|
buyer->SendText("There's not pack slots for the items in your inventory.", LTT_DEFAULT);
|
|
return WERROR_NO_OBJECT;
|
|
}
|
|
|
|
uint32_t coinConsumed = 0;
|
|
|
|
coinConsumed = buyer->ConsumeCoin(totalCost, currencyid);
|
|
|
|
if (coinConsumed < totalCost)
|
|
{
|
|
//This shouldn't happen.
|
|
buyer->SendText("Couldn't find all the money for the payment.", LTT_DEFAULT);
|
|
buyer->SpawnInContainer(currencyid, coinConsumed); //give back what we consumed and abort.
|
|
|
|
return WERROR_NO_OBJECT;
|
|
}
|
|
|
|
buyer->EmitSound(Sound_PickUpItem, 1.0f);
|
|
// clone the weenie
|
|
for (auto desiredItem : desiredItems)
|
|
{
|
|
CWeenieObject *originalWeenie = FindVendorItem(desiredItem->iid)->weenie;
|
|
|
|
if (originalWeenie->m_Qualities.GetInt(ITEM_TYPE_INT, 0) == TYPE_SERVICE)
|
|
{
|
|
DoForcedMotion(Motion_CastSpell);
|
|
MakeSpellcastingManager()->CastSpellInstant(buyer->GetID(), originalWeenie->m_Qualities.GetDID(SPELL_DID, 0));
|
|
DoForcedMotion(Motion_Ready);
|
|
}
|
|
else
|
|
{
|
|
originalWeenie->SetID(CWorld::GenerateGUID(eDynamicGUID));
|
|
buyer->SpawnCloneInContainer(originalWeenie, desiredItem->amount);
|
|
}
|
|
}
|
|
|
|
DoVendorEmote(Buy_VendorTypeEmote, buyer->GetID());
|
|
|
|
if (profile.trade_id) {
|
|
profile.trade_num = currencyamount;
|
|
} else {
|
|
profile.trade_num = 0;
|
|
}
|
|
SendVendorInventory(buyer);
|
|
|
|
return WERROR_NONE;
|
|
}
|
|
|
|
int CVendor::TryBuyItemsFromPlayer(CPlayerWeenie *seller, const std::list<ItemProfile *> &desiredItems)
|
|
{
|
|
const uint32_t MAX_COIN_PURCHASE = 1000000000; // limit to purchases less than 1 billion pyreal
|
|
const uint32_t MAX_COIN_ALLOWED = 2000000000; // limit to max amount of coin to less than 2 billion pyreal
|
|
|
|
// using WERROR_NO_OBJECT as a generic error, change later if it matters
|
|
if (desiredItems.size() >= 100)
|
|
return WERROR_NO_OBJECT;
|
|
|
|
// check price of items
|
|
UINT64 totalValue = 0;
|
|
|
|
const std::list<ItemProfile *> filteredItems = GetFilteredItems(desiredItems, seller);
|
|
for (auto desiredItem : filteredItems)
|
|
{
|
|
CWeenieObject *sellerItem = seller->FindContainedItem(desiredItem->iid);
|
|
if (!sellerItem)
|
|
return WERROR_NO_OBJECT;
|
|
if (sellerItem->InqIntQuality(STACK_SIZE_INT, 1) < desiredItem->amount)
|
|
return WERROR_NO_OBJECT;
|
|
if (sellerItem->HasContainerContents()) // can't buy containers that have items in them
|
|
return WERROR_NO_OBJECT;
|
|
if (desiredItem->amount <= 0)
|
|
continue;
|
|
|
|
if(sellerItem->InqIntQuality(ITEM_TYPE_INT, TYPE_UNDEF) == TYPE_PROMISSORY_NOTE)
|
|
totalValue += (uint32_t)round(sellerItem->InqIntQuality(VALUE_INT, 0));
|
|
else
|
|
totalValue += (uint32_t)round(sellerItem->InqIntQuality(VALUE_INT, 0) * profile.buy_price);
|
|
}
|
|
|
|
if (totalValue >= MAX_COIN_PURCHASE)
|
|
{
|
|
seller->SendText("That purchase is too large.", LTT_DEFAULT);
|
|
return WERROR_NO_OBJECT; // purchase too large
|
|
}
|
|
|
|
//check of we have enough room for the coins.
|
|
int maxStackSize;
|
|
int totalSlotsRequired = 1;
|
|
CWeenieDefaults *weenieDefs = g_pWeenieFactory->GetWeenieDefaults(W_COINSTACK_CLASS);
|
|
if (weenieDefs->m_Qualities.InqInt(MAX_STACK_SIZE_INT, maxStackSize))
|
|
{
|
|
if (maxStackSize < 1)
|
|
maxStackSize = 1;
|
|
totalSlotsRequired = max((int)(totalValue / maxStackSize), 1);
|
|
}
|
|
|
|
//if (seller->Container_GetNumFreeMainPackSlots() < max(totalSlotsRequired - (int)desiredItems.size(), 1)) -- This doesn't take in to account that the items are coming out of side packs, causes error when attempting to spawn pyreals into pack. Eats all items.
|
|
if (seller->Container_GetNumFreeMainPackSlots() < totalSlotsRequired)
|
|
{
|
|
seller->SendText("There's not enough room for the coins in your inventory.", LTT_DEFAULT);
|
|
return WERROR_NO_OBJECT;
|
|
}
|
|
|
|
if (seller->InqIntQuality(COIN_VALUE_INT, 0) + totalValue > MAX_COIN_ALLOWED)
|
|
return WERROR_NO_OBJECT; // user will have too much wealth
|
|
|
|
seller->EmitSound(Sound_PickUpItem, 1.0f);
|
|
|
|
// take away the items purchased...
|
|
for (auto desiredItem : filteredItems)
|
|
{
|
|
//The client won't allow selling partial stacks
|
|
//In fact it will request an item split in inventory before adding partial stacks and once in the sell list
|
|
//will give the user an error message stating you can't sell partial stacks. So this makes it easier for us.
|
|
|
|
CWeenieObject *weenie = seller->FindContainedItem(desiredItem->iid);
|
|
if (!weenie)
|
|
continue;
|
|
weenie->Remove(); //todo: maybe add vendors relisting loot items like they used to way back.
|
|
}
|
|
if (filteredItems.size()>0)
|
|
DoVendorEmote(Sell_VendorTypeEmote, seller->GetID());
|
|
|
|
//and add the coins.
|
|
seller->SpawnInContainer(W_COINSTACK_CLASS, totalValue);
|
|
|
|
if (filteredItems.size() < desiredItems.size())
|
|
seller->NotifyWeenieErrorWithString(WERROR_TRADE_AI_DOESNT_WANT, GetName().c_str());
|
|
|
|
return WERROR_NONE;
|
|
}
|
|
|
|
const std::list<ItemProfile *> CVendor::GetFilteredItems(std::list<ItemProfile *> desiredItems, CPlayerWeenie *seller)
|
|
{
|
|
for (std::list<ItemProfile *>::iterator i = desiredItems.begin(); i != desiredItems.end();)
|
|
{
|
|
auto desiredItem = *i;
|
|
CWeenieObject *sellerItem = seller->FindContainedItem(desiredItem->iid);
|
|
if (!sellerItem)
|
|
{
|
|
i = desiredItems.erase(i);
|
|
continue;
|
|
}
|
|
|
|
auto sellerItemValue = sellerItem->InqIntQuality(STACK_SIZE_INT, TRUE) > 1 ? sellerItem->InqIntQuality(STACK_UNIT_VALUE_INT, TRUE) : sellerItem->GetValue();
|
|
auto sellerItemType = sellerItem->InqIntQuality(ITEM_TYPE_INT, 0, TRUE);
|
|
auto sellerItemHasMana = sellerItem->InqIntQuality(ITEM_MAX_MANA_INT, 0, TRUE);
|
|
|
|
if (sellerItemType != TYPE_PROMISSORY_NOTE &&
|
|
(profile.min_value > sellerItemValue ||
|
|
profile.max_value < sellerItemValue ||
|
|
!(profile.item_types & sellerItemType) ||
|
|
(!profile.magic && sellerItemHasMana) ||
|
|
(profile.trade_id && profile.trade_id != sellerItem->m_Qualities.id)))
|
|
{
|
|
i = desiredItems.erase(i);
|
|
}
|
|
else
|
|
++i;
|
|
}
|
|
return desiredItems;
|
|
}
|
|
|
|
void CVendor::AddVendorItemByAllMatchingNames(const char *name)
|
|
{
|
|
int index = 0;
|
|
uint32_t wcid;
|
|
|
|
while (wcid = g_pWeenieFactory->GetWCIDByName(name, index++))
|
|
{
|
|
AddVendorItem(wcid, -1);
|
|
}
|
|
}
|
|
|
|
void CVendor::GenerateItems()
|
|
{
|
|
ResetItems();
|
|
}
|
|
|
|
void CVendor::GenerateAllItems()
|
|
{
|
|
ResetItems();
|
|
}
|
|
|
|
void CVendor::ValidateItems()
|
|
{
|
|
}
|
|
|
|
void CVendor::PreSpawnCreate()
|
|
{
|
|
CMonsterWeenie::PreSpawnCreate();
|
|
|
|
profile.min_value = InqIntQuality(MERCHANDISE_MIN_VALUE_INT, 0, TRUE);
|
|
profile.max_value = InqIntQuality(MERCHANDISE_MAX_VALUE_INT, 0, TRUE);
|
|
profile.magic = InqBoolQuality(DEAL_MAGICAL_ITEMS_BOOL, FALSE);
|
|
profile.item_types = InqIntQuality(MERCHANDISE_ITEM_TYPES_INT, 0, TRUE);
|
|
profile.buy_price = InqFloatQuality(BUY_PRICE_FLOAT, 0, TRUE);
|
|
profile.sell_price = InqFloatQuality(SELL_PRICE_FLOAT, 0, TRUE);
|
|
profile.trade_id = InqDIDQuality(ALTERNATE_CURRENCY_DID, 0);
|
|
|
|
if (m_Qualities._create_list)
|
|
{
|
|
for (auto i = m_Qualities._create_list->begin(); i != m_Qualities._create_list->end(); i++)
|
|
{
|
|
if (i->destination == DestinationType::Shop_DestinationType || i->destination == DestinationType::Contain_DestinationType)
|
|
{
|
|
if (i->destination == DestinationType::Contain_DestinationType || i->destination == DestinationType::Wield_DestinationType)
|
|
{
|
|
if (i->wcid == profile.trade_id)
|
|
{
|
|
CWeenieDefaults *currency = g_pWeenieFactory->GetWeenieDefaults(i->wcid);
|
|
profile.trade_name = currency->m_Qualities.GetString(NAME_STRING, "");
|
|
}
|
|
}
|
|
else
|
|
AddVendorItem(i->wcid, i->palette, i->shade, -1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CVendor::SendVendorInventory(CWeenieObject *other)
|
|
{
|
|
ValidateItems();
|
|
|
|
BinaryWriter vendorInfo;
|
|
vendorInfo.Write<uint32_t>(0x62);
|
|
vendorInfo.Write<uint32_t>(GetID());
|
|
|
|
profile.Pack(&vendorInfo);
|
|
|
|
vendorInfo.Write<uint32_t>(m_Items.size());
|
|
for (auto item : m_Items)
|
|
{
|
|
ItemProfile itemProfile;
|
|
itemProfile.amount = item->amount;
|
|
itemProfile.iid = item->weenie->GetID(); // for now, use the WCID plus arbitrary number to reference this item
|
|
itemProfile.pwd = PublicWeenieDesc::CreateFromQualities(&item->weenie->m_Qualities);
|
|
itemProfile.pwd->_containerID = GetID();
|
|
itemProfile.Pack(&vendorInfo);
|
|
}
|
|
|
|
other->SendNetMessage(&vendorInfo, PRIVATE_MSG, TRUE, FALSE);
|
|
}
|
|
|
|
void CVendor::DoVendorEmote(int type, uint32_t target_id)
|
|
{
|
|
if (m_Qualities._emote_table)
|
|
{
|
|
PackableList<EmoteSet> *emoteSetList = m_Qualities._emote_table->_emote_table.lookup(Vendor_EmoteCategory);
|
|
|
|
if (emoteSetList)
|
|
{
|
|
double dice = Random::GenFloat(0.0, 1.0);
|
|
|
|
for (auto &emoteSet : *emoteSetList)
|
|
{
|
|
if (emoteSet.vendorType != type)
|
|
continue;
|
|
|
|
if (dice < emoteSet.probability)
|
|
{
|
|
MakeEmoteManager()->ExecuteEmoteSet(emoteSet, target_id);
|
|
|
|
break;
|
|
}
|
|
|
|
dice -= emoteSet.probability;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int CVendor::DoUseResponse(CWeenieObject *player)
|
|
{
|
|
if (IsCompletelyIdle())
|
|
{
|
|
MovementParameters params;
|
|
TurnToObject(player->GetID(), ¶ms);
|
|
}
|
|
|
|
if (profile.trade_id) {
|
|
profile.trade_num = player->RecalculateCoinAmount(profile.trade_id);
|
|
} else {
|
|
profile.trade_num = 0;
|
|
player->RecalculateCoinAmount(W_COINSTACK_CLASS);
|
|
}
|
|
|
|
SendVendorInventory(player);
|
|
DoVendorEmote(Open_VendorTypeEmote, player->GetID());
|
|
m_ActiveBuyers.insert(player->GetID());
|
|
|
|
return CWeenieObject::DoUseResponse(player);
|
|
}
|
|
|
|
void CAvatarVendor::PreSpawnCreate()
|
|
{
|
|
CVendor::PreSpawnCreate();
|
|
|
|
// ResetItems();
|
|
|
|
for (uint32_t i = 0; i < g_pWeenieFactory->m_NumAvatars; i++)
|
|
{
|
|
AddVendorItem(g_pWeenieFactory->m_FirstAvatarWCID + i, -1);
|
|
}
|
|
}
|
|
|
|
void CVendor::CheckRange()
|
|
{
|
|
double UseDist = InqFloatQuality(USE_RADIUS_FLOAT, 0, TRUE);
|
|
for (std::set<uint32_t>::iterator i = m_ActiveBuyers.begin(); i != m_ActiveBuyers.end();)
|
|
{
|
|
CWeenieObject *player = g_pWorld->FindPlayer(*i);
|
|
if(player)
|
|
{
|
|
if (DistanceTo(player, true) > UseDist)
|
|
{
|
|
DoVendorEmote(Close_VendorTypeEmote, player->GetID());
|
|
i = m_ActiveBuyers.erase(i);
|
|
}
|
|
else
|
|
++i;
|
|
}
|
|
else
|
|
i = m_ActiveBuyers.erase(i);
|
|
}
|
|
}
|