diff --git a/common/bazaar.cpp b/common/bazaar.cpp index cce3fcdd3..c07869055 100644 --- a/common/bazaar.cpp +++ b/common/bazaar.cpp @@ -6,7 +6,8 @@ std::vector Bazaar::GetSearchResults( - SharedDatabase &db, + Database &db, + Database &content_db, BazaarSearchCriteria_Struct search, uint32 char_zone_id, int32 char_zone_instance_id @@ -31,8 +32,132 @@ Bazaar::GetSearchResults( char_zone_instance_id ); + static std::map 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 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 item_stat_searches_new = { + {STAT_AC, {" items.ac" , static_cast(0)} }, + {STAT_AGI, {" items.aagi", static_cast(0)} }, + {STAT_CHA, {" items.acha", static_cast(0)} }, + {STAT_DEX, {" items.adex", static_cast(0)} }, + {STAT_INT, {" items.aint", static_cast(0)} }, + {STAT_STA, {" items.asta", static_cast(0)} }, + {STAT_STR, {" items.astr", static_cast(0)} }, + {STAT_WIS, {" items.awis", static_cast(0)} }, + {STAT_COLD, {" items.cr", static_cast(0)} }, + {STAT_DISEASE, {" items.dr", static_cast(0)} }, + {STAT_FIRE, {" items.fr", static_cast(0)} }, + {STAT_MAGIC, {" items.mr", static_cast(0)} }, + {STAT_POISON, {" items.pr", static_cast(0)} }, + {STAT_HP, {" items.hp", static_cast(0)} }, + {STAT_MANA, {" items.mana", static_cast(0)} }, + {STAT_ENDURANCE, {" items.endur", static_cast(0)} }, + {STAT_ATTACK, {" items.attack", static_cast(0)} }, + {STAT_HP_REGEN, {" items.regen", static_cast(0)} }, + {STAT_MANA_REGEN, {" items.manaregen", static_cast(0)} }, + {STAT_HASTE, {" items.haste", static_cast(0)} }, + {STAT_DAMAGE_SHIELD, {" items.damageshield", static_cast(0)} }, + {STAT_DS_MITIGATION, {" items.dsmitigation", static_cast(0)} }, + {STAT_HEAL_AMOUNT, {" items.healamt", static_cast(0)} }, + {STAT_SPELL_DAMAGE, {" items.spelldmg", static_cast(0)} }, + {STAT_CLAIRVOYANCE, {" items.clairvoyance", static_cast(0)} }, + {STAT_HEROIC_AGILITY, {" items.heroic_agi", static_cast(0)} }, + {STAT_HEROIC_CHARISMA, {" items.heroic_cha", static_cast(0)} }, + {STAT_HEROIC_DEXTERITY, {" items.heroic_dex", static_cast(0)} }, + {STAT_HEROIC_INTELLIGENCE, {" items.heroic_int", static_cast(0)} }, + {STAT_HEROIC_STAMINA, {" items.heroic_sta", static_cast(0)} }, + {STAT_HEROIC_STRENGTH, {" items.heroic_str", static_cast(0)} }, + {STAT_HEROIC_WISDOM, {" items.heroic_wis", static_cast(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 search_criteria_trader("TRUE"); + std::string field_criteria_items("FALSE"); + std::string where_criteria_items(" TRUE "); if (search.search_scope == NonRoFBazaarSearchScope) { search_criteria_trader.append( @@ -77,301 +202,115 @@ Bazaar::GetSearchResults( search_criteria_trader.append(fmt::format(" AND trader.item_cost <= {}", (uint64) search.max_cost * 1000)); } - // 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.slot != std::numeric_limits::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])); + } + } - 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 (search.type != std::numeric_limits::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::max()) { + where_criteria_items.append( + fmt::format(" AND items.races & {0} = {0}", GetPlayerRaceBit(GetRaceIDFromPlayerRaceValue(search.race)))); + } + + if (search._class != std::numeric_limits::max()) { + where_criteria_items.append(fmt::format(" AND items.classes & {0} = {0}", GetPlayerClassBit(search._class))); + } + + if (search.item_stat != std::numeric_limits::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 all_entries; + std::vector trader_items_ids{}; - auto results = db.QueryDatabase(query); - - if (!results.Success()) { + 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; } - struct ItemSearchType { - EQ::item::ItemType type; - bool condition; - }; + for (auto const &i: trader_results) { + trader_items_ids.push_back(std::to_string(i.trader.item_id)); + } - struct AddititiveSearchCriteria { - bool should_check; - bool condition; - }; + auto const item_results = ItemsRepository::GetItemsForBazaarSearch( + content_db, + trader_items_ids, + std::string(search.item_name), + field_criteria_items, + where_criteria_items + ); + + if (item_results.empty()) { + LogError("Bazaar - No items found in bazaar search."); + return all_entries; + } + + all_entries.reserve(trader_results.size()); + + for (auto const& t:trader_results) { + if (!item_results.contains(t.trader.item_id)) { + continue; + } - for (auto row: results) { 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; - r.item_id = Strings::ToInt(row[2]); - r.charges = Strings::ToInt(row[4]); - - auto item = db.GetItem(r.item_id); - if (!item) { - continue; - } - - 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 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 item_stat_searches = { - - {STAT_AC, inst->GetItemArmorClass(true)}, - {STAT_AGI, static_cast(inst->GetItemAgi(true))}, - {STAT_CHA, static_cast(inst->GetItemCha(true))}, - {STAT_DEX, static_cast(inst->GetItemDex(true))}, - {STAT_INT, static_cast(inst->GetItemInt(true))}, - {STAT_STA, static_cast(inst->GetItemSta(true))}, - {STAT_STR, static_cast(inst->GetItemStr(true))}, - {STAT_WIS, static_cast(inst->GetItemWis(true))}, - {STAT_COLD, static_cast(inst->GetItemCR(true))}, - {STAT_DISEASE, static_cast(inst->GetItemDR(true))}, - {STAT_FIRE, static_cast(inst->GetItemFR(true))}, - {STAT_MAGIC, static_cast(inst->GetItemMR(true))}, - {STAT_POISON, static_cast(inst->GetItemPR(true))}, - {STAT_HP, static_cast(inst->GetItemHP(true))}, - {STAT_MANA, static_cast(inst->GetItemMana(true))}, - {STAT_ENDURANCE, static_cast(inst->GetItemEndur(true))}, - {STAT_ATTACK, static_cast(inst->GetItemAttack(true))}, - {STAT_HP_REGEN, static_cast(inst->GetItemRegen(true))}, - {STAT_MANA_REGEN, static_cast(inst->GetItemManaRegen(true))}, - {STAT_HASTE, static_cast(inst->GetItemHaste(true))}, - {STAT_DAMAGE_SHIELD, static_cast(inst->GetItemDamageShield(true))}, - {STAT_DS_MITIGATION, static_cast(inst->GetItemDSMitigation(true))}, - {STAT_HEAL_AMOUNT, static_cast(inst->GetItemHealAmt(true))}, - {STAT_SPELL_DAMAGE, static_cast(inst->GetItemSpellDamage(true))}, - {STAT_CLAIRVOYANCE, static_cast(inst->GetItemClairvoyance(true))}, - {STAT_HEROIC_AGILITY, static_cast(inst->GetItemHeroicAgi(true))}, - {STAT_HEROIC_CHARISMA, static_cast(inst->GetItemHeroicCha(true))}, - {STAT_HEROIC_DEXTERITY, static_cast(inst->GetItemHeroicDex(true))}, - {STAT_HEROIC_INTELLIGENCE, static_cast(inst->GetItemHeroicInt(true))}, - {STAT_HEROIC_STAMINA, static_cast(inst->GetItemHeroicSta(true))}, - {STAT_HEROIC_STRENGTH, static_cast(inst->GetItemHeroicStr(true))}, - {STAT_HEROIC_WISDOM, static_cast(inst->GetItemHeroicWis(true))}, - {STAT_BASH, static_cast(inst->GetItemSkillsStat(EQ::skills::SkillBash, true))}, - {STAT_BACKSTAB, static_cast(inst->GetItemBackstabDamage(true))}, - {STAT_DRAGON_PUNCH, static_cast(inst->GetItemSkillsStat(EQ::skills::SkillDragonPunch, true))}, - {STAT_EAGLE_STRIKE, static_cast(inst->GetItemSkillsStat(EQ::skills::SkillEagleStrike, true))}, - {STAT_FLYING_KICK, static_cast(inst->GetItemSkillsStat(EQ::skills::SkillFlyingKick, true))}, - {STAT_KICK, static_cast(inst->GetItemSkillsStat(EQ::skills::SkillKick, true))}, - {STAT_ROUND_KICK, static_cast(inst->GetItemSkillsStat(EQ::skills::SkillRoundKick, true))}, - {STAT_TIGER_CLAW, static_cast(inst->GetItemSkillsStat(EQ::skills::SkillTigerClaw, true))}, - {STAT_FRENZY, static_cast(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 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 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 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(item->Classes & GetPlayerClassBit(search._class)) - }, - { - .should_check = search.race != 0xFFFFFFFF, - .condition = static_cast(item->Races & GetPlayerRaceBit(GetRaceIDFromPlayerRaceValue(search.race))) - }, - { - .should_check = search.augment != 0, - .condition = FindItemAugSlot() - }, - { - .should_check = search.slot != 0xFFFFFFFF, - .condition = static_cast(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; diff --git a/common/bazaar.h b/common/bazaar.h index fca2573af..bec1ae264 100644 --- a/common/bazaar.h +++ b/common/bazaar.h @@ -3,11 +3,13 @@ #include #include "shareddb.h" +#include "../../common/item_instance.h" class Bazaar { public: static std::vector - GetSearchResults(SharedDatabase &db, BazaarSearchCriteria_Struct search, unsigned int char_zone_id, int char_zone_instance_id); + GetSearchResults(Database &content_db, Database &db, BazaarSearchCriteria_Struct search, unsigned int char_zone_id, int char_zone_instance_id); + }; diff --git a/common/database/database_update_manifest.cpp b/common/database/database_update_manifest.cpp index 57819b700..e0c93e552 100644 --- a/common/database/database_update_manifest.cpp +++ b/common/database/database_update_manifest.cpp @@ -6333,6 +6333,52 @@ 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, diff --git a/common/repositories/items_repository.h b/common/repositories/items_repository.h index 919c11dd3..4e5a8868e 100644 --- a/common/repositories/items_repository.h +++ b/common/repositories/items_repository.h @@ -7,6 +7,14 @@ class ItemsRepository: public BaseItemsRepository { public: + struct Bazaar_Results { + uint32 item_id; + std::string name; + bool stackable; + uint32 icon; + uint32 stats; + }; + static std::vector GetItemIDsBySearchCriteria( Database& db, std::string search_string, @@ -37,6 +45,51 @@ public: return item_id_list; } + static std::unordered_map GetItemsForBazaarSearch( + Database& db, + const std::vector &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 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(atoi(row[0])) : 0; + br.name = row[1] ? row[1] : ""; + br.stackable = atoi(row[2]) ? true : false; + br.icon = row[3] ? static_cast(atoi(row[3])) : 0; + br.stats = row[4] ? static_cast(atoi(row[4])) : 0; + + item_list.emplace(br.item_id, br); + } + + return item_list; + } }; diff --git a/common/repositories/trader_repository.h b/common/repositories/trader_repository.h index a8e3c13c1..1892de6be 100644 --- a/common/repositories/trader_repository.h +++ b/common/repositories/trader_repository.h @@ -28,6 +28,11 @@ public: std::vector traders{}; }; + struct BazaarTraderSearch_Struct { + Trader trader; + std::string trader_name; + }; + struct WelcomeData_Struct { uint32 count_of_traders; uint32 count_of_items; @@ -265,6 +270,54 @@ public: return trader; } + + static std::vector GetBazaarTraderDetails( + Database &db, + std::string &search_criteria_trader + ) + { + std::vector 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(strtoul(row[1], nullptr, 10)) : 0; + e.trader.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.trader.aug_slot_1 = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.trader.aug_slot_2 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.trader.aug_slot_3 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.trader.aug_slot_4 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.trader.aug_slot_5 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.trader.aug_slot_6 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.trader.item_sn = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.trader.item_charges = row[10] ? static_cast(atoi(row[10])) : 0; + e.trader.item_cost = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; + e.trader.slot_id = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; + e.trader.char_entity_id = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; + e.trader.char_zone_id = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 0; + e.trader.char_zone_instance_id = row[15] ? static_cast(atoi(row[15])) : 0; + e.trader.active_transaction = row[16] ? static_cast(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 diff --git a/common/version.h b/common/version.h index a99b96575..a7cde55fa 100644 --- a/common/version.h +++ b/common/version.h @@ -42,7 +42,7 @@ * Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9293 +#define CURRENT_BINARY_DATABASE_VERSION 9295 #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9045 #endif diff --git a/zone/client.h b/zone/client.h index 49c691657..819560d1c 100644 --- a/zone/client.h +++ b/zone/client.h @@ -339,7 +339,6 @@ 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); diff --git a/zone/trading.cpp b/zone/trading.cpp index ce7ef317d..486c2f4ff 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -1800,7 +1800,13 @@ void Client::SendBarterWelcome() void Client::DoBazaarSearch(BazaarSearchCriteria_Struct search_criteria) { - auto results = Bazaar::GetSearchResults(database, search_criteria, GetZoneID(), GetInstanceID()); + std::vector results = Bazaar::GetSearchResults( + database, + content_db, + search_criteria, + GetZoneID(), + GetInstanceID() + ); if (results.empty()) { SendBazaarDone(GetID()); return; @@ -1822,317 +1828,6 @@ 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(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 trader_items,