[Merchants] Add New Classic Greed/Faction/Charisma Prices Rule (#4301)

* [Merchants] Add New Classic Greed/Faction/Charisma Prices Rule

* Fix size of greed field.

* Fix { formatting and add {} to one liners

* Fix return type of GetGreedPercent

* Remove code that slipped in from another patch

* Fix greed to be unsigned

* Update client.cpp

* Update client_packet.cpp

* Update client.cpp

Fix bad name in extra log message added manually from merge.

* Update client_packet.cpp

Spacing.

* Update client.cpp

---------

Co-authored-by: Kinglykrab <kinglykrab@gmail.com>
This commit is contained in:
Paul Coene
2024-05-17 11:16:02 -04:00
committed by GitHub
parent a80ab75260
commit c0a8fd097e
11 changed files with 605 additions and 457 deletions
+89 -2
View File
@@ -3761,7 +3761,83 @@ void Client::Escape()
MessageString(Chat::Skills, ESCAPE);
}
float Client::CalcPriceMod(Mob* other, bool reverse)
float Client::CalcClassicPriceMod(Mob* other, bool reverse) {
float price_multiplier = 0.8f;
if (other && other->IsNPC()) {
FACTION_VALUE faction_level = GetFactionLevel(CharacterID(), other->CastToNPC()->GetNPCTypeID(), GetRace(), GetClass(), GetDeity(), other->CastToNPC()->GetPrimaryFaction(), other);
int32 cha = GetCHA();
if (faction_level <= FACTION_AMIABLY) {
cha += 11; // amiable faction grants a defacto 11 charisma bonus
}
uint8 greed = other->CastToNPC()->GetGreedPercent();
// Sony's precise algorithm is unknown, but this produces output that is virtually identical
if (faction_level <= FACTION_INDIFFERENTLY) {
if (cha > 75) {
if (greed) {
// this is derived from curve fitting to a lot of price data
price_multiplier = -0.2487768 + (1.599635 - -0.2487768) / (1 + pow((cha / 135.1495), 1.001983));
price_multiplier += (greed + 25u) / 100.0f; // default vendor markup is 25%; anything above that is 'greedy'
price_multiplier = 1.0f / price_multiplier;
}
else {
// non-greedy merchants use a linear scale
price_multiplier = 1.0f - ((115.0f - cha) * 0.004f);
}
}
else if (cha > 60) {
price_multiplier = 1.0f / (1.25f + (greed / 100.0f));
}
else {
price_multiplier = 1.0f / ((1.0f - (cha - 120.0f) / 220.0f) + (greed / 100.0f));
}
}
else { // apprehensive
if (cha > 75) {
if (greed) {
// this is derived from curve fitting to a lot of price data
price_multiplier = -0.25f + (1.823662 - -0.25f) / (1 + (cha / 135.0f));
price_multiplier += (greed + 25u) / 100.0f; // default vendor markup is 25%; anything above that is 'greedy'
price_multiplier = 1.0f / price_multiplier;
}
else {
price_multiplier = (100.0f - (145.0f - cha) / 2.8f) / 100.0f;
}
}
else if (cha > 60) {
price_multiplier = 1.0f / (1.4f + greed / 100.0f);
}
else {
price_multiplier = 1.0f / ((1.0f + (143.574 - cha) / 196.434) + (greed / 100.0f));
}
}
float maxResult = 1.0f / 1.05; // price reduction caps at this amount
if (price_multiplier > maxResult) {
price_multiplier = maxResult;
}
if (!reverse) {
price_multiplier = 1.0f / price_multiplier;
}
}
LogMerchants(
"[{}] [{}] items at [{}] price multiplier [{}] [{}]",
other->GetName(),
reverse ? "buys" : "sells",
price_multiplier,
reverse ? "from" : "to",
GetName()
);
return price_multiplier;
}
float Client::CalcNewPriceMod(Mob* other, bool reverse)
{
float chaformula = 0;
if (other)
@@ -3807,6 +3883,17 @@ float Client::CalcPriceMod(Mob* other, bool reverse)
return chaformula; //Returns 1.10, expensive stuff!
}
float Client::CalcPriceMod(Mob* other, bool reverse)
{
float price_mod = CalcNewPriceMod(other, reverse);
if (RuleB(Merchant, UseClassicPriceMod)) {
price_mod = CalcClassicPriceMod(other, reverse);
}
return price_mod;
}
void Client::GetGroupAAs(GroupLeadershipAA_Struct *into) const {
memcpy(into, &m_pp.leader_abilities.group, sizeof(GroupLeadershipAA_Struct));
}
@@ -12474,4 +12561,4 @@ void Client::RemoveItemBySerialNumber(uint32 serial_number, uint32 quantity)
}
}
}
}
}
+2
View File
@@ -1127,6 +1127,8 @@ public:
void GoFish(bool guarantee = false, bool use_bait = true);
void ForageItem(bool guarantee = false);
//Calculate vendor price modifier based on CHA: (reverse==selling)
float CalcClassicPriceMod(Mob* other = 0, bool reverse = false);
float CalcNewPriceMod(Mob* other = 0, bool reverse = false);
float CalcPriceMod(Mob* other = 0, bool reverse = false);
void ResetTrade();
void DropInst(const EQ::ItemInstance* inst);
+37 -16
View File
@@ -14041,16 +14041,21 @@ void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app)
EQ::ItemInstance* inst = database.CreateItem(item, charges);
int SinglePrice = 0;
if (RuleB(Merchant, UsePriceMod))
SinglePrice = (item->Price * (RuleR(Merchant, SellCostMod)) * item->SellRate * Client::CalcPriceMod(tmp, false));
else
SinglePrice = (item->Price * (RuleR(Merchant, SellCostMod)) * item->SellRate);
int single_price = (item->Price * item->SellRate);
// Don't use SellCostMod if using UseClassicPriceMod
if (!RuleB(Merchant, UseClassicPriceMod)) {
single_price *= RuleR(Merchant, SellCostMod);
}
if (RuleB(Merchant, UsePriceMod)) {
single_price *= Client::CalcPriceMod(tmp, false);
}
if (item->MaxCharges > 1)
mpo->price = SinglePrice;
mpo->price = single_price;
else
mpo->price = SinglePrice * mp->quantity;
mpo->price = single_price * mp->quantity;
if (mpo->price < 0)
{
@@ -14131,7 +14136,7 @@ void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app)
else {
// Update the charges/quantity in the merchant window
inst->SetCharges(new_charges);
inst->SetPrice(SinglePrice);
inst->SetPrice(single_price);
inst->SetMerchantSlot(mp->itemslot);
inst->SetMerchantCount(new_charges);
@@ -14310,7 +14315,15 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app)
if (RuleB(Merchant, UsePriceMod)) {
for (i = 1; i <= cost_quantity; i++) {
price = (uint32)((item->Price * i)*(RuleR(Merchant, BuyCostMod))*Client::CalcPriceMod(vendor, true) + 0.5); // need to round up, because client does it automatically when displaying price
price = (uint32)(item->Price * i) * Client::CalcPriceMod(vendor, true);
// Don't use SellCostMod if using UseClassicPriceMod
if (!RuleB(Merchant, UseClassicPriceMod)) {
price *= RuleR(Merchant, BuyCostMod);
}
price += 0.5; // need to round up, because client does it automatically when displaying price
if (price > 4000000000) {
cost_quantity = i;
mp->quantity = i;
@@ -14360,11 +14373,12 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app)
break;
}
uint32 price = (
item->Price *
RuleR(Merchant, SellCostMod) *
item->SellRate
);
uint32 price = (item->Price * item->SellRate);
// Don't use SellCostMod if using UseClassicPriceMod
if (!RuleB(Merchant, UseClassicPriceMod)) {
price *= RuleR(Merchant, SellCostMod);
}
if (RuleB(Merchant, UsePriceMod)) {
price *= Client::CalcPriceMod(vendor, false);
@@ -14571,11 +14585,18 @@ void Client::Handle_OP_ShopRequest(const EQApplicationPacket *app)
mco->command = action; // Merchant command 0x01 = open
mco->tab_display = tabs_to_display;
float buy_cost_mod = 1;
// Only use the BuyCostMod if we're not using the classic function.
if (!RuleB(Merchant, UseClassicPriceMod)) {
buy_cost_mod = RuleR(Merchant, BuyCostMod);
}
if (RuleB(Merchant, UsePriceMod)) {
mco->rate = 1 / ((RuleR(Merchant, BuyCostMod)) * Client::CalcPriceMod(tmp, true)); // works
mco->rate = 1 / (buy_cost_mod * Client::CalcPriceMod(tmp, true));
}
else {
mco->rate = 1 / (RuleR(Merchant, BuyCostMod));
mco->rate = 1 / buy_cost_mod;
}
outapp->priority = 6;
+12 -2
View File
@@ -902,9 +902,14 @@ void Client::BulkSendMerchantInventory(int merchant_id, int npcid) {
auto inst = database.CreateItem(item, charges);
if (inst) {
auto item_price = static_cast<uint32>(item->Price * RuleR(Merchant, SellCostMod) * item->SellRate);
auto item_price = static_cast<uint32>(item->Price * item->SellRate);
auto item_charges = charges ? charges : 1;
// Don't use SellCostMod if using UseClassicPriceMod
if (!RuleB(Merchant, UseClassicPriceMod)) {
item_price *= RuleR(Merchant, SellCostMod);
}
if (RuleB(Merchant, UsePriceMod)) {
item_price *= Client::CalcPriceMod(npc);
}
@@ -948,9 +953,14 @@ void Client::BulkSendMerchantInventory(int merchant_id, int npcid) {
auto charges = item->MaxCharges;
auto inst = database.CreateItem(item, charges);
if (inst) {
auto item_price = static_cast<uint32>(item->Price * RuleR(Merchant, SellCostMod) * item->SellRate);
auto item_price = static_cast<uint32>(item->Price * item->SellRate);
auto item_charges = charges ? charges : 1;
// Don't use SellCostMod if using UseClassicPriceMod
if (!RuleB(Merchant, UseClassicPriceMod)) {
item_price *= RuleR(Merchant, SellCostMod);
}
if (RuleB(Merchant, UsePriceMod)) {
item_price *= Client::CalcPriceMod(npc);
}
+1
View File
@@ -268,6 +268,7 @@ public:
inline void MerchantOpenShop() { merchant_open = true; }
inline void MerchantCloseShop() { merchant_open = false; }
inline bool IsMerchantOpen() { return merchant_open; }
inline uint8 GetGreedPercent() { return NPCTypedata->greed; }
inline bool GetParcelMerchant() { return NPCTypedata->is_parcel_merchant; }
void Depop(bool start_spawn_timer = false);
void Stun(int duration);
+1
View File
@@ -1793,6 +1793,7 @@ const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load
t->max_dmg = n.maxdmg;
t->attack_count = n.attack_count;
t->is_parcel_merchant = n.is_parcel_merchant ? true : false;
t->greed = n.greed;
if (!n.special_abilities.empty()) {
strn0cpy(t->special_abilities, n.special_abilities.c_str(), 512);
+1
View File
@@ -155,6 +155,7 @@ struct NPCType
int heroic_strikethrough;
bool keeps_sold_items;
bool is_parcel_merchant;
uint8 greed;
};
#pragma pack()