From 01ae462b726f9d00f14f06d8c40cee3b373d6411 Mon Sep 17 00:00:00 2001 From: P0nk Date: Sun, 16 Jun 2024 12:19:16 +0200 Subject: [PATCH 1/4] Refactor CashShop - add constants for cash types --- src/main/java/client/Character.java | 4 +- .../handlers/CashOperationHandler.java | 6 +- .../server/channel/handlers/MTSHandler.java | 7 +- src/main/java/server/CashShop.java | 154 +++++++++--------- src/main/java/tools/PacketCreator.java | 11 +- 5 files changed, 89 insertions(+), 93 deletions(-) diff --git a/src/main/java/client/Character.java b/src/main/java/client/Character.java index c90d31480c..0bc5926a02 100644 --- a/src/main/java/client/Character.java +++ b/src/main/java/client/Character.java @@ -2068,7 +2068,7 @@ public class Character extends AbstractCharacterObject { this.getCashShop().gainCash(1, nxGain); if (YamlConfig.config.server.USE_ANNOUNCE_NX_COUPON_LOOT) { - showHint("You have earned #e#b" + nxGain + " NX#k#n. (" + this.getCashShop().getCash(1) + " NX)", 300); + showHint("You have earned #e#b" + nxGain + " NX#k#n. (" + this.getCashShop().getCash(CashShop.NX_CREDIT) + " NX)", 300); } this.getMap().pickItemDrop(pickupPacket, mapitem); @@ -2120,7 +2120,7 @@ public class Character extends AbstractCharacterObject { this.getCashShop().gainCash(1, nxGain); if (YamlConfig.config.server.USE_ANNOUNCE_NX_COUPON_LOOT) { - showHint("You have earned #e#b" + nxGain + " NX#k#n. (" + this.getCashShop().getCash(1) + " NX)", 300); + showHint("You have earned #e#b" + nxGain + " NX#k#n. (" + this.getCashShop().getCash(CashShop.NX_CREDIT) + " NX)", 300); } } else if (applyConsumeOnPickup(mItem.getItemId())) { } else if (InventoryManipulator.addFromDrop(client, mItem, true)) { diff --git a/src/main/java/net/server/channel/handlers/CashOperationHandler.java b/src/main/java/net/server/channel/handlers/CashOperationHandler.java index 077c654a5c..86efa554b0 100644 --- a/src/main/java/net/server/channel/handlers/CashOperationHandler.java +++ b/src/main/java/net/server/channel/handlers/CashOperationHandler.java @@ -116,7 +116,7 @@ public final class CashOperationHandler extends AbstractPacketHandler { CashItem cItem = CashItemFactory.getItem(p.readInt()); Map recipient = Character.getCharacterFromDatabase(p.readString()); String message = p.readString(); - if (!canBuy(chr, cItem, cs.getCash(4)) || message.length() < 1 || message.length() > 73) { + if (!canBuy(chr, cItem, cs.getCash(CashShop.NX_PREPAID)) || message.isEmpty() || message.length() > 73) { c.enableCSActions(); return; } @@ -405,7 +405,7 @@ public final class CashOperationHandler extends AbstractPacketHandler { c.sendPacket(PacketCreator.showCash(c.getPlayer())); } else if (action == 0x2E) { //name change CashItem cItem = CashItemFactory.getItem(p.readInt()); - if (cItem == null || !canBuy(chr, cItem, cs.getCash(4))) { + if (cItem == null || !canBuy(chr, cItem, cs.getCash(CashShop.NX_PREPAID))) { c.sendPacket(PacketCreator.showCashShopMessage((byte) 0)); c.enableCSActions(); return; @@ -434,7 +434,7 @@ public final class CashOperationHandler extends AbstractPacketHandler { c.enableCSActions(); } else if (action == 0x31) { //world transfer CashItem cItem = CashItemFactory.getItem(p.readInt()); - if (cItem == null || !canBuy(chr, cItem, cs.getCash(4))) { + if (cItem == null || !canBuy(chr, cItem, cs.getCash(CashShop.NX_PREPAID))) { c.sendPacket(PacketCreator.showCashShopMessage((byte) 0)); c.enableCSActions(); return; diff --git a/src/main/java/net/server/channel/handlers/MTSHandler.java b/src/main/java/net/server/channel/handlers/MTSHandler.java index bc5d3dac74..4634c2bef3 100644 --- a/src/main/java/net/server/channel/handlers/MTSHandler.java +++ b/src/main/java/net/server/channel/handlers/MTSHandler.java @@ -35,6 +35,7 @@ import net.server.Server; import net.server.channel.Channel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import server.CashShop; import server.ItemInformationProvider; import server.MTSItemInfo; import tools.DatabaseConnection; @@ -402,7 +403,7 @@ public final class MTSHandler extends AbstractPacketHandler { ResultSet rs = ps.executeQuery(); if (rs.next()) { int price = rs.getInt("price") + 100 + (int) (rs.getInt("price") * 0.1); // taxes - if (c.getPlayer().getCashShop().getCash(4) >= price) { // FIX + if (c.getPlayer().getCashShop().getCash(CashShop.NX_PREPAID) >= price) { // FIX boolean alwaysnull = true; for (Channel cserv : Server.getInstance().getAllChannels()) { Character victim = cserv.getPlayerStorage().getCharacterById(rs.getInt("seller")); @@ -459,11 +460,11 @@ public final class MTSHandler extends AbstractPacketHandler { ResultSet rs = ps.executeQuery(); if (rs.next()) { int price = rs.getInt("price") + 100 + (int) (rs.getInt("price") * 0.1); - if (c.getPlayer().getCashShop().getCash(4) >= price) { + if (c.getPlayer().getCashShop().getCash(CashShop.NX_PREPAID) >= price) { for (Channel cserv : Server.getInstance().getAllChannels()) { Character victim = cserv.getPlayerStorage().getCharacterById(rs.getInt("seller")); if (victim != null) { - victim.getCashShop().gainCash(4, rs.getInt("price")); + victim.getCashShop().gainCash(CashShop.NX_PREPAID, rs.getInt("price")); } else { try (PreparedStatement pse = con.prepareStatement("SELECT accountid FROM characters WHERE id = ?")) { pse.setInt(1, rs.getInt("seller")); diff --git a/src/main/java/server/CashShop.java b/src/main/java/server/CashShop.java index d7e0ec1e28..6b93dea39d 100644 --- a/src/main/java/server/CashShop.java +++ b/src/main/java/server/CashShop.java @@ -36,7 +36,6 @@ import provider.DataProviderFactory; import provider.DataTool; import provider.wz.WZFiles; import tools.DatabaseConnection; -import tools.PacketCreator; import tools.Pair; import java.sql.Connection; @@ -58,6 +57,71 @@ import static java.util.concurrent.TimeUnit.HOURS; * @author Flav */ public class CashShop { + public static final int NX_CREDIT = 1; + public static final int MAPLE_POINT = 2; + public static final int NX_PREPAID = 4; + + private final int accountId; + private final int characterId; + private int nxCredit; + private int maplePoint; + private int nxPrepaid; + private boolean opened; + private ItemFactory factory; + private final List inventory = new ArrayList<>(); + private final List wishList = new ArrayList<>(); + private int notes = 0; + private final Lock lock = new ReentrantLock(); + + public CashShop(int accountId, int characterId, int jobType) throws SQLException { + this.accountId = accountId; + this.characterId = characterId; + + if (!YamlConfig.config.server.USE_JOINT_CASHSHOP_INVENTORY) { + switch (jobType) { + case 0: + factory = ItemFactory.CASH_EXPLORER; + break; + case 1: + factory = ItemFactory.CASH_CYGNUS; + break; + case 2: + factory = ItemFactory.CASH_ARAN; + break; + } + } else { + factory = ItemFactory.CASH_OVERALL; + } + + try (Connection con = DatabaseConnection.getConnection()) { + try (PreparedStatement ps = con.prepareStatement("SELECT `nxCredit`, `maplePoint`, `nxPrepaid` FROM `accounts` WHERE `id` = ?")) { + ps.setInt(1, accountId); + + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + this.nxCredit = rs.getInt("nxCredit"); + this.maplePoint = rs.getInt("maplePoint"); + this.nxPrepaid = rs.getInt("nxPrepaid"); + } + } + } + + for (Pair item : factory.loadItems(accountId, false)) { + inventory.add(item.getLeft()); + } + + try (PreparedStatement ps = con.prepareStatement("SELECT `sn` FROM `wishlists` WHERE `charid` = ?")) { + ps.setInt(1, characterId); + + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + wishList.add(rs.getInt("sn")); + } + } + } + } + } + public static class CashItem { private final int sn; @@ -259,91 +323,21 @@ public class CashShop { } } - private final int accountId; - private final int characterId; - private int nxCredit; - private int maplePoint; - private int nxPrepaid; - private boolean opened; - private ItemFactory factory; - private final List inventory = new ArrayList<>(); - private final List wishList = new ArrayList<>(); - private int notes = 0; - private final Lock lock = new ReentrantLock(); - - public CashShop(int accountId, int characterId, int jobType) throws SQLException { - this.accountId = accountId; - this.characterId = characterId; - - if (!YamlConfig.config.server.USE_JOINT_CASHSHOP_INVENTORY) { - switch (jobType) { - case 0: - factory = ItemFactory.CASH_EXPLORER; - break; - case 1: - factory = ItemFactory.CASH_CYGNUS; - break; - case 2: - factory = ItemFactory.CASH_ARAN; - break; - } - } else { - factory = ItemFactory.CASH_OVERALL; - } - - try (Connection con = DatabaseConnection.getConnection()) { - try (PreparedStatement ps = con.prepareStatement("SELECT `nxCredit`, `maplePoint`, `nxPrepaid` FROM `accounts` WHERE `id` = ?")) { - ps.setInt(1, accountId); - - try (ResultSet rs = ps.executeQuery()) { - if (rs.next()) { - this.nxCredit = rs.getInt("nxCredit"); - this.maplePoint = rs.getInt("maplePoint"); - this.nxPrepaid = rs.getInt("nxPrepaid"); - } - } - } - - for (Pair item : factory.loadItems(accountId, false)) { - inventory.add(item.getLeft()); - } - - try (PreparedStatement ps = con.prepareStatement("SELECT `sn` FROM `wishlists` WHERE `charid` = ?")) { - ps.setInt(1, characterId); - - try (ResultSet rs = ps.executeQuery()) { - while (rs.next()) { - wishList.add(rs.getInt("sn")); - } - } - } - } - } - public int getCash(int type) { - switch (type) { - case 1: - return nxCredit; - case 2: - return maplePoint; - case 4: - return nxPrepaid; - } + return switch (type) { + case NX_CREDIT -> nxCredit; + case MAPLE_POINT -> maplePoint; + case NX_PREPAID -> nxPrepaid; + default -> 0; + }; - return 0; } public void gainCash(int type, int cash) { switch (type) { - case 1: - nxCredit += cash; - break; - case 2: - maplePoint += cash; - break; - case 4: - nxPrepaid += cash; - break; + case NX_CREDIT -> nxCredit += cash; + case MAPLE_POINT -> maplePoint += cash; + case NX_PREPAID -> nxPrepaid += cash; } } diff --git a/src/main/java/tools/PacketCreator.java b/src/main/java/tools/PacketCreator.java index 7471e903b7..29643ab662 100644 --- a/src/main/java/tools/PacketCreator.java +++ b/src/main/java/tools/PacketCreator.java @@ -77,6 +77,7 @@ import net.server.world.Party; import net.server.world.PartyCharacter; import net.server.world.PartyOperation; import net.server.world.World; +import server.CashShop; import server.CashShop.CashItem; import server.CashShop.CashItemFactory; import server.CashShop.SpecialCashItem; @@ -5580,8 +5581,8 @@ public class PacketCreator { public static Packet showMTSCash(Character chr) { final OutPacket p = OutPacket.create(SendOpcode.MTS_OPERATION2); - p.writeInt(chr.getCashShop().getCash(4)); - p.writeInt(chr.getCashShop().getCash(2)); + p.writeInt(chr.getCashShop().getCash(CashShop.NX_PREPAID)); + p.writeInt(chr.getCashShop().getCash(CashShop.MAPLE_POINT)); return p; } @@ -5689,9 +5690,9 @@ public class PacketCreator { public static Packet showCash(Character mc) { final OutPacket p = OutPacket.create(SendOpcode.QUERY_CASH_RESULT); - p.writeInt(mc.getCashShop().getCash(1)); - p.writeInt(mc.getCashShop().getCash(2)); - p.writeInt(mc.getCashShop().getCash(4)); + p.writeInt(mc.getCashShop().getCash(CashShop.NX_CREDIT)); + p.writeInt(mc.getCashShop().getCash(CashShop.MAPLE_POINT)); + p.writeInt(mc.getCashShop().getCash(CashShop.NX_PREPAID)); return p; } From c7b2d218ef27af06bc3a704a1139234833f45691 Mon Sep 17 00:00:00 2001 From: P0nk Date: Sun, 16 Jun 2024 12:59:30 +0200 Subject: [PATCH 2/4] Fix count not being updated after last Cash shop surprise --- .../handlers/CashShopSurpriseHandler.java | 29 +++++--- src/main/java/server/CashShop.java | 74 ++++++++++--------- 2 files changed, 57 insertions(+), 46 deletions(-) diff --git a/src/main/java/net/server/channel/handlers/CashShopSurpriseHandler.java b/src/main/java/net/server/channel/handlers/CashShopSurpriseHandler.java index 618c3f7d31..7b338dc082 100644 --- a/src/main/java/net/server/channel/handlers/CashShopSurpriseHandler.java +++ b/src/main/java/net/server/channel/handlers/CashShopSurpriseHandler.java @@ -24,26 +24,33 @@ import client.inventory.Item; import net.AbstractPacketHandler; import net.packet.InPacket; import server.CashShop; +import server.CashShop.CashShopSurpriseResult; import tools.PacketCreator; -import tools.Pair; + +import java.util.Optional; /** * @author RonanLana + * @author Ponk */ public class CashShopSurpriseHandler extends AbstractPacketHandler { + @Override public final void handlePacket(InPacket p, Client c) { CashShop cs = c.getPlayer().getCashShop(); - - if (cs.isOpened()) { - Pair cssResult = cs.openCashShopSurprise(); - - if (cssResult != null) { - Item cssItem = cssResult.getLeft(), cssBox = cssResult.getRight(); - c.sendPacket(PacketCreator.onCashGachaponOpenSuccess(c.getAccID(), cssBox.getCashId(), cssBox.getQuantity(), cssItem, cssItem.getItemId(), cssItem.getQuantity(), true)); - } else { - c.sendPacket(PacketCreator.onCashItemGachaponOpenFailed()); - } + if (!cs.isOpened()) { + return; } + + Optional result = cs.openCashShopSurprise(); + if (result.isEmpty()) { + c.sendPacket(PacketCreator.onCashItemGachaponOpenFailed()); + return; + } + + Item usedCashShopSurprise = result.get().usedCashShopSurprise(); + Item reward = result.get().reward(); + c.sendPacket(PacketCreator.onCashGachaponOpenSuccess(c.getAccID(), usedCashShopSurprise.getCashId(), + usedCashShopSurprise.getQuantity(), reward, reward.getItemId(), reward.getQuantity(), true)); } } diff --git a/src/main/java/server/CashShop.java b/src/main/java/server/CashShop.java index 6b93dea39d..ae9a45599e 100644 --- a/src/main/java/server/CashShop.java +++ b/src/main/java/server/CashShop.java @@ -47,6 +47,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -55,6 +56,7 @@ import static java.util.concurrent.TimeUnit.HOURS; /* * @author Flav + * @author Ponk */ public class CashShop { public static final int NX_CREDIT = 1; @@ -177,19 +179,20 @@ public class CashShop { if (ItemConstants.EXPIRING_ITEMS) { if (period == 1) { switch (itemId) { - case ItemId.DROP_COUPON_2X_4H, ItemId.EXP_COUPON_2X_4H: // 4 Hour 2X coupons, the period is 1, but we don't want them to last a day. - item.setExpiration(Server.getInstance().getCurrentTime() + HOURS.toMillis(4)); + case ItemId.DROP_COUPON_2X_4H, + ItemId.EXP_COUPON_2X_4H: // 4 Hour 2X coupons, the period is 1, but we don't want them to last a day. + item.setExpiration(Server.getInstance().getCurrentTime() + HOURS.toMillis(4)); /* } else if(itemId == 5211047 || itemId == 5360014) { // 3 Hour 2X coupons, unused as of now item.setExpiration(Server.getInstance().getCurrentTime() + HOURS.toMillis(3)); */ - break; - case ItemId.EXP_COUPON_3X_2H: - item.setExpiration(Server.getInstance().getCurrentTime() + HOURS.toMillis(2)); - break; - default: - item.setExpiration(Server.getInstance().getCurrentTime() + DAYS.toMillis(1)); - break; + break; + case ItemId.EXP_COUPON_3X_2H: + item.setExpiration(Server.getInstance().getCurrentTime() + HOURS.toMillis(2)); + break; + default: + item.setExpiration(Server.getInstance().getCurrentTime() + DAYS.toMillis(1)); + break; } } else { item.setExpiration(Server.getInstance().getCurrentTime() + DAYS.toMillis(period)); @@ -323,6 +326,9 @@ public class CashShop { } } + public record CashShopSurpriseResult(Item usedCashShopSurprise, Item reward) { + } + public int getCash(int type) { return switch (type) { case NX_CREDIT -> nxCredit; @@ -546,33 +552,31 @@ public class CashShop { return null; } - public synchronized Pair openCashShopSurprise() { - Item css = getCashShopItemByItemid(ItemId.CASH_SHOP_SURPRISE); - - if (css != null) { - if (getItemsSize() >= 100) { - return null; - } - - CashItem cItem = CashItemFactory.getRandomCashItem(); - - if (cItem != null) { - if (css.getQuantity() > 1) { - css.setQuantity((short) (css.getQuantity() - 1)); - } else { - removeFromInventory(css); - } - - Item item = cItem.toItem(); - addToInventory(item); - - return new Pair<>(item, css); - } else { - return null; - } - } else { - return null; + public synchronized Optional openCashShopSurprise() { + Item cashShopSurprise = getCashShopItemByItemid(ItemId.CASH_SHOP_SURPRISE); + if (cashShopSurprise == null || cashShopSurprise.getQuantity() <= 0) { + return Optional.empty(); } + + if (getItemsSize() >= 100) { + return Optional.empty(); + } + + CashItem cashItemReward = CashItemFactory.getRandomCashItem(); + if (cashItemReward == null) { + return Optional.empty(); + } + + short newQuantity = (short) (cashShopSurprise.getQuantity() - 1); + cashShopSurprise.setQuantity(newQuantity); + if (newQuantity <= 0) { + removeFromInventory(cashShopSurprise); + } + + Item itemReward = cashItemReward.toItem(); + addToInventory(itemReward); + + return Optional.of(new CashShopSurpriseResult(cashShopSurprise, itemReward)); } public static Item generateCouponItem(int itemId, short quantity) { From 6ab1af99da81c8d89f52fd34a64a1c5fceacb412 Mon Sep 17 00:00:00 2001 From: P0nk Date: Sun, 16 Jun 2024 13:40:06 +0200 Subject: [PATCH 3/4] Add tests for CashShopSurpriseHandler --- pom.xml | 12 +++- src/main/java/net/packet/ByteBufInPacket.java | 5 ++ .../java/net/packet/ByteBufOutPacket.java | 5 ++ src/main/java/tools/PacketCreator.java | 11 ++-- .../java/net/packet/ByteBufInPacketTest.java | 10 ++- .../java/net/packet/ByteBufOutPacketTest.java | 12 +++- .../handlers/CashShopSurpriseHandlerTest.java | 66 +++++++++++++++++++ src/test/java/testutil/AnyValues.java | 4 ++ src/test/java/testutil/HandlerTest.java | 24 +++++++ src/test/java/testutil/Items.java | 10 +++ src/test/java/testutil/Packets.java | 12 ++++ 11 files changed, 162 insertions(+), 9 deletions(-) create mode 100644 src/test/java/net/server/channel/handlers/CashShopSurpriseHandlerTest.java create mode 100644 src/test/java/testutil/HandlerTest.java create mode 100644 src/test/java/testutil/Items.java create mode 100644 src/test/java/testutil/Packets.java diff --git a/pom.xml b/pom.xml index 1c7ac8da10..ea27a32ed1 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 cosmic-maplestory @@ -52,7 +53,8 @@ 3.2.5 3.4.1 - 3.7.1 + 3.7.1 + 2.0.13 @@ -180,6 +182,12 @@ ${mockito.version} test + + org.mockito + mockito-junit-jupiter + ${mockito.version} + test + diff --git a/src/main/java/net/packet/ByteBufInPacket.java b/src/main/java/net/packet/ByteBufInPacket.java index e79326f7c6..25b139629c 100644 --- a/src/main/java/net/packet/ByteBufInPacket.java +++ b/src/main/java/net/packet/ByteBufInPacket.java @@ -82,6 +82,11 @@ public class ByteBufInPacket implements InPacket { return byteBuf.readerIndex(); } + @Override + public boolean equals(Object o) { + return o instanceof ByteBufInPacket other && byteBuf.equals(other.byteBuf); + } + @Override public String toString() { final int readerIndex = byteBuf.readerIndex(); diff --git a/src/main/java/net/packet/ByteBufOutPacket.java b/src/main/java/net/packet/ByteBufOutPacket.java index ce289603be..376253d280 100644 --- a/src/main/java/net/packet/ByteBufOutPacket.java +++ b/src/main/java/net/packet/ByteBufOutPacket.java @@ -91,4 +91,9 @@ public class ByteBufOutPacket implements OutPacket { public void skip(int numberOfBytes) { writeBytes(new byte[numberOfBytes]); } + + @Override + public boolean equals(Object o) { + return o instanceof ByteBufOutPacket other && byteBuf.equals(other.byteBuf); + } } diff --git a/src/main/java/tools/PacketCreator.java b/src/main/java/tools/PacketCreator.java index 29643ab662..a6cf3cf6eb 100644 --- a/src/main/java/tools/PacketCreator.java +++ b/src/main/java/tools/PacketCreator.java @@ -6480,14 +6480,15 @@ public class PacketCreator { return p; } - public static Packet onCashGachaponOpenSuccess(int accountid, long sn, int remainingBoxes, Item item, int itemid, int nSelectedItemCount, boolean bJackpot) { + public static Packet onCashGachaponOpenSuccess(int accountid, long boxCashId, int remainingBoxes, Item reward, + int rewardItemId, int rewardQuantity, boolean bJackpot) { OutPacket p = OutPacket.create(SendOpcode.CASHSHOP_CASH_ITEM_GACHAPON_RESULT); p.writeByte(0xE5); // subopcode thanks to Ubaware - p.writeLong(sn);// sn of the box used + p.writeLong(boxCashId); p.writeInt(remainingBoxes); - addCashItemInformation(p, item, accountid); - p.writeInt(itemid);// the itemid of the liSN? - p.writeByte(nSelectedItemCount);// the total count now? o.O + addCashItemInformation(p, reward, accountid); + p.writeInt(rewardItemId); + p.writeByte(rewardQuantity); // nSelectedItemCount p.writeBool(bJackpot);// "CashGachaponJackpot" return p; } diff --git a/src/test/java/net/packet/ByteBufInPacketTest.java b/src/test/java/net/packet/ByteBufInPacketTest.java index 6837ecfe1c..34b87e89b8 100644 --- a/src/test/java/net/packet/ByteBufInPacketTest.java +++ b/src/test/java/net/packet/ByteBufInPacketTest.java @@ -235,4 +235,12 @@ class ByteBufInPacketTest { assertEquals(initial.length(), afterReadingOpcode.length()); } -} \ No newline at end of file + + @Test + void equalsShouldCompareBytes() { + ByteBufInPacket packet1 = new ByteBufInPacket(Unpooled.wrappedBuffer(new byte[]{ 11, 22, 33, 44 })); + ByteBufInPacket packet2 = new ByteBufInPacket(Unpooled.wrappedBuffer(new byte[]{ 11, 22, 33, 44 })); + + assertEquals(packet1, packet2); + } +} diff --git a/src/test/java/net/packet/ByteBufOutPacketTest.java b/src/test/java/net/packet/ByteBufOutPacketTest.java index d3f2892f99..1b54a5c32d 100644 --- a/src/test/java/net/packet/ByteBufOutPacketTest.java +++ b/src/test/java/net/packet/ByteBufOutPacketTest.java @@ -203,4 +203,14 @@ class ByteBufOutPacketTest { assertEquals(0, wrapped.readByte()); assertEquals(secondWrittenByte, wrapped.readByte()); } -} \ No newline at end of file + + @Test + void equalsShouldCompareBytes() { + ByteBufOutPacket packet1 = new ByteBufOutPacket(); + packet1.writeBytes(new byte[] { 55, 66, 77, 88 }); + ByteBufOutPacket packet2 = new ByteBufOutPacket(); + packet2.writeBytes(new byte[] { 55, 66, 77, 88 }); + + assertEquals(packet1, packet2); + } +} diff --git a/src/test/java/net/server/channel/handlers/CashShopSurpriseHandlerTest.java b/src/test/java/net/server/channel/handlers/CashShopSurpriseHandlerTest.java new file mode 100644 index 0000000000..ff97b11fe3 --- /dev/null +++ b/src/test/java/net/server/channel/handlers/CashShopSurpriseHandlerTest.java @@ -0,0 +1,66 @@ +package net.server.channel.handlers; + +import client.inventory.Item; +import constants.id.ItemId; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import server.CashShop; +import testutil.HandlerTest; +import testutil.Items; +import testutil.Packets; +import tools.PacketCreator; + +import java.util.Optional; + +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class CashShopSurpriseHandlerTest extends HandlerTest { + private final CashShopSurpriseHandler handler = new CashShopSurpriseHandler(); + + @Mock + private CashShop cashShop; + + @BeforeEach + void prepareCashShop() { + when(chr.getCashShop()).thenReturn(cashShop); + } + + @Test + void shouldDoNothingWhenCsIsNotOpened() { + when(cashShop.isOpened()).thenReturn(false); + + handler.handlePacket(Packets.emptyInPacket(), client); + + verify(cashShop, never()).openCashShopSurprise(); + } + + @Test + void shouldSendFailurePacketWhenFailToOpen() { + when(cashShop.isOpened()).thenReturn(true); + when(cashShop.openCashShopSurprise()).thenReturn(Optional.empty()); + + handler.handlePacket(Packets.emptyInPacket(), client); + + verify(client).sendPacket(PacketCreator.onCashItemGachaponOpenFailed()); + } + + @Test + void shouldSendSuccessPacketWhenSuccessfullyOpen() { + when(cashShop.isOpened()).thenReturn(true); + Item cashShopSurprise = Items.itemWithQuantity(ItemId.CASH_SHOP_SURPRISE, 3); + Item reward = Items.itemWithQuantity(5000012, 1); + when(cashShop.openCashShopSurprise()).thenReturn(Optional.of(new CashShop.CashShopSurpriseResult( + cashShopSurprise, reward))); + + handler.handlePacket(Packets.emptyInPacket(), client); + + verify(client).sendPacket(PacketCreator.onCashGachaponOpenSuccess(ACCOUNT_ID, cashShopSurprise.getCashId(), 3, + reward, 5000012, 1, true)); + } +} diff --git a/src/test/java/testutil/AnyValues.java b/src/test/java/testutil/AnyValues.java index ccf9c0abbf..8c58a8c08b 100644 --- a/src/test/java/testutil/AnyValues.java +++ b/src/test/java/testutil/AnyValues.java @@ -8,6 +8,10 @@ public class AnyValues { return "string"; } + public static short anyShort() { + return 4; + } + public static DaoException daoException() { return new DaoException(string(), new RuntimeException()); } diff --git a/src/test/java/testutil/HandlerTest.java b/src/test/java/testutil/HandlerTest.java new file mode 100644 index 0000000000..32fcaa5299 --- /dev/null +++ b/src/test/java/testutil/HandlerTest.java @@ -0,0 +1,24 @@ +package testutil; + +import client.Character; +import client.Client; +import org.junit.jupiter.api.BeforeEach; +import org.mockito.Mock; + +import static org.mockito.Mockito.lenient; + +public abstract class HandlerTest { + protected static final int ACCOUNT_ID = 1702; + + @Mock + protected Client client; + + @Mock + protected Character chr; + + @BeforeEach + void prepareClient() { + lenient().when(client.getAccID()).thenReturn(ACCOUNT_ID); + lenient().when(client.getPlayer()).thenReturn(chr); + } +} diff --git a/src/test/java/testutil/Items.java b/src/test/java/testutil/Items.java new file mode 100644 index 0000000000..aba37686f1 --- /dev/null +++ b/src/test/java/testutil/Items.java @@ -0,0 +1,10 @@ +package testutil; + +import client.inventory.Item; + +public class Items { + + public static Item itemWithQuantity(int itemId, int quantity) { + return new Item(itemId, AnyValues.anyShort(), (short) quantity); + } +} diff --git a/src/test/java/testutil/Packets.java b/src/test/java/testutil/Packets.java new file mode 100644 index 0000000000..f833694388 --- /dev/null +++ b/src/test/java/testutil/Packets.java @@ -0,0 +1,12 @@ +package testutil; + +import io.netty.buffer.Unpooled; +import net.packet.ByteBufInPacket; +import net.packet.InPacket; + +public class Packets { + + public static InPacket emptyInPacket() { + return new ByteBufInPacket(Unpooled.buffer()); + } +} From 5aeed01e381c52fa467e266a2a28ed710069fdca Mon Sep 17 00:00:00 2001 From: P0nk Date: Sun, 16 Jun 2024 15:08:48 +0200 Subject: [PATCH 4/4] Fix not using the selected Cash shop surprise If you have multiple, it would always use the first one. Now it uses whichever you select (as expected). --- .../handlers/CashShopSurpriseHandler.java | 3 +- src/main/java/server/CashShop.java | 86 ++++++++++--------- .../handlers/CashShopSurpriseHandlerTest.java | 18 ++-- src/test/java/testutil/Packets.java | 10 ++- 4 files changed, 66 insertions(+), 51 deletions(-) diff --git a/src/main/java/net/server/channel/handlers/CashShopSurpriseHandler.java b/src/main/java/net/server/channel/handlers/CashShopSurpriseHandler.java index 7b338dc082..9fff47092f 100644 --- a/src/main/java/net/server/channel/handlers/CashShopSurpriseHandler.java +++ b/src/main/java/net/server/channel/handlers/CashShopSurpriseHandler.java @@ -42,7 +42,8 @@ public class CashShopSurpriseHandler extends AbstractPacketHandler { return; } - Optional result = cs.openCashShopSurprise(); + long cashId = p.readLong(); + Optional result = cs.openCashShopSurprise(cashId); if (result.isEmpty()) { c.sendPacket(PacketCreator.onCashItemGachaponOpenFailed()); return; diff --git a/src/main/java/server/CashShop.java b/src/main/java/server/CashShop.java index ae9a45599e..498fe12fdc 100644 --- a/src/main/java/server/CashShop.java +++ b/src/main/java/server/CashShop.java @@ -29,6 +29,7 @@ import client.inventory.Pet; import config.YamlConfig; import constants.id.ItemId; import constants.inventory.ItemConstants; +import net.jcip.annotations.GuardedBy; import net.server.Server; import provider.Data; import provider.DataProvider; @@ -408,17 +409,6 @@ public class CashShop { } } - public int getItemsSize() { - int size = 0; - lock.lock(); - try { - size = inventory.size(); - } finally { - lock.unlock(); - } - return size; - } - public List getWishList() { return wishList; } @@ -537,46 +527,58 @@ public class CashShop { } } - private Item getCashShopItemByItemid(int itemid) { + public Optional openCashShopSurprise(long cashId) { lock.lock(); try { - for (Item it : inventory) { - if (it.getItemId() == itemid) { - return it; - } + Optional maybeCashShopSurprise = getItemByCashId(cashId); + if (maybeCashShopSurprise.isEmpty() || + maybeCashShopSurprise.get().getItemId() != ItemId.CASH_SHOP_SURPRISE) { + return Optional.empty(); } + + Item cashShopSurprise = maybeCashShopSurprise.get(); + if (cashShopSurprise.getQuantity() <= 0) { + return Optional.empty(); + } + + if (getItemsSize() >= 100) { + return Optional.empty(); + } + + CashItem cashItemReward = CashItemFactory.getRandomCashItem(); + if (cashItemReward == null) { + return Optional.empty(); + } + + short newQuantity = (short) (cashShopSurprise.getQuantity() - 1); + cashShopSurprise.setQuantity(newQuantity); + if (newQuantity <= 0) { + removeFromInventory(cashShopSurprise); + } + + Item itemReward = cashItemReward.toItem(); + addToInventory(itemReward); + + return Optional.of(new CashShopSurpriseResult(cashShopSurprise, itemReward)); } finally { lock.unlock(); } - - return null; } - public synchronized Optional openCashShopSurprise() { - Item cashShopSurprise = getCashShopItemByItemid(ItemId.CASH_SHOP_SURPRISE); - if (cashShopSurprise == null || cashShopSurprise.getQuantity() <= 0) { - return Optional.empty(); + @GuardedBy("lock") + private Optional getItemByCashId(long cashId) { + return inventory.stream() + .filter(item -> item.getCashId() == cashId) + .findAny(); + } + + public int getItemsSize() { + lock.lock(); + try { + return inventory.size(); + } finally { + lock.unlock(); } - - if (getItemsSize() >= 100) { - return Optional.empty(); - } - - CashItem cashItemReward = CashItemFactory.getRandomCashItem(); - if (cashItemReward == null) { - return Optional.empty(); - } - - short newQuantity = (short) (cashShopSurprise.getQuantity() - 1); - cashShopSurprise.setQuantity(newQuantity); - if (newQuantity <= 0) { - removeFromInventory(cashShopSurprise); - } - - Item itemReward = cashItemReward.toItem(); - addToInventory(itemReward); - - return Optional.of(new CashShopSurpriseResult(cashShopSurprise, itemReward)); } public static Item generateCouponItem(int itemId, short quantity) { diff --git a/src/test/java/net/server/channel/handlers/CashShopSurpriseHandlerTest.java b/src/test/java/net/server/channel/handlers/CashShopSurpriseHandlerTest.java index ff97b11fe3..71d0246ab1 100644 --- a/src/test/java/net/server/channel/handlers/CashShopSurpriseHandlerTest.java +++ b/src/test/java/net/server/channel/handlers/CashShopSurpriseHandlerTest.java @@ -2,6 +2,7 @@ package net.server.channel.handlers; import client.inventory.Item; import constants.id.ItemId; +import net.packet.InPacket; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -15,6 +16,7 @@ import tools.PacketCreator; import java.util.Optional; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -31,21 +33,25 @@ class CashShopSurpriseHandlerTest extends HandlerTest { when(chr.getCashShop()).thenReturn(cashShop); } + private InPacket useCashShopSurprisePacket(long cashId) { + return Packets.buildInPacket(out -> out.writeLong(cashId)); + } + @Test void shouldDoNothingWhenCsIsNotOpened() { when(cashShop.isOpened()).thenReturn(false); - handler.handlePacket(Packets.emptyInPacket(), client); + handler.handlePacket(useCashShopSurprisePacket(123), client); - verify(cashShop, never()).openCashShopSurprise(); + verify(cashShop, never()).openCashShopSurprise(anyLong()); } @Test void shouldSendFailurePacketWhenFailToOpen() { when(cashShop.isOpened()).thenReturn(true); - when(cashShop.openCashShopSurprise()).thenReturn(Optional.empty()); + when(cashShop.openCashShopSurprise(anyLong())).thenReturn(Optional.empty()); - handler.handlePacket(Packets.emptyInPacket(), client); + handler.handlePacket(useCashShopSurprisePacket(456), client); verify(client).sendPacket(PacketCreator.onCashItemGachaponOpenFailed()); } @@ -55,10 +61,10 @@ class CashShopSurpriseHandlerTest extends HandlerTest { when(cashShop.isOpened()).thenReturn(true); Item cashShopSurprise = Items.itemWithQuantity(ItemId.CASH_SHOP_SURPRISE, 3); Item reward = Items.itemWithQuantity(5000012, 1); - when(cashShop.openCashShopSurprise()).thenReturn(Optional.of(new CashShop.CashShopSurpriseResult( + when(cashShop.openCashShopSurprise(789)).thenReturn(Optional.of(new CashShop.CashShopSurpriseResult( cashShopSurprise, reward))); - handler.handlePacket(Packets.emptyInPacket(), client); + handler.handlePacket(useCashShopSurprisePacket(789), client); verify(client).sendPacket(PacketCreator.onCashGachaponOpenSuccess(ACCOUNT_ID, cashShopSurprise.getCashId(), 3, reward, 5000012, 1, true)); diff --git a/src/test/java/testutil/Packets.java b/src/test/java/testutil/Packets.java index f833694388..6bd9db8a9c 100644 --- a/src/test/java/testutil/Packets.java +++ b/src/test/java/testutil/Packets.java @@ -2,11 +2,17 @@ package testutil; import io.netty.buffer.Unpooled; import net.packet.ByteBufInPacket; +import net.packet.ByteBufOutPacket; import net.packet.InPacket; +import net.packet.OutPacket; + +import java.util.function.Consumer; public class Packets { - public static InPacket emptyInPacket() { - return new ByteBufInPacket(Unpooled.buffer()); + public static InPacket buildInPacket(Consumer contentProvider) { + OutPacket builderInput = new ByteBufOutPacket(); + contentProvider.accept(builderInput); + return new ByteBufInPacket(Unpooled.wrappedBuffer(builderInput.getBytes())); } }