mirror of
https://github.com/EQEmu/Server.git
synced 2026-05-27 04:42:27 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 29bdc86d12 | |||
| f2f05479dc | |||
| 5e0b388cf9 |
@@ -1,68 +1,3 @@
|
||||
## [22.62.0] 1/26/2025
|
||||
|
||||
### Bazaar
|
||||
|
||||
* Improve Bazaar Search Performance ([#4615](https://github.com/EQEmu/Server/pull/4615)) @neckkola 2025-01-27
|
||||
|
||||
### CLI
|
||||
|
||||
* Add --skip-backup to world database:updates ([#4605](https://github.com/EQEmu/Server/pull/4605)) @Akkadius 2025-01-22
|
||||
|
||||
### Database
|
||||
|
||||
* Change npc_types walkspeed to be of type float ([#4589](https://github.com/EQEmu/Server/pull/4589)) @Akkadius 2025-01-07
|
||||
|
||||
### Databuckets
|
||||
|
||||
* Add Account Scoped Databuckets ([#4603](https://github.com/EQEmu/Server/pull/4603)) @Akkadius 2025-01-21
|
||||
* Implement Nested Databuckets ([#4604](https://github.com/EQEmu/Server/pull/4604)) @Akkadius 2025-01-27
|
||||
|
||||
### Feature
|
||||
|
||||
* Add Alternate Bazaar Search Approach ([#4600](https://github.com/EQEmu/Server/pull/4600)) @neckkola 2025-01-20
|
||||
* Add Support for Item Previews ([#4599](https://github.com/EQEmu/Server/pull/4599)) @Kinglykrab 2025-01-20
|
||||
* Evolving Item Support for RoF2 ([#4496](https://github.com/EQEmu/Server/pull/4496)) @neckkola 2025-01-20
|
||||
* Implement Custom Pet Names ([#4594](https://github.com/EQEmu/Server/pull/4594)) @catapultam-habeo 2025-01-22
|
||||
|
||||
### Fixes
|
||||
|
||||
* Add Bazaar BulkSendTrader Limit for RoF2 ([#4590](https://github.com/EQEmu/Server/pull/4590)) @neckkola 2025-01-08
|
||||
* CLI help menu from parsing correctly in World @Akkadius 2025-01-22
|
||||
* Delete later in RemoveItem second case @Akkadius 2025-01-25
|
||||
* Fix query error in character_evolving_items @Akkadius 2025-01-21
|
||||
* Repair a memory leak in #summonitem ([#4591](https://github.com/EQEmu/Server/pull/4591)) @neckkola 2025-01-08
|
||||
* Repair an incorrect safe_delete call memory leak. ([#4588](https://github.com/EQEmu/Server/pull/4588)) @neckkola 2025-01-07
|
||||
* Repair levers opening the Evolving XP Transfer Window ([#4607](https://github.com/EQEmu/Server/pull/4607)) @neckkola 2025-01-23
|
||||
* Update a few Bazaar RoF2 routines for memory leaks ([#4592](https://github.com/EQEmu/Server/pull/4592)) @neckkola 2025-01-08
|
||||
* Update database version to match manifest @Akkadius 2025-01-21
|
||||
* Update trader add/remove packets to limits for RoF2 ([#4595](https://github.com/EQEmu/Server/pull/4595)) @neckkola 2025-01-19
|
||||
|
||||
### Linux
|
||||
|
||||
* Implement KSM Kernel Samepage Merging with Maps ([#4601](https://github.com/EQEmu/Server/pull/4601)) @Akkadius 2025-01-21
|
||||
|
||||
### Memory Leak
|
||||
|
||||
* Change raw pointer to unique_ptr to avoid potential leak in dbg stream ([#4616](https://github.com/EQEmu/Server/pull/4616)) @KimLS 2025-01-27
|
||||
* Fix leak in BuyTraderItemOutsideBazaar ([#4609](https://github.com/EQEmu/Server/pull/4609)) @Akkadius 2025-01-24
|
||||
* Fix leak in Client::RemoveDuplicateLore ([#4614](https://github.com/EQEmu/Server/pull/4614)) @Akkadius 2025-01-24
|
||||
* Fix leak in NPC::RemoveItem ([#4611](https://github.com/EQEmu/Server/pull/4611)) @Akkadius 2025-01-24
|
||||
* Fix leak in QuestManager::varlink ([#4610](https://github.com/EQEmu/Server/pull/4610)) @Akkadius 2025-01-24
|
||||
* Fix leaks in Client::Handle_OP_AugmentItem ([#4612](https://github.com/EQEmu/Server/pull/4612)) @Akkadius 2025-01-24
|
||||
* Fix memory leak in Client::Handle_OP_MoveMultipleItems ([#4613](https://github.com/EQEmu/Server/pull/4613)) @Akkadius 2025-01-24
|
||||
|
||||
### Performance
|
||||
|
||||
* Client / NPC Position Update Optimizations ([#4602](https://github.com/EQEmu/Server/pull/4602)) @Akkadius 2025-01-21
|
||||
|
||||
### Quest API
|
||||
|
||||
* Add SetAAEXPPercentage to Perl/Lua ([#4597](https://github.com/EQEmu/Server/pull/4597)) @Kinglykrab 2025-01-19
|
||||
|
||||
### Zone
|
||||
|
||||
* Implement zone player count sharding ([#4536](https://github.com/EQEmu/Server/pull/4536)) @Akkadius 2025-01-08
|
||||
|
||||
## [22.61.0] 1/6/2025
|
||||
|
||||
### Bots
|
||||
|
||||
+286
-225
@@ -6,8 +6,7 @@
|
||||
|
||||
std::vector<BazaarSearchResultsFromDB_Struct>
|
||||
Bazaar::GetSearchResults(
|
||||
Database &db,
|
||||
Database &content_db,
|
||||
SharedDatabase &db,
|
||||
BazaarSearchCriteria_Struct search,
|
||||
uint32 char_zone_id,
|
||||
int32 char_zone_instance_id
|
||||
@@ -32,132 +31,8 @@ Bazaar::GetSearchResults(
|
||||
char_zone_instance_id
|
||||
);
|
||||
|
||||
static std::map<uint8, uint32> item_slot_searches_new = {
|
||||
{EQ::invslot::slotCharm, 1},
|
||||
{EQ::invslot::slotEar1, 2},
|
||||
{EQ::invslot::slotHead, 4},
|
||||
{EQ::invslot::slotFace, 8},
|
||||
{EQ::invslot::slotEar2, 16},
|
||||
{EQ::invslot::slotNeck, 32},
|
||||
{EQ::invslot::slotShoulders, 64},
|
||||
{EQ::invslot::slotArms, 128},
|
||||
{EQ::invslot::slotBack, 256},
|
||||
{EQ::invslot::slotWrist1, 512},
|
||||
{EQ::invslot::slotWrist2, 1024},
|
||||
{EQ::invslot::slotRange, 2048},
|
||||
{EQ::invslot::slotHands, 4096},
|
||||
{EQ::invslot::slotPrimary, 8192},
|
||||
{EQ::invslot::slotSecondary, 16384},
|
||||
{EQ::invslot::slotFinger1, 32768},
|
||||
{EQ::invslot::slotFinger2, 65536},
|
||||
{EQ::invslot::slotChest, 131072},
|
||||
{EQ::invslot::slotLegs, 262144},
|
||||
{EQ::invslot::slotFeet, 524288},
|
||||
{EQ::invslot::slotWaist, 1048576},
|
||||
{EQ::invslot::slotPowerSource, 2097152},
|
||||
{EQ::invslot::slotAmmo, 4194304},
|
||||
};
|
||||
|
||||
struct ItemSearchType {
|
||||
EQ::item::ItemType type;
|
||||
std::string condition;
|
||||
};
|
||||
|
||||
std::vector<ItemSearchType> item_search_types_new = {
|
||||
{EQ::item::ItemType::ItemTypeBook, " AND (items.itemclass = 2 or items.itemclass = 31)"},
|
||||
{EQ::item::ItemType::ItemTypeContainer, " AND (items.itemclass = 1 or items.itemclass = 67)"},
|
||||
{EQ::item::ItemType::ItemTypeAllEffects, " AND (items.scrolleffect > 0 && items.scrolleffect < 65000)"},
|
||||
{EQ::item::ItemType::ItemTypeUnknown9, " AND items.worneffect = 998"},
|
||||
{EQ::item::ItemType::ItemTypeUnknown10, " AND (items.worneffect >= 1298 && items.worneffect <= 1307)"},
|
||||
{EQ::item::ItemType::ItemTypeFocusEffect, " AND items.focuseffect > 0"},
|
||||
{EQ::item::ItemType::ItemTypeArmor, " AND items.itemtype = 10"},
|
||||
{EQ::item::ItemType::ItemType1HBlunt, " AND items.itemtype = 3"},
|
||||
{EQ::item::ItemType::ItemType1HPiercing, " AND items.itemtype = 2"},
|
||||
{EQ::item::ItemType::ItemType1HSlash, " AND items.itemtype = 0"},
|
||||
{EQ::item::ItemType::ItemType2HBlunt, " AND items.itemtype = 4"},
|
||||
{EQ::item::ItemType::ItemType2HSlash, " AND items.itemtype = 1"},
|
||||
{EQ::item::ItemType::ItemTypeBow, " AND items.itemtype = 5"},
|
||||
{EQ::item::ItemType::ItemTypeShield, " AND items.itemtype = 8"},
|
||||
{EQ::item::ItemType::ItemTypeMisc, " AND items.itemtype = 11"},
|
||||
{EQ::item::ItemType::ItemTypeFood, " AND items.itemtype = 14"},
|
||||
{EQ::item::ItemType::ItemTypeDrink, " AND items.itemtype = 15"},
|
||||
{EQ::item::ItemType::ItemTypeLight, " AND items.itemtype = 16"},
|
||||
{EQ::item::ItemType::ItemTypeCombinable, " AND items.itemtype = 17"},
|
||||
{EQ::item::ItemType::ItemTypeBandage, " AND items.itemtype = 18"},
|
||||
{EQ::item::ItemType::ItemTypeSmallThrowing, " AND (items.itemtype = 19 OR items.itemtype = 7)"},
|
||||
{EQ::item::ItemType::ItemTypeSpell, " AND items.itemtype = 20"},
|
||||
{EQ::item::ItemType::ItemTypePotion, " AND items.itemtype = 21"},
|
||||
{EQ::item::ItemType::ItemTypeBrassInstrument, " AND items.itemtype = 25"},
|
||||
{EQ::item::ItemType::ItemTypeWindInstrument, " AND items.itemtype = 23"},
|
||||
{EQ::item::ItemType::ItemTypeStringedInstrument, " AND items.itemtype = 24"},
|
||||
{EQ::item::ItemType::ItemTypePercussionInstrument, " AND items.itemtype = 26"},
|
||||
{EQ::item::ItemType::ItemTypeArrow, " AND items.itemtype = 27"},
|
||||
{EQ::item::ItemType::ItemTypeJewelry, " AND items.itemtype = 29"},
|
||||
{EQ::item::ItemType::ItemTypeNote, " AND items.itemtype = 32"},
|
||||
{EQ::item::ItemType::ItemTypeKey, " AND items.itemtype = 33"},
|
||||
{EQ::item::ItemType::ItemType2HPiercing, " AND items.itemtype = 35"},
|
||||
{EQ::item::ItemType::ItemTypeAlcohol, " AND items.itemtype = 38"},
|
||||
{EQ::item::ItemType::ItemTypeMartial, " AND items.itemtype = 45"},
|
||||
{EQ::item::ItemType::ItemTypeAugmentation, " AND items.itemtype = 54"},
|
||||
{EQ::item::ItemType::ItemTypeAlternateAbility, " AND items.itemtype = 57"},
|
||||
{EQ::item::ItemType::ItemTypeCount, " AND items.itemtype = 65"},
|
||||
{EQ::item::ItemType::ItemTypeCollectible, " AND items.itemtype = 66"}
|
||||
};
|
||||
|
||||
// item stat searches
|
||||
struct ItemStatSearch {
|
||||
std::string query_string;
|
||||
EQ::skills::SkillType skill_type;
|
||||
};
|
||||
|
||||
std::map<uint32, ItemStatSearch> item_stat_searches_new = {
|
||||
{STAT_AC, {" items.ac" , static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_AGI, {" items.aagi", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_CHA, {" items.acha", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_DEX, {" items.adex", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_INT, {" items.aint", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_STA, {" items.asta", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_STR, {" items.astr", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_WIS, {" items.awis", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_COLD, {" items.cr", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_DISEASE, {" items.dr", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_FIRE, {" items.fr", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_MAGIC, {" items.mr", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_POISON, {" items.pr", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_HP, {" items.hp", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_MANA, {" items.mana", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_ENDURANCE, {" items.endur", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_ATTACK, {" items.attack", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_HP_REGEN, {" items.regen", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_MANA_REGEN, {" items.manaregen", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_HASTE, {" items.haste", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_DAMAGE_SHIELD, {" items.damageshield", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_DS_MITIGATION, {" items.dsmitigation", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_HEAL_AMOUNT, {" items.healamt", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_SPELL_DAMAGE, {" items.spelldmg", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_CLAIRVOYANCE, {" items.clairvoyance", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_HEROIC_AGILITY, {" items.heroic_agi", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_HEROIC_CHARISMA, {" items.heroic_cha", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_HEROIC_DEXTERITY, {" items.heroic_dex", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_HEROIC_INTELLIGENCE, {" items.heroic_int", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_HEROIC_STAMINA, {" items.heroic_sta", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_HEROIC_STRENGTH, {" items.heroic_str", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_HEROIC_WISDOM, {" items.heroic_wis", static_cast<EQ::skills::SkillType>(0)} },
|
||||
{STAT_BASH, {" items.skillmodvalue", EQ::skills::SkillBash} },
|
||||
{STAT_BACKSTAB, {" items.backstabdmg", EQ::skills::SkillBackstab} },
|
||||
{STAT_DRAGON_PUNCH, {" items.skillmodvalue", EQ::skills::SkillDragonPunch} },
|
||||
{STAT_EAGLE_STRIKE, {" items.skillmodvalue", EQ::skills::SkillEagleStrike} },
|
||||
{STAT_FLYING_KICK, {" items.skillmodvalue", EQ::skills::SkillFlyingKick} },
|
||||
{STAT_KICK, {" items.skillmodvalue", EQ::skills::SkillKick} },
|
||||
{STAT_ROUND_KICK, {" items.skillmodvalue", EQ::skills::SkillRoundKick} },
|
||||
{STAT_TIGER_CLAW, {" items.skillmodvalue", EQ::skills::SkillTigerClaw} },
|
||||
{STAT_FRENZY, {" items.skillmodvalue", EQ::skills::SkillFrenzy} },
|
||||
};
|
||||
|
||||
bool convert = false;
|
||||
std::string search_criteria_trader("TRUE");
|
||||
std::string field_criteria_items("FALSE");
|
||||
std::string where_criteria_items(" TRUE ");
|
||||
std::string search_criteria_trader("TRUE ");
|
||||
|
||||
if (search.search_scope == NonRoFBazaarSearchScope) {
|
||||
search_criteria_trader.append(
|
||||
@@ -202,115 +77,301 @@ Bazaar::GetSearchResults(
|
||||
search_criteria_trader.append(fmt::format(" AND trader.item_cost <= {}", (uint64) search.max_cost * 1000));
|
||||
}
|
||||
|
||||
if (search.slot != std::numeric_limits<uint32>::max()) {
|
||||
if (item_slot_searches_new.contains(search.slot)) {
|
||||
where_criteria_items.append(
|
||||
fmt::format(" AND items.slots & {0} = {0}", item_slot_searches_new[search.slot]));
|
||||
}
|
||||
}
|
||||
// not yet implemented
|
||||
// if (search.prestige != 0) {
|
||||
// 0xffffffff prestige only, 0xfffffffe non-prestige, 0 all
|
||||
// search_criteria.append(fmt::format(" AND items.type = {} ", search.prestige));
|
||||
// }
|
||||
|
||||
if (search.type != std::numeric_limits<uint32>::max()) {
|
||||
for (auto const &[type, condition]: item_search_types_new) {
|
||||
if (type == search.type) {
|
||||
where_criteria_items.append(condition);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (search.race != std::numeric_limits<uint32>::max()) {
|
||||
where_criteria_items.append(
|
||||
fmt::format(" AND items.races & {0} = {0}", GetPlayerRaceBit(GetRaceIDFromPlayerRaceValue(search.race))));
|
||||
}
|
||||
|
||||
if (search._class != std::numeric_limits<uint32>::max()) {
|
||||
where_criteria_items.append(fmt::format(" AND items.classes & {0} = {0}", GetPlayerClassBit(search._class)));
|
||||
}
|
||||
|
||||
if (search.item_stat != std::numeric_limits<uint32>::max()) {
|
||||
if (item_stat_searches_new.contains(search.item_stat)) {
|
||||
field_criteria_items = fmt::format("{}", item_stat_searches_new[search.item_stat].query_string);
|
||||
if (item_stat_searches_new[search.item_stat].skill_type) {
|
||||
where_criteria_items.append(
|
||||
fmt::format(" AND items.skillmodtype = {} ", item_stat_searches_new[search.item_stat].skill_type));
|
||||
}
|
||||
else {
|
||||
where_criteria_items.append(
|
||||
fmt::format(" AND {} > 0 ", item_stat_searches_new[search.item_stat].query_string));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (search.augment) {
|
||||
where_criteria_items.append(fmt::format(
|
||||
" AND (items.augslot1type = {0} OR "
|
||||
"items.augslot2type = {0} OR "
|
||||
"items.augslot3type = {0} OR "
|
||||
"items.augslot4type = {0} OR "
|
||||
"items.augslot5type = {0} OR "
|
||||
"items.augslot6type = {0})",
|
||||
search.augment)
|
||||
);
|
||||
}
|
||||
|
||||
if (search.min_level != 1) {
|
||||
where_criteria_items.append(fmt::format(" AND items.reclevel >= {}", search.min_level));
|
||||
}
|
||||
|
||||
if (search.max_level != 100) {
|
||||
where_criteria_items.append(fmt::format(" AND items.reclevel <= {}", search.max_level));
|
||||
}
|
||||
|
||||
std::vector<BazaarSearchResultsFromDB_Struct> all_entries;
|
||||
std::vector<std::string> trader_items_ids{};
|
||||
|
||||
auto const trader_results = TraderRepository::GetBazaarTraderDetails(db, search_criteria_trader);
|
||||
if (trader_results.empty()) {
|
||||
LogTradingDetail("Bazaar - No traders found in bazaar search.");
|
||||
return all_entries;
|
||||
}
|
||||
|
||||
for (auto const &i: trader_results) {
|
||||
trader_items_ids.push_back(std::to_string(i.trader.item_id));
|
||||
}
|
||||
|
||||
auto const item_results = ItemsRepository::GetItemsForBazaarSearch(
|
||||
content_db,
|
||||
trader_items_ids,
|
||||
std::string(search.item_name),
|
||||
field_criteria_items,
|
||||
where_criteria_items
|
||||
std::string query = fmt::format(
|
||||
"SELECT COUNT(item_id), trader.char_id, trader.item_id, trader.item_sn, trader.item_charges, trader.item_cost, "
|
||||
"trader.slot_id, SUM(trader.item_charges), trader.char_zone_id, trader.char_entity_id, character_data.name, "
|
||||
"aug_slot_1, aug_slot_2, aug_slot_3, aug_slot_4, aug_slot_5, aug_slot_6, trader.char_zone_instance_id "
|
||||
"FROM trader, character_data "
|
||||
"WHERE {} AND trader.char_id = character_data.id "
|
||||
"GROUP BY trader.item_sn, trader.item_charges, trader.char_id",
|
||||
search_criteria_trader.c_str()
|
||||
);
|
||||
|
||||
if (item_results.empty()) {
|
||||
LogError("Bazaar - No items found in bazaar search.");
|
||||
std::vector<BazaarSearchResultsFromDB_Struct> all_entries;
|
||||
|
||||
auto results = db.QueryDatabase(query);
|
||||
|
||||
if (!results.Success()) {
|
||||
return all_entries;
|
||||
}
|
||||
|
||||
all_entries.reserve(trader_results.size());
|
||||
struct ItemSearchType {
|
||||
EQ::item::ItemType type;
|
||||
bool condition;
|
||||
};
|
||||
|
||||
for (auto const& t:trader_results) {
|
||||
if (!item_results.contains(t.trader.item_id)) {
|
||||
struct AddititiveSearchCriteria {
|
||||
bool should_check;
|
||||
bool condition;
|
||||
};
|
||||
|
||||
for (auto row: results) {
|
||||
BazaarSearchResultsFromDB_Struct r{};
|
||||
|
||||
r.item_id = Strings::ToInt(row[2]);
|
||||
r.charges = Strings::ToInt(row[4]);
|
||||
|
||||
auto item = db.GetItem(r.item_id);
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BazaarSearchResultsFromDB_Struct r{};
|
||||
r.count = 1;
|
||||
r.trader_id = t.trader.char_id;
|
||||
r.serial_number = t.trader.item_sn;
|
||||
r.cost = t.trader.item_cost;
|
||||
r.slot_id = t.trader.slot_id;
|
||||
r.sum_charges = t.trader.item_charges;
|
||||
r.stackable = item_results.at(t.trader.item_id).stackable;
|
||||
r.icon_id = item_results.at(t.trader.item_id).icon;
|
||||
r.trader_zone_id = t.trader.char_zone_id;
|
||||
r.trader_zone_instance_id = t.trader.char_zone_instance_id;
|
||||
r.trader_entity_id = t.trader.char_entity_id;
|
||||
r.serial_number_RoF = fmt::format("{:016}\0", t.trader.item_sn);
|
||||
r.item_name = fmt::format("{:.63}\0", item_results.at(t.trader.item_id).name);
|
||||
r.trader_name = fmt::format("{:.63}\0", t.trader_name);
|
||||
r.item_stat = item_results.at(t.trader.item_id).stats;
|
||||
uint32 aug_slot_1 = Strings::ToUnsignedInt(row[11]);
|
||||
uint32 aug_slot_2 = Strings::ToUnsignedInt(row[12]);
|
||||
uint32 aug_slot_3 = Strings::ToUnsignedInt(row[13]);
|
||||
uint32 aug_slot_4 = Strings::ToUnsignedInt(row[14]);
|
||||
uint32 aug_slot_5 = Strings::ToUnsignedInt(row[15]);
|
||||
uint32 aug_slot_6 = Strings::ToUnsignedInt(row[16]);
|
||||
|
||||
std::unique_ptr<EQ::ItemInstance> inst(
|
||||
db.CreateItem(
|
||||
item,
|
||||
r.charges,
|
||||
aug_slot_1,
|
||||
aug_slot_2,
|
||||
aug_slot_3,
|
||||
aug_slot_4,
|
||||
aug_slot_5,
|
||||
aug_slot_6
|
||||
)
|
||||
);
|
||||
|
||||
if (!inst->GetItem()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
r.count = Strings::ToInt(row[0]);
|
||||
r.trader_id = Strings::ToInt(row[1]);
|
||||
r.serial_number = Strings::ToInt(row[3]);
|
||||
r.cost = Strings::ToInt(row[5]);
|
||||
r.slot_id = Strings::ToInt(row[6]);
|
||||
r.sum_charges = Strings::ToInt(row[7]);
|
||||
r.stackable = item->Stackable;
|
||||
r.icon_id = item->Icon;
|
||||
r.trader_zone_id = Strings::ToInt(row[8]);
|
||||
r.trader_zone_instance_id = Strings::ToInt(row[17]);
|
||||
r.trader_entity_id = Strings::ToInt(row[9]);
|
||||
r.serial_number_RoF = fmt::format("{:016}\0", Strings::ToInt(row[3]));
|
||||
r.item_name = fmt::format("{:.63}\0", item->Name);
|
||||
r.trader_name = fmt::format("{:.63}\0", std::string(row[10]).c_str());
|
||||
|
||||
LogTradingDetail(
|
||||
"Searching against item [{}] ({}) for trader [{}]",
|
||||
item->Name,
|
||||
item->ID,
|
||||
r.trader_name
|
||||
);
|
||||
|
||||
// item stat searches
|
||||
std::map<uint32, uint32> item_stat_searches = {
|
||||
|
||||
{STAT_AC, inst->GetItemArmorClass(true)},
|
||||
{STAT_AGI, static_cast<uint32>(inst->GetItemAgi(true))},
|
||||
{STAT_CHA, static_cast<uint32>(inst->GetItemCha(true))},
|
||||
{STAT_DEX, static_cast<uint32>(inst->GetItemDex(true))},
|
||||
{STAT_INT, static_cast<uint32>(inst->GetItemInt(true))},
|
||||
{STAT_STA, static_cast<uint32>(inst->GetItemSta(true))},
|
||||
{STAT_STR, static_cast<uint32>(inst->GetItemStr(true))},
|
||||
{STAT_WIS, static_cast<uint32>(inst->GetItemWis(true))},
|
||||
{STAT_COLD, static_cast<uint32>(inst->GetItemCR(true))},
|
||||
{STAT_DISEASE, static_cast<uint32>(inst->GetItemDR(true))},
|
||||
{STAT_FIRE, static_cast<uint32>(inst->GetItemFR(true))},
|
||||
{STAT_MAGIC, static_cast<uint32>(inst->GetItemMR(true))},
|
||||
{STAT_POISON, static_cast<uint32>(inst->GetItemPR(true))},
|
||||
{STAT_HP, static_cast<uint32>(inst->GetItemHP(true))},
|
||||
{STAT_MANA, static_cast<uint32>(inst->GetItemMana(true))},
|
||||
{STAT_ENDURANCE, static_cast<uint32>(inst->GetItemEndur(true))},
|
||||
{STAT_ATTACK, static_cast<uint32>(inst->GetItemAttack(true))},
|
||||
{STAT_HP_REGEN, static_cast<uint32>(inst->GetItemRegen(true))},
|
||||
{STAT_MANA_REGEN, static_cast<uint32>(inst->GetItemManaRegen(true))},
|
||||
{STAT_HASTE, static_cast<uint32>(inst->GetItemHaste(true))},
|
||||
{STAT_DAMAGE_SHIELD, static_cast<uint32>(inst->GetItemDamageShield(true))},
|
||||
{STAT_DS_MITIGATION, static_cast<uint32>(inst->GetItemDSMitigation(true))},
|
||||
{STAT_HEAL_AMOUNT, static_cast<uint32>(inst->GetItemHealAmt(true))},
|
||||
{STAT_SPELL_DAMAGE, static_cast<uint32>(inst->GetItemSpellDamage(true))},
|
||||
{STAT_CLAIRVOYANCE, static_cast<uint32>(inst->GetItemClairvoyance(true))},
|
||||
{STAT_HEROIC_AGILITY, static_cast<uint32>(inst->GetItemHeroicAgi(true))},
|
||||
{STAT_HEROIC_CHARISMA, static_cast<uint32>(inst->GetItemHeroicCha(true))},
|
||||
{STAT_HEROIC_DEXTERITY, static_cast<uint32>(inst->GetItemHeroicDex(true))},
|
||||
{STAT_HEROIC_INTELLIGENCE, static_cast<uint32>(inst->GetItemHeroicInt(true))},
|
||||
{STAT_HEROIC_STAMINA, static_cast<uint32>(inst->GetItemHeroicSta(true))},
|
||||
{STAT_HEROIC_STRENGTH, static_cast<uint32>(inst->GetItemHeroicStr(true))},
|
||||
{STAT_HEROIC_WISDOM, static_cast<uint32>(inst->GetItemHeroicWis(true))},
|
||||
{STAT_BASH, static_cast<uint32>(inst->GetItemSkillsStat(EQ::skills::SkillBash, true))},
|
||||
{STAT_BACKSTAB, static_cast<uint32>(inst->GetItemBackstabDamage(true))},
|
||||
{STAT_DRAGON_PUNCH, static_cast<uint32>(inst->GetItemSkillsStat(EQ::skills::SkillDragonPunch, true))},
|
||||
{STAT_EAGLE_STRIKE, static_cast<uint32>(inst->GetItemSkillsStat(EQ::skills::SkillEagleStrike, true))},
|
||||
{STAT_FLYING_KICK, static_cast<uint32>(inst->GetItemSkillsStat(EQ::skills::SkillFlyingKick, true))},
|
||||
{STAT_KICK, static_cast<uint32>(inst->GetItemSkillsStat(EQ::skills::SkillKick, true))},
|
||||
{STAT_ROUND_KICK, static_cast<uint32>(inst->GetItemSkillsStat(EQ::skills::SkillRoundKick, true))},
|
||||
{STAT_TIGER_CLAW, static_cast<uint32>(inst->GetItemSkillsStat(EQ::skills::SkillTigerClaw, true))},
|
||||
{STAT_FRENZY, static_cast<uint32>(inst->GetItemSkillsStat(EQ::skills::SkillFrenzy, true))},
|
||||
};
|
||||
|
||||
r.item_stat = item_stat_searches.contains(search.item_stat) ? item_stat_searches[search.item_stat] : 0;
|
||||
if (item_stat_searches.contains(search.item_stat) && item_stat_searches[search.item_stat] <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
static std::map<uint8, uint32> item_slot_searches = {
|
||||
{EQ::invslot::slotCharm, 1},
|
||||
{EQ::invslot::slotEar1, 2},
|
||||
{EQ::invslot::slotHead, 4},
|
||||
{EQ::invslot::slotFace, 8},
|
||||
{EQ::invslot::slotEar2, 16},
|
||||
{EQ::invslot::slotNeck, 32},
|
||||
{EQ::invslot::slotShoulders, 64},
|
||||
{EQ::invslot::slotArms, 128},
|
||||
{EQ::invslot::slotBack, 256},
|
||||
{EQ::invslot::slotWrist1, 512},
|
||||
{EQ::invslot::slotWrist2, 1024},
|
||||
{EQ::invslot::slotRange, 2048},
|
||||
{EQ::invslot::slotHands, 4096},
|
||||
{EQ::invslot::slotPrimary, 8192},
|
||||
{EQ::invslot::slotSecondary, 16384},
|
||||
{EQ::invslot::slotFinger1, 32768},
|
||||
{EQ::invslot::slotFinger2, 65536},
|
||||
{EQ::invslot::slotChest, 131072},
|
||||
{EQ::invslot::slotLegs, 262144},
|
||||
{EQ::invslot::slotFeet, 524288},
|
||||
{EQ::invslot::slotWaist, 1048576},
|
||||
{EQ::invslot::slotPowerSource, 2097152},
|
||||
{EQ::invslot::slotAmmo, 4194304},
|
||||
};
|
||||
|
||||
auto GetEquipmentSlotBit = [&](uint32 slot) -> uint32 {
|
||||
return item_slot_searches.contains(slot) ? item_slot_searches[slot] : 0;
|
||||
};
|
||||
|
||||
auto FindItemAugSlot = [&]() -> bool {
|
||||
for (auto const &s: inst->GetItem()->AugSlotType) {
|
||||
return s == search.augment;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// item type searches
|
||||
std::vector<ItemSearchType> item_search_types = {
|
||||
{EQ::item::ItemType::ItemTypeAll, true},
|
||||
{EQ::item::ItemType::ItemTypeBook, item->ItemClass == EQ::item::ItemType::ItemTypeBook},
|
||||
{EQ::item::ItemType::ItemTypeContainer, item->ItemClass == EQ::item::ItemType::ItemTypeContainer ||
|
||||
item->IsClassBag()},
|
||||
{EQ::item::ItemType::ItemTypeAllEffects, item->Scroll.Effect > 0 && item->Scroll.Effect < 65000},
|
||||
{EQ::item::ItemType::ItemTypeUnknown9, item->Worn.Effect == 998},
|
||||
{EQ::item::ItemType::ItemTypeUnknown10, item->Worn.Effect >= 1298 && item->Worn.Effect <= 1307},
|
||||
{EQ::item::ItemType::ItemTypeFocusEffect, item->Focus.Effect > 0},
|
||||
{EQ::item::ItemType::ItemTypeArmor, item->ItemType == EQ::item::ItemType::ItemTypeArmor},
|
||||
{EQ::item::ItemType::ItemType1HBlunt, item->ItemType == EQ::item::ItemType::ItemType1HBlunt},
|
||||
{EQ::item::ItemType::ItemType1HPiercing, item->ItemType == EQ::item::ItemType::ItemType1HPiercing},
|
||||
{EQ::item::ItemType::ItemType1HSlash, item->ItemType == EQ::item::ItemType::ItemType1HSlash},
|
||||
{EQ::item::ItemType::ItemType2HBlunt, item->ItemType == EQ::item::ItemType::ItemType2HBlunt},
|
||||
{EQ::item::ItemType::ItemType2HSlash, item->ItemType == EQ::item::ItemType::ItemType2HSlash},
|
||||
{EQ::item::ItemType::ItemTypeBow, item->ItemType == EQ::item::ItemType::ItemTypeBow},
|
||||
{EQ::item::ItemType::ItemTypeShield, item->ItemType == EQ::item::ItemType::ItemTypeShield},
|
||||
{EQ::item::ItemType::ItemTypeMisc, item->ItemType == EQ::item::ItemType::ItemTypeMisc},
|
||||
{EQ::item::ItemType::ItemTypeFood, item->ItemType == EQ::item::ItemType::ItemTypeFood},
|
||||
{EQ::item::ItemType::ItemTypeDrink, item->ItemType == EQ::item::ItemType::ItemTypeDrink},
|
||||
{EQ::item::ItemType::ItemTypeLight, item->ItemType == EQ::item::ItemType::ItemTypeLight},
|
||||
{EQ::item::ItemType::ItemTypeCombinable, item->ItemType == EQ::item::ItemType::ItemTypeCombinable},
|
||||
{EQ::item::ItemType::ItemTypeBandage, item->ItemType == EQ::item::ItemType::ItemTypeBandage},
|
||||
{EQ::item::ItemType::ItemTypeSmallThrowing, item->ItemType == EQ::item::ItemType::ItemTypeSmallThrowing ||
|
||||
item->ItemType == EQ::item::ItemType::ItemTypeLargeThrowing},
|
||||
{EQ::item::ItemType::ItemTypeSpell, item->ItemType == EQ::item::ItemType::ItemTypeSpell},
|
||||
{EQ::item::ItemType::ItemTypePotion, item->ItemType == EQ::item::ItemType::ItemTypePotion},
|
||||
{EQ::item::ItemType::ItemTypeBrassInstrument, item->ItemType == EQ::item::ItemType::ItemTypeBrassInstrument},
|
||||
{EQ::item::ItemType::ItemTypeWindInstrument, item->ItemType == EQ::item::ItemType::ItemTypeWindInstrument},
|
||||
{EQ::item::ItemType::ItemTypeStringedInstrument, item->ItemType == EQ::item::ItemType::ItemTypeStringedInstrument},
|
||||
{EQ::item::ItemType::ItemTypePercussionInstrument, item->ItemType == EQ::item::ItemType::ItemTypePercussionInstrument},
|
||||
{EQ::item::ItemType::ItemTypeArrow, item->ItemType == EQ::item::ItemType::ItemTypeArrow},
|
||||
{EQ::item::ItemType::ItemTypeJewelry, item->ItemType == EQ::item::ItemType::ItemTypeJewelry},
|
||||
{EQ::item::ItemType::ItemTypeNote, item->ItemType == EQ::item::ItemType::ItemTypeNote},
|
||||
{EQ::item::ItemType::ItemTypeKey, item->ItemType == EQ::item::ItemType::ItemTypeKey},
|
||||
{EQ::item::ItemType::ItemType2HPiercing, item->ItemType == EQ::item::ItemType::ItemType2HPiercing},
|
||||
{EQ::item::ItemType::ItemTypeAlcohol, item->ItemType == EQ::item::ItemType::ItemTypeAlcohol},
|
||||
{EQ::item::ItemType::ItemTypeMartial, item->ItemType == EQ::item::ItemType::ItemTypeMartial},
|
||||
{EQ::item::ItemType::ItemTypeAugmentation, item->ItemType == EQ::item::ItemType::ItemTypeAugmentation},
|
||||
{EQ::item::ItemType::ItemTypeAlternateAbility, item->ItemType == EQ::item::ItemType::ItemTypeAlternateAbility},
|
||||
{EQ::item::ItemType::ItemTypeCount, item->ItemType == EQ::item::ItemType::ItemTypeCount},
|
||||
{EQ::item::ItemType::ItemTypeCollectible, item->ItemType == EQ::item::ItemType::ItemTypeCollectible}
|
||||
};
|
||||
|
||||
bool met_filter = false;
|
||||
bool has_filter = false;
|
||||
|
||||
for (auto &i: item_search_types) {
|
||||
if (i.type == search.type) {
|
||||
has_filter = true;
|
||||
if (i.condition) {
|
||||
LogTradingDetail("Item [{}] met search criteria for type [{}]", item->Name, uint8(i.type));
|
||||
met_filter = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (has_filter && !met_filter) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: Add catch-all item type filter for specific item types
|
||||
|
||||
// item additive searches
|
||||
std::vector<AddititiveSearchCriteria> item_additive_searches = {
|
||||
{
|
||||
.should_check = search.min_level != 1 && inst->GetItemRequiredLevel(true) > 0,
|
||||
.condition = inst->GetItemRequiredLevel(true) >= search.min_level
|
||||
},
|
||||
{
|
||||
.should_check = search.max_level != 1 && inst->GetItemRequiredLevel(true) > 0,
|
||||
.condition = inst->GetItemRequiredLevel(true) <= search.max_level
|
||||
},
|
||||
{
|
||||
.should_check = !std::string(search.item_name).empty(),
|
||||
.condition = Strings::ContainsLower(item->Name, search.item_name)
|
||||
},
|
||||
{
|
||||
.should_check = search._class != 0xFFFFFFFF,
|
||||
.condition = static_cast<bool>(item->Classes & GetPlayerClassBit(search._class))
|
||||
},
|
||||
{
|
||||
.should_check = search.race != 0xFFFFFFFF,
|
||||
.condition = static_cast<bool>(item->Races & GetPlayerRaceBit(GetRaceIDFromPlayerRaceValue(search.race)))
|
||||
},
|
||||
{
|
||||
.should_check = search.augment != 0,
|
||||
.condition = FindItemAugSlot()
|
||||
},
|
||||
{
|
||||
.should_check = search.slot != 0xFFFFFFFF,
|
||||
.condition = static_cast<bool>(item->Slots & GetEquipmentSlotBit(search.slot))
|
||||
},
|
||||
};
|
||||
|
||||
bool should_add = true;
|
||||
|
||||
for (auto &i: item_additive_searches) {
|
||||
LogTradingDetail(
|
||||
"Checking item [{}] for search criteria - should_check [{}] condition [{}]",
|
||||
item->Name,
|
||||
i.should_check,
|
||||
i.condition
|
||||
);
|
||||
if (i.should_check && !i.condition) {
|
||||
should_add = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!should_add) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LogTradingDetail("Found item [{}] meeting search criteria.", r.item_name);
|
||||
if (RuleB(Bazaar, UseAlternateBazaarSearch)) {
|
||||
if (convert || (r.trader_zone_id == Zones::BAZAAR && r.trader_zone_instance_id != char_zone_instance_id)) {
|
||||
r.trader_id = TraderRepository::TRADER_CONVERT_ID + r.trader_zone_instance_id;
|
||||
|
||||
+1
-3
@@ -3,13 +3,11 @@
|
||||
|
||||
#include <vector>
|
||||
#include "shareddb.h"
|
||||
#include "../../common/item_instance.h"
|
||||
|
||||
class Bazaar {
|
||||
public:
|
||||
static std::vector<BazaarSearchResultsFromDB_Struct>
|
||||
GetSearchResults(Database &content_db, Database &db, BazaarSearchCriteria_Struct search, unsigned int char_zone_id, int char_zone_instance_id);
|
||||
|
||||
GetSearchResults(SharedDatabase &db, BazaarSearchCriteria_Struct search, unsigned int char_zone_id, int char_zone_instance_id);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -6333,52 +6333,6 @@ CREATE TABLE `character_pet_name` (
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
)",
|
||||
},
|
||||
ManifestEntry{
|
||||
.version = 9294,
|
||||
.description = "2025_01_26_items_table_bazaar_search_indexes.sql",
|
||||
.check = "SHOW CREATE TABLE `items`",
|
||||
.condition = "missing",
|
||||
.match = "idx_slots_reclevel",
|
||||
.sql = R"(
|
||||
-- indexes for the `items` table
|
||||
CREATE INDEX idx_slots_reclevel ON items (slots, reclevel);
|
||||
CREATE INDEX idx_itemclass_itemtype ON items (itemclass, itemtype);
|
||||
CREATE INDEX idx_augment_slots ON items (
|
||||
augslot1type,
|
||||
augslot2type,
|
||||
augslot3type,
|
||||
augslot4type,
|
||||
augslot5type,
|
||||
augslot6type
|
||||
);
|
||||
CREATE INDEX idx_races_classes ON items (races, classes);
|
||||
|
||||
-- common stat fields
|
||||
CREATE INDEX idx_item_ac ON items (ac);
|
||||
CREATE INDEX idx_item_hp ON items (hp);
|
||||
CREATE INDEX idx_item_mana ON items (mana);
|
||||
CREATE INDEX idx_item_reclevel ON items (reclevel);
|
||||
CREATE INDEX idx_item_type_skill ON items (itemtype, skillmodtype);
|
||||
)",
|
||||
.content_schema_update = true
|
||||
},
|
||||
ManifestEntry{
|
||||
.version = 9295,
|
||||
.description = "2025_01_26_trader_table_bazaar_search_indexes.sql",
|
||||
.check = "SHOW CREATE TABLE `trader`",
|
||||
.condition = "missing",
|
||||
.match = "idx_trader_item",
|
||||
.sql = R"(
|
||||
-- indexes for the `trader` table
|
||||
CREATE INDEX idx_trader_item ON trader (item_id, item_cost);
|
||||
CREATE INDEX idx_trader_char ON trader (char_id, char_zone_id, char_zone_instance_id);
|
||||
CREATE INDEX idx_trader_item_sn ON trader (item_sn);
|
||||
CREATE INDEX idx_trader_item_cost ON trader (item_cost);
|
||||
CREATE INDEX idx_trader_active_transaction ON trader (active_transaction);
|
||||
)",
|
||||
.content_schema_update = false
|
||||
},
|
||||
|
||||
// -- template; copy/paste this when you need to create a new entry
|
||||
// ManifestEntry{
|
||||
// .version = 9228,
|
||||
|
||||
@@ -152,6 +152,9 @@ void EQ::Net::DaybreakConnectionManager::Process()
|
||||
connection->SendKeepAlive();
|
||||
}
|
||||
}
|
||||
|
||||
connection->Process();
|
||||
break;
|
||||
}
|
||||
case StatusDisconnecting:
|
||||
connection->Process();
|
||||
@@ -435,7 +438,7 @@ void EQ::Net::DaybreakConnection::ProcessPacket(Packet &p)
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_encode_passes[0] == EncodeCompression || m_encode_passes[1] == EncodeCompression)
|
||||
if (m_encode_passes[0] & EncodeCompression || m_encode_passes[1] & EncodeCompression)
|
||||
{
|
||||
EQ::Net::DynamicPacket temp;
|
||||
temp.PutPacket(0, p);
|
||||
@@ -492,40 +495,52 @@ void EQ::Net::DaybreakConnection::ProcessPacket(Packet &p)
|
||||
void EQ::Net::DaybreakConnection::ProcessQueue()
|
||||
{
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
auto stream = &m_streams[i];
|
||||
auto &stream = m_streams[i];
|
||||
for (;;) {
|
||||
if (stream == nullptr)
|
||||
break;
|
||||
|
||||
auto iter = stream->packet_queue.find(stream->sequence_in);
|
||||
if (iter == stream->packet_queue.end()) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto &packet = iter->second;
|
||||
auto packet = iter->second;
|
||||
stream->packet_queue.erase(iter);
|
||||
ProcessDecodedPacket(*packet);
|
||||
delete packet;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EQ::Net::DaybreakConnection::RemoveFromQueue(int stream, uint16_t seq)
|
||||
{
|
||||
auto s = &m_streams[stream];
|
||||
auto &s = m_streams[stream];
|
||||
if (s == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto iter = s->packet_queue.find(seq);
|
||||
if (iter != s->packet_queue.end()) {
|
||||
auto &packet = iter->second;
|
||||
auto packet = iter->second;
|
||||
s->packet_queue.erase(iter);
|
||||
delete packet;
|
||||
}
|
||||
}
|
||||
|
||||
void EQ::Net::DaybreakConnection::AddToQueue(int stream, uint16_t seq, const Packet &p)
|
||||
{
|
||||
auto s = &m_streams[stream];
|
||||
auto &s = m_streams[stream];
|
||||
if (s == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto iter = s->packet_queue.find(seq);
|
||||
if (iter == s->packet_queue.end()) {
|
||||
DynamicPacket *out = new DynamicPacket();
|
||||
out->PutPacket(0, p);
|
||||
|
||||
s->packet_queue.emplace(std::make_pair(seq, std::unique_ptr<Packet>(out)));
|
||||
s->packet_queue.emplace(std::make_pair(seq, out));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -674,7 +689,10 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p)
|
||||
auto header = p.GetSerialize<DaybreakReliableHeader>(0);
|
||||
auto sequence = NetworkToHost(header.sequence);
|
||||
auto stream_id = header.opcode - OP_Packet;
|
||||
auto stream = &m_streams[stream_id];
|
||||
auto &stream = m_streams[stream_id];
|
||||
if (stream == nullptr) {
|
||||
stream.reset(new DaybreakReliableStream());
|
||||
}
|
||||
|
||||
auto order = CompareSequence(stream->sequence_in, sequence);
|
||||
if (order == SequenceFuture) {
|
||||
@@ -703,7 +721,10 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p)
|
||||
auto header = p.GetSerialize<DaybreakReliableHeader>(0);
|
||||
auto sequence = NetworkToHost(header.sequence);
|
||||
auto stream_id = header.opcode - OP_Fragment;
|
||||
auto stream = &m_streams[stream_id];
|
||||
auto &stream = m_streams[stream_id];
|
||||
if (stream == nullptr) {
|
||||
stream.reset(new DaybreakReliableStream());
|
||||
}
|
||||
|
||||
auto order = CompareSequence(stream->sequence_in, sequence);
|
||||
|
||||
@@ -1097,7 +1118,11 @@ void EQ::Net::DaybreakConnection::ProcessResend(int stream)
|
||||
|
||||
auto resends = 0;
|
||||
auto now = Clock::now();
|
||||
auto s = &m_streams[stream];
|
||||
auto &s = m_streams[stream];
|
||||
if (s == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &entry : s->sent_packets) {
|
||||
auto time_since_last_send = std::chrono::duration_cast<std::chrono::milliseconds>(now - entry.second.last_sent);
|
||||
if (entry.second.times_resent == 0) {
|
||||
@@ -1159,7 +1184,11 @@ void EQ::Net::DaybreakConnection::Ack(int stream, uint16_t seq)
|
||||
{
|
||||
|
||||
auto now = Clock::now();
|
||||
auto s = &m_streams[stream];
|
||||
auto &s = m_streams[stream];
|
||||
if (s == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto iter = s->sent_packets.begin();
|
||||
while (iter != s->sent_packets.end()) {
|
||||
auto order = CompareSequence(seq, iter->first);
|
||||
@@ -1183,7 +1212,11 @@ void EQ::Net::DaybreakConnection::Ack(int stream, uint16_t seq)
|
||||
void EQ::Net::DaybreakConnection::OutOfOrderAck(int stream, uint16_t seq)
|
||||
{
|
||||
auto now = Clock::now();
|
||||
auto s = &m_streams[stream];
|
||||
auto &s = m_streams[stream];
|
||||
if (s == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto iter = s->sent_packets.find(seq);
|
||||
if (iter != s->sent_packets.end()) {
|
||||
uint64_t round_time = (uint64_t)std::chrono::duration_cast<std::chrono::milliseconds>(now - iter->second.last_sent).count();
|
||||
@@ -1399,7 +1432,11 @@ void EQ::Net::DaybreakConnection::InternalQueuePacket(Packet &p, int stream_id,
|
||||
return;
|
||||
}
|
||||
|
||||
auto stream = &m_streams[stream_id];
|
||||
auto &stream = m_streams[stream_id];
|
||||
if (stream == nullptr) {
|
||||
stream.reset(new DaybreakReliableStream());
|
||||
}
|
||||
|
||||
auto max_raw_size = m_max_packet_size - m_crc_bytes - DaybreakReliableHeader::size() - 1; // -1 for compress flag
|
||||
size_t length = p.Length();
|
||||
if (length > max_raw_size) {
|
||||
|
||||
@@ -133,6 +133,38 @@ namespace EQ
|
||||
uint64_t bytes_before_encode;
|
||||
};
|
||||
|
||||
//Refactoring this stuff
|
||||
struct DaybreakSentPacket
|
||||
{
|
||||
DynamicPacket packet;
|
||||
Timestamp last_sent;
|
||||
Timestamp first_sent;
|
||||
size_t times_resent;
|
||||
size_t resend_delay;
|
||||
};
|
||||
|
||||
class DaybreakReliableStream
|
||||
{
|
||||
public:
|
||||
DaybreakReliableStream() {
|
||||
sequence_in = 0;
|
||||
sequence_out = 0;
|
||||
fragment_current_bytes = 0;
|
||||
fragment_total_bytes = 0;
|
||||
}
|
||||
//private:
|
||||
|
||||
uint16_t sequence_in;
|
||||
uint16_t sequence_out;
|
||||
std::map<uint16_t, Packet*> packet_queue;
|
||||
|
||||
DynamicPacket fragment_packet;
|
||||
uint32_t fragment_current_bytes;
|
||||
uint32_t fragment_total_bytes;
|
||||
|
||||
std::map<uint16_t, DaybreakSentPacket> sent_packets;
|
||||
};
|
||||
|
||||
class DaybreakConnectionManager;
|
||||
class DaybreakConnection;
|
||||
class DaybreakConnection
|
||||
@@ -181,36 +213,7 @@ namespace EQ
|
||||
Timestamp m_close_time;
|
||||
double m_outgoing_budget;
|
||||
|
||||
struct DaybreakSentPacket
|
||||
{
|
||||
DynamicPacket packet;
|
||||
Timestamp last_sent;
|
||||
Timestamp first_sent;
|
||||
size_t times_resent;
|
||||
size_t resend_delay;
|
||||
};
|
||||
|
||||
struct DaybreakStream
|
||||
{
|
||||
DaybreakStream() {
|
||||
sequence_in = 0;
|
||||
sequence_out = 0;
|
||||
fragment_current_bytes = 0;
|
||||
fragment_total_bytes = 0;
|
||||
}
|
||||
|
||||
uint16_t sequence_in;
|
||||
uint16_t sequence_out;
|
||||
std::map<uint16_t, std::unique_ptr<Packet>> packet_queue;
|
||||
|
||||
DynamicPacket fragment_packet;
|
||||
uint32_t fragment_current_bytes;
|
||||
uint32_t fragment_total_bytes;
|
||||
|
||||
std::map<uint16_t, DaybreakSentPacket> sent_packets;
|
||||
};
|
||||
|
||||
DaybreakStream m_streams[4];
|
||||
std::unique_ptr<DaybreakReliableStream> m_streams[4];
|
||||
std::weak_ptr<DaybreakConnection> m_self;
|
||||
|
||||
void Process();
|
||||
@@ -272,6 +275,14 @@ namespace EQ
|
||||
resend_timeout = 30000;
|
||||
connection_close_time = 2000;
|
||||
outgoing_data_rate = 0.0;
|
||||
//this is emperically based on what the client seems to set for theirs
|
||||
//this is the max number of packets that can be sent/recv per channel before acks come in
|
||||
//we'll use this to preallocate some buffers
|
||||
max_outgoing_packets_per_channel = 400;
|
||||
max_incoming_packets_per_channel = 400;
|
||||
|
||||
//This is the max size of a packet that can be sent or received
|
||||
max_total_packet_size = 1024 * 1024 * 16;
|
||||
}
|
||||
|
||||
size_t max_packet_size;
|
||||
@@ -295,6 +306,9 @@ namespace EQ
|
||||
DaybreakEncodeType encode_passes[2];
|
||||
int port;
|
||||
double outgoing_data_rate;
|
||||
int max_outgoing_packets_per_channel;
|
||||
int max_incoming_packets_per_channel;
|
||||
int max_total_packet_size;
|
||||
};
|
||||
|
||||
class DaybreakConnectionManager
|
||||
|
||||
@@ -7,14 +7,6 @@
|
||||
|
||||
class ItemsRepository: public BaseItemsRepository {
|
||||
public:
|
||||
struct Bazaar_Results {
|
||||
uint32 item_id;
|
||||
std::string name;
|
||||
bool stackable;
|
||||
uint32 icon;
|
||||
uint32 stats;
|
||||
};
|
||||
|
||||
static std::vector<int32> GetItemIDsBySearchCriteria(
|
||||
Database& db,
|
||||
std::string search_string,
|
||||
@@ -45,51 +37,6 @@ public:
|
||||
return item_id_list;
|
||||
}
|
||||
|
||||
static std::unordered_map<uint32, Bazaar_Results> GetItemsForBazaarSearch(
|
||||
Database& db,
|
||||
const std::vector<std::string> &search_ids,
|
||||
const std::string &name,
|
||||
const std::string &field_criteria_items,
|
||||
const std::string &where_criteria_items,
|
||||
const uint32 query_limit = 0
|
||||
)
|
||||
{
|
||||
auto query = fmt::format(
|
||||
"SELECT id, name, stackable, icon, {} "
|
||||
"FROM items "
|
||||
"WHERE `name` LIKE '%%{}%%' AND {} AND id IN({}) "
|
||||
"ORDER BY id ASC",
|
||||
field_criteria_items,
|
||||
Strings::Escape(name),
|
||||
where_criteria_items,
|
||||
Strings::Implode(",", search_ids)
|
||||
);
|
||||
|
||||
if (query_limit >= 1) {
|
||||
query += fmt::format(" LIMIT {}", query_limit);
|
||||
}
|
||||
|
||||
std::unordered_map<uint32, Bazaar_Results> item_list;
|
||||
|
||||
auto results = db.QueryDatabase(query);
|
||||
if (!results.Success() || !results.RowCount()) {
|
||||
return item_list;
|
||||
}
|
||||
|
||||
item_list.reserve(results.RowCount());
|
||||
for (auto row : results) {
|
||||
Bazaar_Results br{};
|
||||
br.item_id = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
|
||||
br.name = row[1] ? row[1] : "";
|
||||
br.stackable = atoi(row[2]) ? true : false;
|
||||
br.icon = row[3] ? static_cast<int32_t>(atoi(row[3])) : 0;
|
||||
br.stats = row[4] ? static_cast<int32_t>(atoi(row[4])) : 0;
|
||||
|
||||
item_list.emplace(br.item_id, br);
|
||||
}
|
||||
|
||||
return item_list;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -28,11 +28,6 @@ public:
|
||||
std::vector<DistinctTraders_Struct> traders{};
|
||||
};
|
||||
|
||||
struct BazaarTraderSearch_Struct {
|
||||
Trader trader;
|
||||
std::string trader_name;
|
||||
};
|
||||
|
||||
struct WelcomeData_Struct {
|
||||
uint32 count_of_traders;
|
||||
uint32 count_of_items;
|
||||
@@ -270,54 +265,6 @@ public:
|
||||
|
||||
return trader;
|
||||
}
|
||||
|
||||
static std::vector<BazaarTraderSearch_Struct> GetBazaarTraderDetails(
|
||||
Database &db,
|
||||
std::string &search_criteria_trader
|
||||
)
|
||||
{
|
||||
std::vector<BazaarTraderSearch_Struct> all_entries{};
|
||||
|
||||
auto query = fmt::format(
|
||||
"SELECT trader.*, c.`name` FROM `trader` INNER JOIN character_data AS c ON trader.char_id = c.id "
|
||||
"WHERE {} ORDER BY trader.char_id ASC",
|
||||
search_criteria_trader
|
||||
);
|
||||
|
||||
auto results = db.QueryDatabase(query);
|
||||
|
||||
if (results.RowCount() == 0) {
|
||||
return all_entries;
|
||||
}
|
||||
|
||||
all_entries.reserve(results.RowCount());
|
||||
for (auto row = results.begin(); row != results.end(); ++row) {
|
||||
BazaarTraderSearch_Struct e{};
|
||||
|
||||
e.trader.id = row[0] ? strtoull(row[0], nullptr, 10) : 0;
|
||||
e.trader.char_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
|
||||
e.trader.item_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
|
||||
e.trader.aug_slot_1 = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
|
||||
e.trader.aug_slot_2 = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
|
||||
e.trader.aug_slot_3 = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
|
||||
e.trader.aug_slot_4 = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
|
||||
e.trader.aug_slot_5 = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
|
||||
e.trader.aug_slot_6 = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
|
||||
e.trader.item_sn = row[9] ? static_cast<uint32_t>(strtoul(row[9], nullptr, 10)) : 0;
|
||||
e.trader.item_charges = row[10] ? static_cast<int32_t>(atoi(row[10])) : 0;
|
||||
e.trader.item_cost = row[11] ? static_cast<uint32_t>(strtoul(row[11], nullptr, 10)) : 0;
|
||||
e.trader.slot_id = row[12] ? static_cast<uint8_t>(strtoul(row[12], nullptr, 10)) : 0;
|
||||
e.trader.char_entity_id = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0;
|
||||
e.trader.char_zone_id = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
|
||||
e.trader.char_zone_instance_id = row[15] ? static_cast<int32_t>(atoi(row[15])) : 0;
|
||||
e.trader.active_transaction = row[16] ? static_cast<uint8_t>(strtoul(row[16], nullptr, 10)) : 0;
|
||||
e.trader_name = row[17] ? row[17] : std::string("");
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
|
||||
return all_entries;
|
||||
}
|
||||
};
|
||||
|
||||
#endif //EQEMU_TRADER_REPOSITORY_H
|
||||
|
||||
+2
-2
@@ -25,7 +25,7 @@
|
||||
|
||||
// Build variables
|
||||
// these get injected during the build pipeline
|
||||
#define CURRENT_VERSION "22.62.0-dev" // always append -dev to the current version for custom-builds
|
||||
#define CURRENT_VERSION "22.61.0-dev" // always append -dev to the current version for custom-builds
|
||||
#define LOGIN_VERSION "0.8.0"
|
||||
#define COMPILE_DATE __DATE__
|
||||
#define COMPILE_TIME __TIME__
|
||||
@@ -42,7 +42,7 @@
|
||||
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
|
||||
*/
|
||||
|
||||
#define CURRENT_BINARY_DATABASE_VERSION 9295
|
||||
#define CURRENT_BINARY_DATABASE_VERSION 9293
|
||||
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9045
|
||||
|
||||
#endif
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "eqemu-server",
|
||||
"version": "22.62.0",
|
||||
"version": "22.61.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/EQEmu/Server.git"
|
||||
|
||||
@@ -339,6 +339,7 @@ public:
|
||||
const char *message9 = nullptr);
|
||||
void Tell_StringID(uint32 string_id, const char *who, const char *message);
|
||||
void SendColoredText(uint32 color, std::string message);
|
||||
void SendBazaarResults(uint32 trader_id, uint32 in_class, uint32 in_race, uint32 item_stat, uint32 item_slot, uint32 item_type, char item_name[64], uint32 min_price, uint32 max_price);
|
||||
void SendTraderItem(uint32 item_id,uint16 quantity, TraderRepository::Trader &trader);
|
||||
void DoBazaarSearch(BazaarSearchCriteria_Struct search_criteria);
|
||||
uint16 FindTraderItem(int32 SerialNumber,uint16 Quantity);
|
||||
|
||||
+27
-116
@@ -1,15 +1,12 @@
|
||||
#include "data_bucket.h"
|
||||
#include "entity.h"
|
||||
#include "zonedb.h"
|
||||
#include "mob.h"
|
||||
#include "worldserver.h"
|
||||
#include <ctime>
|
||||
#include <cctype>
|
||||
#include "../common/json/json.hpp"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
extern WorldServer worldserver;
|
||||
const std::string NESTED_KEY_DELIMITER = ".";
|
||||
|
||||
std::vector<DataBucketsRepository::DataBuckets> g_data_bucket_cache = {};
|
||||
|
||||
@@ -28,13 +25,8 @@ void DataBucket::SetData(const std::string &bucket_key, const std::string &bucke
|
||||
DataBucket::SetData(k);
|
||||
}
|
||||
|
||||
void DataBucket::SetData(const DataBucketKey &k_)
|
||||
void DataBucket::SetData(const DataBucketKey &k)
|
||||
{
|
||||
DataBucketKey k = k_; // copy the key so we can modify it
|
||||
if (k.key.find(NESTED_KEY_DELIMITER) != std::string::npos) {
|
||||
k.key = Strings::Split(k.key, NESTED_KEY_DELIMITER).front();
|
||||
}
|
||||
|
||||
auto b = DataBucketsRepository::NewEntity();
|
||||
auto r = GetData(k, true);
|
||||
// if we have an entry, use it
|
||||
@@ -68,48 +60,9 @@ void DataBucket::SetData(const DataBucketKey &k_)
|
||||
|
||||
b.expires = expires_time_unix;
|
||||
b.value = k.value;
|
||||
b.key_ = k.key;
|
||||
|
||||
// Check for nested keys (keys with dots)
|
||||
if (k_.key.find(NESTED_KEY_DELIMITER) != std::string::npos) {
|
||||
// Retrieve existing JSON or create a new one
|
||||
std::string existing_value = r.id > 0 ? r.value : "{}";
|
||||
json json_value = json::object();
|
||||
|
||||
try {
|
||||
json_value = json::parse(existing_value);
|
||||
} catch (json::parse_error &e) {
|
||||
LogError("Failed to parse JSON for key [{}]: {}", k_.key, e.what());
|
||||
json_value = json::object(); // Reset to an empty object on error
|
||||
}
|
||||
|
||||
// Recursively merge new key-value pair into the JSON object
|
||||
auto nested_keys = Strings::Split(k_.key, NESTED_KEY_DELIMITER);
|
||||
json *current = &json_value;
|
||||
|
||||
for (size_t i = 0; i < nested_keys.size(); ++i) {
|
||||
const std::string &key_part = nested_keys[i];
|
||||
if (i == nested_keys.size() - 1) {
|
||||
// Set the value at the final key
|
||||
(*current)[key_part] = k_.value;
|
||||
} else {
|
||||
// Traverse or create nested objects
|
||||
if (!current->contains(key_part)) {
|
||||
(*current)[key_part] = json::object();
|
||||
} else if (!(*current)[key_part].is_object()) {
|
||||
// If key exists but is not an object, reset to object to avoid conflicts
|
||||
(*current)[key_part] = json::object();
|
||||
}
|
||||
current = &(*current)[key_part];
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize JSON back to string
|
||||
b.value = json_value.dump();
|
||||
b.key_ = nested_keys.front(); // Use the top-level key
|
||||
}
|
||||
|
||||
if (bucket_id) {
|
||||
|
||||
// update the cache if it exists
|
||||
if (CanCache(k)) {
|
||||
for (auto &e: g_data_bucket_cache) {
|
||||
@@ -123,6 +76,7 @@ void DataBucket::SetData(const DataBucketKey &k_)
|
||||
DataBucketsRepository::UpdateOne(database, b);
|
||||
}
|
||||
else {
|
||||
b.key_ = k.key;
|
||||
b = DataBucketsRepository::InsertOne(database, b);
|
||||
|
||||
// add to cache if it doesn't exist
|
||||
@@ -138,56 +92,12 @@ std::string DataBucket::GetData(const std::string &bucket_key)
|
||||
return GetData(DataBucketKey{.key = bucket_key}).value;
|
||||
}
|
||||
|
||||
DataBucketsRepository::DataBuckets DataBucket::ExtractNestedValue(
|
||||
const DataBucketsRepository::DataBuckets &bucket,
|
||||
const std::string &full_key)
|
||||
{
|
||||
auto nested_keys = Strings::Split(full_key, NESTED_KEY_DELIMITER);
|
||||
json json_value;
|
||||
|
||||
try {
|
||||
json_value = json::parse(bucket.value); // Parse the JSON
|
||||
} catch (json::parse_error &ex) {
|
||||
LogError("Failed to parse JSON for key [{}]: {}", bucket.key_, ex.what());
|
||||
return DataBucketsRepository::NewEntity(); // Return empty entity on parse error
|
||||
}
|
||||
|
||||
// Start from the top-level key (e.g., "progression")
|
||||
json *current = &json_value;
|
||||
|
||||
// Traverse the JSON structure
|
||||
for (const auto &key_part: nested_keys) {
|
||||
LogDataBuckets("Looking for key part [{}] in JSON", key_part);
|
||||
|
||||
if (!current->contains(key_part)) {
|
||||
LogDataBuckets("Key part [{}] not found in JSON for [{}]", key_part, full_key);
|
||||
return DataBucketsRepository::NewEntity();
|
||||
}
|
||||
|
||||
current = &(*current)[key_part];
|
||||
}
|
||||
|
||||
// Create a new entity with the extracted value
|
||||
DataBucketsRepository::DataBuckets result = bucket; // Copy the original bucket
|
||||
result.value = current->is_string() ? current->get<std::string>() : current->dump();
|
||||
return result;
|
||||
}
|
||||
|
||||
// GetData fetches bucket data from the database or cache if it exists
|
||||
// if the bucket doesn't exist, it will be added to the cache as a miss
|
||||
// if ignore_misses_cache is true, the bucket will not be added to the cache as a miss
|
||||
// the only place we should be ignoring the misses cache is on the initial read during SetData
|
||||
DataBucketsRepository::DataBuckets DataBucket::GetData(const DataBucketKey &k_, bool ignore_misses_cache)
|
||||
DataBucketsRepository::DataBuckets DataBucket::GetData(const DataBucketKey &k, bool ignore_misses_cache)
|
||||
{
|
||||
DataBucketKey k = k_; // Copy the key so we can modify it
|
||||
|
||||
bool is_nested_key = k.key.find(NESTED_KEY_DELIMITER) != std::string::npos;
|
||||
|
||||
// Extract the top-level key for nested keys
|
||||
if (is_nested_key) {
|
||||
k.key = Strings::Split(k.key, NESTED_KEY_DELIMITER).front();
|
||||
}
|
||||
|
||||
LogDataBuckets(
|
||||
"Getting bucket key [{}] bot_id [{}] account_id [{}] character_id [{}] npc_id [{}]",
|
||||
k.key,
|
||||
@@ -199,9 +109,9 @@ DataBucketsRepository::DataBuckets DataBucket::GetData(const DataBucketKey &k_,
|
||||
|
||||
bool can_cache = CanCache(k);
|
||||
|
||||
// Attempt to retrieve the value from the cache
|
||||
// check the cache first if we can cache
|
||||
if (can_cache) {
|
||||
for (const auto &e : g_data_bucket_cache) {
|
||||
for (const auto &e: g_data_bucket_cache) {
|
||||
if (CheckBucketMatch(e, k)) {
|
||||
if (e.expires > 0 && e.expires < std::time(nullptr)) {
|
||||
LogDataBuckets("Attempted to read expired key [{}] removing from cache", e.key_);
|
||||
@@ -209,32 +119,37 @@ DataBucketsRepository::DataBuckets DataBucket::GetData(const DataBucketKey &k_,
|
||||
return DataBucketsRepository::NewEntity();
|
||||
}
|
||||
|
||||
LogDataBuckets("Returning key [{}] value [{}] from cache", e.key_, e.value);
|
||||
|
||||
if (is_nested_key) {
|
||||
return ExtractNestedValue(e, k_.key);
|
||||
// this is a bucket miss, return empty entity
|
||||
// we still cache bucket misses, so we don't have to hit the database
|
||||
if (e.id == 0) {
|
||||
return DataBucketsRepository::NewEntity();
|
||||
}
|
||||
|
||||
LogDataBuckets("Returning key [{}] value [{}] from cache", e.key_, e.value);
|
||||
return e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch the value from the database
|
||||
auto r = DataBucketsRepository::GetWhere(
|
||||
database,
|
||||
fmt::format(
|
||||
" {} `key` = '{}' LIMIT 1",
|
||||
"{} `key` = '{}' LIMIT 1",
|
||||
DataBucket::GetScopedDbFilters(k),
|
||||
k.key
|
||||
)
|
||||
);
|
||||
|
||||
if (r.empty()) {
|
||||
// Handle cache misses
|
||||
if (!ignore_misses_cache && can_cache) {
|
||||
|
||||
// if we're ignoring the misses cache, don't add to the cache
|
||||
// the only place this is ignored is during the initial read of SetData
|
||||
bool add_to_misses_cache = !ignore_misses_cache && can_cache;
|
||||
if (add_to_misses_cache) {
|
||||
size_t size_before = g_data_bucket_cache.size();
|
||||
|
||||
// cache bucket misses, so we don't have to hit the database
|
||||
// when scripts try to read a bucket that doesn't exist
|
||||
g_data_bucket_cache.emplace_back(
|
||||
DataBucketsRepository::DataBuckets{
|
||||
.id = 0,
|
||||
@@ -260,21 +175,22 @@ DataBucketsRepository::DataBuckets DataBucket::GetData(const DataBucketKey &k_,
|
||||
);
|
||||
}
|
||||
|
||||
return DataBucketsRepository::NewEntity();
|
||||
return {};
|
||||
}
|
||||
|
||||
auto bucket = r.front();
|
||||
|
||||
// If the entry has expired, delete it
|
||||
if (bucket.expires > 0 && bucket.expires < static_cast<long long>(std::time(nullptr))) {
|
||||
// if the entry has expired, delete it
|
||||
if (bucket.expires > 0 && bucket.expires < (long long) std::time(nullptr)) {
|
||||
DeleteData(k);
|
||||
return DataBucketsRepository::NewEntity();
|
||||
return {};
|
||||
}
|
||||
|
||||
// Add the value to the cache if it doesn't exist
|
||||
// add to cache if it doesn't exist
|
||||
if (can_cache) {
|
||||
bool has_cache = false;
|
||||
for (const auto &e : g_data_bucket_cache) {
|
||||
|
||||
for (auto &e: g_data_bucket_cache) {
|
||||
if (e.id == bucket.id) {
|
||||
has_cache = true;
|
||||
break;
|
||||
@@ -286,11 +202,6 @@ DataBucketsRepository::DataBuckets DataBucket::GetData(const DataBucketKey &k_,
|
||||
}
|
||||
}
|
||||
|
||||
// Handle nested key extraction
|
||||
if (is_nested_key) {
|
||||
return ExtractNestedValue(bucket, k_.key);
|
||||
}
|
||||
|
||||
return bucket;
|
||||
}
|
||||
|
||||
|
||||
+2
-4
@@ -45,9 +45,9 @@ public:
|
||||
static bool GetDataBuckets(Mob *mob);
|
||||
|
||||
// scoped bucket methods
|
||||
static void SetData(const DataBucketKey &k_);
|
||||
static void SetData(const DataBucketKey &k);
|
||||
static bool DeleteData(const DataBucketKey &k);
|
||||
static DataBucketsRepository::DataBuckets GetData(const DataBucketKey &k_, bool ignore_misses_cache = false);
|
||||
static DataBucketsRepository::DataBuckets GetData(const DataBucketKey &k, bool ignore_misses_cache = false);
|
||||
static std::string GetDataExpires(const DataBucketKey &k);
|
||||
static std::string GetDataRemaining(const DataBucketKey &k);
|
||||
static std::string GetScopedDbFilters(const DataBucketKey &k);
|
||||
@@ -63,8 +63,6 @@ public:
|
||||
static void ClearCache();
|
||||
static void DeleteFromCache(uint64 id, DataBucketLoadType::Type type);
|
||||
static bool CanCache(const DataBucketKey &key);
|
||||
static DataBucketsRepository::DataBuckets
|
||||
ExtractNestedValue(const DataBucketsRepository::DataBuckets &bucket, const std::string &full_key);
|
||||
};
|
||||
|
||||
#endif //EQEMU_DATABUCKET_H
|
||||
|
||||
+312
-7
@@ -1800,13 +1800,7 @@ void Client::SendBarterWelcome()
|
||||
|
||||
void Client::DoBazaarSearch(BazaarSearchCriteria_Struct search_criteria)
|
||||
{
|
||||
std::vector<BazaarSearchResultsFromDB_Struct> results = Bazaar::GetSearchResults(
|
||||
database,
|
||||
content_db,
|
||||
search_criteria,
|
||||
GetZoneID(),
|
||||
GetInstanceID()
|
||||
);
|
||||
auto results = Bazaar::GetSearchResults(database, search_criteria, GetZoneID(), GetInstanceID());
|
||||
if (results.empty()) {
|
||||
SendBazaarDone(GetID());
|
||||
return;
|
||||
@@ -1828,6 +1822,317 @@ void Client::DoBazaarSearch(BazaarSearchCriteria_Struct search_criteria)
|
||||
SendBazaarDeliveryCosts();
|
||||
}
|
||||
|
||||
void Client::SendBazaarResults(
|
||||
uint32 trader_id,
|
||||
uint32 in_class,
|
||||
uint32 in_race,
|
||||
uint32 item_stat,
|
||||
uint32 item_slot,
|
||||
uint32 item_type,
|
||||
char item_name[64],
|
||||
uint32 min_price,
|
||||
uint32 max_price
|
||||
)
|
||||
{
|
||||
std::string search_values = " COUNT(item_id), trader.*, items.name ";
|
||||
std::string search_criteria = " WHERE trader.item_id = items.id ";
|
||||
|
||||
if (trader_id > 0) {
|
||||
Client *trader = entity_list.GetClientByID(trader_id);
|
||||
|
||||
if (trader) {
|
||||
search_criteria.append(StringFormat(" AND trader.char_id = %i", trader->CharacterID()));
|
||||
}
|
||||
}
|
||||
|
||||
if (min_price != 0) {
|
||||
search_criteria.append(StringFormat(" AND trader.item_cost >= %i", min_price));
|
||||
}
|
||||
|
||||
if (max_price != 0) {
|
||||
search_criteria.append(StringFormat(" AND trader.item_cost <= %i", max_price));
|
||||
}
|
||||
|
||||
if (strlen(item_name) > 0) {
|
||||
char *safeName = RemoveApostrophes(item_name);
|
||||
search_criteria.append(StringFormat(" AND items.name LIKE '%%%s%%'", safeName));
|
||||
safe_delete_array(safeName);
|
||||
}
|
||||
|
||||
if (in_class != 0xFFFFFFFF) {
|
||||
search_criteria.append(StringFormat(" AND MID(REVERSE(BIN(items.classes)), %i, 1) = 1", in_class));
|
||||
}
|
||||
|
||||
if (in_race != 0xFFFFFFFF) {
|
||||
search_criteria.append(StringFormat(" AND MID(REVERSE(BIN(items.races)), %i, 1) = 1", in_race));
|
||||
}
|
||||
|
||||
if (item_slot != 0xFFFFFFFF) {
|
||||
search_criteria.append(StringFormat(" AND MID(REVERSE(BIN(items.slots)), %i, 1) = 1", item_slot + 1));
|
||||
}
|
||||
|
||||
switch (item_type) {
|
||||
case 0xFFFFFFFF:
|
||||
break;
|
||||
case 0:
|
||||
// 1H Slashing
|
||||
search_criteria.append(" AND items.itemtype = 0 AND damage > 0");
|
||||
break;
|
||||
case 31:
|
||||
search_criteria.append(" AND items.itemclass = 2");
|
||||
break;
|
||||
case 46:
|
||||
search_criteria.append(" AND items.scrolleffect > 0 AND items.scrolleffect < 65000");
|
||||
break;
|
||||
case 47:
|
||||
search_criteria.append(" AND items.worneffect = 998");
|
||||
break;
|
||||
case 48:
|
||||
search_criteria.append(" AND items.worneffect >= 1298 AND items.worneffect <= 1307");
|
||||
break;
|
||||
case 49:
|
||||
search_criteria.append(" AND items.focuseffect > 0");
|
||||
break;
|
||||
|
||||
default:
|
||||
search_criteria.append(StringFormat(" AND items.itemtype = %i", item_type));
|
||||
}
|
||||
|
||||
switch (item_stat) {
|
||||
|
||||
case STAT_AC:
|
||||
search_criteria.append(" AND items.ac > 0");
|
||||
search_values.append(", items.ac");
|
||||
break;
|
||||
|
||||
case STAT_AGI:
|
||||
search_criteria.append(" AND items.aagi > 0");
|
||||
search_values.append(", items.aagi");
|
||||
break;
|
||||
|
||||
case STAT_CHA:
|
||||
search_criteria.append(" AND items.acha > 0");
|
||||
search_values.append(", items.acha");
|
||||
break;
|
||||
|
||||
case STAT_DEX:
|
||||
search_criteria.append(" AND items.adex > 0");
|
||||
search_values.append(", items.adex");
|
||||
break;
|
||||
|
||||
case STAT_INT:
|
||||
search_criteria.append(" AND items.aint > 0");
|
||||
search_values.append(", items.aint");
|
||||
break;
|
||||
|
||||
case STAT_STA:
|
||||
search_criteria.append(" AND items.asta > 0");
|
||||
search_values.append(", items.asta");
|
||||
break;
|
||||
|
||||
case STAT_STR:
|
||||
search_criteria.append(" AND items.astr > 0");
|
||||
search_values.append(", items.astr");
|
||||
break;
|
||||
|
||||
case STAT_WIS:
|
||||
search_criteria.append(" AND items.awis > 0");
|
||||
search_values.append(", items.awis");
|
||||
break;
|
||||
|
||||
case STAT_COLD:
|
||||
search_criteria.append(" AND items.cr > 0");
|
||||
search_values.append(", items.cr");
|
||||
break;
|
||||
|
||||
case STAT_DISEASE:
|
||||
search_criteria.append(" AND items.dr > 0");
|
||||
search_values.append(", items.dr");
|
||||
break;
|
||||
|
||||
case STAT_FIRE:
|
||||
search_criteria.append(" AND items.fr > 0");
|
||||
search_values.append(", items.fr");
|
||||
break;
|
||||
|
||||
case STAT_MAGIC:
|
||||
search_criteria.append(" AND items.mr > 0");
|
||||
search_values.append(", items.mr");
|
||||
break;
|
||||
|
||||
case STAT_POISON:
|
||||
search_criteria.append(" AND items.pr > 0");
|
||||
search_values.append(", items.pr");
|
||||
break;
|
||||
|
||||
case STAT_HP:
|
||||
search_criteria.append(" AND items.hp > 0");
|
||||
search_values.append(", items.hp");
|
||||
break;
|
||||
|
||||
case STAT_MANA:
|
||||
search_criteria.append(" AND items.mana > 0");
|
||||
search_values.append(", items.mana");
|
||||
break;
|
||||
|
||||
case STAT_ENDURANCE:
|
||||
search_criteria.append(" AND items.endur > 0");
|
||||
search_values.append(", items.endur");
|
||||
break;
|
||||
|
||||
case STAT_ATTACK:
|
||||
search_criteria.append(" AND items.attack > 0");
|
||||
search_values.append(", items.attack");
|
||||
break;
|
||||
|
||||
case STAT_HP_REGEN:
|
||||
search_criteria.append(" AND items.regen > 0");
|
||||
search_values.append(", items.regen");
|
||||
break;
|
||||
|
||||
case STAT_MANA_REGEN:
|
||||
search_criteria.append(" AND items.manaregen > 0");
|
||||
search_values.append(", items.manaregen");
|
||||
break;
|
||||
|
||||
case STAT_HASTE:
|
||||
search_criteria.append(" AND items.haste > 0");
|
||||
search_values.append(", items.haste");
|
||||
break;
|
||||
|
||||
case STAT_DAMAGE_SHIELD:
|
||||
search_criteria.append(" AND items.damageshield > 0");
|
||||
search_values.append(", items.damageshield");
|
||||
break;
|
||||
|
||||
default:
|
||||
search_values.append(", 0");
|
||||
break;
|
||||
}
|
||||
|
||||
std::string query = StringFormat(
|
||||
"SELECT %s, SUM(charges), items.stackable "
|
||||
"FROM trader, items %s GROUP BY items.id, charges, char_id LIMIT %i",
|
||||
search_values.c_str(),
|
||||
search_criteria.c_str(),
|
||||
RuleI(Bazaar, MaxSearchResults)
|
||||
);
|
||||
|
||||
auto results = database.QueryDatabase(query);
|
||||
|
||||
if (!results.Success()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LogTrading("SRCH: [{}]", query.c_str());
|
||||
|
||||
int Size = 0;
|
||||
uint32 ID = 0;
|
||||
|
||||
if (results.RowCount() == static_cast<unsigned long>(RuleI(Bazaar, MaxSearchResults))) {
|
||||
Message(
|
||||
Chat::Yellow,
|
||||
"Your search reached the limit of %i results. Please narrow your search down by selecting more options.",
|
||||
RuleI(Bazaar, MaxSearchResults));
|
||||
}
|
||||
|
||||
if (results.RowCount() == 0) {
|
||||
auto outapp2 = new EQApplicationPacket(OP_BazaarSearch, sizeof(BazaarReturnDone_Struct));
|
||||
BazaarReturnDone_Struct *brds = (BazaarReturnDone_Struct *) outapp2->pBuffer;
|
||||
brds->TraderID = ID;
|
||||
brds->Type = BazaarSearchDone;
|
||||
brds->Unknown008 = 0xFFFFFFFF;
|
||||
brds->Unknown012 = 0xFFFFFFFF;
|
||||
brds->Unknown016 = 0xFFFFFFFF;
|
||||
QueuePacket(outapp2);
|
||||
safe_delete(outapp2);
|
||||
return;
|
||||
}
|
||||
|
||||
Size = results.RowCount() * sizeof(BazaarSearchResults_Struct);
|
||||
auto buffer = new uchar[Size];
|
||||
uchar *bufptr = buffer;
|
||||
memset(buffer, 0, Size);
|
||||
|
||||
int Action = BazaarSearchResults;
|
||||
uint32 Cost = 0;
|
||||
int32 SerialNumber = 0;
|
||||
char temp_buffer[64] = {0};
|
||||
int Count = 0;
|
||||
uint32 StatValue = 0;
|
||||
|
||||
for (auto &row = results.begin(); row != results.end(); ++row) {
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, Action);
|
||||
Count = Strings::ToInt(row[0]);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, Count);
|
||||
SerialNumber = Strings::ToInt(row[3]);
|
||||
VARSTRUCT_ENCODE_TYPE(int32, bufptr, SerialNumber);
|
||||
Client *Trader2 = entity_list.GetClientByCharID(Strings::ToInt(row[1]));
|
||||
if (Trader2) {
|
||||
ID = Trader2->GetID();
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, ID);
|
||||
}
|
||||
else {
|
||||
LogTrading("Unable to find trader: [{}]\n", Strings::ToInt(row[1]));
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, 0);
|
||||
}
|
||||
Cost = Strings::ToInt(row[5]);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, Cost);
|
||||
StatValue = Strings::ToInt(row[8]);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, StatValue);
|
||||
bool Stackable = Strings::ToInt(row[10]);
|
||||
if (Stackable) {
|
||||
int Charges = Strings::ToInt(row[9]);
|
||||
sprintf(temp_buffer, "%s(%i)", row[7], Charges);
|
||||
}
|
||||
else {
|
||||
sprintf(temp_buffer, "%s(%i)", row[7], Count);
|
||||
}
|
||||
|
||||
memcpy(bufptr, &temp_buffer, strlen(temp_buffer));
|
||||
|
||||
bufptr += 64;
|
||||
|
||||
// Extra fields for SoD+
|
||||
//
|
||||
if (Trader2) {
|
||||
sprintf(temp_buffer, "%s", Trader2->GetName());
|
||||
}
|
||||
else {
|
||||
sprintf(temp_buffer, "Unknown");
|
||||
}
|
||||
|
||||
memcpy(bufptr, &temp_buffer, strlen(temp_buffer));
|
||||
|
||||
bufptr += 64;
|
||||
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, Strings::ToInt(row[1])); // ItemID
|
||||
}
|
||||
|
||||
auto outapp = new EQApplicationPacket(OP_BazaarSearch, Size);
|
||||
|
||||
memcpy(outapp->pBuffer, buffer, Size);
|
||||
|
||||
QueuePacket(outapp);
|
||||
|
||||
safe_delete(outapp);
|
||||
safe_delete_array(buffer);
|
||||
|
||||
auto outapp2 = new EQApplicationPacket(OP_BazaarSearch, sizeof(BazaarReturnDone_Struct));
|
||||
BazaarReturnDone_Struct *brds = (BazaarReturnDone_Struct *) outapp2->pBuffer;
|
||||
|
||||
brds->TraderID = ID;
|
||||
brds->Type = BazaarSearchDone;
|
||||
|
||||
brds->Unknown008 = 0xFFFFFFFF;
|
||||
brds->Unknown012 = 0xFFFFFFFF;
|
||||
brds->Unknown016 = 0xFFFFFFFF;
|
||||
|
||||
QueuePacket(outapp2);
|
||||
|
||||
safe_delete(outapp2);
|
||||
}
|
||||
|
||||
static void UpdateTraderCustomerItemsAdded(
|
||||
uint32 customer_id,
|
||||
std::vector<BaseTraderRepository::Trader> trader_items,
|
||||
|
||||
Reference in New Issue
Block a user