Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 14 additions & 43 deletions data/scripts/lib/shops.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1537,55 +1537,26 @@ LootShopConfigTable = {
},
}

-- ATTENTION: Only use it to create value for custom items that are not bought by any NPC.
-- Adding items here will generate value for party hunt analyser but not to cyclopedia.
local garbage = {
"Spatial Warp Almanac",
"assassin star",
"big bone",
"bug meat",
"cape",
"combat knife",
"dirty cape",
"dirty fur",
"energy bar",
"fishbone",
"flash arrow",
"great health potion",
"great mana potion",
"great spirit potion",
"ham",
"health potion",
"knife",
"mana potion",
"royal star",
"strong health potion",
"strong mana potion",
"supreme health potion",
"the spatial warp almanac",
"ultimate health potion",
"ultimate mana potion",
"ultimate spirit potion",
"onyx arrow",
"small stone",
1047, -- bone
1048, -- bone
3115, -- bone
2920, -- torch
6558, -- demonic blood
27509, -- heavy crystal fragment
27713, -- heavy crystal fragment
--"Spatial Warp Almanac",
--6558, -- demonic blood
}

LootShopConfigTable["garbage"] = {}

for _, itemNameOrId in ipairs(garbage) do
local item = ItemType(itemNameOrId)
if item and item:getId() > 0 then
local suplyShop = FindSupplyShopItem(item:getName())
local price = 1
if suplyShop then
price = math.ceil(suplyShop.buy / 3)
if #garbage > 0 then
for _, itemNameOrId in ipairs(garbage) do
local item = ItemType(itemNameOrId)
if item and item:getId() > 0 then
local suplyShop = FindSupplyShopItem(item:getName())
local price = 1
if suplyShop then
price = math.ceil(suplyShop.buy / 3)
end
table.insert(LootShopConfigTable["garbage"], { itemName = item:getName(), clientId = item:getId(), sell = price })
end
table.insert(LootShopConfigTable["garbage"], { itemName = item:getName(), clientId = item:getId(), sell = price })
end
end

Expand Down
60 changes: 51 additions & 9 deletions src/creatures/players/grouping/party.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "creatures/players/vocations/vocation.hpp"
#include "game/game.hpp"
#include "game/movement/position.hpp"
#include "lib/di/container.hpp"
#include "lua/callbacks/event_callback.hpp"
#include "lua/callbacks/events_callbacks.hpp"
#include "lua/creature/events.hpp"
Expand Down Expand Up @@ -827,10 +828,19 @@ void Party::addPlayerLoot(const std::shared_ptr<Player> &player, const std::shar
}

