Compare commits

...

13 Commits

Author SHA1 Message Date
Ponk
238c01baf4 Merge pull request #245 from P0nk/fix/energy-charge-crash #patch
Fix Energy charge crashing certain other players
2024-06-16 19:13:03 +02:00
P0nk
5a4bdd343c Fix Energy charge crashing certain other players
Crabo in #bug-report (2024-06-10):
"(...) this will crash everyone in the map besides the bucc and 1 character, when a bucc (or TB) charges energy and a character with an ID that's a multiple of 102 is in the same map (and the energy reaches that number so if character ID is 204 it will reach that after 2 hits and DC the whole map besides the bucc and that char with id 204).
 Thanks to others for helping me fix it. Thought I'd report it!"
2024-06-16 19:09:21 +02:00
Ponk
d916502f58 Merge pull request #244 from P0nk/fix/cash-shop-surprise-package #patch
Fix able to get package from Cash shop surprise
2024-06-16 16:15:30 +02:00
P0nk
1791365e0f Fix able to get package from Cash shop surprise
Packages aren't real items and crash the client.
2024-06-16 16:06:29 +02:00
Ponk
de2a86c859 Merge pull request #243 from P0nk/fix/last-cash-shop-surprise #patch
Fix using the last Cash shop surprise not removing the item
2024-06-16 15:12:25 +02:00
P0nk
5aeed01e38 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).
2024-06-16 15:08:48 +02:00
P0nk
6ab1af99da Add tests for CashShopSurpriseHandler 2024-06-16 13:56:54 +02:00
P0nk
c7b2d218ef Fix count not being updated after last Cash shop surprise 2024-06-16 12:59:30 +02:00
P0nk
01ae462b72 Refactor CashShop - add constants for cash types 2024-06-16 12:19:16 +02:00
Ponk
eb603e7ee9 Merge pull request #242 from P0nk/fix/cash-shop-surprise-count #patch
Fix cash shop surprise count not properly updated on usage
2024-06-15 08:35:58 +02:00
P0nk
b67b29def5 Fix cash shop surprise count not properly updated on usage
This reverts the hacky solution made in db82cbcfae
2024-06-15 08:31:24 +02:00
Ponk
d22d9b603b Merge pull request #228 from HarkuLi/feat/fix-npc-gain-meso #patch
NPC: Fix type casting error for `gainMeso()` method
2024-06-14 21:44:10 +02:00
HarkuLi
0d684c1400 NPC: Fix type casting error for gainMeso() method
Number type values might be passed into the `gainMeso()` method in js
scripts, and thus it expects a `gainMeso(Double gain)` method in the
`NPCConversationManager` class.
2024-03-02 15:38:22 +08:00
19 changed files with 380 additions and 191 deletions

12
pom.xml
View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cosmic-maplestory</groupId>
@@ -52,7 +53,8 @@
<!-- Maven plugins -->
<maven-surefire-plugin.version>3.2.5</maven-surefire-plugin.version> <!-- For running unit tests -->
<maven-jar-plugin.version>3.4.1</maven-jar-plugin.version> <!-- Disabled. (for building thin jar) -->
<maven-assembly-plugin.version>3.7.1</maven-assembly-plugin.version> <!-- For packaging the executable fat jar -->
<maven-assembly-plugin.version>3.7.1
</maven-assembly-plugin.version> <!-- For packaging the executable fat jar -->
<!-- Dependencies -->
<slf4j-api.version>2.0.13</slf4j-api.version> <!-- Logging facade -->
@@ -180,6 +182,12 @@
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

View File

@@ -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;

View File

@@ -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;

View File

@@ -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();

View File

@@ -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);
}
}

View File

@@ -116,7 +116,7 @@ public final class CashOperationHandler extends AbstractPacketHandler {
CashItem cItem = CashItemFactory.getItem(p.readInt());
Map<String, String> 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;

View File

@@ -24,27 +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<Item, Item> 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));
c.sendPacket(PacketCreator.showCashInventory(c));
} else {
c.sendPacket(PacketCreator.onCashItemGachaponOpenFailed());
}
if (!cs.isOpened()) {
return;
}
long cashId = p.readLong();
Optional<CashShopSurpriseResult> 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));
}
}

View File

@@ -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"));

View File

@@ -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);
}

View File

