Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aab9823a06 | ||
|
|
3f800c4a68 | ||
|
|
31e901ab6d | ||
|
|
238c01baf4 | ||
|
|
5a4bdd343c | ||
|
|
d916502f58 | ||
|
|
1791365e0f | ||
|
|
de2a86c859 | ||
|
|
5aeed01e38 | ||
|
|
6ab1af99da | ||
|
|
c7b2d218ef | ||
|
|
01ae462b72 | ||
|
|
eb603e7ee9 | ||
|
|
b67b29def5 | ||
|
|
d22d9b603b | ||
|
|
16b0a36c86 | ||
|
|
313f48d4ce | ||
|
|
db82cbcfae | ||
|
|
eed47a9064 | ||
|
|
0d684c1400 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -21,3 +21,6 @@
|
||||
# Database
|
||||
database/docker-db-data
|
||||
database/docker-pg-db-data
|
||||
|
||||
# macOS files
|
||||
.DS_Store
|
||||
|
||||
12
pom.xml
12
pom.xml
@@ -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>
|
||||
|
||||
|
||||
@@ -32,14 +32,14 @@ function action(mode, type, selection) {
|
||||
} else if (status == 1) {
|
||||
if (selection == 0) {
|
||||
apqpoints = cm.getPlayer().getAriantPoints();
|
||||
if (apqpoints < 100) {
|
||||
cm.sendOk("Your Battle Arena score: #b" + apqpoints + "#k points. You need to surpass #b100 points#k so that I can give you the #bPalm Tree Beach Chair#k. Talk to me again when you have enough points.");
|
||||
cm.dispose();
|
||||
if (apqpoints >= 100) {
|
||||
cm.sendNext("Wow, it looks like you got the #b100#k points ready to trade, let's trade?!");
|
||||
} else if (apqpoints + arena.getAriantRewardTier(cm.getPlayer()) >= 100) {
|
||||
cm.sendOk("Your Battle Arena score: #b" + apqpoints + "#k points and you pratically already have that score! Talk to my wife, #p2101016#to get them and then re-chat with me!");
|
||||
cm.sendOk("Your Battle Arena score: #b" + apqpoints + "#k points and you pratically already have that score! Talk to my wife, #p2101016# to get them and then re-chat with me!");
|
||||
cm.dispose();
|
||||
} else {
|
||||
cm.sendNext("Wow, it looks like you got the #b100#k points ready to trade, let's trade?!");
|
||||
cm.sendOk("Your Battle Arena score: #b" + apqpoints + "#k points. You need to surpass #b100 points#k so that I can give you the #bPalm Tree Beach Chair#k. Talk to me again when you have enough points.");
|
||||
cm.dispose();
|
||||
}
|
||||
} else if (selection == 1) {
|
||||
cm.sendOk("The main objective of the Battle Arena is to allow the player to accumulate points so that they can be traded honorably for the highest prize: the #bPalm Tree Beach Chair#k. Collect points during the battles and talk to me when it's time to get the prize. In each battle, the player is given the opportunity to score points based on the amount of jewelry that the player has at the end. But be careful! If your points distance from other players #ris too high#k, this will have been all for nothing and you will earn mere #r1 point#k only.");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<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));
|
||||
} 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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -93,6 +93,7 @@ public final class UseCatchItemHandler extends AbstractPacketHandler {
|
||||
mob.getMap().killMonster(mob, null, false);
|
||||
InventoryManipulator.removeById(c, InventoryType.USE, itemId, 1, true, true);
|
||||
InventoryManipulator.addById(c, ItemId.ARPQ_SPIRIT_JEWEL, (short) 1, "", -1);
|
||||
chr.updateAriantScore();
|
||||
} else {
|
||||
chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 0));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<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;
|
||||
@@ -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<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<>();
|
||||
|
||||
@@ -170,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);
|
||||
@@ -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<Integer, List<Integer>> 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<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) {
|
||||
@@ -242,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -525,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) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
19
src/test/java/constants/id/ItemIdTest.java
Normal file
19
src/test/java/constants/id/ItemIdTest.java
Normal 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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
24
src/test/java/testutil/HandlerTest.java
Normal file
24
src/test/java/testutil/HandlerTest.java
Normal 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);
|
||||
}
|
||||
}
|
||||
10
src/test/java/testutil/Items.java
Normal file
10
src/test/java/testutil/Items.java
Normal 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);
|
||||
}
|
||||
}
|
||||
18
src/test/java/testutil/Packets.java
Normal file
18
src/test/java/testutil/Packets.java
Normal 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()));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user