diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/catalog/CatalogItem.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/catalog/CatalogItem.java index 7bfa22f8..8c9746dc 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/catalog/CatalogItem.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/catalog/CatalogItem.java @@ -174,6 +174,14 @@ public class CatalogItem implements ISerialize, Runnable, Comparable 0) { + return this.offerId; + } + + return haveOffer(this) ? this.id : -1; + } + public boolean isLimited() { return this.limitedStack > 0; } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/catalog/CatalogManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/catalog/CatalogManager.java index 29e6ff11..86dfb6c1 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/catalog/CatalogManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/catalog/CatalogManager.java @@ -494,10 +494,11 @@ public class CatalogManager { item = new CatalogItem(set); page.addItem(item); - if (item.getOfferId() != -1) { - page.addOfferId(item.getOfferId()); + int searchOfferId = item.getSearchOfferId(); + if (searchOfferId != -1) { + page.addOfferId(searchOfferId); - this.offerDefs.put(item.getOfferId(), item.getId()); + this.offerDefs.put(searchOfferId, item.getId()); } } else item.update(set); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/CatalogSearchedItemEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/CatalogSearchedItemEvent.java index 0851e03e..4c02cc5a 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/CatalogSearchedItemEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/CatalogSearchedItemEvent.java @@ -12,10 +12,15 @@ public class CatalogSearchedItemEvent extends MessageHandler { public void handle() throws Exception { int offerId = this.packet.readInt(); - int pageId = Emulator.getGameEnvironment().getCatalogManager().offerDefs.get(offerId); + int catalogItemId = Emulator.getGameEnvironment().getCatalogManager().offerDefs.get(offerId); - if (pageId != 0) { - CatalogPage page = Emulator.getGameEnvironment().getCatalogManager().getCatalogPage(Emulator.getGameEnvironment().getCatalogManager().getCatalogItem(pageId).getPageId()); + if (catalogItemId != 0) { + CatalogItem requestedItem = Emulator.getGameEnvironment().getCatalogManager().getCatalogItem(catalogItemId); + if (requestedItem == null) { + return; + } + + CatalogPage page = Emulator.getGameEnvironment().getCatalogManager().getCatalogPage(requestedItem.getPageId()); if (page != null) { TIntObjectIterator iterator = page.getCatalogItems().iterator(); @@ -25,7 +30,7 @@ public class CatalogSearchedItemEvent extends MessageHandler { CatalogItem item = iterator.value(); - if (item.getOfferId() == offerId) { + if (item.getSearchOfferId() == offerId) { this.client.sendResponse(new CatalogSearchResultComposer(item)); return; } diff --git a/Emulator/src/test/java/com/eu/habbo/messages/incoming/catalog/CatalogSearchOfferIdContractTest.java b/Emulator/src/test/java/com/eu/habbo/messages/incoming/catalog/CatalogSearchOfferIdContractTest.java new file mode 100644 index 00000000..b738723a --- /dev/null +++ b/Emulator/src/test/java/com/eu/habbo/messages/incoming/catalog/CatalogSearchOfferIdContractTest.java @@ -0,0 +1,55 @@ +package com.eu.habbo.messages.incoming.catalog; + +import org.junit.jupiter.api.Test; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class CatalogSearchOfferIdContractTest { + private static String source(String path) throws Exception { + return Files.readString(Path.of(path)); + } + + @Test + void catalogItemsExposeStableSearchOfferIdWhenDatabaseOfferIdIsMissing() throws Exception { + String source = source("src/main/java/com/eu/habbo/habbohotel/catalog/CatalogItem.java"); + + int method = source.indexOf("public int getSearchOfferId()"); + int rawGuard = source.indexOf("this.offerId > 0", method); + int fallback = source.indexOf("return haveOffer(this) ? this.id : -1", rawGuard); + + assertTrue(method > -1, "CatalogItem should expose a search-safe offer id"); + assertTrue(rawGuard > method, "CatalogItem should preserve valid positive database offer ids"); + assertTrue(fallback > rawGuard, + "CatalogItem should fall back to catalog item id when offer_id is missing but the item can be offered"); + } + + @Test + void catalogManagerIndexesSearchOfferIdsInsteadOfRawOfferIds() throws Exception { + String source = source("src/main/java/com/eu/habbo/habbohotel/catalog/CatalogManager.java"); + + int searchOffer = source.indexOf("int searchOfferId = item.getSearchOfferId()"); + int addOffer = source.indexOf("page.addOfferId(searchOfferId)", searchOffer); + int offerDefs = source.indexOf("this.offerDefs.put(searchOfferId, item.getId())", addOffer); + + assertTrue(searchOffer > -1, "CatalogManager should calculate the runtime search offer id"); + assertTrue(addOffer > searchOffer, "CatalogManager should expose runtime search offer ids in catalog pages"); + assertTrue(offerDefs > addOffer, "CatalogManager should map runtime search offer ids back to catalog items"); + assertTrue(!source.contains("this.offerDefs.put(item.getOfferId(), item.getId())"), + "CatalogManager must not index raw -1 offer ids for catalog search"); + } + + @Test + void catalogSearchLookupResolvesCatalogItemIdsAndComparesSearchOfferIds() throws Exception { + String source = source("src/main/java/com/eu/habbo/messages/incoming/catalog/CatalogSearchedItemEvent.java"); + + assertTrue(source.contains("int catalogItemId ="), + "Catalog search lookup should name offerDefs values as catalog item ids"); + assertTrue(source.contains("getCatalogItem(catalogItemId)"), + "Catalog search should resolve the mapped catalog item directly"); + assertTrue(source.contains("item.getSearchOfferId() == offerId"), + "Catalog search should compare runtime search offer ids, not raw database offer ids"); + } +}