@@ -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;
@@ -36,7 +37,6 @@ import provider.DataProviderFactory;
import provider.DataTool;
import provider.wz.WZFiles;
import tools.DatabaseConnection;
import tools.PacketCreator;
import tools.Pair;
import java.sql.Connection;
@@ -48,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;
@@ -56,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<Item> inventory = new ArrayList<>();
private final List<Integer> 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, InventoryType> 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;
@@ -113,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));
@@ -163,7 +232,6 @@ public class CashShop {
public static class CashItemFactory {
private static volatile Map<Integer, CashItem> items = new HashMap<>();
private static volatile List<Integer> randomitemsns = new ArrayList<>();
private static volatile Map<Integer, List<Integer>> packages = new HashMap<>();
private static volatile List<SpecialCashItem> specialcashitems = new ArrayList<>();
@@ -171,7 +239,6 @@ public class CashShop {
DataProvider etc = DataProviderFactory.getDataProvider(WZFiles.ETC);
Map<Integer, CashItem> loadedItems = new HashMap<>();
List<Integer> onSaleItems = new ArrayList<>();
for (Data item : etc.getData("Commodity.img").getChildren()) {
int sn = DataTool.getIntConvert("SN", item);
int itemId = DataTool.getIntConvert("ItemId", item);
@@ -180,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<Integer, List<Integer>> loadedPackages = new HashMap<>();
for (Data cashPackage : etc.getData("CashPackage.img").getChildren()) {
@@ -213,13 +275,20 @@ public class CashShop {
CashItemFactory.specialcashitems = loadedSpecialItems;
}
public static CashItem getRandomCashItem() {
if (randomitemsns.isEmpty()) {
return null;
public static Optional<CashItem> getRandomCashItem() {
if (items.isEmpty()) {
return Optional.empty();
}
int rnd = (int) (Math.random() * randomitemsns.size());
return items.get(randomitemsns.get(rnd));
List<CashItem> itemPool = items.values().stream()
.filter(CashItem::isOnSale)
.filter(cashItem -> !ItemId.isCashPackage(cashItem.itemId))
.toList();
return Optional.of(getRandomItem(itemPool));
}
private static CashItem getRandomItem(List<CashItem> items) {
return items.get(new Random().nextInt(items.size()));
}
public static CashItem getItem(int sn) {
@@ -243,107 +312,26 @@ public class CashShop {
public static List<SpecialCashItem> getSpecialCashItems() {
return specialcashitems;
}
public static void reloadSpecialCashItems() {//Yay?
List<SpecialCashItem> 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<Item> inventory = new ArrayList<>();
private final List<Integer> 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, InventoryType> 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;
}
}
@@ -408,17 +396,6 @@ public class CashShop {
}
}
public int getItemsSize() {
int size = 0;
lock.lock();
try {
size = inventory.size();
} finally {
lock.unlock();
}
return size;
}
public List<Integer> getWishList() {
return wishList;
}
@@ -537,47 +514,57 @@ public class CashShop {
}
}
private Item getCashShopItemByItemid(int itemid) {
public Optional<CashShopSurpriseResult> openCashShopSurprise(long cashId) {
lock.lock();
try {
for (Item it : inventory) {
if (it.getItemId() == itemid) {
return it;
}
Optional<Item> 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<CashItem> 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<Item, Item> openCashShopSurprise() {
Item css = getCashShopItemByItemid(ItemId.CASH_SHOP_SURPRISE);
@GuardedBy("lock")
private Optional<Item> getItemByCashId(long cashId) {
return inventory.stream()
.filter(item -> item.getCashId() == cashId)
.findAny();
}
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 int getItemsSize() {
lock.lock();
try {
return inventory.size();
} finally {
lock.unlock();
}
}

View File

@@ -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;
}

View File

@@ -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));
}
}

View File

@@ -235,4 +235,12 @@ class ByteBufInPacketTest {
assertEquals(initial.length(), afterReadingOpcode.length());
}
}
@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);
}
}

View File

@@ -203,4 +203,14 @@ class ByteBufOutPacketTest {
assertEquals(0, wrapped.readByte());
assertEquals(secondWrittenByte, wrapped.readByte());
}
}
@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);
}
}

View File

@@ -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));
}
}

View File

@@ -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());
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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<OutPacket> contentProvider) {
OutPacket builderInput = new ByteBufOutPacket();
contentProvider.accept(builderInput);
return new ByteBufInPacket(Unpooled.wrappedBuffer(builderInput.getBytes()));
}
}