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/client/Character.java b/src/main/java/client/Character.java index c90d31480c..ea94e94acd 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)) { @@ -6063,7 +6063,8 @@ public class Character extends AbstractCharacterObject { sendPacket(PacketCreator.giveBuff(energybar, 0, stat)); sendPacket(PacketCreator.showOwnBuffEffect(energycharge.getId(), 2)); getMap().broadcastPacket(this, PacketCreator.showBuffEffect(id, energycharge.getId(), 2)); - getMap().broadcastPacket(this, PacketCreator.giveForeignBuff(energybar, stat)); + getMap().broadcastPacket(this, PacketCreator.giveForeignPirateBuff(id, energycharge.getId(), + ceffect.getDuration(), stat)); } if (energybar >= 10000 && energybar < 11000) { energybar = 15000; diff --git a/src/main/java/constants/id/ItemId.java b/src/main/java/constants/id/ItemId.java index 2624966f0b..dda0323a2f 100644 --- a/src/main/java/constants/id/ItemId.java +++ b/src/main/java/constants/id/ItemId.java @@ -312,6 +312,10 @@ public class ItemId { return itemId == NX_CARD_100 || itemId == NX_CARD_250; } + public static boolean isCashPackage(int itemId) { + return itemId / 10000 == 910; + } + // Face expression private static final int FACE_EXPRESSION_MIN = 5160000; private static final int FACE_EXPRESSION_MAX = 5160014; 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/net/server/channel/handlers/AbstractDealDamageHandler.java b/src/main/java/net/server/channel/handlers/AbstractDealDamageHandler.java index 8a882e2a0c..c661d03efc 100644 --- a/src/main/java/net/server/channel/handlers/AbstractDealDamageHandler.java +++ b/src/main/java/net/server/channel/handlers/AbstractDealDamageHandler.java @@ -689,9 +689,10 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler { calcDmgMax = chr.calculateMaxBaseDamage(chr.getTotalWatk()); } + StatEffect effect = null; if (ret.skill != 0) { Skill skill = SkillFactory.getSkill(ret.skill); - StatEffect effect = skill.getEffect(ret.skilllevel); + effect = skill.getEffect(ret.skilllevel); if (magic) { // Since the skill is magic based, use the magic formula @@ -930,6 +931,16 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler { damage = -Integer.MAX_VALUE + damage - 1; } + if(effect != null) { + int maxattack = Math.max(effect.getBulletCount(), effect.getAttackCount()); + if (shadowPartner) { + maxattack = maxattack * 2; + } + if (ret.numDamage > maxattack) { + AutobanFactory.DAMAGE_HACK.addPoint(chr.getAutobanManager(), "Too many lines: " + ret.numDamage + " Max lines: " + maxattack + " SID: " + ret.skill + " MobID: " + (monster != null ? monster.getId() : "null") + " Map: " + chr.getMap().getMapName() + " (" + chr.getMapId() + ")"); + } + } + allDamageNumbers.add(damage); } if (ret.skill != Corsair.RAPID_FIRE || ret.skill != Aran.HIDDEN_FULL_DOUBLE || ret.skill != Aran.HIDDEN_FULL_TRIPLE || ret.skill != Aran.HIDDEN_OVER_DOUBLE || ret.skill != Aran.HIDDEN_OVER_TRIPLE) { 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/CashShopSurpriseHandler.java b/src/main/java/net/server/channel/handlers/CashShopSurpriseHandler.java index 0d56fd2b29..9fff47092f 100644 --- a/src/main/java/net/server/channel/handlers/CashShopSurpriseHandler.java +++ b/src/main/java/net/server/channel/handlers/CashShopSurpriseHandler.java @@ -24,26 +24,34 @@ 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.getSN(), cssBox.getQuantity(), cssItem, cssItem.getItemId(), cssItem.getQuantity(), true)); - } else { - c.sendPacket(PacketCreator.onCashItemGachaponOpenFailed()); - } + if (!cs.isOpened()) { + return; } + + long cashId = p.readLong(); + Optional result = cs.openCashShopSurprise(cashId); + 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/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/scripting/npc/NPCConversationManager.java b/src/main/java/scripting/npc/NPCConversationManager.java index 0fc4f94efe..bec34cb939 100644 --- a/src/main/java/scripting/npc/NPCConversationManager.java +++ b/src/main/java/scripting/npc/NPCConversationManager.java @@ -278,6 +278,10 @@ public class NPCConversationManager extends AbstractPlayerInteraction { getPlayer().gainMeso(gain); } + public void gainMeso(Double gain) { + getPlayer().gainMeso(gain.intValue()); + } + public void gainExp(int gain) { getPlayer().gainExp(gain, true, true); } diff --git a/src/main/java/server/CashShop.java b/src/main/java/server/CashShop.java index 346a909391..6801764fe1 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; @@ -47,6 +48,8 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.Random; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -55,8 +58,74 @@ import static java.util.concurrent.TimeUnit.HOURS; /* * @author Flav + * @author Ponk */ 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; @@ -112,19 +181,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)); @@ -162,7 +232,6 @@ public class CashShop { public static class CashItemFactory { private static volatile Map items = new HashMap<>(); - private static volatile List randomitemsns = new ArrayList<>(); private static volatile Map> packages = new HashMap<>(); private static volatile List specialcashitems = new ArrayList<>(); @@ -170,7 +239,6 @@ public class CashShop { DataProvider etc = DataProviderFactory.getDataProvider(WZFiles.ETC); Map loadedItems = new HashMap<>(); - List onSaleItems = new ArrayList<>(); for (Data item : etc.getData("Commodity.img").getChildren()) { int sn = DataTool.getIntConvert("SN", item); int itemId = DataTool.getIntConvert("ItemId", item); @@ -179,13 +247,8 @@ public class CashShop { short count = (short) DataTool.getIntConvert("Count", item, 1); boolean onSale = DataTool.getIntConvert("OnSale", item, 0) == 1; loadedItems.put(sn, new CashItem(sn, itemId, price, period, count, onSale)); - - if (onSale) { - onSaleItems.add(sn); - } } CashItemFactory.items = loadedItems; - CashItemFactory.randomitemsns = onSaleItems; Map> loadedPackages = new HashMap<>(); for (Data cashPackage : etc.getData("CashPackage.img").getChildren()) { @@ -212,13 +275,20 @@ public class CashShop { CashItemFactory.specialcashitems = loadedSpecialItems; } - public static CashItem getRandomCashItem() { - if (randomitemsns.isEmpty()) { - return null; + public static Optional getRandomCashItem() { + if (items.isEmpty()) { + return Optional.empty(); } - int rnd = (int) (Math.random() * randomitemsns.size()); - return items.get(randomitemsns.get(rnd)); + List itemPool = items.values().stream() + .filter(CashItem::isOnSale) + .filter(cashItem -> !ItemId.isCashPackage(cashItem.itemId)) + .toList(); + return Optional.of(getRandomItem(itemPool)); + } + + private static CashItem getRandomItem(List items) { + return items.get(new Random().nextInt(items.size())); } public static CashItem getItem(int sn) { @@ -242,107 +312,26 @@ public class CashShop { public static List getSpecialCashItems() { return specialcashitems; } - - public static void reloadSpecialCashItems() {//Yay? - List loadedSpecialItems = new ArrayList<>(); - try (Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("SELECT * FROM specialcashitems"); - ResultSet rs = ps.executeQuery()) { - while (rs.next()) { - loadedSpecialItems.add(new SpecialCashItem(rs.getInt("sn"), rs.getInt("modifier"), rs.getByte("info"))); - } - } catch (SQLException ex) { - ex.printStackTrace(); - } - CashItemFactory.specialcashitems = loadedSpecialItems; - } } - 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 record CashShopSurpriseResult(Item usedCashShopSurprise, Item reward) { } 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; } } @@ -525,47 +514,57 @@ 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(); + } + + Optional cashItemReward = CashItemFactory.getRandomCashItem(); + if (cashItemReward.isEmpty()) { + return Optional.empty(); + } + + short newQuantity = (short) (cashShopSurprise.getQuantity() - 1); + cashShopSurprise.setQuantity(newQuantity); + if (newQuantity <= 0) { + removeFromInventory(cashShopSurprise); + } + + Item itemReward = cashItemReward.get().toItem(); + addToInventory(itemReward); + + return Optional.of(new CashShopSurpriseResult(cashShopSurprise, itemReward)); } finally { lock.unlock(); } - - return null; } - public synchronized Pair openCashShopSurprise() { - Item css = getCashShopItemByItemid(ItemId.CASH_SHOP_SURPRISE); + @GuardedBy("lock") + private Optional getItemByCashId(long cashId) { + return inventory.stream() + .filter(item -> item.getCashId() == cashId) + .findAny(); + } - if (css != null) { - CashItem cItem = CashItemFactory.getRandomCashItem(); - - if (cItem != null) { - if (css.getQuantity() > 1) { - /* if(NOT ENOUGH SPACE) { looks like we're not dealing with cash inventory limit whatsoever, k then - return null; - } */ - - 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 int getItemsSize() { + lock.lock(); + try { + return inventory.size(); + } finally { + lock.unlock(); } } diff --git a/src/main/java/tools/PacketCreator.java b/src/main/java/tools/PacketCreator.java index 7471e903b7..a6cf3cf6eb 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; } @@ -6479,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/constants/id/ItemIdTest.java b/src/test/java/constants/id/ItemIdTest.java new file mode 100644 index 0000000000..988eebacea --- /dev/null +++ b/src/test/java/constants/id/ItemIdTest.java @@ -0,0 +1,19 @@ +package constants.id; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ItemIdTest { + + @Test + void isCashPackage() { + assertTrue(ItemId.isCashPackage(9102237)); + } + + @Test + void isNotCashPackage() { + assertFalse(ItemId.isCashPackage(4000000)); + } +} 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..71d0246ab1 --- /dev/null +++ b/src/test/java/net/server/channel/handlers/CashShopSurpriseHandlerTest.java @@ -0,0 +1,72 @@ +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; +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.ArgumentMatchers.anyLong; +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); + } + + private InPacket useCashShopSurprisePacket(long cashId) { + return Packets.buildInPacket(out -> out.writeLong(cashId)); + } + + @Test + void shouldDoNothingWhenCsIsNotOpened() { + when(cashShop.isOpened()).thenReturn(false); + + handler.handlePacket(useCashShopSurprisePacket(123), client); + + verify(cashShop, never()).openCashShopSurprise(anyLong()); + } + + @Test + void shouldSendFailurePacketWhenFailToOpen() { + when(cashShop.isOpened()).thenReturn(true); + when(cashShop.openCashShopSurprise(anyLong())).thenReturn(Optional.empty()); + + handler.handlePacket(useCashShopSurprisePacket(456), 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(789)).thenReturn(Optional.of(new CashShop.CashShopSurpriseResult( + cashShopSurprise, reward))); + + 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/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..6bd9db8a9c --- /dev/null +++ b/src/test/java/testutil/Packets.java @@ -0,0 +1,18 @@ +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 buildInPacket(Consumer contentProvider) { + OutPacket builderInput = new ByteBufOutPacket(); + contentProvider.accept(builderInput); + return new ByteBufInPacket(Unpooled.wrappedBuffer(builderInput.getBytes())); + } +}