if (priceType == LEADER_PRICE) {
playerAnalyzer->lootPrice += leader->getItemCustomPrice(item->getID()) * count;
uint64_t customPrice = leader->getItemCustomPrice(item->getID());
playerAnalyzer->lootPrice += customPrice * count;
} else {
const std::map<uint16_t, uint64_t> itemMap { { item->getID(), count } };
playerAnalyzer->lootPrice += g_game().getItemMarketPrice(itemMap, false);
// Use market average price instead of NPC price
uint64_t averagePrice = g_game().getItemMarketAveragePrice(item->getID(), item->getTier());
if (averagePrice > 0) {
playerAnalyzer->lootPrice += averagePrice * count;
} else {
// fallback to NPC Buy price or 0
const std::map<uint16_t, uint64_t> itemMap { { item->getID(), count } };
uint64_t marketPrice = g_game().getItemMarketPrice(itemMap, false); // true for NPC selling price, false = NPC Buying from player
playerAnalyzer->lootPrice += marketPrice;
}
}
updateTrackerAnalyzer();
}
Expand All @@ -856,8 +866,14 @@ void Party::addPlayerSupply(const std::shared_ptr<Player> &player, const std::sh
if (priceType == LEADER_PRICE) {
playerAnalyzer->supplyPrice += leader->getItemCustomPrice(item->getID(), true);
} else {
const std::map<uint16_t, uint64_t> itemMap { { item->getID(), 1 } };
playerAnalyzer->supplyPrice += g_game().getItemMarketPrice(itemMap, true);
// Use market average price instead of NPC price
uint64_t averagePrice = g_game().getItemMarketAveragePrice(item->getID(), item->getTier());
if (averagePrice > 0) {
playerAnalyzer->supplyPrice += averagePrice;
} else {
const std::map<uint16_t, uint64_t> itemMap { { item->getID(), 1 } };
playerAnalyzer->supplyPrice += g_game().getItemMarketPrice(itemMap, true);
}
}
updateTrackerAnalyzer();
}
Expand Down Expand Up @@ -909,19 +925,45 @@ void Party::reloadPrices() const {

for (const auto &analyzer : membersData) {
if (priceType == MARKET_PRICE) {
analyzer->lootPrice = g_game().getItemMarketPrice(analyzer->lootMap, false);
analyzer->supplyPrice = g_game().getItemMarketPrice(analyzer->supplyMap, true);
// Use market average prices instead of NPC prices
analyzer->lootPrice = 0;
for (const auto &[itemId, count] : analyzer->lootMap) {
uint64_t averagePrice = g_game().getItemMarketAveragePrice(itemId, 0);
if (averagePrice > 0) {
analyzer->lootPrice += averagePrice * count;
} else {
// fallback to NPC Buy price or 0
std::map<uint16_t, uint64_t> singleItemMap = { { itemId, static_cast<uint64_t>(count) } };
uint64_t marketPrice = g_game().getItemMarketPrice(singleItemMap, false);
analyzer->lootPrice += marketPrice;
}
}

analyzer->supplyPrice = 0;
for (const auto &[itemId, count] : analyzer->supplyMap) {
uint64_t averagePrice = g_game().getItemMarketAveragePrice(itemId, 0);
if (averagePrice > 0) {
analyzer->supplyPrice += averagePrice * count;
} else {
// fallback to NPC Sell price or 0
std::map<uint16_t, uint64_t> singleItemMap = { { itemId, static_cast<uint64_t>(count) } };
uint64_t leaderprice = g_game().getItemMarketPrice(singleItemMap, true);
analyzer->supplyPrice += leaderprice;
}
}
continue;
}

analyzer->lootPrice = 0;
for (const auto &[itemId, price] : analyzer->lootMap) {
analyzer->lootPrice += leader->getItemCustomPrice(itemId) * price;
uint64_t customPrice = leader->getItemCustomPrice(itemId);
analyzer->lootPrice += customPrice * price;
}

analyzer->supplyPrice = 0;
for (const auto &[itemId, price] : analyzer->supplyMap) {
analyzer->supplyPrice += leader->getItemCustomPrice(itemId, true) * price;
uint64_t customPrice = leader->getItemCustomPrice(itemId, true);
analyzer->supplyPrice += customPrice * price;
}
}
}
Expand Down
76 changes: 71 additions & 5 deletions src/game/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3345,29 +3345,95 @@ ObjectCategory_t Game::getObjectCategory(const ItemType &it) {

uint64_t Game::getItemMarketPrice(const std::map<uint16_t, uint64_t> &itemMap, bool buyPrice) const {
uint64_t total = 0;
g_logger().debug("[getItemMarketPrice] - Starting calculation with " + std::to_string(itemMap.size()) + " items, buyPrice: " + (buyPrice ? "true" : "false"));

for (const auto &it : itemMap) {
uint64_t itemValue = 0;
if (it.first == ITEM_GOLD_COIN) {
total += it.second;
itemValue = it.second;
total += itemValue;
g_logger().debug("[getItemMarketPrice] - Item ID " + std::to_string(it.first) + " (GOLD_COIN), Count " + std::to_string(it.second) + ", Value per unit: 1, Total value: " + std::to_string(itemValue));
} else if (it.first == ITEM_PLATINUM_COIN) {
total += 100 * it.second;
itemValue = 100 * it.second;
total += itemValue;
g_logger().debug("[getItemMarketPrice] - Item ID " + std::to_string(it.first) + " (PLATINUM_COIN), Count " + std::to_string(it.second) + ", Value per unit: 100, Total value: " + std::to_string(itemValue));
} else if (it.first == ITEM_CRYSTAL_COIN) {
total += 10000 * it.second;
itemValue = 10000 * it.second;
total += itemValue;
g_logger().debug("[getItemMarketPrice] - Item ID " + std::to_string(it.first) + " (CRYSTAL_COIN), Count " + std::to_string(it.second) + ", Value per unit: 10000, Total value: " + std::to_string(itemValue));
} else {
auto marketIt = itemsPriceMap.find(it.first);
if (marketIt != itemsPriceMap.end()) {
for (auto &[tier, price] : (*marketIt).second) {
total += price * it.second;
itemValue = price * it.second;
total += itemValue;
g_logger().debug("[getItemMarketPrice] - Item ID " + std::to_string(it.first) + ", Count " + std::to_string(it.second) + ", Tier " + std::to_string(tier) + ", Using MARKET_PRICE: " + std::to_string(price) + " per item, Total value: " + std::to_string(itemValue));
}
} else {
const ItemType &iType = Item::items[it.first];
total += (buyPrice ? iType.buyPrice : iType.sellPrice) * it.second;
uint64_t npcPrice = buyPrice ? iType.buyPrice : iType.sellPrice;
itemValue = npcPrice * it.second;
total += itemValue;
g_logger().debug("[getItemMarketPrice] - Item ID " + std::to_string(it.first) + ", Count " + std::to_string(it.second) + ", Using NPC_PRICE (" + (buyPrice ? "buy" : "sell") + "): " + std::to_string(npcPrice) + " per item, Total value: " + std::to_string(itemValue));
}
}
}

g_logger().debug("[getItemMarketPrice] - Final total: " + std::to_string(total));
return total;
}

uint64_t Game::getItemMarketAveragePrice(uint16_t itemId, uint8_t tier) const {
// Handle special coins first
if (itemId == ITEM_GOLD_COIN) {
return 1;
} else if (itemId == ITEM_PLATINUM_COIN) {
return 100;
} else if (itemId == ITEM_CRYSTAL_COIN) {
return 10000;
}

// Get historical market statistics for this item
const auto &purchaseStats = IOMarket::getInstance().getPurchaseStatistics();
const auto &saleStats = IOMarket::getInstance().getSaleStatistics();

uint64_t purchaseAverage = 0;
uint64_t saleAverage = 0;
bool hasPurchaseData = false;
bool hasSaleData = false;

// Calculate average from purchase history (people buying items)
auto purchaseIt = purchaseStats.find(itemId);
if (purchaseIt != purchaseStats.end()) {
auto tierIt = purchaseIt->second.find(tier);
if (tierIt != purchaseIt->second.end() && tierIt->second.numTransactions > 0) {
purchaseAverage = tierIt->second.totalPrice / tierIt->second.numTransactions;
hasPurchaseData = true;
}
}

// Calculate average from sale history (people selling items)
auto saleIt = saleStats.find(itemId);
if (saleIt != saleStats.end()) {
auto tierIt = saleIt->second.find(tier);
if (tierIt != saleIt->second.end() && tierIt->second.numTransactions > 0) {
saleAverage = tierIt->second.totalPrice / tierIt->second.numTransactions;
hasSaleData = true;
}
}

// Return average of purchase and sale historical data
if (hasPurchaseData && hasSaleData) {
return (purchaseAverage + saleAverage) / 2;
} else if (hasPurchaseData) {
return purchaseAverage;
} else if (hasSaleData) {
return saleAverage;
}

return 0;
}

std::shared_ptr<Item> searchForItem(const std::shared_ptr<Container> &container, uint16_t itemId, bool hasTier /* = false*/, uint8_t tier /* = 0*/) {
if (!container) {
return nullptr;
Expand Down
1 change: 1 addition & 0 deletions src/game/game.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ class Game {
ObjectCategory_t getObjectCategory(const ItemType &it);

uint64_t getItemMarketPrice(const std::map<uint16_t, uint64_t> &itemMap, bool buyPrice) const;
uint64_t getItemMarketAveragePrice(uint16_t itemId, uint8_t tier = 0) const;

void loadPlayersRecord();
void checkPlayersRecord();
Expand Down
Loading