Skip to content

[Feature] Bazaar Unique Item ID and Offline Trading #4878

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 46 commits into
base: master
Choose a base branch
from

Conversation

neckkola
Copy link
Contributor

@neckkola neckkola commented May 4, 2025

Description

This update allows for proper unique item ids within the bazaar, utilizing the RoF2 model of a 17 character string. It is generated using a random hash generator which is unique across zones. It also allows for easier determination of multi-instance bazaars to ensure 100% accuracy in unique ids.

Further, it removes the workaround for hot bars. Previously, there was code upon character login to ensure that item ids (which were simple uint32s, reused across zones) were not reused to ensure that hot bar items would not 'change'. This code was expensive now that there are large bags. This is removed.

On first boot of world, a conversion routine runs to create unique ids for all character inventory items. This is done once, though I left the routine in zone inventory load as a fail safe.

Trader, Buyer, Barter, Parcels, Evolving Items have been updated to reflect the new unique item id.

This feature also allows off line trading for traders/buyers. Using the 'offline mode' will remove the active player, and place them in offline mode. /who and /who all have been updated along with the Guild Status window. Therefore, there are login server changes to manage the offline status, displaying the appropriate messaging to clients when they login, should they have an offline character active.

I have also rewritten most of the bazaar code to be more efficient and better manage resources. I have also added more graceful transaction rollbacks for when a bazaar transaction fails.

This is not ready for merge. I would like to ask if there are any interested parties in testing? I have been trough many, though would always appreciate another set of eyes, and attempts to break things. :-) Testing with the alternate bazaar functionality (multi-instance) would also be appreciated.

It does not have 'Direct to Inventory' functionality (which the previous version also did not have) though THJ has implemented so I will see if they will allow me to add to this in a future version.

Thank you!

Type of change

  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

Testing

I have tested locally, and the offline functionality has been tested in a prod environment (not with the new unique item id) for several weeks. Is ok at present.

Clients tested:
RoF2

Checklist

  • I have tested my changes
  • I have performed a self-review of my code. Ensuring variables, functions and methods are named in a human-readable way, comments are added only where naming of variables, functions and methods can't give enough context.
  • [WILL DO AFTER TESTING] I have made corresponding changes to the documentation (if applicable, if not delete this line)
  • I own the changes of my code and take responsibility for the potential issues that occur
  • If my changes make database schema changes, I have tested the changes on a local database (attach image). Updated version.h CURRENT_BINARY_DATABASE_VERSION to the new version. (Delete this if not applicable)

@neckkola neckkola force-pushed the AddUniqueItemSerialNumberWithOfflineTrading branch 2 times, most recently from 6b09e22 to 7140751 Compare May 4, 2025 22:54
@Valorith
Copy link
Contributor

Valorith commented May 5, 2025

You sir, are amazing!

@fryguy503
Copy link
Contributor

I do not believe the offline trader portion will fully function with the current public login server however, it will require a private login server or public server revamp. Neckkola can chime in on that.

@catapultam-habeo
Copy link
Contributor

It does not have 'Direct to Inventory' functionality (which the previous version also did not have) though THJ has implemented so I will see if they will allow me to add to this in a future version.

You are more than welcome to take what I did there, though it is intensely rudimentary.

@neckkola neckkola force-pushed the AddUniqueItemSerialNumberWithOfflineTrading branch from a1a046a to 7bf464b Compare May 17, 2025 00:09
@fryguy503
Copy link
Contributor

client_packet.cpp:834 will need changed:

From:

TraderRepository::DeleteWhere(database, fmt::format("`char_id` = '{}'", CharacterID()));

To:

TraderRepository::DeleteWhere(database, fmt::format("`character_id` = '{}'", CharacterID()));

@neckkola neckkola force-pushed the AddUniqueItemSerialNumberWithOfflineTrading branch from 522484f to 7d58dc9 Compare June 7, 2025 00:01
@neckkola
Copy link
Contributor Author

neckkola commented Jun 7, 2025

@Akkadius, or others with knowledge. Looking for some help on the build failure. Looks like the tests.exe is failing in IPCMutexTest. Any assistance or point me in the right direction would be appreciated. I compiled locally and tests.exe runs correctly. Thanks!

@fryguy503
Copy link
Contributor

I know it doesn't solve your issue, but just general feedback, been running this PR for a while now and no major issues.

std::vector<std::string> trader_items_ids{};
std::unordered_set<std::string> trader_items_ids{};

// auto const trader_results = TraderRepository::GetBazaarTraderDetails(db, search_criteria_trader, search.max_results);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove this comment ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

if (trader_results.empty()) {
LogTradingDetail("Bazaar - No traders found in bazaar search.");
return all_entries;
}

