#include "bazaar.h" #include "../../common/item_instance.h" #include "repositories/trader_repository.h" #include std::vector Bazaar::GetSearchResults( SharedDatabase &db, BazaarSearchCriteria_Struct search, uint32 char_zone_id ) { LogTrading( "Searching for items with search criteria - item_name [{}] min_cost [{}] max_cost [{}] min_level [{}] " "max_level [{}] max_results [{}] prestige [{}] augment [{}] trader_entity_id [{}] trader_id [{}] " "search_scope [{}] char_zone_id [{}]", search.item_name, search.min_cost, search.max_cost, search.min_level, search.max_level, search.max_results, search.prestige, search.augment, search.trader_entity_id, search.trader_id, search.search_scope, char_zone_id ); std::string search_criteria_trader("TRUE "); if (search.search_scope == NonRoFBazaarSearchScope) { search_criteria_trader.append( fmt::format( " AND trader.char_entity_id = {} AND trader.char_zone_id = {}", search.trader_entity_id, Zones::BAZAAR ) ); } else if (search.search_scope == Local_Scope) { search_criteria_trader.append(fmt::format(" AND trader.char_zone_id = {}", char_zone_id)); } else if (search.trader_id > 0) { search_criteria_trader.append(fmt::format(" AND trader.char_id = {}", search.trader_id)); } if (search.min_cost != 0) { search_criteria_trader.append(fmt::format(" AND trader.item_cost >= {}", search.min_cost * 1000)); } if (search.max_cost != 0) { 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)); // } 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 " "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() ); std::vector all_entries; auto results = db.QueryDatabase(query); if (!results.Success()) { return all_entries; } struct ItemSearchType { EQ::item::ItemType type; bool condition; }; 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; } 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_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}, {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); all_entries.push_back(r); } if (all_entries.size() > search.max_results) { all_entries.resize(search.max_results); } LogTrading("Returning [{}] items from search results", all_entries.size()); return all_entries; }