mirror of
https://github.com/EQEmu/Server.git
synced 2026-05-26 07:52:27 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c966f26ac1 | |||
| fe66c24352 | |||
| bdcded7d45 | |||
| f939f25aa1 | |||
| 401f1038f3 | |||
| 95c6560e7c | |||
| 59f645b5c3 | |||
| bbfed8300c | |||
| 07c762068f | |||
| 6525051d2d | |||
| b7f94e8315 | |||
| d1f368ab7f | |||
| 1f3ac2dc4f | |||
| 7a226ca4ef |
@@ -1,3 +1,87 @@
|
||||
## [22.62.1] 1/27/2025
|
||||
|
||||
### Memory Leak
|
||||
|
||||
* Revert "Change raw pointer to unique_ptr to avoid potential leak in dbg stream" ([#4616](https://github.com/EQEmu/Server/pull/4616)) @Akkadius 2025-01-27
|
||||
|
||||
### Performance
|
||||
|
||||
* Re-use ClientUpdate packet memory ([#4619](https://github.com/EQEmu/Server/pull/4619)) @Akkadius 2025-01-27
|
||||
* Re-use OP_Animation packet ([#4621](https://github.com/EQEmu/Server/pull/4621)) @Akkadius 2025-01-27
|
||||
* Re-use OP_Damage packet memory ([#4625](https://github.com/EQEmu/Server/pull/4625)) @Akkadius 2025-01-27
|
||||
* Re-use OP_HPUpdate packet memory ([#4622](https://github.com/EQEmu/Server/pull/4622)) @Akkadius 2025-01-27
|
||||
* Re-use OP_PlayerStateAdd packet memory ([#4626](https://github.com/EQEmu/Server/pull/4626)) @Akkadius 2025-01-27
|
||||
* Re-use OP_SendFindableNPCs packet memory ([#4623](https://github.com/EQEmu/Server/pull/4623)) @Akkadius 2025-01-27
|
||||
|
||||
### Repop
|
||||
|
||||
* Make #repop instant ([#4620](https://github.com/EQEmu/Server/pull/4620)) @Akkadius 2025-01-27
|
||||
|
||||
## [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
|
||||
|
||||
+225
-286
@@ -6,7 +6,8 @@
|
||||
|
||||
std::vector<BazaarSearchResultsFromDB_Struct>
|
||||
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<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 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<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]));
|
||||
}
|
||||
}
|
||||
|
||||
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<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 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<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;
|
||||
|
||||
+3
-1
@@ -3,11 +3,13 @@
|
||||
|
||||
#include <vector>
|
||||
#include "shareddb.h"
|
||||
#include "../../common/item_instance.h"
|
||||
|
||||
class Bazaar {
|
||||
public:
|
||||
static std::vector<BazaarSearchResultsFromDB_Struct>
|
||||
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);
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -152,9 +152,6 @@ void EQ::Net::DaybreakConnectionManager::Process()
|
||||
connection->SendKeepAlive();
|
||||
}
|
||||
}
|
||||
|
||||
connection->Process();
|
||||
break;
|
||||
}
|
||||
case StatusDisconnecting:
|
||||
connection->Process();
|
||||
@@ -438,7 +435,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);
|
||||
@@ -495,10 +492,8 @@ 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()) {
|
||||
@@ -515,11 +510,7 @@ void EQ::Net::DaybreakConnection::ProcessQueue()
|
||||
|
||||
void EQ::Net::DaybreakConnection::RemoveFromQueue(int stream, uint16_t seq)
|
||||
{
|
||||
auto &s = m_streams[stream];
|
||||
if (s == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto s = &m_streams[stream];
|
||||
auto iter = s->packet_queue.find(seq);
|
||||
if (iter != s->packet_queue.end()) {
|
||||
auto packet = iter->second;
|
||||
@@ -530,11 +521,7 @@ void EQ::Net::DaybreakConnection::RemoveFromQueue(int stream, uint16_t seq)
|
||||
|
||||
void EQ::Net::DaybreakConnection::AddToQueue(int stream, uint16_t seq, const Packet &p)
|
||||
{
|
||||
auto &s = m_streams[stream];
|
||||
if (s == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto s = &m_streams[stream];
|
||||
auto iter = s->packet_queue.find(seq);
|
||||
if (iter == s->packet_queue.end()) {
|
||||
DynamicPacket *out = new DynamicPacket();
|
||||
@@ -689,10 +676,7 @@ 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];
|
||||
if (stream == nullptr) {
|
||||
stream.reset(new DaybreakReliableStream());
|
||||
}
|
||||
auto stream = &m_streams[stream_id];
|
||||
|
||||
auto order = CompareSequence(stream->sequence_in, sequence);
|
||||
if (order == SequenceFuture) {
|
||||
@@ -721,10 +705,7 @@ 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];
|
||||
if (stream == nullptr) {
|
||||
stream.reset(new DaybreakReliableStream());
|
||||
}
|
||||
auto stream = &m_streams[stream_id];
|
||||
|
||||
auto order = CompareSequence(stream->sequence_in, sequence);
|
||||
|
||||
@@ -1118,11 +1099,7 @@ void EQ::Net::DaybreakConnection::ProcessResend(int stream)
|
||||
|
||||
auto resends = 0;
|
||||
auto now = Clock::now();
|
||||
auto &s = m_streams[stream];
|
||||
if (s == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto s = &m_streams[stream];
|
||||
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) {
|
||||
@@ -1184,11 +1161,7 @@ void EQ::Net::DaybreakConnection::Ack(int stream, uint16_t seq)
|
||||
{
|
||||
|
||||
auto now = Clock::now();
|
||||
auto &s = m_streams[stream];
|
||||
if (s == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto s = &m_streams[stream];
|
||||
auto iter = s->sent_packets.begin();
|
||||
while (iter != s->sent_packets.end()) {
|
||||
auto order = CompareSequence(seq, iter->first);
|
||||
@@ -1212,11 +1185,7 @@ 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];
|
||||
if (s == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto s = &m_streams[stream];
|
||||
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();
|
||||
@@ -1432,11 +1401,7 @@ void EQ::Net::DaybreakConnection::InternalQueuePacket(Packet &p, int stream_id,
|
||||
return;
|
||||
}
|
||||
|
||||
auto &stream = m_streams[stream_id];
|
||||
if (stream == nullptr) {
|
||||
stream.reset(new DaybreakReliableStream());
|
||||
}
|
||||
|
||||
auto stream = &m_streams[stream_id];
|
||||
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,38 +133,6 @@ 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
|
||||
@@ -213,7 +181,36 @@ namespace EQ
|
||||
Timestamp m_close_time;
|
||||
double m_outgoing_budget;
|
||||
|
||||
std::unique_ptr<DaybreakReliableStream> m_streams[4];
|
||||
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, 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::weak_ptr<DaybreakConnection> m_self;
|
||||
|
||||
void Process();
|
||||
@@ -275,14 +272,6 @@ 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;
|
||||
@@ -306,9 +295,6 @@ 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,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<int32> GetItemIDsBySearchCriteria(
|
||||
Database& db,
|
||||
std::string search_string,
|
||||
@@ -37,6 +45,51 @@ 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,6 +28,11 @@ 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;
|
||||
@@ -265,6 +270,54 @@ 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.61.0-dev" // always append -dev to the current version for custom-builds
|
||||
#define CURRENT_VERSION "22.62.1-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 9293
|
||||
#define CURRENT_BINARY_DATABASE_VERSION 9295
|
||||
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9045
|
||||
|
||||
#endif
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "eqemu-server",
|
||||
"version": "22.61.0",
|
||||
"version": "22.62.1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/EQEmu/Server.git"
|
||||
|
||||
@@ -11,6 +11,6 @@ require (
|
||||
github.com/golang/protobuf v1.3.2 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/net v0.23.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
)
|
||||
|
||||
@@ -14,8 +14,8 @@ golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
||||
+20
-26
@@ -2559,16 +2559,12 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy
|
||||
const uint8 killed_level = GetLevel();
|
||||
|
||||
if (GetClass() == Class::LDoNTreasure) { // open chest
|
||||
auto outapp = new EQApplicationPacket(OP_Animation, sizeof(Animation_Struct));
|
||||
|
||||
auto a = (Animation_Struct*) outapp->pBuffer;
|
||||
|
||||
static EQApplicationPacket p(OP_Animation, sizeof(Animation_Struct));
|
||||
auto a = (Animation_Struct*) p.pBuffer;
|
||||
a->spawnid = GetID();
|
||||
a->action = 0x0F;
|
||||
a->speed = 10;
|
||||
|
||||
entity_list.QueueCloseClients(this, outapp);
|
||||
safe_delete(outapp);
|
||||
entity_list.QueueCloseClients(this, &p);
|
||||
}
|
||||
|
||||
auto app = new EQApplicationPacket(OP_Death, sizeof(Death_Struct));
|
||||
@@ -3415,15 +3411,15 @@ void Mob::DamageShield(Mob* attacker, bool spell_ds) {
|
||||
|
||||
attacker->Damage(this, -DS, spellid, EQ::skills::SkillAbjuration/*hackish*/, false);
|
||||
//we can assume there is a spell now
|
||||
auto outapp = new EQApplicationPacket(OP_Damage, sizeof(CombatDamage_Struct));
|
||||
CombatDamage_Struct* cds = (CombatDamage_Struct*)outapp->pBuffer;
|
||||
cds->target = attacker->GetID();
|
||||
cds->source = GetID();
|
||||
cds->type = spellbonuses.DamageShieldType;
|
||||
cds->spellid = 0x0;
|
||||
cds->damage = DS;
|
||||
entity_list.QueueCloseClients(this, outapp);
|
||||
safe_delete(outapp);
|
||||
|
||||
static EQApplicationPacket p(OP_Damage, sizeof(CombatDamage_Struct));
|
||||
auto b = (CombatDamage_Struct *) p.pBuffer;
|
||||
b->target = attacker->GetID();
|
||||
b->source = GetID();
|
||||
b->type = spellbonuses.DamageShieldType;
|
||||
b->spellid = 0x0;
|
||||
b->damage = DS;
|
||||
entity_list.QueueCloseClients(this, &p);
|
||||
}
|
||||
else if (DS > 0 && !spell_ds) {
|
||||
//we are healing the attacker...
|
||||
@@ -4540,8 +4536,8 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
|
||||
|
||||
//send damage packet...
|
||||
if (!iBuffTic) { //buff ticks do not send damage, instead they just call SendHPUpdate(), which is done above
|
||||
auto outapp = new EQApplicationPacket(OP_Damage, sizeof(CombatDamage_Struct));
|
||||
CombatDamage_Struct* a = (CombatDamage_Struct*)outapp->pBuffer;
|
||||
static EQApplicationPacket p(OP_Damage, sizeof(CombatDamage_Struct));
|
||||
auto a = (CombatDamage_Struct *) p.pBuffer;
|
||||
a->target = GetID();
|
||||
|
||||
if (!attacker) {
|
||||
@@ -4622,7 +4618,7 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
|
||||
if (!FromDamageShield) {
|
||||
entity_list.QueueCloseClients(
|
||||
attacker, /* Sender */
|
||||
outapp, /* packet */
|
||||
&p, /* packet */
|
||||
false, /* Skip Sender */
|
||||
((IsValidSpell(spell_id)) ? RuleI(Range, SpellMessages) : RuleI(Range, DamageMessages)),
|
||||
0, /* don't skip anyone on spell */
|
||||
@@ -4696,11 +4692,11 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
|
||||
filter = FilterMyMisses;
|
||||
|
||||
if (attacker->IsClient()) {
|
||||
attacker->CastToClient()->QueuePacket(outapp, true, CLIENT_CONNECTED, filter);
|
||||
attacker->CastToClient()->QueuePacket(&p, true, CLIENT_CONNECTED, filter);
|
||||
} else {
|
||||
entity_list.QueueCloseClients(
|
||||
attacker, /* Sender */
|
||||
outapp, /* packet */
|
||||
&p, /* packet */
|
||||
false, /* Skip Sender */
|
||||
(
|
||||
IsValidSpell(spell_id) ?
|
||||
@@ -4755,7 +4751,7 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
|
||||
a->type = DamageTypeSpell;
|
||||
entity_list.QueueCloseClients(
|
||||
this, /* Sender */
|
||||
outapp, /* packet */
|
||||
&p, /* packet */
|
||||
false, /* Skip Sender */
|
||||
range, /* distance packet travels at the speed of sound */
|
||||
0, /* don't skip anyone on spell */
|
||||
@@ -4766,7 +4762,7 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
|
||||
else {
|
||||
//I dont think any filters apply to damage affecting us
|
||||
if (IsClient()) {
|
||||
CastToClient()->QueuePacket(outapp);
|
||||
CastToClient()->QueuePacket(&p);
|
||||
}
|
||||
|
||||
// Send normal message to observers
|
||||
@@ -4776,7 +4772,7 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
|
||||
if (!owner || (owner && !owner->IsClient())) {
|
||||
entity_list.QueueCloseClients(
|
||||
this, /* Sender */
|
||||
outapp, /* packet */
|
||||
&p, /* packet */
|
||||
true, /* Skip Sender */
|
||||
range, /* distance packet travels at the speed of sound */
|
||||
(IsValidSpell(spell_id) && skill_used != EQ::skills::SkillTigerClaw) ? 0 : skip,
|
||||
@@ -4786,8 +4782,6 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
safe_delete(outapp);
|
||||
}
|
||||
else {
|
||||
//else, it is a buff tic...
|
||||
|
||||
+5
-4
@@ -735,21 +735,22 @@ bool Aura::Process()
|
||||
|
||||
if (movement_type == AuraMovement::Follow && GetPosition() != owner->GetPosition() && movement_timer.Check()) {
|
||||
m_Position = owner->GetPosition();
|
||||
auto app = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
|
||||
auto spu = (PlayerPositionUpdateServer_Struct *) app->pBuffer;
|
||||
|
||||
static EQApplicationPacket packet(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
|
||||
auto spu = (PlayerPositionUpdateServer_Struct *) packet.pBuffer;
|
||||
|
||||
MakeSpawnUpdate(spu);
|
||||
auto it = spawned_for.begin();
|
||||
while (it != spawned_for.end()) {
|
||||
auto client = entity_list.GetClientByID(*it);
|
||||
if (client) {
|
||||
client->QueuePacket(app);
|
||||
client->QueuePacket(&packet);
|
||||
++it;
|
||||
}
|
||||
else {
|
||||
it = spawned_for.erase(it);
|
||||
}
|
||||
}
|
||||
safe_delete(app);
|
||||
}
|
||||
// TODO: waypoints?
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
+12
-16
@@ -4842,11 +4842,10 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) {
|
||||
auto boat_delta = glm::vec4(ppu->delta_x, ppu->delta_y, ppu->delta_z, EQ10toFloat(ppu->delta_heading));
|
||||
cmob->SetDelta(boat_delta);
|
||||
|
||||
auto outapp = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
|
||||
PlayerPositionUpdateServer_Struct *ppus = (PlayerPositionUpdateServer_Struct *) outapp->pBuffer;
|
||||
static EQApplicationPacket outapp(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
|
||||
auto *ppus = (PlayerPositionUpdateServer_Struct *) outapp.pBuffer;
|
||||
cmob->MakeSpawnUpdate(ppus);
|
||||
entity_list.QueueCloseClients(cmob, outapp, true, 300, this, false);
|
||||
safe_delete(outapp);
|
||||
entity_list.QueueCloseClients(cmob, &outapp, true, 300, this, false);
|
||||
|
||||
/* Update the boat's position on the server, without sending an update */
|
||||
cmob->GMMove(ppu->x_pos, ppu->y_pos, ppu->z_pos, EQ12toFloat(ppu->heading));
|
||||
@@ -5020,15 +5019,15 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) {
|
||||
m_Position.w = new_heading;
|
||||
|
||||
/* Broadcast update to other clients */
|
||||
auto outapp = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
|
||||
PlayerPositionUpdateServer_Struct *position_update = (PlayerPositionUpdateServer_Struct *) outapp->pBuffer;
|
||||
static EQApplicationPacket outapp(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
|
||||
PlayerPositionUpdateServer_Struct *position_update = (PlayerPositionUpdateServer_Struct *) outapp.pBuffer;
|
||||
|
||||
MakeSpawnUpdate(position_update);
|
||||
|
||||
if (gm_hide_me) {
|
||||
entity_list.QueueClientsStatus(this, outapp, true, Admin(), AccountStatus::Max);
|
||||
entity_list.QueueClientsStatus(this, &outapp, true, Admin(), AccountStatus::Max);
|
||||
} else {
|
||||
entity_list.QueueCloseClients(this, outapp, true, RuleI(Range, ClientPositionUpdates), nullptr, true);
|
||||
entity_list.QueueCloseClients(this, &outapp, true, RuleI(Range, ClientPositionUpdates), nullptr, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -5037,12 +5036,10 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) {
|
||||
Raid *raid = GetRaid();
|
||||
|
||||
if (raid) {
|
||||
raid->QueueClients(this, outapp, true, true, (RuleI(Range, ClientPositionUpdates) * -1));
|
||||
raid->QueueClients(this, &outapp, true, true, (RuleI(Range, ClientPositionUpdates) * -1));
|
||||
} else if (group) {
|
||||
group->QueueClients(this, outapp, true, true, (RuleI(Range, ClientPositionUpdates) * -1));
|
||||
group->QueueClients(this, &outapp, true, true, (RuleI(Range, ClientPositionUpdates) * -1));
|
||||
}
|
||||
|
||||
safe_delete(outapp);
|
||||
}
|
||||
|
||||
if (zone->watermap) {
|
||||
@@ -16747,13 +16744,12 @@ bool Client::CanTradeFVNoDropItem()
|
||||
|
||||
void Client::SendMobPositions()
|
||||
{
|
||||
auto p = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
|
||||
auto *s = (PlayerPositionUpdateServer_Struct *) p->pBuffer;
|
||||
static EQApplicationPacket p(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
|
||||
auto *s = (PlayerPositionUpdateServer_Struct *) p.pBuffer;
|
||||
for (auto &m: entity_list.GetMobList()) {
|
||||
m.second->MakeSpawnUpdate(s);
|
||||
QueuePacket(p, false);
|
||||
QueuePacket(&p, false);
|
||||
}
|
||||
safe_delete(p);
|
||||
}
|
||||
|
||||
struct RecordKillCheck {
|
||||
|
||||
+116
-27
@@ -1,12 +1,15 @@
|
||||
#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 = {};
|
||||
|
||||
@@ -25,8 +28,13 @@ 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
|
||||
@@ -60,9 +68,48 @@ 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) {
|
||||
@@ -76,7 +123,6 @@ 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
|
||||
@@ -92,12 +138,56 @@ 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,
|
||||
@@ -109,9 +199,9 @@ DataBucketsRepository::DataBuckets DataBucket::GetData(const DataBucketKey &k, b
|
||||
|
||||
bool can_cache = CanCache(k);
|
||||
|
||||
// check the cache first if we can cache
|
||||
// Attempt to retrieve the value from the 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_);
|
||||
@@ -119,37 +209,32 @@ DataBucketsRepository::DataBuckets DataBucket::GetData(const DataBucketKey &k, b
|
||||
return DataBucketsRepository::NewEntity();
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
if (is_nested_key) {
|
||||
return ExtractNestedValue(e, k_.key);
|
||||
}
|
||||
|
||||
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()) {
|
||||
|
||||
// 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) {
|
||||
// Handle cache misses
|
||||
if (!ignore_misses_cache && can_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,
|
||||
@@ -175,22 +260,21 @@ DataBucketsRepository::DataBuckets DataBucket::GetData(const DataBucketKey &k, b
|
||||
);
|
||||
}
|
||||
|
||||
return {};
|
||||
return DataBucketsRepository::NewEntity();
|
||||
}
|
||||
|
||||
auto bucket = r.front();
|
||||
|
||||
// if the entry has expired, delete it
|
||||
if (bucket.expires > 0 && bucket.expires < (long long) std::time(nullptr)) {
|
||||
// If the entry has expired, delete it
|
||||
if (bucket.expires > 0 && bucket.expires < static_cast<long long>(std::time(nullptr))) {
|
||||
DeleteData(k);
|
||||
return {};
|
||||
return DataBucketsRepository::NewEntity();
|
||||
}
|
||||
|
||||
// add to cache if it doesn't exist
|
||||
// Add the value to the cache if it doesn't exist
|
||||
if (can_cache) {
|
||||
bool has_cache = false;
|
||||
|
||||
for (auto &e: g_data_bucket_cache) {
|
||||
for (const auto &e : g_data_bucket_cache) {
|
||||
if (e.id == bucket.id) {
|
||||
has_cache = true;
|
||||
break;
|
||||
@@ -202,6 +286,11 @@ DataBucketsRepository::DataBuckets DataBucket::GetData(const DataBucketKey &k, b
|
||||
}
|
||||
}
|
||||
|
||||
// Handle nested key extraction
|
||||
if (is_nested_key) {
|
||||
return ExtractNestedValue(bucket, k_.key);
|
||||
}
|
||||
|
||||
return bucket;
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -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,6 +63,8 @@ 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
|
||||
|
||||
+28
-34
@@ -5319,15 +5319,12 @@ void EntityList::SendFindableNPCList(Client *c)
|
||||
return;
|
||||
}
|
||||
|
||||
auto outapp = new EQApplicationPacket(OP_SendFindableNPCs, sizeof(FindableNPC_Struct));
|
||||
|
||||
FindableNPC_Struct *fnpcs = (FindableNPC_Struct *)outapp->pBuffer;
|
||||
|
||||
fnpcs->Unknown109 = 0x16;
|
||||
fnpcs->Unknown110 = 0x06;
|
||||
fnpcs->Unknown111 = 0x24;
|
||||
|
||||
fnpcs->Action = 0;
|
||||
static EQApplicationPacket p(OP_SendFindableNPCs, sizeof(FindableNPC_Struct));
|
||||
auto b = (FindableNPC_Struct*) p.pBuffer;
|
||||
b->Unknown109 = 0x16;
|
||||
b->Unknown110 = 0x06;
|
||||
b->Unknown111 = 0x24;
|
||||
b->Action = 0;
|
||||
|
||||
auto it = npc_list.begin();
|
||||
while (it != npc_list.end()) {
|
||||
@@ -5335,50 +5332,47 @@ void EntityList::SendFindableNPCList(Client *c)
|
||||
NPC *n = it->second;
|
||||
|
||||
if (n->IsFindable()) {
|
||||
fnpcs->EntityID = n->GetID();
|
||||
strn0cpy(fnpcs->Name, n->GetCleanName(), sizeof(fnpcs->Name));
|
||||
strn0cpy(fnpcs->LastName, n->GetLastName(), sizeof(fnpcs->LastName));
|
||||
fnpcs->Race = n->GetRace();
|
||||
fnpcs->Class = n->GetClass();
|
||||
b->EntityID = n->GetID();
|
||||
strn0cpy(b->Name, n->GetCleanName(), sizeof(b->Name));
|
||||
strn0cpy(b->LastName, n->GetLastName(), sizeof(b->LastName));
|
||||
b->Race = n->GetRace();
|
||||
b->Class = n->GetClass();
|
||||
|
||||
c->QueuePacket(outapp);
|
||||
c->QueuePacket(&p);
|
||||
}
|
||||
}
|
||||
++it;
|
||||
}
|
||||
safe_delete(outapp);
|
||||
}
|
||||
|
||||
void EntityList::UpdateFindableNPCState(NPC *n, bool Remove)
|
||||
{
|
||||
if (!n || !n->IsFindable())
|
||||
if (!n || !n->IsFindable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto outapp = new EQApplicationPacket(OP_SendFindableNPCs, sizeof(FindableNPC_Struct));
|
||||
static EQApplicationPacket p(OP_SendFindableNPCs, sizeof(FindableNPC_Struct));
|
||||
auto b = (FindableNPC_Struct *) p.pBuffer;
|
||||
b->Unknown109 = 0x16;
|
||||
b->Unknown110 = 0x06;
|
||||
b->Unknown111 = 0x24;
|
||||
|
||||
FindableNPC_Struct *fnpcs = (FindableNPC_Struct *)outapp->pBuffer;
|
||||
|
||||
fnpcs->Unknown109 = 0x16;
|
||||
fnpcs->Unknown110 = 0x06;
|
||||
fnpcs->Unknown111 = 0x24;
|
||||
|
||||
fnpcs->Action = Remove ? 1: 0;
|
||||
fnpcs->EntityID = n->GetID();
|
||||
strn0cpy(fnpcs->Name, n->GetCleanName(), sizeof(fnpcs->Name));
|
||||
strn0cpy(fnpcs->LastName, n->GetLastName(), sizeof(fnpcs->LastName));
|
||||
fnpcs->Race = n->GetRace();
|
||||
fnpcs->Class = n->GetClass();
|
||||
b->Action = Remove ? 1 : 0;
|
||||
b->EntityID = n->GetID();
|
||||
strn0cpy(b->Name, n->GetCleanName(), sizeof(b->Name));
|
||||
strn0cpy(b->LastName, n->GetLastName(), sizeof(b->LastName));
|
||||
b->Race = n->GetRace();
|
||||
b->Class = n->GetClass();
|
||||
|
||||
auto it = client_list.begin();
|
||||
while (it != client_list.end()) {
|
||||
Client *c = it->second;
|
||||
if (c && (c->ClientVersion() >= EQ::versions::ClientVersion::SoD))
|
||||
c->QueuePacket(outapp);
|
||||
if (c && (c->ClientVersion() >= EQ::versions::ClientVersion::SoD)) {
|
||||
c->QueuePacket(&p);
|
||||
}
|
||||
|
||||
++it;
|
||||
}
|
||||
|
||||
safe_delete(outapp);
|
||||
}
|
||||
|
||||
void EntityList::HideCorpses(Client *c, uint8 CurrentMode, uint8 NewMode)
|
||||
|
||||
+22
-31
@@ -125,7 +125,7 @@ Mob::Mob(
|
||||
tmHidden(-1),
|
||||
mitigation_ac(0),
|
||||
m_specialattacks(eSpecialAttacks::None),
|
||||
attack_anim_timer(500),
|
||||
attack_anim_timer(100),
|
||||
position_update_melee_push_timer(500),
|
||||
hate_list_cleanup_timer(6000),
|
||||
m_scan_close_mobs_timer(6000),
|
||||
@@ -1522,16 +1522,12 @@ void Mob::SendHPUpdate(bool force_update_all)
|
||||
last_hp
|
||||
);
|
||||
|
||||
auto client_packet = new EQApplicationPacket(OP_HPUpdate, sizeof(SpawnHPUpdate_Struct));
|
||||
auto *hp_packet_client = (SpawnHPUpdate_Struct *) client_packet->pBuffer;
|
||||
|
||||
hp_packet_client->cur_hp = static_cast<uint32>(CastToClient()->GetHP() - itembonuses.HP);
|
||||
hp_packet_client->spawn_id = GetID();
|
||||
hp_packet_client->max_hp = CastToClient()->GetMaxHP() - itembonuses.HP;
|
||||
|
||||
CastToClient()->QueuePacket(client_packet);
|
||||
|
||||
safe_delete(client_packet);
|
||||
static EQApplicationPacket p(OP_HPUpdate, sizeof(SpawnHPUpdate_Struct));
|
||||
auto b = (SpawnHPUpdate_Struct*) p.pBuffer;
|
||||
b->cur_hp = static_cast<uint32>(CastToClient()->GetHP() - itembonuses.HP);
|
||||
b->spawn_id = GetID();
|
||||
b->max_hp = CastToClient()->GetMaxHP() - itembonuses.HP;
|
||||
CastToClient()->QueuePacket(&p);
|
||||
|
||||
ResetHPUpdateTimer();
|
||||
|
||||
@@ -3540,24 +3536,21 @@ void Mob::DoAnim(const int animation_id, int animation_speed, bool ackreq, eqFil
|
||||
return;
|
||||
}
|
||||
|
||||
auto outapp = new EQApplicationPacket(OP_Animation, sizeof(Animation_Struct));
|
||||
auto *a = (Animation_Struct *) outapp->pBuffer;
|
||||
|
||||
static EQApplicationPacket p(OP_Animation, sizeof(Animation_Struct));
|
||||
auto a = (Animation_Struct*) p.pBuffer;
|
||||
a->spawnid = GetID();
|
||||
a->action = animation_id;
|
||||
a->speed = animation_speed ? animation_speed : 10;
|
||||
|
||||
entity_list.QueueCloseClients(
|
||||
this, /* Sender */
|
||||
outapp, /* Packet */
|
||||
&p, /* Packet */
|
||||
false, /* Ignore Sender */
|
||||
RuleI(Range, Anims),
|
||||
0, /* Skip this mob */
|
||||
ackreq, /* Packet ACK */
|
||||
filter /* eqFilterType filter */
|
||||
);
|
||||
|
||||
safe_delete(outapp);
|
||||
}
|
||||
|
||||
void Mob::ShowBuffs(Client* c) {
|
||||
@@ -7717,28 +7710,26 @@ bool Mob::CanRaceEquipItem(uint32 item_id)
|
||||
|
||||
void Mob::SendAddPlayerState(PlayerState new_state)
|
||||
{
|
||||
auto app = new EQApplicationPacket(OP_PlayerStateAdd, sizeof(PlayerState_Struct));
|
||||
auto ps = (PlayerState_Struct *)app->pBuffer;
|
||||
static EQApplicationPacket p(OP_PlayerStateAdd, sizeof(PlayerState_Struct));
|
||||
auto b = (PlayerState_Struct *) p.pBuffer;
|
||||
|
||||
ps->spawn_id = GetID();
|
||||
ps->state = static_cast<uint32>(new_state);
|
||||
b->spawn_id = GetID();
|
||||
b->state = static_cast<uint32>(new_state);
|
||||
|
||||
AddPlayerState(ps->state);
|
||||
entity_list.QueueClients(nullptr, app);
|
||||
safe_delete(app);
|
||||
AddPlayerState(b->state);
|
||||
entity_list.QueueClients(nullptr, &p);
|
||||
}
|
||||
|
||||
void Mob::SendRemovePlayerState(PlayerState old_state)
|
||||
{
|
||||
auto app = new EQApplicationPacket(OP_PlayerStateRemove, sizeof(PlayerState_Struct));
|
||||
auto ps = (PlayerState_Struct *)app->pBuffer;
|
||||
static EQApplicationPacket p(OP_PlayerStateRemove, sizeof(PlayerState_Struct));
|
||||
auto b = (PlayerState_Struct *) p.pBuffer;
|
||||
|
||||
ps->spawn_id = GetID();
|
||||
ps->state = static_cast<uint32>(old_state);
|
||||
b->spawn_id = GetID();
|
||||
b->state = static_cast<uint32>(old_state);
|
||||
|
||||
RemovePlayerState(ps->state);
|
||||
entity_list.QueueClients(nullptr, app);
|
||||
safe_delete(app);
|
||||
RemovePlayerState(b->state);
|
||||
entity_list.QueueClients(nullptr, &p);
|
||||
}
|
||||
|
||||
int32 Mob::GetMeleeMitigation() {
|
||||
|
||||
@@ -824,8 +824,8 @@ void MobMovementManager::SendCommandToClients(
|
||||
return;
|
||||
}
|
||||
|
||||
EQApplicationPacket outapp(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
|
||||
auto *spu = (PlayerPositionUpdateServer_Struct *) outapp.pBuffer;
|
||||
static EQApplicationPacket p(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
|
||||
auto *spu = (PlayerPositionUpdateServer_Struct *) p.pBuffer;
|
||||
|
||||
FillCommandStruct(spu, mob, delta_x, delta_y, delta_z, delta_heading, anim);
|
||||
|
||||
@@ -862,7 +862,7 @@ void MobMovementManager::SendCommandToClients(
|
||||
}
|
||||
}
|
||||
|
||||
c->QueuePacket(&outapp, false);
|
||||
c->QueuePacket(&p, false);
|
||||
c->m_last_seen_mob_position[mob->GetID()] = mob->GetPosition();
|
||||
}
|
||||
}
|
||||
@@ -924,7 +924,7 @@ void MobMovementManager::SendCommandToClients(
|
||||
}
|
||||
}
|
||||
|
||||
c->QueuePacket(&outapp, false);
|
||||
c->QueuePacket(&p, false);
|
||||
c->m_last_seen_mob_position[mob->GetID()] = mob->GetPosition();
|
||||
}
|
||||
}
|
||||
|
||||
+3
-4
@@ -3824,13 +3824,12 @@ int NPC::GetRolledItemCount(uint32 item_id)
|
||||
|
||||
void NPC::SendPositionToClients()
|
||||
{
|
||||
auto p = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
|
||||
auto *s = (PlayerPositionUpdateServer_Struct *) p->pBuffer;
|
||||
static EQApplicationPacket p(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
|
||||
auto *s = (PlayerPositionUpdateServer_Struct *) p.pBuffer;
|
||||
for (auto &c: entity_list.GetClientList()) {
|
||||
MakeSpawnUpdate(s);
|
||||
c.second->QueuePacket(p, false);
|
||||
c.second->QueuePacket(&p, false);
|
||||
}
|
||||
safe_delete(p);
|
||||
}
|
||||
|
||||
void NPC::HandleRoambox()
|
||||
|
||||
@@ -533,6 +533,7 @@ bool ZoneDatabase::PopulateZoneSpawnList(uint32 zoneid, LinkedList<Spawn2*> &spa
|
||||
);
|
||||
|
||||
spawn2_list.Insert(new_spawn);
|
||||
new_spawn->Process();
|
||||
}
|
||||
|
||||
LogInfo("Loaded [{}] spawn2 entries", Strings::Commify(l.size()));
|
||||
|
||||
+15
-18
@@ -953,9 +953,9 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
|
||||
auto action_packet =
|
||||
new EQApplicationPacket(OP_Action, sizeof(Action_Struct));
|
||||
Action_Struct* action = (Action_Struct*) action_packet->pBuffer;
|
||||
auto message_packet =
|
||||
new EQApplicationPacket(OP_Damage, sizeof(CombatDamage_Struct));
|
||||
CombatDamage_Struct *cd = (CombatDamage_Struct *)message_packet->pBuffer;
|
||||
|
||||
static EQApplicationPacket p(OP_Damage, sizeof(CombatDamage_Struct));
|
||||
auto cd = (CombatDamage_Struct *) p.pBuffer;
|
||||
|
||||
action->target = GetID();
|
||||
action->source = caster ? caster->GetID() : GetID();
|
||||
@@ -978,16 +978,15 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
|
||||
caster->CastToClient()->QueuePacket(action_packet);
|
||||
}
|
||||
|
||||
CastToClient()->QueuePacket(message_packet);
|
||||
CastToClient()->QueuePacket(&p);
|
||||
|
||||
if (caster && caster->IsClient() && caster != this) {
|
||||
caster->CastToClient()->QueuePacket(message_packet);
|
||||
caster->CastToClient()->QueuePacket(&p);
|
||||
}
|
||||
|
||||
CastToClient()->SetBindPoint(spells[spell_id].base_value[i] - 1);
|
||||
Save();
|
||||
safe_delete(action_packet);
|
||||
safe_delete(message_packet);
|
||||
} else {
|
||||
if (!zone->CanBind()) {
|
||||
MessageString(Chat::SpellFailure, CANNOT_BIND);
|
||||
@@ -1002,9 +1001,9 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
|
||||
auto action_packet = new EQApplicationPacket(
|
||||
OP_Action, sizeof(Action_Struct));
|
||||
Action_Struct* action = (Action_Struct*) action_packet->pBuffer;
|
||||
auto message_packet = new EQApplicationPacket(
|
||||
OP_Damage, sizeof(CombatDamage_Struct));
|
||||
CombatDamage_Struct *cd = (CombatDamage_Struct *)message_packet->pBuffer;
|
||||
|
||||
static EQApplicationPacket p(OP_Damage, sizeof(CombatDamage_Struct));
|
||||
auto cd = (CombatDamage_Struct *) p.pBuffer;
|
||||
|
||||
action->target = GetID();
|
||||
action->source = caster ? caster->GetID() : GetID();
|
||||
@@ -1027,24 +1026,23 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
|
||||
caster->CastToClient()->QueuePacket(action_packet);
|
||||
}
|
||||
|
||||
CastToClient()->QueuePacket(message_packet);
|
||||
CastToClient()->QueuePacket(&p);
|
||||
|
||||
if (caster->IsClient() && caster != this) {
|
||||
caster->CastToClient()->QueuePacket(message_packet);
|
||||
caster->CastToClient()->QueuePacket(&p);
|
||||
}
|
||||
|
||||
CastToClient()->SetBindPoint(spells[spell_id].base_value[i] - 1);
|
||||
Save();
|
||||
safe_delete(action_packet);
|
||||
safe_delete(message_packet);
|
||||
}
|
||||
} else {
|
||||
auto action_packet =
|
||||
new EQApplicationPacket(OP_Action, sizeof(Action_Struct));
|
||||
Action_Struct* action = (Action_Struct*) action_packet->pBuffer;
|
||||
auto message_packet = new EQApplicationPacket(
|
||||
OP_Damage, sizeof(CombatDamage_Struct));
|
||||
CombatDamage_Struct *cd = (CombatDamage_Struct *)message_packet->pBuffer;
|
||||
|
||||
static EQApplicationPacket p(OP_Damage, sizeof(CombatDamage_Struct));
|
||||
auto cd = (CombatDamage_Struct *) p.pBuffer;
|
||||
|
||||
action->target = GetID();
|
||||
action->source = caster ? caster->GetID() : GetID();
|
||||
@@ -1067,16 +1065,15 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
|
||||
caster->CastToClient()->QueuePacket(action_packet);
|
||||
}
|
||||
|
||||
CastToClient()->QueuePacket(message_packet);
|
||||
CastToClient()->QueuePacket(&p);
|
||||
|
||||
if (caster->IsClient() && caster != this) {
|
||||
caster->CastToClient()->QueuePacket(message_packet);
|
||||
caster->CastToClient()->QueuePacket(&p);
|
||||
}
|
||||
|
||||
CastToClient()->SetBindPoint(spells[spell_id].base_value[i] - 1);
|
||||
Save();
|
||||
safe_delete(action_packet);
|
||||
safe_delete(message_packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-4
@@ -4564,8 +4564,8 @@ bool Mob::SpellOnTarget(
|
||||
}
|
||||
}
|
||||
|
||||
message_packet = new EQApplicationPacket(OP_Damage, sizeof(CombatDamage_Struct));
|
||||
CombatDamage_Struct *cd = (CombatDamage_Struct *)message_packet->pBuffer;
|
||||
static EQApplicationPacket p(OP_Damage, sizeof(CombatDamage_Struct));
|
||||
auto cd = (CombatDamage_Struct *) p.pBuffer;
|
||||
cd->target = action->target;
|
||||
cd->source = action->source;
|
||||
cd->type = action->type;
|
||||
@@ -4583,7 +4583,7 @@ bool Mob::SpellOnTarget(
|
||||
) {
|
||||
entity_list.QueueCloseClients(
|
||||
spelltar, /* Sender */
|
||||
message_packet, /* Packet */
|
||||
&p, /* Packet */
|
||||
false, /* Ignore Sender */
|
||||
RuleI(Range, SpellMessages),
|
||||
0, /* Skip this mob */
|
||||
@@ -4593,7 +4593,6 @@ bool Mob::SpellOnTarget(
|
||||
}
|
||||
|
||||
safe_delete(action_packet);
|
||||
safe_delete(message_packet);
|
||||
|
||||
/*
|
||||
Bug: When an HP buff with a heal effect is applied for first time, the heal portion of the effect heals the client and
|
||||
|
||||
+7
-312
@@ -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<BazaarSearchResultsFromDB_Struct> 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<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