for (auto const &i: trader_results) {
trader_items_ids.push_back(std::to_string(i.trader.item_id));
trader_items_ids.emplace(std::to_string(i.trader.item_id));
//trader_items_ids.push_back(std::to_string(i.trader.item_id));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove this comment ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Comment on lines 336 to 365
// for (auto const& t:item_results) {
// BazaarSearchResultsFromDB_Struct r{};
// r.count = 1;
// r.trader_id = t.trader.character_id;
// r.item_unique_id = t.trader.item_unique_id;
// r.cost = t.trader.item_cost;
// r.slot_id = t.trader.slot_id;
// r.charges = t.trader.item_charges;
// r.stackable = t.stackable;
// r.icon_id = t.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.item_name = fmt::format("{:.63}\0", t.name);
// r.trader_name = fmt::format("{:.63}\0", t.trader_name);
// r.item_stat = t.stats;
//
// if (RuleB(Bazaar, UseAlternateBazaarSearch)) {
// if (convert ||
// char_zone_id != Zones::BAZAAR ||
// (char_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;
// }
// }
//
// all_entries.push_back(r);
// }

// if (all_entries.size() > search.max_results) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove if going away

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

@@ -439,7 +439,7 @@ namespace RoF2
break;
}
default: {
LogTradingDetail("Unhandled action <red>[{}]", sub_action);
//LogTradingDetail("Unhandled action <red>[{}]", sub_action);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove if not keeping

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

@@ -533,7 +533,7 @@ namespace RoF2
break;
}
default: {
LogTrading("(RoF2) Unhandled action <red>[{}]", action);
//LogTradingDetail("(RoF2) Unhandled action <red>[{}]", action);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove if not keeping

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

@@ -622,10 +622,10 @@ namespace RoF2
break;
}
default: {
LogTrading(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove if not keeping

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated


FINISH_DIRECT_DECODE();
break;
}
default: {
LogTrading("(RoF2) Unhandled action <red>[{}]", action);
//LogTradingDetail("(RoF2) Unhandled action <red>[{}]", action);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove if not keeping

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

@@ -2529,7 +2529,7 @@ namespace Titanium

IN(action);
memcpy(emu->player_name, eq->player_name, sizeof(emu->player_name));
IN(serial_number);
//FIXIN(serial_number);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove if not keeping

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

@@ -3618,7 +3618,7 @@ namespace UF

IN(action);
memcpy(emu->player_name, eq->player_name, sizeof(emu->player_name));
IN(serial_number);
//FIXIN(serial_number);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove if not keeping

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

del_time -= RuleI(Character, InvSnapshotHistoryD) * 86400;
}

DeleteWhere(db, fmt::format("`character_id` = '{}' AND `time_index` <= '{}'", character_id, del_time));
Copy link
Member

@Akkadius Akkadius Jun 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Numbers shouldn't be quoted in queries, it throws off the query planner

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

static void ListCharacterInvSnapshots(Database &db, uint32 character_id, std::list<std::pair<uint32, int>> &is_list)
{
const std::string &query = fmt::format(
"SELECT `time_index`, COUNT(*) FROM `inventory_snapshots` WHERE "
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Numbers shouldn't be quoted in queries, it throws off the query planner

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

}

const std::string &query = fmt::format(
"SELECT * FROM `inventory_snapshots` WHERE `character_id` = '{}' "
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Numbers shouldn't be quoted in queries, it throws off the query planner

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

{
const std::string &query = fmt::format(
"SELECT `slot_id`, `item_id` FROM `inventory_snapshots` "
"WHERE `character_id` = '{}' AND `time_index` = '{}' ORDER BY `slot_id`",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Numbers shouldn't be quoted in queries, it throws off the query planner

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

{
const std::string &query = fmt::format(
"SELECT slot_id, item_id FROM `inventory_snapshots` "
"WHERE `time_index` = '{0}' AND `character_id` = '{1}' AND `slot_id` NOT IN ("
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Numbers shouldn't be quoted in queries, it throws off the query planner

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

{
const std::string &query = fmt::format(
"SELECT `slot_id`, `item_id` FROM `inventory` WHERE "
"`character_id` = '{0}' AND `slot_id` NOT IN ("
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Numbers shouldn't be quoted in queries, it throws off the query planner

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

uint32 time_index = time(nullptr);
std::vector<InventorySnapshots> queue{};

auto inventory = InventoryRepository::GetWhere(db, fmt::format("`character_id` = '{}'", character_id));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Numbers shouldn't be quoted in queries, it throws off the query planner

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated


static bool RestoreCharacterInvSnapshot(Database &db, uint32 character_id, uint32 timestamp)
{
InventoryRepository::DeleteWhere(db, fmt::format("`character_id` = '{}'", character_id));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Numbers shouldn't be quoted in queries, it throws off the query planner

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

@neckkola neckkola force-pushed the AddUniqueItemSerialNumberWithOfflineTrading branch from a620e91 to d21147f Compare August 11, 2025 21:35
@neckkola
Copy link
Contributor Author

I have rebased, and updated based on comments. Ran some basic tests. I do need to update the GetSerialNumber functions to remove them and clean up that side. Will work on that later.

… they are no longer used.

Updated sharedbank to store unique_item_id instead of guid.
…ed now.

Added sharedbank conversion to create unique item ids on world load.
@neckkola
Copy link
Contributor Author

neckkola commented Aug 12, 2025

Ran more tests. Corrected a few issues and also implemented shared bank for unique ids. Should be ready for another review. Also cleaned up unused functions. Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants