Compare commits

..

26 Commits

Author SHA1 Message Date
Ponk
aa3686ed0b Merge pull request #251 from P0nk/cleanup/banish #minor
Remove custom banish scroll feature
2024-07-17 19:47:15 +02:00
P0nk
3850b63cec Add migration for <v0.14.0 to fix shop dc 2024-07-17 19:24:19 +02:00
P0nk
a7df8a4f49 Remove banish scroll from WZ files 2024-07-17 18:50:44 +02:00
P0nk
7071b13e41 Remove banish scroll from shops 2024-07-17 18:39:50 +02:00
P0nk
2324ae7f9e Refactor BanishInfo - make it a record 2024-07-17 18:11:50 +02:00
P0nk
402163c33d Remove "banishable town scroll" feature 2024-07-17 17:45:38 +02:00
P0nk
3356e42e71 Remove "spikes avoid banish" feature 2024-07-16 20:17:21 +02:00
P0nk
205e263255 Specify required Java version 2024-07-16 20:16:26 +02:00
Ponk
aab9823a06 Merge pull request #227 from HarkuLi/feat/fix-ariant-coliseum #patch
Ariant Coliseum: Fix score counting and trade conversation
2024-06-19 17:58:38 +02:00
HarkuLi
3f800c4a68 Ignore .DS_Store files 2024-06-19 18:43:26 +08:00
HarkuLi
31e901ab6d Ariant Coliseum: Fix score counting and trade conversation 2024-06-19 18:42:31 +08:00
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
Ponk
16b0a36c86 Merge pull request #238 from channarit1994/master #patch
Surprise Box Implementation
2024-06-14 21:33:08 +02:00
Channarit Sittiparat
db82cbcfae Surprise Box Implementation
- Added showCashInventory after a successful gachapon opening in the CashShopSurpriseHandler.
- Added getItemsSize() condition to check the inventory size before proceeding with the cash shop surprise opening
2024-06-13 20:02:40 +07: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
44 changed files with 451 additions and 371 deletions

3
.gitignore vendored
View File

@@ -21,3 +21,6 @@
# Database
database/docker-db-data
database/docker-pg-db-data
# macOS files
.DS_Store

View File

@@ -71,7 +71,7 @@ You will start by installing the database server and client, and then run some s
You will start by cloning the repository, then configure the database properties and lastly start the server.
#### Prerequisites
* Java 21+ (I recommend [Amazon Corretto](https://aws.amazon.com/corretto))
* Java 21 (I recommend [Amazon Corretto](https://aws.amazon.com/corretto))
* IDE (I recommend [IntelliJ IDEA](https://www.jetbrains.com/idea/))
#### Steps
@@ -107,7 +107,7 @@ You will start by installing the game with the old installer, then overwrite som
1. Download _MapleGlobal-v83-setup.exe_ from my [Google Drive](https://drive.google.com/drive/folders/1hgnb92MGL6xqEp9szEMBh0K9pSJcJ6IT). This is the official installer from back then.
2. Install it in a directory of your choice.
3. Delete the following files from the installation directory: _HShield_ (entire directory), _ASPLnchr.exe_, _MapleStory.exe_, and _Patcher.exe_.
4. Download _CosmicWZ-2024-05-21-v0.13.0.zip_ from my [Google Drive](https://drive.google.com/drive/folders/1hgnb92MGL6xqEp9szEMBh0K9pSJcJ6IT).
4. Download _CosmicWZ-2024-07-17-v0.14.0.zip_ from my [Google Drive](https://drive.google.com/drive/folders/1hgnb92MGL6xqEp9szEMBh0K9pSJcJ6IT).
5. Unzip it and copy all .wz-files into the installation directory. Replace the existing ones.
6. Download _HeavenMS-localhost-WINDOW.exe_ from [hostr.co](https://hostr.co/amuX5SLeeVZx). This is a client modified to connect to your localhost instead of Nexon's server (along with some fixes and custom changes).
- Your antivirus will likely detect the file as a trojan or similar and automatically delete it. To prevent this from happening, add your _Downloads_ directory and the installation directory as exclusions in your antivirus software. On W11, this is under "Virus & threat protection settings" -> "Add or remove exclusions".

View File

@@ -257,7 +257,6 @@ server:
USE_BUFF_MOST_SIGNIFICANT: true #When applying buffs, the player will stick with the highest stat boost among the listed, rather than overwriting stats.
USE_BUFF_EVERLASTING: false #Every applied buff on players holds expiration time so high it'd be considered permanent. Suggestion thanks to Vcoc.
USE_MULTIPLE_SAME_EQUIP_DROP: true #Enables multiple drops by mobs of the same equipment, number of possible drops based on the quantities provided at the drop data.
USE_BANISHABLE_TOWN_SCROLL: false #Enables town scrolls to act as if it's a "player banish", rendering the antibanish scroll effect available.
USE_ENABLE_FULL_RESPAWN: false #At respawn task, always respawn missing mobs when they're available. Spawn count doesn't depend on how many players are currently there.
USE_ENABLE_CHAT_LOG: false #Write in-game chat to log
USE_REBIRTH_SYSTEM: false #Flag to enable/disable rebirth system
@@ -395,7 +394,6 @@ server:
USE_EQUIPMNT_LVLUP_SLOTS: false #Equips can upgrade slots at level up.
USE_EQUIPMNT_LVLUP_POWER: false #Enable more powerful stat upgrades at equip level up.
USE_EQUIPMNT_LVLUP_CASH: false #Enable equip leveling up on cash equipments as well.
USE_SPIKES_AVOID_BANISH: false #Shoes equipped with spikes prevents mobs from banishing wearer.
MAX_EQUIPMNT_LVLUP_STAT_UP: 10000 #Max stat upgrade an equipment can have on a levelup.
MAX_EQUIPMNT_STAT: 32767 #Max stat on an equipment by leveling up.
USE_EQUIPMNT_LVLUP: 1 #All equips lvlup at max level of N, set 1 to disable.

View File

@@ -131,39 +131,6 @@ INSERT IGNORE INTO `shopitems` (`shopid`, `itemid`, `price`, `pitch`, `position`
(9201101, 2012008, 4200000, 0, 164),
(9201101, 2022251, 3800000, 0, 168);
# adding antibanish scrolls
INSERT IGNORE INTO `shopitems` (`shopid`, `itemid`, `price`, `pitch`, `position`) VALUES
(1001100, 2030100, 450, 0, 130),
(1011100, 2030100, 450, 0, 142),
(1021100, 2030100, 450, 0, 142),
(1031100, 2030100, 450, 0, 146),
(1051002, 2030100, 450, 0, 142),
(1052116, 2030100, 450, 0, 118),
(1061001, 2030100, 450, 0, 126),
(1061002, 2030100, 450, 0, 130),
(1091002, 2030100, 450, 0, 130),
(1100002, 2030100, 450, 0, 138),
(2012005, 2030100, 450, 0, 126),
(2022001, 2030100, 450, 0, 126),
(2030009, 2030100, 450, 0, 126),
(2040051, 2030100, 450, 0, 102),
(2041006, 2030100, 450, 0, 134),
(2051000, 2030100, 450, 0, 134),
(2060004, 2030100, 450, 0, 134),
(2070001, 2030100, 450, 0, 134),
(2080001, 2030100, 450, 0, 134),
(2090003, 2030100, 450, 0, 126),
(2093002, 2030100, 450, 0, 126),
(2100004, 2030100, 450, 0, 130),
(2110001, 2030100, 450, 0, 130),
(2130000, 2030100, 450, 0, 126),
(9201060, 2030100, 450, 0, 114),
(9270021, 2030100, 450, 0, 118),
(9270022, 2030100, 450, 0, 114), -- Thanks Rednor for finding duplicate item on NPC
(1338, 2030100, 450, 0, 114),
(9270057, 2030100, 450, 0, 4),
(9270065, 2030100, 450, 0, 3);
-- Thanks to Vcoc
-- GMShop: Sacks, GmEquip, Cheese & Onyx, Utils,
-- Arrows, Bullets, Throwings and Capsules,
@@ -301,7 +268,6 @@ INSERT INTO `shopitems` ( `shopid`, `itemid`, `price`, `pitch`, `position`) VALU
(1200002, 2070000, 500, 0, 108),
(1200002, 2061000, 1, 0, 120),
(1200002, 2060000, 1, 0, 124),
(1200002, 2030100, 400, 0, 128),
(1200002, 2030000, 400, 0, 132),
(1200002, 2020028, 3000, 0, 136),
(1200002, 2010004, 310, 0, 140),
@@ -323,7 +289,6 @@ INSERT INTO `shopitems` ( `shopid`, `itemid`, `price`, `pitch`, `position`) VALU
(1301000, 2070000, 500, 0, 108),
(1301000, 2061000, 1, 0, 112),
(1301000, 2060000, 1, 0, 116),
(1301000, 2030100, 400, 0, 120),
(1301000, 2030000, 400, 0, 124),
(1301000, 2022000, 1650, 0, 128),
(1301000, 2022003, 1100, 0, 132),
@@ -348,4 +313,4 @@ INSERT INTO `shopitems` ( `shopid`, `itemid`, `price`, `pitch`, `position`) VALU
(9270019, 1482004, 75000, 0, 100),
(9270020, 1052113, 120000, 0, 92),
(9270020, 1052110, 100000, 0, 96),
(9270020, 1002625, 75000, 0, 100);
(9270020, 1002625, 75000, 0, 100);

View File

@@ -0,0 +1,13 @@
# Migrations
The files contained in this directory are intended to be run manually when transitioning from an earlier version of Cosmic to a more recent one.
Not every version comes with an associated migration script. Only those with breaking changes such as removal of custom assets that would otherwise crash the client.
This is a temporary solution until automatic database migrations are in place.
## How to
Each script is only intended to be run __once__.
When a new migration is available, simply run the SQL script in HeidiSQL (or other SQL client of choice).
If there are multiple new migrations that you haven't run, run them in order starting with the lowest version and ending with the highest version.

View File

@@ -0,0 +1,2 @@
DELETE FROM shopitems
WHERE itemid = 2030100; # Return Scroll - Banished Area (a custom item added in HeavenMS)

View File

@@ -385,7 +385,6 @@
2030016 - Phyllia's Warp Powder - Warp powder made by fairy Phyllia. Teleports you to Magatia when used inside the Nihal desert region.
2030019 - Return Scroll to Nautilus - This scroll enables you to return to the Pirate village, Nautilus. This is a one use item and will disappear after use.
2030020 - Return Scroll to New Leaf City - Use this scroll to venture back to New Leaf City whenever you want!
2030100 - Return Scroll - Banished Area - Returns you to the map where you were last banished. Requires immediate use and have changed neither maps nor channels.
2031000 - Masked Man's Invitation - An invitation from the Masked Man to the Halloween Party at the Haunted Mansion. Double-click to move straight to the mansion.
2031001 - Studio Invitation - An invitation to the studio for the event "For Guild Only".
2040000 - Scroll for Helmet for DEF - Improves the helmet's weapon def.\nSuccess rate:100%, weapon def. +1

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

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

View File

@@ -116,6 +116,7 @@ import server.events.Events;
import server.events.RescueGaga;
import server.events.gm.Fitness;
import server.events.gm.Ola;
import server.life.BanishInfo;
import server.life.MobSkill;
import server.life.MobSkillFactory;
import server.life.MobSkillId;
@@ -352,9 +353,6 @@ public class Character extends AbstractCharacterObject {
private int targetHpBarHash = 0;
private long targetHpBarTime = 0;
private long nextWarningTime = 0;
private int banishMap = -1;
private int banishSp = -1;
private long banishTime = 0;
private long lastExpGainTime;
private boolean pendingNameChange; //only used to change name on logout, not to be relied upon elsewhere
private long loginTime;
@@ -1356,48 +1354,14 @@ public class Character extends AbstractCharacterObject {
}
}
public boolean canRecoverLastBanish() {
return System.currentTimeMillis() - this.banishTime < MINUTES.toMillis(5);
}
public Pair<Integer, Integer> getLastBanishData() {
return new Pair<>(this.banishMap, this.banishSp);
}
public void clearBanishPlayerData() {
this.banishMap = -1;
this.banishSp = -1;
this.banishTime = 0;
}
public void setBanishPlayerData(int banishMap, int banishSp, long banishTime) {
this.banishMap = banishMap;
this.banishSp = banishSp;
this.banishTime = banishTime;
}
public void changeMapBanish(int mapid, String portal, String msg) {
if (YamlConfig.config.server.USE_SPIKES_AVOID_BANISH) {
for (Item it : this.getInventory(InventoryType.EQUIPPED).list()) {
if ((it.getFlag() & ItemConstants.SPIKES) == ItemConstants.SPIKES) {
return;
}
}
}
int banMap = this.getMapId();
int banSp = this.getMap().findClosestPlayerSpawnpoint(this.getPosition()).getId();
long banTime = System.currentTimeMillis();
if (msg != null) {
dropMessage(5, msg);
public void changeMapBanish(BanishInfo banishInfo) {
if (banishInfo.msg() != null) {
dropMessage(5, banishInfo.msg());
}
MapleMap map_ = getWarpMap(mapid);
Portal portal_ = map_.getPortal(portal);
Portal portal_ = map_.getPortal(banishInfo.portal());
changeMap(map_, portal_ != null ? portal_ : map_.getRandomPlayerSpawnpoint());
setBanishPlayerData(banMap, banSp, banTime);
}
public void changeMap(int map) {
@@ -1780,7 +1744,6 @@ public class Character extends AbstractCharacterObject {
this.mapTransitioning.set(true);
this.unregisterChairBuff();
this.clearBanishPlayerData();
Trade.cancelTrade(this, Trade.TradeResult.UNSUCCESSFUL_ANOTHER_MAP);
this.closePlayerInteractions();
@@ -2068,7 +2031,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 +2083,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 +6026,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

@@ -1518,7 +1518,6 @@ public class Client extends ChannelInboundHandlerAdapter {
player.getInventory(InventoryType.EQUIPPED).checked(false); //test
player.getMap().removePlayer(player);
player.clearBanishPlayerData();
player.getClient().getChannelServer().removePlayer(player);
player.saveCharToDB();

View File

@@ -105,7 +105,6 @@ public class ServerConfig {
public boolean USE_BUFF_MOST_SIGNIFICANT;
public boolean USE_BUFF_EVERLASTING;
public boolean USE_MULTIPLE_SAME_EQUIP_DROP;
public boolean USE_BANISHABLE_TOWN_SCROLL;
public boolean USE_ENABLE_FULL_RESPAWN;
public boolean USE_ENABLE_CHAT_LOG;
public boolean USE_REBIRTH_SYSTEM;
@@ -243,7 +242,6 @@ public class ServerConfig {
public boolean USE_EQUIPMNT_LVLUP_SLOTS;
public boolean USE_EQUIPMNT_LVLUP_POWER;
public boolean USE_EQUIPMNT_LVLUP_CASH;
public boolean USE_SPIKES_AVOID_BANISH;
public int MAX_EQUIPMNT_LVLUP_STAT_UP;
public int MAX_EQUIPMNT_STAT;
public int USE_EQUIPMNT_LVLUP;

View File

@@ -165,7 +165,6 @@ public class ItemId {
public static final int EYEDROP = 2050001;
public static final int TONIC = 2050002;
public static final int HOLY_WATER = 2050003;
public static final int ANTI_BANISH_SCROLL = 2030100;
private static final int DOJO_PARTY_ALL_CURE = 2022433;
private static final int CARNIVAL_PARTY_ALL_CURE = 2022163;
public static final int WHITE_ELIXIR = 2022544;
@@ -312,6 +311,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

@@ -134,11 +134,7 @@ public final class ItemConstants {
}
public static boolean isTownScroll(int itemId) {
return itemId >= 2030000 && itemId < ItemId.ANTI_BANISH_SCROLL;
}
public static boolean isAntibanishScroll(int itemId) {
return itemId == ItemId.ANTI_BANISH_SCROLL;
return itemId >= 2030000;
}
public static boolean isCleanSlate(int scrollId) {

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

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

@@ -23,23 +23,25 @@ import client.Character;
import client.Client;
import net.AbstractPacketHandler;
import net.packet.InPacket;
import server.life.LifeFactory.BanishInfo;
import server.life.BanishInfo;
import server.life.Monster;
public final class MobBanishPlayerHandler extends AbstractPacketHandler {
@Override
public final void handlePacket(InPacket p, Client c) {
int mobid = p.readInt(); // mob banish handling detected thanks to MedicOP
public void handlePacket(InPacket p, Client c) {
int mobId = p.readInt(); // mob banish handling detected thanks to MedicOP
Character chr = c.getPlayer();
Monster mob = chr.getMap().getMonsterById(mobid);
if (mob != null) {
BanishInfo banishInfo = mob.getBanish();
if (banishInfo != null) {
chr.changeMapBanish(banishInfo.getMap(), banishInfo.getPortal(), banishInfo.getMsg());
}
Monster mob = chr.getMap().getMonsterById(mobId);
if (mob == null) {
return;
}
BanishInfo banishInfo = mob.getBanish();
if (banishInfo == null) {
return;
}
chr.changeMapBanish(banishInfo);
}
}
}

View File

@@ -173,7 +173,7 @@ public final class MoveLifeHandler extends AbstractMovementPacketHandler {
if (banishPlayers != null) {
for (Character chr : banishPlayers) {
chr.changeMapBanish(monster.getBanish().getMap(), monster.getBanish().getPortal(), monster.getBanish().getMsg());
chr.changeMapBanish(monster.getBanish());
}
}
}

View File

@@ -289,7 +289,7 @@ public final class TakeDamageHandler extends AbstractPacketHandler {
}
for (Character player : banishPlayers) { // chill, if this list ever gets non-empty an attacker does exist, trust me :)
player.changeMapBanish(attacker.getBanish().getMap(), attacker.getBanish().getPortal(), attacker.getBanish().getMsg());
player.changeMapBanish(attacker.getBanish());
}
}
}

View File

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

View File

@@ -27,7 +27,6 @@ import client.Disease;
import client.inventory.InventoryType;
import client.inventory.Item;
import client.inventory.manipulator.InventoryManipulator;
import config.YamlConfig;
import constants.id.ItemId;
import constants.inventory.ItemConstants;
import net.AbstractPacketHandler;
@@ -73,23 +72,8 @@ public final class UseItemHandler extends AbstractPacketHandler {
remove(c, slot);
return;
} else if (ItemConstants.isTownScroll(itemId)) {
int banMap = chr.getMapId();
int banSp = chr.getMap().findClosestPlayerSpawnpoint(chr.getPosition()).getId();
long banTime = currentServerTime();
if (ii.getItemEffect(toUse.getItemId()).applyTo(chr)) {
if (YamlConfig.config.server.USE_BANISHABLE_TOWN_SCROLL) {
chr.setBanishPlayerData(banMap, banSp, banTime);
}
remove(c, slot);
}
return;
} else if (ItemConstants.isAntibanishScroll(itemId)) {
if (ii.getItemEffect(toUse.getItemId()).applyTo(chr)) {
remove(c, slot);
} else {
chr.dropMessage(5, "You cannot recover from a banish state at the moment.");
}
return;
}

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

View File

@@ -977,18 +977,8 @@ public class StatEffect {
Portal pt;
if (moveTo == MapId.NONE) {
if (sourceid != ItemId.ANTI_BANISH_SCROLL) {
target = applyto.getMap().getReturnMap();
pt = target.getRandomPlayerSpawnpoint();
} else {
if (!applyto.canRecoverLastBanish()) {
return false;
}
Pair<Integer, Integer> lastBanishInfo = applyto.getLastBanishData();
target = applyto.getWarpMap(lastBanishInfo.getLeft());
pt = target.getPortal(lastBanishInfo.getRight());
}
target = applyto.getMap().getReturnMap();
pt = target.getRandomPlayerSpawnpoint();
} else {
target = applyto.getClient().getWorldServer().getChannel(applyto.getClient().getChannel()).getMapFactory().getMap(moveTo);
int targetid = target.getId() / 10000000;

View File

@@ -0,0 +1,9 @@
package server.life;
import java.util.Objects;
public record BanishInfo(int map, String portal, String msg) {
public BanishInfo {
Objects.requireNonNull(portal, "BanishInfo portal");
}
}

View File

@@ -231,7 +231,10 @@ public class LifeFactory {
Data banishData = monsterInfoData.getChildByPath("ban");
if (banishData != null) {
stats.setBanishInfo(new BanishInfo(DataTool.getString("banMsg", banishData), DataTool.getInt("banMap/0/field", banishData, -1), DataTool.getString("banMap/0/portal", banishData, "sp")));
int map = DataTool.getInt("banMap/0/field", banishData, -1);
String portal = DataTool.getString("banMap/0/portal", banishData, "sp");
String msg = DataTool.getString("banMsg", banishData);
stats.setBanishInfo(new BanishInfo(map, portal, msg));
}
int noFlip = DataTool.getInt("noFlip", monsterInfoData, 0);
@@ -296,31 +299,6 @@ public class LifeFactory {
return DataTool.getString(nid + "/d0", npcStringData, "(...)");
}
public static class BanishInfo {
private final int map;
private final String portal;
private final String msg;
public BanishInfo(String msg, int map, String portal) {
this.msg = msg;
this.map = map;
this.portal = portal;
}
public int getMap() {
return map;
}
public String getPortal() {
return portal;
}
public String getMsg() {
return msg;
}
}
public static class loseItem {
private final int id;

View File

@@ -56,7 +56,6 @@ import org.slf4j.LoggerFactory;
import scripting.event.EventInstanceManager;
import server.StatEffect;
import server.TimerManager;
import server.life.LifeFactory.BanishInfo;
import server.loot.LootManager;
import server.maps.AbstractAnimatedMapObject;
import server.maps.MapObjectType;

View File

@@ -21,7 +21,6 @@
*/
package server.life;
import server.life.LifeFactory.BanishInfo;
import server.life.LifeFactory.loseItem;
import server.life.LifeFactory.selfDestruction;
import tools.Pair;

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

@@ -8,9 +8,9 @@ import provider.DataProviderFactory;
import provider.DataTool;
import provider.wz.DataType;
import provider.wz.WZFiles;
import server.life.BanishInfo;
import server.life.Element;
import server.life.ElementalEffectiveness;
import server.life.LifeFactory.BanishInfo;
import server.life.LifeFactory.loseItem;
import server.life.LifeFactory.selfDestruction;
import server.life.MobSkillId;
@@ -134,7 +134,10 @@ public class MonsterStatFetcher {
}
Data banishData = monsterInfoData.getChildByPath("ban");
if (banishData != null) {
stats.setBanishInfo(new BanishInfo(DataTool.getString("banMsg", banishData), DataTool.getInt("banMap/0/field", banishData, -1), DataTool.getString("banMap/0/portal", banishData, "sp")));
int map = DataTool.getInt("banMap/0/field", banishData, -1);
String portal = DataTool.getString("banMap/0/portal", banishData, "sp");
String msg = DataTool.getString("banMsg", banishData);
stats.setBanishInfo(new BanishInfo(map, portal, msg));
}
monsterStats.put(mid, stats);

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

View File

@@ -12,10 +12,6 @@
<int name="id" value="4000001"/>
<int name="count" value="100"/>
</imgdir>
<imgdir name="1">
<int name="id" value="2030100"/>
<int name="count" value="12"/>
</imgdir>
</imgdir>
</imgdir>
<imgdir name="1">

View File

@@ -210,7 +210,7 @@
<int name="moveTo" value="120000000"/>
</imgdir>
</imgdir>
<imgdir name="02030100">
<imgdir name="02030020">
<imgdir name="info">
<canvas name="icon" width="32" height="30">
<vector name="origin" x="0" y="29"/>
@@ -218,10 +218,10 @@
<canvas name="iconRaw" width="32" height="28">
<vector name="origin" x="0" y="29"/>
</canvas>
<int name="price" value="225"/>
<int name="price" value="250"/>
</imgdir>
<imgdir name="spec">
<int name="moveTo" value="999999999"/>
<int name="moveTo" value="600000000"/>
</imgdir>
</imgdir>
<imgdir name="02031000">
@@ -380,18 +380,4 @@
<int name="randomMoveInFieldSet" value="1"/>
</imgdir>
</imgdir>
<imgdir name="02030020">
<imgdir name="info">
<canvas name="icon" width="32" height="30">
<vector name="origin" x="0" y="29"/>
</canvas>
<canvas name="iconRaw" width="32" height="28">
<vector name="origin" x="0" y="29"/>
</canvas>
<int name="price" value="250"/>
</imgdir>
<imgdir name="spec">
<int name="moveTo" value="600000000"/>
</imgdir>
</imgdir>
</imgdir>

View File

@@ -1499,10 +1499,6 @@
<string name="name" value="Return Scroll to Nautilus"/>
<string name="desc" value="This scroll enables you to return to the Pirate village, Nautilus. This is a one-use item and will disappear after use."/>
</imgdir>
<imgdir name="2030100">
<string name="name" value="Return Scroll - Banished Area"/>
<string name="desc" value="Returns you to the map where you were last banished. Requires immediate use and have changed neither maps nor channels."/>
</imgdir>
<imgdir name="2031000">
<string name="name" value="Masked Man&apos;s Invitation"/>
<string name="desc" value="An invitation from the Masked Man to the Halloween Party at the Haunted Mansion. Double-click to move straight to the mansion."/>