Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
314916279a | ||
|
|
dbf1a1bb36 | ||
|
|
8f2b8dd013 | ||
|
|
8039852aa3 | ||
|
|
d3b567953d | ||
|
|
5064ee936a | ||
|
|
86da9b0b29 | ||
|
|
acac203e42 | ||
|
|
cdd1c8cb61 | ||
|
|
439753eb6d | ||
|
|
e30700de66 | ||
|
|
994d1723b6 | ||
|
|
2d40a89c55 | ||
|
|
802cc2b5f5 | ||
|
|
2ffca90d29 | ||
|
|
8f2c2dc08f | ||
|
|
cad10c4d5c | ||
|
|
aa3686ed0b | ||
|
|
3850b63cec | ||
|
|
a7df8a4f49 | ||
|
|
7071b13e41 | ||
|
|
2324ae7f9e | ||
|
|
402163c33d | ||
|
|
3356e42e71 | ||
|
|
205e263255 | ||
|
|
aab9823a06 | ||
|
|
0245e7d1af | ||
|
|
3f800c4a68 | ||
|
|
31e901ab6d | ||
|
|
94a08d86a0 | ||
|
|
bcc7bedbc9 | ||
|
|
a878a4f3f9 | ||
|
|
238c01baf4 | ||
|
|
5a4bdd343c | ||
|
|
d916502f58 | ||
|
|
1791365e0f | ||
|
|
de2a86c859 | ||
|
|
5aeed01e38 | ||
|
|
6ab1af99da | ||
|
|
c7b2d218ef | ||
|
|
01ae462b72 | ||
|
|
eb603e7ee9 | ||
|
|
b67b29def5 | ||
|
|
d22d9b603b | ||
|
|
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
|
||||
|
||||
@@ -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".
|
||||
|
||||
@@ -249,7 +249,6 @@ server:
|
||||
USE_ENFORCE_MERCHANT_SAVE: true #Forces automatic DB save on merchant owners, at every item movement on shop.
|
||||
USE_ENFORCE_MDOOR_POSITION: false #Forces mystic door to be spawned near spawnpoints.
|
||||
USE_SPAWN_CLEAN_MDOOR: false #Makes mystic doors to be spawned without deploy animation. This clears disconnecting issues that may happen when trying to cancel doors a couple seconds after deployment.
|
||||
USE_SPAWN_LOOT_ON_ANIMATION: false #Makes loot appear some time after the mob has been killed (following the mob death animation, instead of instantly).
|
||||
USE_SPAWN_RELEVANT_LOOT: true #Forces to only spawn loots that are collectable by the player or any of their party members.
|
||||
USE_ERASE_PERMIT_ON_OPENSHOP: true #Forces "shop permit" item to be consumed when player deploy his/her player shop.
|
||||
USE_ERASE_UNTRADEABLE_DROP: true #Forces flagged untradeable items to disappear when dropped.
|
||||
@@ -257,10 +256,8 @@ 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
|
||||
USE_MAP_OWNERSHIP_SYSTEM: false #Flag to enable/disable map ownership system
|
||||
USE_FISHING_SYSTEM: false #Flag to enable/disable custom fishing system
|
||||
USE_NPCS_SCRIPTABLE: true #Flag to enable/disable serverside predefined script NPCs.
|
||||
@@ -318,7 +315,6 @@ server:
|
||||
NAME_CHANGE_COOLDOWN: 2592000000 # (30*24*60*60*1000) Cooldown for name changes, default (GMS) is 30 days.
|
||||
WORLD_TRANSFER_COOLDOWN: 2592000000 # (30*24*60*60*1000) Cooldown for world tranfers, default is same as name change (30 days).
|
||||
INSTANT_NAME_CHANGE: false #Whether or not to wait for server restart to apply name changes. Does on reconnect otherwise (requires queries on every login).
|
||||
REBIRTH_NPC_ID: 9010021 #ID of the NPC that should be replaced with the rebirth mechanic, if enabled.
|
||||
|
||||
#Dangling Items/Locks Configuration
|
||||
ITEM_EXPIRE_TIME: 180000 # (3 * 60 * 1000) Time before items start disappearing. Recommended to be set up to 3 minutes.
|
||||
@@ -395,7 +391,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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
13
database/sql/migration/README.md
Normal file
13
database/sql/migration/README.md
Normal 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.
|
||||
2
database/sql/migration/v0.14.0.sql
Normal file
2
database/sql/migration/v0.14.0.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
DELETE FROM shopitems
|
||||
WHERE itemid = 2030100; # Return Scroll - Banished Area (a custom item added in HeavenMS)
|
||||
@@ -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
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>
|
||||
|
||||
|
||||
@@ -58,8 +58,8 @@ function action(mode, type, selection) {
|
||||
}
|
||||
}
|
||||
if (cm.getChar().getGender() == 1) {
|
||||
for (var i = 0; i < fface.length; i++) {
|
||||
pushIfItemExists(facenew, fface[i] + cm.getChar().getFace()
|
||||
for (var i = 0; i < fface_v.length; i++) {
|
||||
pushIfItemExists(facenew, fface_v[i] + cm.getChar().getFace()
|
||||
% 1000 - (cm.getChar().getFace()
|
||||
% 100));
|
||||
}
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
This file is part of the OdinMS Maple Story Server
|
||||
Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc>
|
||||
Matthias Butz <matze@odinms.de>
|
||||
Jan Christian Meyer <vimes@odinms.de>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation version 3 as published by
|
||||
the Free Software Foundation. You may not use, modify or distribute
|
||||
this program under any other version of the GNU Affero General Public
|
||||
License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/* Rebirth NPC
|
||||
@author Ronan
|
||||
@author wejrox
|
||||
*/
|
||||
var status;
|
||||
var jobId = 0;
|
||||
|
||||
function start() {
|
||||
status = -1;
|
||||
const YamlConfig = Java.type('config.YamlConfig');
|
||||
if (!YamlConfig.config.server.USE_REBIRTH_SYSTEM) {
|
||||
cm.sendOk("Rebirths aren't enabled on this server, how did you get here?");
|
||||
cm.dispose();
|
||||
return;
|
||||
}
|
||||
action(1, 0, 0);
|
||||
}
|
||||
|
||||
function action(mode, type, selection) {
|
||||
if (mode === 1) {
|
||||
status++;
|
||||
} else {
|
||||
cm.dispose();
|
||||
return;
|
||||
}
|
||||
if (status === 0) {
|
||||
cm.sendNext("Come to me when you want to be reborn again. You currently have a total of #r" + cm.getChar().getReborns() + " #krebirths.");
|
||||
} else if (status === 1) {
|
||||
cm.sendSimple("What do you want me to do today: \r\n \r\n #L0##bI want to be reborn!#l \r\n #L1##bNothing for now...#k#l");
|
||||
} else if (status === 2) {
|
||||
if (selection === 0) {
|
||||
if (cm.getChar().getLevel() === cm.getChar().getMaxClassLevel()) {
|
||||
cm.sendSimple("I see... and which path would you like to take? \r\n\r\n #L0##bExplorer (Beginner)#l \r\n #L1##bCygnus Knight (Noblesse)#l \r\n #L2##bAran (Legend)#l");
|
||||
} else {
|
||||
cm.sendOk("It looks like your journey has not yet ended... come back when you're level " + cm.getChar().getMaxClassLevel());
|
||||
cm.dispose();
|
||||
}
|
||||
} else if (selection === 1) {
|
||||
cm.sendOk("See you soon!")
|
||||
cm.dispose();
|
||||
}
|
||||
} else if (status === 3) {
|
||||
// 0 => beginner, 1000 => noblesse, 2000 => legend
|
||||
// makes this very easy :-)
|
||||
jobId = selection * 1000;
|
||||
|
||||
var job = "";
|
||||
if (selection === 0) job = "Beginner";
|
||||
else if (selection === 1) job = "Noblesse";
|
||||
else if (selection === 2) job = "Legend";
|
||||
cm.sendYesNo("Are you sure you want to be reborn as a " + job + "?");
|
||||
}
|
||||
else if (status === 4 && type === 1) {
|
||||
cm.getChar().executeRebornAsId(jobId);
|
||||
cm.sendOk("You have now been reborn. That's a total of #r" + cm.getChar().getReborns() + "#k rebirths");
|
||||
cm.dispose();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -153,7 +154,6 @@ import tools.LongTool;
|
||||
import tools.PacketCreator;
|
||||
import tools.Pair;
|
||||
import tools.Randomizer;
|
||||
import tools.exceptions.NotEnabledException;
|
||||
import tools.packets.WeddingPackets;
|
||||
|
||||
import java.awt.*;
|
||||
@@ -352,9 +352,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 +1353,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 +1743,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 +2030,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 +2082,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 +6025,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;
|
||||
@@ -10998,70 +10961,6 @@ public class Character extends AbstractCharacterObject {
|
||||
}
|
||||
}
|
||||
|
||||
public void setReborns(int value) {
|
||||
if (!YamlConfig.config.server.USE_REBIRTH_SYSTEM) {
|
||||
yellowMessage("Rebirth system is not enabled!");
|
||||
throw new NotEnabledException();
|
||||
}
|
||||
|
||||
try (Connection con = DatabaseConnection.getConnection();
|
||||
PreparedStatement ps = con.prepareStatement("UPDATE characters SET reborns=? WHERE id=?;")) {
|
||||
ps.setInt(1, value);
|
||||
ps.setInt(2, id);
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void addReborns() {
|
||||
setReborns(getReborns() + 1);
|
||||
}
|
||||
|
||||
public int getReborns() {
|
||||
if (!YamlConfig.config.server.USE_REBIRTH_SYSTEM) {
|
||||
yellowMessage("Rebirth system is not enabled!");
|
||||
throw new NotEnabledException();
|
||||
}
|
||||
|
||||
try (Connection con = DatabaseConnection.getConnection();
|
||||
PreparedStatement ps = con.prepareStatement("SELECT reborns FROM characters WHERE id=?;")) {
|
||||
ps.setInt(1, id);
|
||||
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
rs.next();
|
||||
return rs.getInt(1);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public void executeReborn() {
|
||||
// default to beginner: job id = 0
|
||||
// this prevents a breaking change
|
||||
executeRebornAs(Job.BEGINNER);
|
||||
}
|
||||
|
||||
public void executeRebornAsId(int jobId) {
|
||||
executeRebornAs(Job.getById(jobId));
|
||||
}
|
||||
|
||||
public void executeRebornAs(Job job) {
|
||||
if (!YamlConfig.config.server.USE_REBIRTH_SYSTEM) {
|
||||
yellowMessage("Rebirth system is not enabled!");
|
||||
throw new NotEnabledException();
|
||||
}
|
||||
if (getLevel() != getMaxClassLevel()) {
|
||||
return;
|
||||
}
|
||||
addReborns();
|
||||
changeJob(job);
|
||||
setLevel(0);
|
||||
levelUp(true);
|
||||
}
|
||||
|
||||
//EVENTS
|
||||
private byte team = 0;
|
||||
private Fitness fitness;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -552,16 +552,14 @@ public class AssignAPProcessor {
|
||||
return false;
|
||||
}
|
||||
|
||||
int hp = player.getMaxHp();
|
||||
int level_ = player.getLevel();
|
||||
if (hp < level_ * 14 + 148) {
|
||||
int hplose = -takeHp(player.getJob());
|
||||
if (player.getMaxHp() + hplose < getMinHp(player.getJob(), player.getLevel())) {
|
||||
player.message("You don't have the minimum HP pool required to swap.");
|
||||
c.sendPacket(PacketCreator.enableActions());
|
||||
return false;
|
||||
}
|
||||
|
||||
int curHp = player.getHp();
|
||||
int hplose = -takeHp(player.getJob());
|
||||
player.assignHP(hplose, -1);
|
||||
if (!YamlConfig.config.server.USE_FIXED_RATIO_HPMP_UPDATE) {
|
||||
player.updateHp(Math.max(1, curHp + hplose));
|
||||
@@ -583,29 +581,14 @@ public class AssignAPProcessor {
|
||||
return false;
|
||||
}
|
||||
|
||||
int mp = player.getMaxMp();
|
||||
int level = player.getLevel();
|
||||
Job job = player.getJob();
|
||||
|
||||
boolean canWash = true;
|
||||
if (job.isA(Job.SPEARMAN) && mp < 4 * level + 156) {
|
||||
canWash = false;
|
||||
} else if ((job.isA(Job.FIGHTER) || job.isA(Job.ARAN1)) && mp < 4 * level + 56) {
|
||||
canWash = false;
|
||||
} else if (job.isA(Job.THIEF) && job.getId() % 100 > 0 && mp < level * 14 - 4) {
|
||||
canWash = false;
|
||||
} else if (mp < level * 14 + 148) {
|
||||
canWash = false;
|
||||
}
|
||||
|
||||
if (!canWash) {
|
||||
int mplose = -takeMp(player.getJob());
|
||||
if (player.getMaxMp() + mplose < getMinMp(player.getJob(), player.getLevel())) {
|
||||
player.message("You don't have the minimum MP pool required to swap.");
|
||||
c.sendPacket(PacketCreator.enableActions());
|
||||
return false;
|
||||
}
|
||||
|
||||
int curMp = player.getMp();
|
||||
int mplose = -takeMp(job);
|
||||
player.assignMP(mplose, -1);
|
||||
if (!YamlConfig.config.server.USE_FIXED_RATIO_HPMP_UPDATE) {
|
||||
player.updateMp(Math.max(0, curMp + mplose));
|
||||
@@ -896,4 +879,109 @@ public class AssignAPProcessor {
|
||||
return MaxMP;
|
||||
}
|
||||
|
||||
public static int getMinHp(Job job, int level) {
|
||||
int multiplier = 0;
|
||||
int offset = 0;
|
||||
|
||||
if (job == Job.WARRIOR ||
|
||||
job.isA(Job.PAGE) ||
|
||||
job.isA(Job.SPEARMAN) ||
|
||||
job == Job.DAWNWARRIOR1 ||
|
||||
job == Job.ARAN1) {
|
||||
multiplier = 24; offset = 118;
|
||||
|
||||
} else if (job.isA(Job.FIGHTER) ||
|
||||
job.isA(Job.DAWNWARRIOR2) ||
|
||||
job.isA(Job.ARAN2)) {
|
||||
multiplier = 24; offset = 418;
|
||||
|
||||
} else if (job.isA(Job.MAGICIAN) ||
|
||||
job.isA(Job.BLAZEWIZARD1)) {
|
||||
multiplier = 10; offset = 54;
|
||||
|
||||
} else if (job == Job.BOWMAN ||
|
||||
job == Job.THIEF ||
|
||||
job == Job.WINDARCHER1 ||
|
||||
job == Job.NIGHTWALKER1) {
|
||||
multiplier = 20; offset = 58;
|
||||
|
||||
} else if (job.isA(Job.HUNTER) ||
|
||||
job.isA(Job.CROSSBOWMAN) ||
|
||||
job.isA(Job.ASSASSIN) ||
|
||||
job.isA(Job.BANDIT) ||
|
||||
job.isA(Job.WINDARCHER2) ||
|
||||
job.isA(Job.NIGHTWALKER2)) {
|
||||
multiplier = 20; offset = 358;
|
||||
|
||||
} else if (job == Job.PIRATE ||
|
||||
job == Job.THUNDERBREAKER1) {
|
||||
multiplier = 22; offset = 38;
|
||||
|
||||
} else if (job.isA(Job.BRAWLER) ||
|
||||
job.isA(Job.GUNSLINGER) ||
|
||||
job.isA(Job.THUNDERBREAKER2)) {
|
||||
multiplier = 22; offset = 338;
|
||||
|
||||
} else if (job == Job.BEGINNER ||
|
||||
job == Job.NOBLESSE) {
|
||||
multiplier = 12; offset = 38;
|
||||
}
|
||||
|
||||
return (multiplier * level) + offset;
|
||||
}
|
||||
|
||||
public static int getMinMp(Job job, int level) {
|
||||
int multiplier = 0;
|
||||
int offset = 0;
|
||||
|
||||
if (job == Job.WARRIOR ||
|
||||
job.isA(Job.FIGHTER) ||
|
||||
job.isA(Job.DAWNWARRIOR1) ||
|
||||
job.isA(Job.ARAN1)) {
|
||||
multiplier = 4; offset = 55;
|
||||
|
||||
} else if (job.isA(Job.PAGE) ||
|
||||
job.isA(Job.SPEARMAN)) {
|
||||
multiplier = 4; offset = 155;
|
||||
|
||||
} else if (job == Job.MAGICIAN ||
|
||||
job == Job.BLAZEWIZARD1) {
|
||||
multiplier = 22; offset = -1;
|
||||
|
||||
} else if (job.isA(Job.FP_WIZARD) ||
|
||||
job.isA(Job.IL_WIZARD) ||
|
||||
job.isA(Job.CLERIC) ||
|
||||
job.isA(Job.BLAZEWIZARD2)) {
|
||||
multiplier = 22; offset = 449;
|
||||
|
||||
} else if (job == Job.BOWMAN ||
|
||||
job == Job.THIEF ||
|
||||
job == Job.WINDARCHER1 ||
|
||||
job == Job.NIGHTWALKER1) {
|
||||
multiplier = 14; offset = -15;
|
||||
|
||||
} else if (job.isA(Job.HUNTER) ||
|
||||
job.isA(Job.CROSSBOWMAN) ||
|
||||
job.isA(Job.ASSASSIN) ||
|
||||
job.isA(Job.BANDIT) ||
|
||||
job.isA(Job.WINDARCHER2) ||
|
||||
job.isA(Job.NIGHTWALKER2)) {
|
||||
multiplier = 14; offset = 135;
|
||||
|
||||
} else if (job == Job.PIRATE ||
|
||||
job == Job.THUNDERBREAKER1) {
|
||||
multiplier = 18; offset = -55;
|
||||
|
||||
} else if (job.isA(Job.BRAWLER) ||
|
||||
job.isA(Job.GUNSLINGER) ||
|
||||
job.isA(Job.THUNDERBREAKER2)) {
|
||||
multiplier = 18; offset = 95;
|
||||
|
||||
} else if (job == Job.BEGINNER ||
|
||||
job == Job.NOBLESSE) {
|
||||
multiplier = 10; offset = -5;
|
||||
}
|
||||
|
||||
return (multiplier * level) + offset;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,6 @@ public class ServerConfig {
|
||||
public boolean USE_ENFORCE_MERCHANT_SAVE;
|
||||
public boolean USE_ENFORCE_MDOOR_POSITION;
|
||||
public boolean USE_SPAWN_CLEAN_MDOOR;
|
||||
public boolean USE_SPAWN_LOOT_ON_ANIMATION;
|
||||
public boolean USE_SPAWN_RELEVANT_LOOT;
|
||||
public boolean USE_ERASE_PERMIT_ON_OPENSHOP;
|
||||
public boolean USE_ERASE_UNTRADEABLE_DROP;
|
||||
@@ -105,10 +104,8 @@ 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;
|
||||
public boolean USE_MAP_OWNERSHIP_SYSTEM;
|
||||
public boolean USE_FISHING_SYSTEM;
|
||||
public boolean USE_NPCS_SCRIPTABLE;
|
||||
@@ -166,7 +163,6 @@ public class ServerConfig {
|
||||
public long NAME_CHANGE_COOLDOWN;
|
||||
public long WORLD_TRANSFER_COOLDOWN = NAME_CHANGE_COOLDOWN;//Cooldown for world tranfers, default is same as name change (30 days).
|
||||
public boolean INSTANT_NAME_CHANGE;
|
||||
public int REBIRTH_NPC_ID;
|
||||
|
||||
//Dangling Items/Locks Configuration
|
||||
public int ITEM_EXPIRE_TIME;
|
||||
@@ -243,7 +239,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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,6 @@ import server.life.MonsterDropEntry;
|
||||
import server.life.MonsterInformationProvider;
|
||||
import server.maps.MapItem;
|
||||
import server.maps.MapObject;
|
||||
import server.maps.MapObjectType;
|
||||
import server.maps.MapleMap;
|
||||
import tools.PacketCreator;
|
||||
import tools.Randomizer;
|
||||
@@ -113,14 +112,18 @@ import static java.util.concurrent.TimeUnit.MINUTES;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
|
||||
private static final int EXPLODED_MESO_SPREAD_DELAY = 100;
|
||||
private static final int EXPLODED_MESO_MAX_DELAY = 1000;
|
||||
|
||||
public static class AttackInfo {
|
||||
|
||||
public int numAttacked, numDamage, numAttackedAndDamage, skill, skilllevel, stance, direction, rangedirection, charge, display;
|
||||
public Map<Integer, List<Integer>> allDamage;
|
||||
public Map<Integer, AttackTarget> targets;
|
||||
public boolean ranged, magic;
|
||||
public int speed = 4;
|
||||
public Point position = new Point();
|
||||
public List<Integer> explodedMesos;
|
||||
public Short attackDelay;
|
||||
|
||||
public StatEffect getAttackEffect(Character chr, Skill theSkill) {
|
||||
Skill mySkill = theSkill;
|
||||
@@ -146,6 +149,9 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: add position
|
||||
public record AttackTarget(short delay, List<Integer> damageLines) {}
|
||||
|
||||
protected void applyAttack(AttackInfo attack, final Character player, int attackCount) {
|
||||
final MapleMap map = player.getMap();
|
||||
if (map.isOwnershipRestricted(player)) {
|
||||
@@ -207,50 +213,14 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
//WTF IS THIS F3,1
|
||||
/*if (attackCount != attack.numDamage && attack.skill != ChiefBandit.MESO_EXPLOSION && attack.skill != NightWalker.VAMPIRE && attack.skill != WindArcher.WIND_SHOT && attack.skill != Aran.COMBO_SMASH && attack.skill != Aran.COMBO_FENRIR && attack.skill != Aran.COMBO_TEMPEST && attack.skill != NightLord.NINJA_AMBUSH && attack.skill != Shadower.NINJA_AMBUSH) {
|
||||
return;
|
||||
}*/
|
||||
|
||||
int totDamage = 0;
|
||||
|
||||
if (attack.skill == ChiefBandit.MESO_EXPLOSION) {
|
||||
int delay = 0;
|
||||
for (Integer oned : attack.allDamage.keySet()) {
|
||||
MapObject mapobject = map.getMapObject(oned);
|
||||
if (mapobject != null && mapobject.getType() == MapObjectType.ITEM) {
|
||||
final MapItem mapitem = (MapItem) mapobject;
|
||||
if (mapitem.getMeso() == 0) { //Maybe it is possible some how?
|
||||
return;
|
||||
}
|
||||
|
||||
mapitem.lockItem();
|
||||
try {
|
||||
if (mapitem.isPickedUp()) {
|
||||
return;
|
||||
}
|
||||
TimerManager.getInstance().schedule(() -> {
|
||||
mapitem.lockItem();
|
||||
try {
|
||||
if (mapitem.isPickedUp()) {
|
||||
return;
|
||||
}
|
||||
map.pickItemDrop(PacketCreator.removeItemFromMap(mapitem.getObjectId(), 4, 0), mapitem);
|
||||
} finally {
|
||||
mapitem.unlockItem();
|
||||
}
|
||||
}, delay);
|
||||
delay += 100;
|
||||
} finally {
|
||||
mapitem.unlockItem();
|
||||
}
|
||||
} else if (mapobject != null && mapobject.getType() != MapObjectType.MONSTER) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
removeExplodedMesos(map, attack);
|
||||
}
|
||||
for (Integer oned : attack.allDamage.keySet()) {
|
||||
final Monster monster = map.getMonsterByOid(oned);
|
||||
|
||||
for (Map.Entry<Integer, AttackTarget> target : attack.targets.entrySet()) {
|
||||
final Monster monster = map.getMonsterByOid(target.getKey());
|
||||
if (monster != null) {
|
||||
double distance = player.getPosition().distanceSq(monster.getPosition());
|
||||
double distanceToDetect = 200000.0;
|
||||
@@ -285,7 +255,7 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
|
||||
}
|
||||
|
||||
int totDamageToOneMonster = 0;
|
||||
List<Integer> onedList = attack.allDamage.get(oned);
|
||||
List<Integer> onedList = target.getValue().damageLines();
|
||||
|
||||
if (attack.magic) { // thanks BHB, Alex (CanIGetaPR) for noticing no immunity status check here
|
||||
if (monster.isBuffed(MonsterStatus.MAGIC_IMMUNITY)) {
|
||||
@@ -321,7 +291,7 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
|
||||
Skill pickpocket = SkillFactory.getSkill(ChiefBandit.PICKPOCKET);
|
||||
int picklv = (player.isGM()) ? pickpocket.getMaxLevel() : player.getSkillLevel(pickpocket);
|
||||
if (picklv > 0) {
|
||||
int delay = 0;
|
||||
short delay = 0;
|
||||
final int maxmeso = player.getBuffedValue(BuffStat.PICKPOCKET);
|
||||
for (Integer eachd : onedList) {
|
||||
eachd += Integer.MAX_VALUE;
|
||||
@@ -334,7 +304,9 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
|
||||
eachdf = eachd;
|
||||
}
|
||||
|
||||
TimerManager.getInstance().schedule(() -> map.spawnMesoDrop(Math.min((int) Math.max(((double) eachdf / (double) 20000) * (double) maxmeso, 1), maxmeso), new Point((int) (monster.getPosition().getX() + Randomizer.nextInt(100) - 50), (int) (monster.getPosition().getY())), monster, player, true, (byte) 2), delay);
|
||||
int meso = Math.min((int) Math.max(((double) eachdf / (double) 20000) * (double) maxmeso, 1), maxmeso);
|
||||
Point position = new Point((int) (monster.getPosition().getX() + Randomizer.nextInt(100) - 50), (int) (monster.getPosition().getY()));
|
||||
map.spawnMesoDrop(meso, position, monster, player, true, (byte) 2, delay);
|
||||
delay += 100;
|
||||
}
|
||||
}
|
||||
@@ -360,7 +332,7 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
|
||||
List<MonsterDropEntry> toSteal = new ArrayList<>();
|
||||
toSteal.add(mi.retrieveDrop(monster.getId()).get(i));
|
||||
|
||||
map.dropItemsFromMonster(toSteal, player, monster);
|
||||
map.dropItemsFromMonster(toSteal, player, monster, target.getValue().delay());
|
||||
monster.addStolen(toSteal.get(0).itemId);
|
||||
}
|
||||
}
|
||||
@@ -480,7 +452,7 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
|
||||
StatEffect mortal = mortalBlow.getEffect(skillLevel);
|
||||
if (monster.getHp() <= (monster.getStats().getHp() * mortal.getX()) / 100) {
|
||||
if (Randomizer.rand(1, 100) <= mortal.getY()) {
|
||||
map.damageMonster(player, monster, Integer.MAX_VALUE); // thanks Conrad for noticing reduced EXP gain from skill kill
|
||||
map.damageMonster(player, monster, Integer.MAX_VALUE, target.getValue().delay()); // thanks Conrad for noticing reduced EXP gain from skill kill
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -546,7 +518,7 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
|
||||
map.broadcastMessage(PacketCreator.damageMonster(monster.getObjectId(), totDamageToOneMonster));
|
||||
}
|
||||
|
||||
map.damageMonster(player, monster, totDamageToOneMonster);
|
||||
map.damageMonster(player, monster, totDamageToOneMonster, target.getValue().delay());
|
||||
}
|
||||
if (monster.isBuffed(MonsterStatus.WEAPON_REFLECT) && !attack.magic) {
|
||||
for (MobSkillId msId : monster.getSkills()) {
|
||||
@@ -573,7 +545,8 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private static void damageMonsterWithSkill(final Character attacker, final MapleMap map, final Monster monster, final int damage, int skillid, int fixedTime) {
|
||||
private static void damageMonsterWithSkill(final Character attacker, final MapleMap map, final Monster monster,
|
||||
final int damage, int skillid, int fixedTime) {
|
||||
int animationTime;
|
||||
|
||||
if (fixedTime == 0) {
|
||||
@@ -600,7 +573,7 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
|
||||
ret.numAttackedAndDamage = p.readByte();
|
||||
ret.numAttacked = (ret.numAttackedAndDamage >>> 4) & 0xF;
|
||||
ret.numDamage = ret.numAttackedAndDamage & 0xF;
|
||||
ret.allDamage = new HashMap<>();
|
||||
ret.targets = new HashMap<>();
|
||||
ret.skill = p.readInt();
|
||||
ret.ranged = ranged;
|
||||
ret.magic = magic;
|
||||
@@ -623,41 +596,9 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
|
||||
ret.direction = p.readByte();
|
||||
ret.stance = p.readByte();
|
||||
if (ret.skill == ChiefBandit.MESO_EXPLOSION) {
|
||||
if (ret.numAttackedAndDamage == 0) {
|
||||
p.skip(10);
|
||||
int bullets = p.readByte();
|
||||
for (int j = 0; j < bullets; j++) {
|
||||
int mesoid = p.readInt();
|
||||
p.skip(1);
|
||||
ret.allDamage.put(mesoid, null);
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
p.skip(6);
|
||||
}
|
||||
for (int i = 0; i < ret.numAttacked + 1; i++) {
|
||||
int oid = p.readInt();
|
||||
if (i < ret.numAttacked) {
|
||||
p.skip(12);
|
||||
int bullets = p.readByte();
|
||||
List<Integer> allDamageNumbers = new ArrayList<>();
|
||||
for (int j = 0; j < bullets; j++) {
|
||||
int damage = p.readInt();
|
||||
allDamageNumbers.add(damage);
|
||||
}
|
||||
ret.allDamage.put(oid, allDamageNumbers);
|
||||
p.skip(4);
|
||||
} else {
|
||||
int bullets = p.readByte();
|
||||
for (int j = 0; j < bullets; j++) {
|
||||
int mesoid = p.readInt();
|
||||
p.skip(1);
|
||||
ret.allDamage.put(mesoid, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
return parseMesoExplosion(p, ret);
|
||||
}
|
||||
|
||||
if (ranged) {
|
||||
p.readByte();
|
||||
ret.speed = p.readByte();
|
||||
@@ -814,10 +755,12 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
|
||||
}
|
||||
for (int i = 0; i < ret.numAttacked; i++) {
|
||||
int oid = p.readInt();
|
||||
p.skip(14);
|
||||
List<Integer> allDamageNumbers = new ArrayList<>();
|
||||
Monster monster = chr.getMap().getMonsterByOid(oid);
|
||||
|
||||
p.skip(4);
|
||||
Point curPos = p.readPos();
|
||||
Point nextPos = p.readPos();
|
||||
short delay = p.readShort();
|
||||
List<Integer> damageLines = new ArrayList<>();
|
||||
final Monster monster = chr.getMap().getMonsterByOid(oid);
|
||||
if (chr.getBuffEffect(BuffStat.WK_CHARGE) != null) {
|
||||
// Charge, so now we need to check elemental effectiveness
|
||||
int sourceID = chr.getBuffSource(BuffStat.WK_CHARGE);
|
||||
@@ -941,12 +884,12 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
|
||||
}
|
||||
}
|
||||
|
||||
allDamageNumbers.add(damage);
|
||||
damageLines.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) {
|
||||
p.skip(4);
|
||||
}
|
||||
ret.allDamage.put(oid, allDamageNumbers);
|
||||
ret.targets.put(oid, new AttackTarget(delay, damageLines));
|
||||
}
|
||||
if (ret.skill == NightWalker.POISON_BOMB) { // Poison Bomb
|
||||
p.skip(4);
|
||||
@@ -955,7 +898,67 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static int rand(int l, int u) {
|
||||
return (int) ((Math.random() * (u - l + 1)) + l);
|
||||
private AttackInfo parseMesoExplosion(InPacket p, AttackInfo attackInfo) {
|
||||
p.skip(6);
|
||||
|
||||
Map<Integer, List<Integer>> targetDamage = new HashMap<>();
|
||||
for (int i = 0; i < attackInfo.numAttacked; i++) {
|
||||
int mobOid = p.readInt();
|
||||
p.skip(4);
|
||||
Point curPos = p.readPos();
|
||||
Point nextPos = p.readPos();
|
||||
int damageLines = p.readByte();
|
||||
List<Integer> allDamageNumbers = new ArrayList<>();
|
||||
for (int j = 0; j < damageLines; j++) {
|
||||
int damage = p.readInt();
|
||||
allDamageNumbers.add(damage);
|
||||
}
|
||||
p.skip(4);
|
||||
targetDamage.put(mobOid, allDamageNumbers);
|
||||
}
|
||||
|
||||
p.skip(4);
|
||||
|
||||
List<Integer> explodedMesos = new ArrayList<>();
|
||||
int explodedMesoCount = p.readByte();
|
||||
for (int j = 0; j < explodedMesoCount; j++) {
|
||||
int mesoOid = p.readInt();
|
||||
p.skip(1);
|
||||
explodedMesos.add(mesoOid);
|
||||
}
|
||||
attackInfo.explodedMesos = explodedMesos;
|
||||
|
||||
final short attackDelay = p.readShort();
|
||||
attackInfo.attackDelay = attackDelay;
|
||||
|
||||
Map<Integer, AttackTarget> targets = new HashMap<>();
|
||||
targetDamage.forEach((id, damage) -> targets.put(id, new AttackTarget(attackDelay, damage)));
|
||||
attackInfo.targets = targets;
|
||||
return attackInfo;
|
||||
}
|
||||
|
||||
private void removeExplodedMesos(MapleMap map, AttackInfo attack) {
|
||||
int index = 0;
|
||||
for (Integer mesoId : attack.explodedMesos) {
|
||||
MapObject mapobject = map.getMapObject(mesoId);
|
||||
if (!(mapobject instanceof MapItem mapItem)) {
|
||||
return;
|
||||
}
|
||||
if (mapItem.getMeso() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
mapItem.lockItem();
|
||||
try {
|
||||
if (mapItem.isPickedUp()) {
|
||||
return;
|
||||
}
|
||||
int delay = attack.attackDelay + (index++ % 5) * EXPLODED_MESO_SPREAD_DELAY;
|
||||
delay = Math.min(delay, EXPLODED_MESO_MAX_DELAY);
|
||||
map.pickItemDrop(PacketCreator.removeExplodedMesoFromMap(mapItem.getObjectId(), (short) delay), mapItem);
|
||||
} finally {
|
||||
mapItem.unlockItem();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ public final class AdminCommandHandler extends AbstractPacketHandler {
|
||||
for (int x = 0; x < amount; x++) {
|
||||
Monster monster = (Monster) monsterx.get(x);
|
||||
if (monster.getId() == mobToKill) {
|
||||
c.getPlayer().getMap().killMonster(monster, c.getPlayer(), true);
|
||||
c.getPlayer().getMap().killMonster(monster, c.getPlayer(), true, (short) 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -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,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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,9 @@ public final class CloseRangeDamageHandler extends AbstractDealDamageHandler {
|
||||
c.sendPacket(PacketCreator.getEnergy("energy", chr.getDojoEnergy()));
|
||||
}
|
||||
|
||||
chr.getMap().broadcastMessage(chr, PacketCreator.closeRangeAttack(chr, attack.skill, attack.skilllevel, attack.stance, attack.numAttackedAndDamage, attack.allDamage, attack.speed, attack.direction, attack.display), false, true);
|
||||
chr.getMap().broadcastMessage(chr, PacketCreator.closeRangeAttack(chr, attack.skill, attack.skilllevel,
|
||||
attack.stance, attack.numAttackedAndDamage, attack.targets, attack.speed, attack.direction,
|
||||
attack.display), false, true);
|
||||
int numFinisherOrbs = 0;
|
||||
Integer comboBuff = chr.getBuffedValue(BuffStat.COMBO);
|
||||
if (GameConstants.isFinisherSkill(attack.skill)) {
|
||||
@@ -139,9 +141,9 @@ public final class CloseRangeDamageHandler extends AbstractDealDamageHandler {
|
||||
}
|
||||
if (attack.numAttacked > 0 && attack.skill == DragonKnight.SACRIFICE) {
|
||||
int totDamageToOneMonster = 0; // sacrifice attacks only 1 mob with 1 attack
|
||||
final Iterator<List<Integer>> dmgIt = attack.allDamage.values().iterator();
|
||||
final Iterator<AttackTarget> dmgIt = attack.targets.values().iterator();
|
||||
if (dmgIt.hasNext()) {
|
||||
totDamageToOneMonster = dmgIt.next().get(0);
|
||||
totDamageToOneMonster = dmgIt.next().damageLines().getFirst();
|
||||
}
|
||||
|
||||
chr.safeAddHP(-1 * totDamageToOneMonster * attack.getAttackEffect(chr, null).getX() / 100);
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -66,7 +66,8 @@ public final class MagicDamageHandler extends AbstractDealDamageHandler {
|
||||
}
|
||||
|
||||
int charge = (attack.skill == Evan.FIRE_BREATH || attack.skill == Evan.ICE_BREATH || attack.skill == FPArchMage.BIG_BANG || attack.skill == ILArchMage.BIG_BANG || attack.skill == Bishop.BIG_BANG) ? attack.charge : -1;
|
||||
Packet packet = PacketCreator.magicAttack(chr, attack.skill, attack.skilllevel, attack.stance, attack.numAttackedAndDamage, attack.allDamage, charge, attack.speed, attack.direction, attack.display);
|
||||
Packet packet = PacketCreator.magicAttack(chr, attack.skill, attack.skilllevel, attack.stance,
|
||||
attack.numAttackedAndDamage, attack.targets, charge, attack.speed, attack.direction, attack.display);
|
||||
|
||||
chr.getMap().broadcastMessage(chr, packet, false, true);
|
||||
StatEffect effect = attack.getAttackEffect(chr, null);
|
||||
@@ -84,8 +85,8 @@ public final class MagicDamageHandler extends AbstractDealDamageHandler {
|
||||
Skill eaterSkill = SkillFactory.getSkill((chr.getJob().getId() - (chr.getJob().getId() % 10)) * 10000);// MP Eater, works with right job
|
||||
int eaterLevel = chr.getSkillLevel(eaterSkill);
|
||||
if (eaterLevel > 0) {
|
||||
for (Integer singleDamage : attack.allDamage.keySet()) {
|
||||
eaterSkill.getEffect(eaterLevel).applyPassive(chr, chr.getMap().getMapObject(singleDamage), 0);
|
||||
for (Integer oid : attack.targets.keySet()) {
|
||||
eaterSkill.getEffect(eaterLevel).applyPassive(chr, chr.getMap().getMapObject(oid), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,8 @@ public final class MesoDropHandler extends AbstractPacketHandler {
|
||||
if (player.attemptCatchFish(meso)) {
|
||||
player.getMap().disappearingMesoDrop(meso, player, player, player.getPosition());
|
||||
} else {
|
||||
player.getMap().spawnMesoDrop(meso, player.getPosition(), player, player, true, (byte) 2);
|
||||
player.getMap().spawnMesoDrop(meso, player.getPosition(), player, player, true, (byte) 2,
|
||||
(short) 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,21 +57,24 @@ public final class MobDamageMobHandler extends AbstractPacketHandler {
|
||||
Monster attacker = map.getMonsterByOid(from);
|
||||
Monster damaged = map.getMonsterByOid(to);
|
||||
|
||||
if (attacker != null && damaged != null) {
|
||||
int maxDmg = calcMaxDamage(attacker, damaged, magic); // thanks Darter (YungMoozi) for reporting unchecked dmg
|
||||
|
||||
if (dmg > maxDmg) {
|
||||
AutobanFactory.DAMAGE_HACK.alert(c.getPlayer(), "Possible packet editing hypnotize damage exploit."); // thanks Rien dev team
|
||||
String attackerName = MonsterInformationProvider.getInstance().getMobNameFromId(attacker.getId());
|
||||
String damagedName = MonsterInformationProvider.getInstance().getMobNameFromId(damaged.getId());
|
||||
log.warn("Chr {} had hypnotized {} to attack {} with damage {} (max: {})", c.getPlayer().getName(),
|
||||
attackerName, damagedName, dmg, maxDmg);
|
||||
dmg = maxDmg;
|
||||
}
|
||||
|
||||
map.damageMonster(chr, damaged, dmg);
|
||||
map.broadcastMessage(chr, PacketCreator.damageMonster(to, dmg), false);
|
||||
if (attacker == null || damaged == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int maxDmg = calcMaxDamage(attacker, damaged, magic); // thanks Darter (YungMoozi) for reporting unchecked dmg
|
||||
|
||||
if (dmg > maxDmg) {
|
||||
AutobanFactory.DAMAGE_HACK.alert(c.getPlayer(), "Possible packet editing hypnotize damage exploit."); // thanks Rien dev team
|
||||
String attackerName = MonsterInformationProvider.getInstance().getMobNameFromId(attacker.getId());
|
||||
String damagedName = MonsterInformationProvider.getInstance().getMobNameFromId(damaged.getId());
|
||||
log.warn("Chr {} had hypnotized {} to attack {} with damage {} (max: {})", c.getPlayer().getName(),
|
||||
attackerName, damagedName, dmg, maxDmg);
|
||||
dmg = maxDmg;
|
||||
}
|
||||
|
||||
map.damageMonster(chr, damaged, dmg);
|
||||
map.broadcastMessage(chr, PacketCreator.damageMonster(to, dmg), false);
|
||||
|
||||
}
|
||||
|
||||
private static int calcMaxDamage(Monster attacker, Monster damaged, boolean magic) {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,8 +70,6 @@ public final class NPCTalkHandler extends AbstractPacketHandler {
|
||||
NPCScriptManager.getInstance().start(c, npc.getId(), "gachapon", null);
|
||||
} else if (npc.getName().endsWith("Maple TV")) {
|
||||
NPCScriptManager.getInstance().start(c, npc.getId(), "mapleTV", null);
|
||||
} else if (YamlConfig.config.server.USE_REBIRTH_SYSTEM && npc.getId() == YamlConfig.config.server.REBIRTH_NPC_ID) {
|
||||
NPCScriptManager.getInstance().start(c, npc.getId(), "rebirth", null);
|
||||
} else {
|
||||
boolean hasNpcScript = NPCScriptManager.getInstance().start(c, npc.getId(), oid, null);
|
||||
if (!hasNpcScript) {
|
||||
@@ -97,4 +95,4 @@ public final class NPCTalkHandler extends AbstractPacketHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,11 +448,6 @@ public final class PlayerLoggedinHandler extends AbstractPacketHandler {
|
||||
Entry::getValue
|
||||
));
|
||||
|
||||
// Any npc be specified as the rebirth npc. Allow the npc to use custom scripts explicitly.
|
||||
if (YamlConfig.config.server.USE_REBIRTH_SYSTEM) {
|
||||
npcsIds.put(YamlConfig.config.server.REBIRTH_NPC_ID, "Rebirth");
|
||||
}
|
||||
|
||||
c.sendPacket(PacketCreator.setNPCScriptable(npcsIds));
|
||||
}
|
||||
|
||||
|
||||
@@ -83,17 +83,23 @@ public final class RangedAttackHandler extends AbstractDealDamageHandler {
|
||||
}
|
||||
|
||||
if (attack.skill == Buccaneer.ENERGY_ORB || attack.skill == ThunderBreaker.SPARK || attack.skill == Shadower.TAUNT || attack.skill == NightLord.TAUNT) {
|
||||
chr.getMap().broadcastMessage(chr, PacketCreator.rangedAttack(chr, attack.skill, attack.skilllevel, attack.stance, attack.numAttackedAndDamage, 0, attack.allDamage, attack.speed, attack.direction, attack.display), false);
|
||||
chr.getMap().broadcastMessage(chr, PacketCreator.rangedAttack(chr, attack.skill, attack.skilllevel,
|
||||
attack.stance, attack.numAttackedAndDamage, 0, attack.targets, attack.speed,
|
||||
attack.direction, attack.display), false);
|
||||
applyAttack(attack, chr, 1);
|
||||
} else if (attack.skill == ThunderBreaker.SHARK_WAVE && chr.getSkillLevel(ThunderBreaker.SHARK_WAVE) > 0) {
|
||||
chr.getMap().broadcastMessage(chr, PacketCreator.rangedAttack(chr, attack.skill, attack.skilllevel, attack.stance, attack.numAttackedAndDamage, 0, attack.allDamage, attack.speed, attack.direction, attack.display), false);
|
||||
chr.getMap().broadcastMessage(chr, PacketCreator.rangedAttack(chr, attack.skill, attack.skilllevel,
|
||||
attack.stance, attack.numAttackedAndDamage, 0, attack.targets, attack.speed,
|
||||
attack.direction, attack.display), false);
|
||||
applyAttack(attack, chr, 1);
|
||||
|
||||
for (int i = 0; i < attack.numAttacked; i++) {
|
||||
chr.handleEnergyChargeGain();
|
||||
}
|
||||
} else if (attack.skill == Aran.COMBO_SMASH || attack.skill == Aran.COMBO_FENRIR || attack.skill == Aran.COMBO_TEMPEST) {
|
||||
chr.getMap().broadcastMessage(chr, PacketCreator.rangedAttack(chr, attack.skill, attack.skilllevel, attack.stance, attack.numAttackedAndDamage, 0, attack.allDamage, attack.speed, attack.direction, attack.display), false);
|
||||
chr.getMap().broadcastMessage(chr, PacketCreator.rangedAttack(chr, attack.skill, attack.skilllevel,
|
||||
attack.stance, attack.numAttackedAndDamage, 0, attack.targets, attack.speed,
|
||||
attack.direction, attack.display), false);
|
||||
if (attack.skill == Aran.COMBO_SMASH && chr.getCombo() >= 30) {
|
||||
chr.setCombo((short) 0);
|
||||
applyAttack(attack, chr, 1);
|
||||
@@ -213,10 +219,14 @@ public final class RangedAttackHandler extends AbstractDealDamageHandler {
|
||||
case 3221001: // Pierce
|
||||
case 5221004: // Rapid Fire
|
||||
case 13111002: // KoC Hurricane
|
||||
packet = PacketCreator.rangedAttack(chr, attack.skill, attack.skilllevel, attack.rangedirection, attack.numAttackedAndDamage, visProjectile, attack.allDamage, attack.speed, attack.direction, attack.display);
|
||||
packet = PacketCreator.rangedAttack(chr, attack.skill, attack.skilllevel, attack.rangedirection,
|
||||
attack.numAttackedAndDamage, visProjectile, attack.targets, attack.speed,
|
||||
attack.direction, attack.display);
|
||||
break;
|
||||
default:
|
||||
packet = PacketCreator.rangedAttack(chr, attack.skill, attack.skilllevel, attack.stance, attack.numAttackedAndDamage, visProjectile, attack.allDamage, attack.speed, attack.direction, attack.display);
|
||||
packet = PacketCreator.rangedAttack(chr, attack.skill, attack.skilllevel, attack.stance,
|
||||
attack.numAttackedAndDamage, visProjectile, attack.targets, attack.speed,
|
||||
attack.direction, attack.display);
|
||||
break;
|
||||
}
|
||||
chr.getMap().broadcastMessage(chr, packet, false, true);
|
||||
|
||||
@@ -41,31 +41,14 @@ import server.life.MonsterInformationProvider;
|
||||
import server.maps.Summon;
|
||||
import tools.PacketCreator;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class SummonDamageHandler extends AbstractDealDamageHandler {
|
||||
private static final Logger log = LoggerFactory.getLogger(SummonDamageHandler.class);
|
||||
|
||||
public final class SummonAttackEntry {
|
||||
|
||||
private final int monsterOid;
|
||||
private final int damage;
|
||||
|
||||
public SummonAttackEntry(int monsterOid, int damage) {
|
||||
this.monsterOid = monsterOid;
|
||||
this.damage = damage;
|
||||
}
|
||||
|
||||
public int getMonsterOid() {
|
||||
return monsterOid;
|
||||
}
|
||||
|
||||
public int getDamage() {
|
||||
return damage;
|
||||
}
|
||||
|
||||
}
|
||||
public record SummonAttackTarget(int monsterOid, int damage, short delay) {}
|
||||
|
||||
@Override
|
||||
public void handlePacket(InPacket p, Client c) {
|
||||
@@ -86,17 +69,21 @@ public final class SummonDamageHandler extends AbstractDealDamageHandler {
|
||||
Skill summonSkill = SkillFactory.getSkill(summon.getSkill());
|
||||
StatEffect summonEffect = summonSkill.getEffect(summon.getSkillLevel());
|
||||
p.skip(4);
|
||||
List<SummonAttackEntry> allDamage = new ArrayList<>();
|
||||
List<SummonAttackTarget> targets = new ArrayList<>();
|
||||
byte direction = p.readByte();
|
||||
int numAttacked = p.readByte();
|
||||
p.skip(8); // I failed lol (mob x,y and summon x,y), Thanks Gerald
|
||||
for (int x = 0; x < numAttacked; x++) {
|
||||
int monsterOid = p.readInt(); // attacked oid
|
||||
p.skip(18);
|
||||
p.skip(8);
|
||||
Point curPos = p.readPos();
|
||||
Point nextPos = p.readPos();
|
||||
short delay = p.readShort();
|
||||
int damage = p.readInt();
|
||||
allDamage.add(new SummonAttackEntry(monsterOid, damage));
|
||||
targets.add(new SummonAttackTarget(monsterOid, damage, delay));
|
||||
}
|
||||
player.getMap().broadcastMessage(player, PacketCreator.summonAttack(player.getId(), summon.getObjectId(), direction, allDamage), summon.getPosition());
|
||||
player.getMap().broadcastMessage(player, PacketCreator.summonAttack(player.getId(), summon.getObjectId(),
|
||||
direction, targets), summon.getPosition());
|
||||
|
||||
if (player.getMap().isOwnershipRestricted(player)) {
|
||||
return;
|
||||
@@ -104,25 +91,28 @@ public final class SummonDamageHandler extends AbstractDealDamageHandler {
|
||||
|
||||
boolean magic = summonEffect.getWatk() == 0;
|
||||
int maxDmg = calcMaxDamage(summonEffect, player, magic); // thanks Darter (YungMoozi) for reporting unchecked max dmg
|
||||
for (SummonAttackEntry attackEntry : allDamage) {
|
||||
int damage = attackEntry.getDamage();
|
||||
Monster target = player.getMap().getMonsterByOid(attackEntry.getMonsterOid());
|
||||
if (target != null) {
|
||||
if (damage > maxDmg) {
|
||||
AutobanFactory.DAMAGE_HACK.alert(c.getPlayer(), "Possible packet editing summon damage exploit.");
|
||||
final String mobName = MonsterInformationProvider.getInstance().getMobNameFromId(target.getId());
|
||||
log.info("Possible exploit - chr {} used a summon of skillId {} to attack {} with damage {} (max: {})",
|
||||
c.getPlayer().getName(), summon.getSkill(), mobName, damage, maxDmg);
|
||||
damage = maxDmg;
|
||||
}
|
||||
|
||||
if (damage > 0 && summonEffect.getMonsterStati().size() > 0) {
|
||||
if (summonEffect.makeChanceResult()) {
|
||||
target.applyStatus(player, new MonsterStatusEffect(summonEffect.getMonsterStati(), summonSkill, null, false), summonEffect.isPoison(), 4000);
|
||||
}
|
||||
}
|
||||
player.getMap().damageMonster(player, target, damage);
|
||||
for (SummonAttackTarget target : targets) {
|
||||
int damage = target.damage();
|
||||
Monster mob = player.getMap().getMonsterByOid(target.monsterOid());
|
||||
if (mob == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (damage > maxDmg) {
|
||||
AutobanFactory.DAMAGE_HACK.alert(c.getPlayer(), "Possible packet editing summon damage exploit.");
|
||||
final String mobName = MonsterInformationProvider.getInstance().getMobNameFromId(mob.getId());
|
||||
log.info("Possible exploit - chr {} used a summon of skillId {} to attack {} with damage {} (max: {})",
|
||||
c.getPlayer().getName(), summon.getSkill(), mobName, damage, maxDmg);
|
||||
damage = maxDmg;
|
||||
}
|
||||
|
||||
if (damage > 0 && summonEffect.getMonsterStati().size() > 0) {
|
||||
if (summonEffect.makeChanceResult()) {
|
||||
mob.applyStatus(player, new MonsterStatusEffect(summonEffect.getMonsterStati(), summonSkill, null, false), summonEffect.isPoison(), 4000);
|
||||
}
|
||||
}
|
||||
player.getMap().damageMonster(player, mob, damage, target.delay());
|
||||
|
||||
}
|
||||
|
||||
if (summon.getSkill() == Outlaw.GAVIOTA) { // thanks Periwinks for noticing Gaviota not cancelling after grenade toss
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ public final class UseCatchItemHandler extends AbstractPacketHandler {
|
||||
case ItemId.PHEROMONE_PERFUME:
|
||||
if (mob.getId() == MobId.TAMABLE_HOG) {
|
||||
chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1));
|
||||
mob.getMap().killMonster(mob, null, false);
|
||||
killMonster(mob);
|
||||
InventoryManipulator.removeById(c, InventoryType.USE, itemId, 1, true, true);
|
||||
InventoryManipulator.addById(c, ItemId.HOG, (short) 1, "", -1);
|
||||
}
|
||||
@@ -72,7 +72,7 @@ public final class UseCatchItemHandler extends AbstractPacketHandler {
|
||||
if ((abm.getLastSpam(10) + 1000) < currentServerTime()) {
|
||||
if (mob.getHp() < ((mob.getMaxHp() / 10) * 4)) {
|
||||
chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1));
|
||||
mob.getMap().killMonster(mob, null, false);
|
||||
killMonster(mob);
|
||||
InventoryManipulator.removeById(c, InventoryType.USE, itemId, 1, true, true);
|
||||
InventoryManipulator.addById(c, ItemId.GHOST_SACK, (short) 1, "", -1);
|
||||
} else {
|
||||
@@ -90,9 +90,10 @@ public final class UseCatchItemHandler extends AbstractPacketHandler {
|
||||
if (chr.canHold(ItemId.ARPQ_SPIRIT_JEWEL, 1)) {
|
||||
if (Math.random() < 0.5) { // 50% chance
|
||||
chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1));
|
||||
mob.getMap().killMonster(mob, null, false);
|
||||
killMonster(mob);
|
||||
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));
|
||||
}
|
||||
@@ -112,7 +113,7 @@ public final class UseCatchItemHandler extends AbstractPacketHandler {
|
||||
if (mob.getId() == MobId.LOST_RUDOLPH) {
|
||||
if (mob.getHp() < ((mob.getMaxHp() / 10) * 4)) {
|
||||
chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1));
|
||||
mob.getMap().killMonster(mob, null, false);
|
||||
killMonster(mob);
|
||||
InventoryManipulator.removeById(c, InventoryType.USE, itemId, 1, true, true);
|
||||
InventoryManipulator.addById(c, ItemId.TAMED_RUDOLPH, (short) 1, "", -1);
|
||||
} else {
|
||||
@@ -125,7 +126,7 @@ public final class UseCatchItemHandler extends AbstractPacketHandler {
|
||||
if (mob.getId() == MobId.KING_SLIME_DOJO) {
|
||||
if (mob.getHp() < ((mob.getMaxHp() / 10) * 3)) {
|
||||
chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1));
|
||||
mob.getMap().killMonster(mob, null, false);
|
||||
killMonster(mob);
|
||||
InventoryManipulator.removeById(c, InventoryType.USE, itemId, 1, true, true);
|
||||
InventoryManipulator.addById(c, ItemId.MONSTER_MARBLE_1, (short) 1, "", -1);
|
||||
} else {
|
||||
@@ -138,7 +139,7 @@ public final class UseCatchItemHandler extends AbstractPacketHandler {
|
||||
if (mob.getId() == MobId.FAUST_DOJO) {
|
||||
if (mob.getHp() < ((mob.getMaxHp() / 10) * 3)) {
|
||||
chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1));
|
||||
mob.getMap().killMonster(mob, null, false);
|
||||
killMonster(mob);
|
||||
InventoryManipulator.removeById(c, InventoryType.USE, itemId, 1, true, true);
|
||||
InventoryManipulator.addById(c, ItemId.MONSTER_MARBLE_2, (short) 1, "", -1);
|
||||
} else {
|
||||
@@ -151,7 +152,7 @@ public final class UseCatchItemHandler extends AbstractPacketHandler {
|
||||
if (mob.getId() == MobId.MUSHMOM_DOJO) {
|
||||
if (mob.getHp() < ((mob.getMaxHp() / 10) * 3)) {
|
||||
chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1));
|
||||
mob.getMap().killMonster(mob, null, false);
|
||||
killMonster(mob);
|
||||
InventoryManipulator.removeById(c, InventoryType.USE, itemId, 1, true, true);
|
||||
InventoryManipulator.addById(c, ItemId.MONSTER_MARBLE_3, (short) 1, "", -1);
|
||||
} else {
|
||||
@@ -164,7 +165,7 @@ public final class UseCatchItemHandler extends AbstractPacketHandler {
|
||||
if (mob.getId() == MobId.POISON_FLOWER) {
|
||||
if (mob.getHp() < ((mob.getMaxHp() / 10) * 4)) {
|
||||
chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1));
|
||||
mob.getMap().killMonster(mob, null, false);
|
||||
killMonster(mob);
|
||||
InventoryManipulator.removeById(c, InventoryType.USE, itemId, 1, true, true);
|
||||
InventoryManipulator.addById(c, ItemId.EPQ_MONSTER_MARBLE, (short) 1, "", -1);
|
||||
} else {
|
||||
@@ -178,7 +179,7 @@ public final class UseCatchItemHandler extends AbstractPacketHandler {
|
||||
if ((abm.getLastSpam(10) + 3000) < currentServerTime()) {
|
||||
abm.spam(10);
|
||||
chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1));
|
||||
mob.getMap().killMonster(mob, null, false);
|
||||
killMonster(mob);
|
||||
InventoryManipulator.removeById(c, InventoryType.USE, itemId, 1, true, true);
|
||||
InventoryManipulator.addById(c, ItemId.FISH_NET_WITH_A_CATCH, (short) 1, "", -1);
|
||||
} else {
|
||||
@@ -201,7 +202,7 @@ public final class UseCatchItemHandler extends AbstractPacketHandler {
|
||||
if (timeCatch != 0 && (abm.getLastSpam(10) + timeCatch) < currentServerTime()) {
|
||||
if (mobHp != 0 && mob.getHp() < ((mob.getMaxHp() / 100) * mobHp)) {
|
||||
chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1));
|
||||
mob.getMap().killMonster(mob, null, false);
|
||||
killMonster(mob);
|
||||
InventoryManipulator.removeById(c, InventoryType.USE, itemId, 1, true, true);
|
||||
InventoryManipulator.addById(c, itemGanho, (short) 1, "", -1);
|
||||
} else if (mob.getId() != MobId.P_JUNIOR) {
|
||||
@@ -219,4 +220,8 @@ public final class UseCatchItemHandler extends AbstractPacketHandler {
|
||||
// System.out.println("UseCatchItemHandler: \r\n" + slea.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static void killMonster(Monster mob) {
|
||||
mob.getMap().killMonster(mob, null, false, (short) 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import server.TimerManager;
|
||||
import server.life.LifeFactory;
|
||||
import server.life.Monster;
|
||||
import server.maps.MapMonitor;
|
||||
import server.maps.MapleMap;
|
||||
import server.maps.Reactor;
|
||||
import server.maps.ReactorDropEntry;
|
||||
import server.partyquest.CarnivalFactory;
|
||||
@@ -43,7 +44,6 @@ import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
/**
|
||||
* @author Lerk
|
||||
@@ -52,7 +52,6 @@ import java.util.concurrent.ScheduledFuture;
|
||||
public class ReactorActionManager extends AbstractPlayerInteraction {
|
||||
private final Reactor reactor;
|
||||
private final Invocable iv;
|
||||
private ScheduledFuture<?> sprayTask = null;
|
||||
|
||||
public ReactorActionManager(Client c, Reactor reactor, Invocable iv) {
|
||||
super(c);
|
||||
@@ -172,7 +171,8 @@ public class ReactorActionManager extends AbstractPlayerInteraction {
|
||||
int range = maxMeso - minMeso;
|
||||
int displayDrop = (int) (Math.random() * range) + minMeso;
|
||||
int mesoDrop = (displayDrop * c.getWorldServer().getMesoRate());
|
||||
reactor.getMap().spawnMesoDrop(mesoDrop, reactor.getMap().calcDropPos(dropPos, reactor.getPosition()), reactor, c.getPlayer(), false, (byte) 2);
|
||||
reactor.getMap().spawnMesoDrop(mesoDrop, reactor.getMap().calcDropPos(dropPos,
|
||||
reactor.getPosition()), reactor, c.getPlayer(), false, (byte) 2, (short) 0);
|
||||
} else {
|
||||
Item drop;
|
||||
|
||||
@@ -182,31 +182,24 @@ public class ReactorActionManager extends AbstractPlayerInteraction {
|
||||
drop = ii.randomizeStats((Equip) ii.getEquipById(d.itemId));
|
||||
}
|
||||
|
||||
reactor.getMap().dropFromReactor(getPlayer(), reactor, drop, dropPos, (short) d.questid);
|
||||
reactor.getMap().dropFromReactor(getPlayer(), reactor, drop, dropPos, (short) d.questid, (short) 0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
final Reactor r = reactor;
|
||||
final List<ReactorDropEntry> dropItems = items;
|
||||
final int worldMesoRate = c.getWorldServer().getMesoRate();
|
||||
|
||||
dropPos.x -= (12 * items.size());
|
||||
|
||||
sprayTask = TimerManager.getInstance().register(() -> {
|
||||
if (dropItems.isEmpty()) {
|
||||
sprayTask.cancel(false);
|
||||
return;
|
||||
}
|
||||
|
||||
ReactorDropEntry d = dropItems.remove(0);
|
||||
short delay = 0;
|
||||
for (ReactorDropEntry d : items) {
|
||||
if (d.itemId == 0) {
|
||||
int range = maxMeso - minMeso;
|
||||
int displayDrop = (int) (Math.random() * range) + minMeso;
|
||||
int mesoDrop = (displayDrop * worldMesoRate);
|
||||
r.getMap().spawnMesoDrop(mesoDrop, r.getMap().calcDropPos(dropPos, r.getPosition()), r, chr, false, (byte) 2);
|
||||
int mesoDrop = displayDrop * worldMesoRate;
|
||||
MapleMap map = reactor.getMap();
|
||||
map.spawnMesoDrop(mesoDrop, map.calcDropPos(dropPos, reactor.getPosition()), reactor, chr,
|
||||
false, (byte) 2, delay);
|
||||
} else {
|
||||
Item drop;
|
||||
|
||||
final Item drop;
|
||||
if (ItemConstants.getInventoryType(d.itemId) != InventoryType.EQUIP) {
|
||||
drop = new Item(d.itemId, (short) 0, (short) 1);
|
||||
} else {
|
||||
@@ -214,11 +207,12 @@ public class ReactorActionManager extends AbstractPlayerInteraction {
|
||||
drop = ii.randomizeStats((Equip) ii.getEquipById(d.itemId));
|
||||
}
|
||||
|
||||
r.getMap().dropFromReactor(getPlayer(), r, drop, dropPos, (short) d.questid);
|
||||
reactor.getMap().dropFromReactor(getPlayer(), reactor, drop, dropPos, (short) d.questid, delay);
|
||||
}
|
||||
|
||||
dropPos.x += 25;
|
||||
}, 200);
|
||||
delay += 200;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,4 +327,4 @@ public class ReactorActionManager extends AbstractPlayerInteraction {
|
||||
getPlayer().getMap().getBlueTeamBuffs().remove(skil);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
9
src/main/java/server/life/BanishInfo.java
Normal file
9
src/main/java/server/life/BanishInfo.java
Normal 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");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -826,12 +825,12 @@ public class Monster extends AbstractLoadedLife {
|
||||
}
|
||||
|
||||
if (htKilled) {
|
||||
reviveMap.killMonster(ht, killer, true);
|
||||
reviveMap.killMonster(ht, killer, true, (short) 0);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = MobId.DEAD_HORNTAIL_MAX; i >= MobId.DEAD_HORNTAIL_MIN; i--) {
|
||||
reviveMap.killMonster(reviveMap.getMonsterById(i), killer, true);
|
||||
reviveMap.killMonster(reviveMap.getMonsterById(i), killer, true, (short) 0);
|
||||
}
|
||||
} else if (controller != null) {
|
||||
mob.aggroSwitchController(controller, aggro);
|
||||
|
||||
@@ -58,7 +58,7 @@ public class MonsterInformationProvider {
|
||||
|
||||
private final Map<Integer, List<MonsterDropEntry>> drops = new HashMap<>();
|
||||
private final List<MonsterGlobalDropEntry> globaldrops = new ArrayList<>();
|
||||
private final Map<Integer, List<MonsterGlobalDropEntry>> continentdrops = new HashMap<>();
|
||||
private final Map<Integer, List<MonsterGlobalDropEntry>> continentDrops = new HashMap<>();
|
||||
|
||||
private final Map<Integer, List<Integer>> dropsChancePool = new HashMap<>(); // thanks to ronan
|
||||
private final Set<Integer> hasNoMultiEquipDrops = new HashSet<>();
|
||||
@@ -76,23 +76,15 @@ public class MonsterInformationProvider {
|
||||
retrieveGlobal();
|
||||
}
|
||||
|
||||
public final List<MonsterGlobalDropEntry> getRelevantGlobalDrops(int mapid) {
|
||||
int continentid = mapid / 100000000;
|
||||
public final List<MonsterGlobalDropEntry> getRelevantGlobalDrops(int mapId) {
|
||||
final int continentId = mapId / 100000000;
|
||||
return continentDrops.computeIfAbsent(continentId, this::loadContinentDrops);
|
||||
}
|
||||
|
||||
List<MonsterGlobalDropEntry> contiItems = continentdrops.get(continentid);
|
||||
if (contiItems == null) { // continent separated global drops found thanks to marcuswoon
|
||||
contiItems = new ArrayList<>();
|
||||
|
||||
for (MonsterGlobalDropEntry e : globaldrops) {
|
||||
if (e.continentid < 0 || e.continentid == continentid) {
|
||||
contiItems.add(e);
|
||||
}
|
||||
}
|
||||
|
||||
continentdrops.put(continentid, contiItems);
|
||||
}
|
||||
|
||||
return contiItems;
|
||||
private List<MonsterGlobalDropEntry> loadContinentDrops(int continentId) {
|
||||
return globaldrops.stream()
|
||||
.filter(dropEntry -> dropEntry.continentid < 0 || dropEntry.continentid == continentId)
|
||||
.toList();
|
||||
}
|
||||
|
||||
private void retrieveGlobal() {
|
||||
@@ -291,7 +283,7 @@ public class MonsterInformationProvider {
|
||||
extraMultiEquipDrops.clear();
|
||||
dropsChancePool.clear();
|
||||
globaldrops.clear();
|
||||
continentdrops.clear();
|
||||
continentDrops.clear();
|
||||
retrieveGlobal();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -208,7 +208,8 @@ public class MapItem extends AbstractMapObject {
|
||||
if (chr.needQuestItem(questid, getItemId())) {
|
||||
this.lockItem();
|
||||
try {
|
||||
client.sendPacket(PacketCreator.dropItemFromMapObject(chr, this, null, getPosition(), (byte) 2));
|
||||
client.sendPacket(PacketCreator.dropItemFromMapObject(chr, this, null, getPosition(),
|
||||
(byte) 2, (short) 0));
|
||||
} finally {
|
||||
this.unlockItem();
|
||||
}
|
||||
@@ -219,4 +220,4 @@ public class MapItem extends AbstractMapObject {
|
||||
public void sendDestroyData(final Client client) {
|
||||
client.sendPacket(PacketCreator.removeItemFromMap(getObjectId(), 1, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@ import java.util.Calendar;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
@@ -121,7 +120,6 @@ public class MapleMap {
|
||||
private final Map<String, Integer> environment = new LinkedHashMap<>();
|
||||
private final Map<MapItem, Long> droppedItems = new LinkedHashMap<>();
|
||||
private final LinkedList<WeakReference<MapObject>> registeredDrops = new LinkedList<>();
|
||||
private final Map<MobLootEntry, Long> mobLootEntries = new HashMap(20);
|
||||
private final List<Runnable> statUpdateRunnables = new ArrayList(50);
|
||||
private final List<Rectangle> areas = new ArrayList<>();
|
||||
private FootholdTree footholds = null;
|
||||
@@ -160,7 +158,6 @@ public class MapleMap {
|
||||
private MonsterAggroCoordinator aggroMonitor = null; // aggroMonitor activity in sync with itemMonitor
|
||||
private ScheduledFuture<?> itemMonitor = null;
|
||||
private ScheduledFuture<?> expireItemsTask = null;
|
||||
private ScheduledFuture<?> mobSpawnLootTask = null;
|
||||
private ScheduledFuture<?> characterStatUpdateTask = null;
|
||||
private short itemMonitorTimeout;
|
||||
private Pair<Integer, String> timeMob = null;
|
||||
@@ -654,9 +651,10 @@ public class MapleMap {
|
||||
}
|
||||
}
|
||||
|
||||
private byte dropItemsFromMonsterOnMap(List<MonsterDropEntry> dropEntry, Point pos, byte d, int chRate, byte droptype, int mobpos, Character chr, Monster mob) {
|
||||
private byte dropItemsFromMonsterOnMap(List<MonsterDropEntry> dropEntry, Point pos, byte index, int chRate,
|
||||
byte droptype, int mobpos, Character chr, Monster mob, short delay) {
|
||||
if (dropEntry.isEmpty()) {
|
||||
return d;
|
||||
return index;
|
||||
}
|
||||
|
||||
Collections.shuffle(dropEntry);
|
||||
@@ -670,9 +668,9 @@ public class MapleMap {
|
||||
|
||||
if (Randomizer.nextInt(999999) < dropChance) {
|
||||
if (droptype == 3) {
|
||||
pos.x = mobpos + ((d % 2 == 0) ? (40 * ((d + 1) / 2)) : -(40 * (d / 2)));
|
||||
pos.x = mobpos + ((index % 2 == 0) ? (40 * ((index + 1) / 2)) : -(40 * (index / 2)));
|
||||
} else {
|
||||
pos.x = mobpos + ((d % 2 == 0) ? (25 * ((d + 1) / 2)) : -(25 * (d / 2)));
|
||||
pos.x = mobpos + ((index % 2 == 0) ? (25 * ((index + 1) / 2)) : -(25 * (index / 2)));
|
||||
}
|
||||
if (de.itemId == 0) { // meso
|
||||
int mesos = Randomizer.nextInt(de.Maximum - de.Minimum) + de.Minimum;
|
||||
@@ -686,7 +684,8 @@ public class MapleMap {
|
||||
mesos = Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
spawnMesoDrop(mesos, calcDropPos(pos, mob.getPosition()), mob, chr, false, droptype);
|
||||
spawnMesoDrop(mesos, calcDropPos(pos, mob.getPosition()), mob, chr, false, droptype,
|
||||
delay);
|
||||
}
|
||||
} else {
|
||||
if (ItemConstants.getInventoryType(de.itemId) == InventoryType.EQUIP) {
|
||||
@@ -694,16 +693,17 @@ public class MapleMap {
|
||||
} else {
|
||||
idrop = new Item(de.itemId, (short) 0, (short) (de.Maximum != 1 ? Randomizer.nextInt(de.Maximum - de.Minimum) + de.Minimum : 1));
|
||||
}
|
||||
spawnDrop(idrop, calcDropPos(pos, mob.getPosition()), mob, chr, droptype, de.questid);
|
||||
spawnDrop(idrop, calcDropPos(pos, mob.getPosition()), mob, chr, droptype, de.questid, delay);
|
||||
}
|
||||
d++;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
return d;
|
||||
return index;
|
||||
}
|
||||
|
||||
private byte dropGlobalItemsFromMonsterOnMap(List<MonsterGlobalDropEntry> globalEntry, Point pos, byte d, byte droptype, int mobpos, Character chr, Monster mob) {
|
||||
private byte dropGlobalItemsFromMonsterOnMap(List<MonsterGlobalDropEntry> globalEntry, Point pos, byte d,
|
||||
byte droptype, int mobpos, Character chr, Monster mob, short delay) {
|
||||
Collections.shuffle(globalEntry);
|
||||
|
||||
Item idrop;
|
||||
@@ -722,7 +722,7 @@ public class MapleMap {
|
||||
} else {
|
||||
idrop = new Item(de.itemId, (short) 0, (short) (de.Maximum != 1 ? Randomizer.nextInt(de.Maximum - de.Minimum) + de.Minimum : 1));
|
||||
}
|
||||
spawnDrop(idrop, calcDropPos(pos, mob.getPosition()), mob, chr, droptype, de.questid);
|
||||
spawnDrop(idrop, calcDropPos(pos, mob.getPosition()), mob, chr, droptype, de.questid, delay);
|
||||
d++;
|
||||
}
|
||||
}
|
||||
@@ -731,7 +731,7 @@ public class MapleMap {
|
||||
return d;
|
||||
}
|
||||
|
||||
private void dropFromMonster(final Character chr, final Monster mob, final boolean useBaseRate) {
|
||||
private void dropFromMonster(final Character chr, final Monster mob, final boolean useBaseRate, short delay) {
|
||||
if (mob.dropsDisabled() || !dropsOn) {
|
||||
return;
|
||||
}
|
||||
@@ -739,7 +739,6 @@ public class MapleMap {
|
||||
final byte droptype = (byte) (mob.getStats().isExplosiveReward() ? 3 : mob.getStats().isFfaLoot() ? 2 : chr.getParty() != null ? 1 : 0);
|
||||
final int mobpos = mob.getPosition().x;
|
||||
int chRate = !mob.isBoss() ? chr.getDropRate() : chr.getBossDropRate();
|
||||
byte d = 1;
|
||||
Point pos = new Point(0, mob.getPosition().y);
|
||||
|
||||
MonsterStatusEffect stati = mob.getStati(MonsterStatus.SHOWDOWN);
|
||||
@@ -752,7 +751,7 @@ public class MapleMap {
|
||||
}
|
||||
|
||||
final MonsterInformationProvider mi = MonsterInformationProvider.getInstance();
|
||||
final List<MonsterGlobalDropEntry> globalEntry = mi.getRelevantGlobalDrops(this.getId());
|
||||
final List<MonsterGlobalDropEntry> globalEntry = new ArrayList<>(mi.getRelevantGlobalDrops(mapid));
|
||||
|
||||
final List<MonsterDropEntry> dropEntry = new ArrayList<>();
|
||||
final List<MonsterDropEntry> visibleQuestEntry = new ArrayList<>();
|
||||
@@ -765,10 +764,20 @@ public class MapleMap {
|
||||
return;
|
||||
}
|
||||
|
||||
registerMobItemDrops(droptype, mobpos, chRate, pos, dropEntry, visibleQuestEntry, otherQuestEntry, globalEntry, chr, mob);
|
||||
|
||||
byte index = 1;
|
||||
// Normal Drops
|
||||
index = dropItemsFromMonsterOnMap(dropEntry, pos, index, chRate, droptype, mobpos, chr, mob, delay);
|
||||
|
||||
// Global Drops
|
||||
index = dropGlobalItemsFromMonsterOnMap(globalEntry, pos, index, droptype, mobpos, chr, mob, delay);
|
||||
|
||||
// Quest Drops
|
||||
index = dropItemsFromMonsterOnMap(visibleQuestEntry, pos, index, chRate, droptype, mobpos, chr, mob, delay);
|
||||
dropItemsFromMonsterOnMap(otherQuestEntry, pos, index, chRate, droptype, mobpos, chr, mob, delay);
|
||||
}
|
||||
|
||||
public void dropItemsFromMonster(List<MonsterDropEntry> list, final Character chr, final Monster mob) {
|
||||
public void dropItemsFromMonster(List<MonsterDropEntry> list, final Character chr, final Monster mob, short delay) {
|
||||
if (mob.dropsDisabled() || !dropsOn) {
|
||||
return;
|
||||
}
|
||||
@@ -779,15 +788,17 @@ public class MapleMap {
|
||||
byte d = 1;
|
||||
Point pos = new Point(0, mob.getPosition().y);
|
||||
|
||||
dropItemsFromMonsterOnMap(list, pos, d, chRate, droptype, mobpos, chr, mob);
|
||||
dropItemsFromMonsterOnMap(list, pos, d, chRate, droptype, mobpos, chr, mob, delay);
|
||||
}
|
||||
|
||||
public void dropFromFriendlyMonster(final Character chr, final Monster mob) {
|
||||
dropFromMonster(chr, mob, true);
|
||||
dropFromMonster(chr, mob, true, (short) 0);
|
||||
}
|
||||
|
||||
public void dropFromReactor(final Character chr, final Reactor reactor, Item drop, Point dropPos, short questid) {
|
||||
spawnDrop(drop, this.calcDropPos(dropPos, reactor.getPosition()), reactor, chr, (byte) (chr.getParty() != null ? 1 : 0), questid);
|
||||
public void dropFromReactor(final Character chr, final Reactor reactor, Item drop, Point dropPos, short questid,
|
||||
short delay) {
|
||||
spawnDrop(drop, this.calcDropPos(dropPos, reactor.getPosition()), reactor, chr,
|
||||
(byte) (chr.getParty() != null ? 1 : 0), questid, delay);
|
||||
}
|
||||
|
||||
private void stopItemMonitor() {
|
||||
@@ -797,11 +808,6 @@ public class MapleMap {
|
||||
expireItemsTask.cancel(false);
|
||||
expireItemsTask = null;
|
||||
|
||||
if (YamlConfig.config.server.USE_SPAWN_LOOT_ON_ANIMATION) {
|
||||
mobSpawnLootTask.cancel(false);
|
||||
mobSpawnLootTask = null;
|
||||
}
|
||||
|
||||
characterStatUpdateTask.cancel(false);
|
||||
characterStatUpdateTask = null;
|
||||
}
|
||||
@@ -858,17 +864,6 @@ public class MapleMap {
|
||||
|
||||
expireItemsTask = TimerManager.getInstance().register(() -> makeDisappearExpiredItemDrops(), YamlConfig.config.server.ITEM_EXPIRE_CHECK, YamlConfig.config.server.ITEM_EXPIRE_CHECK);
|
||||
|
||||
if (YamlConfig.config.server.USE_SPAWN_LOOT_ON_ANIMATION) {
|
||||
lootLock.lock();
|
||||
try {
|
||||
mobLootEntries.clear();
|
||||
} finally {
|
||||
lootLock.unlock();
|
||||
}
|
||||
|
||||
mobSpawnLootTask = TimerManager.getInstance().register(() -> spawnMobItemDrops(), 200, 200);
|
||||
}
|
||||
|
||||
characterStatUpdateTask = TimerManager.getInstance().register(() -> runCharacterStatUpdate(), 200, 200);
|
||||
|
||||
itemMonitorTimeout = 1;
|
||||
@@ -965,63 +960,6 @@ public class MapleMap {
|
||||
}
|
||||
}
|
||||
|
||||
private void registerMobItemDrops(byte droptype, int mobpos, int chRate, Point pos, List<MonsterDropEntry> dropEntry, List<MonsterDropEntry> visibleQuestEntry, List<MonsterDropEntry> otherQuestEntry, List<MonsterGlobalDropEntry> globalEntry, Character chr, Monster mob) {
|
||||
MobLootEntry mle = new MobLootEntry(droptype, mobpos, chRate, pos, dropEntry, visibleQuestEntry, otherQuestEntry, globalEntry, chr, mob);
|
||||
|
||||
if (YamlConfig.config.server.USE_SPAWN_LOOT_ON_ANIMATION) {
|
||||
int animationTime = mob.getAnimationTime("die1");
|
||||
|
||||
lootLock.lock();
|
||||
try {
|
||||
long timeNow = Server.getInstance().getCurrentTime();
|
||||
mobLootEntries.put(mle, timeNow + ((long) (0.42 * animationTime)));
|
||||
} finally {
|
||||
lootLock.unlock();
|
||||
}
|
||||
} else {
|
||||
mle.run();
|
||||
}
|
||||
}
|
||||
|
||||
private void spawnMobItemDrops() {
|
||||
Set<Entry<MobLootEntry, Long>> mleList;
|
||||
|
||||
lootLock.lock();
|
||||
try {
|
||||
mleList = new HashSet<>(mobLootEntries.entrySet());
|
||||
} finally {
|
||||
lootLock.unlock();
|
||||
}
|
||||
|
||||
long timeNow = Server.getInstance().getCurrentTime();
|
||||
List<MobLootEntry> toRemove = new LinkedList<>();
|
||||
for (Entry<MobLootEntry, Long> mlee : mleList) {
|
||||
if (mlee.getValue() < timeNow) {
|
||||
toRemove.add(mlee.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
if (!toRemove.isEmpty()) {
|
||||
List<MobLootEntry> toSpawnLoot = new LinkedList<>();
|
||||
|
||||
lootLock.lock();
|
||||
try {
|
||||
for (MobLootEntry mle : toRemove) {
|
||||
Long mler = mobLootEntries.remove(mle);
|
||||
if (mler != null) {
|
||||
toSpawnLoot.add(mle);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
lootLock.unlock();
|
||||
}
|
||||
|
||||
for (MobLootEntry mle : toSpawnLoot) {
|
||||
mle.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<MapItem> getDroppedItems() {
|
||||
objectRLock.lock();
|
||||
try {
|
||||
@@ -1123,7 +1061,8 @@ public class MapleMap {
|
||||
}
|
||||
}
|
||||
|
||||
private void spawnDrop(final Item idrop, final Point dropPos, final MapObject dropper, final Character chr, final byte droptype, final short questid) {
|
||||
private void spawnDrop(final Item idrop, final Point dropPos, final MapObject dropper, final Character chr,
|
||||
final byte droptype, final short questid, short delay) {
|
||||
final MapItem mdrop = new MapItem(idrop, dropPos, dropper, chr, chr.getClient(), droptype, false, questid);
|
||||
mdrop.setDropTime(Server.getInstance().getCurrentTime());
|
||||
spawnAndAddRangedMapObject(mdrop, c -> {
|
||||
@@ -1132,7 +1071,8 @@ public class MapleMap {
|
||||
if (chr1.needQuestItem(questid, idrop.getItemId())) {
|
||||
mdrop.lockItem();
|
||||
try {
|
||||
c.sendPacket(PacketCreator.dropItemFromMapObject(chr1, mdrop, dropper.getPosition(), dropPos, (byte) 1));
|
||||
c.sendPacket(PacketCreator.dropItemFromMapObject(chr1, mdrop, dropper.getPosition(), dropPos,
|
||||
(byte) 1, delay));
|
||||
} finally {
|
||||
mdrop.unlockItem();
|
||||
}
|
||||
@@ -1143,7 +1083,8 @@ public class MapleMap {
|
||||
activateItemReactors(mdrop, chr.getClient());
|
||||
}
|
||||
|
||||
public final void spawnMesoDrop(final int meso, final Point position, final MapObject dropper, final Character owner, final boolean playerDrop, final byte droptype) {
|
||||
public final void spawnMesoDrop(final int meso, final Point position, final MapObject dropper,
|
||||
final Character owner, final boolean playerDrop, final byte droptype, short delay) {
|
||||
final Point droppos = calcDropPos(position, position);
|
||||
final MapItem mdrop = new MapItem(meso, droppos, dropper, owner, owner.getClient(), droptype, playerDrop);
|
||||
mdrop.setDropTime(Server.getInstance().getCurrentTime());
|
||||
@@ -1151,7 +1092,8 @@ public class MapleMap {
|
||||
spawnAndAddRangedMapObject(mdrop, c -> {
|
||||
mdrop.lockItem();
|
||||
try {
|
||||
c.sendPacket(PacketCreator.dropItemFromMapObject(c.getPlayer(), mdrop, dropper.getPosition(), droppos, (byte) 1));
|
||||
c.sendPacket(PacketCreator.dropItemFromMapObject(c.getPlayer(), mdrop, dropper.getPosition(), droppos,
|
||||
(byte) 1, delay));
|
||||
} finally {
|
||||
mdrop.unlockItem();
|
||||
}
|
||||
@@ -1166,7 +1108,7 @@ public class MapleMap {
|
||||
|
||||
mdrop.lockItem();
|
||||
try {
|
||||
broadcastItemDropMessage(mdrop, dropper.getPosition(), droppos, (byte) 3, mdrop.getPosition());
|
||||
broadcastItemDropMessage(mdrop, dropper.getPosition(), droppos, (byte) 3, (short) 0, mdrop.getPosition());
|
||||
} finally {
|
||||
mdrop.unlockItem();
|
||||
}
|
||||
@@ -1178,7 +1120,7 @@ public class MapleMap {
|
||||
|
||||
mdrop.lockItem();
|
||||
try {
|
||||
broadcastItemDropMessage(mdrop, dropper.getPosition(), droppos, (byte) 3, mdrop.getPosition());
|
||||
broadcastItemDropMessage(mdrop, dropper.getPosition(), droppos, (byte) 3, (short) 0, mdrop.getPosition());
|
||||
} finally {
|
||||
mdrop.unlockItem();
|
||||
}
|
||||
@@ -1326,7 +1268,11 @@ public class MapleMap {
|
||||
return count;
|
||||
}
|
||||
|
||||
public boolean damageMonster(final Character chr, final Monster monster, final int damage) {
|
||||
public boolean damageMonster(Character chr, Monster monster, int damage) {
|
||||
return damageMonster(chr, monster, damage, (short) 0);
|
||||
}
|
||||
|
||||
public boolean damageMonster(final Character chr, final Monster monster, final int damage, short delay) {
|
||||
if (monster.getId() == MobId.ZAKUM_1) {
|
||||
for (MapObject object : chr.getMap().getMapObjects()) {
|
||||
Monster mons = chr.getMap().getMonsterByOid(object.getObjectId());
|
||||
@@ -1337,22 +1283,23 @@ public class MapleMap {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (monster.isAlive()) {
|
||||
boolean killed = monster.damage(chr, damage, false);
|
||||
|
||||
selfDestruction selfDestr = monster.getStats().selfDestruction();
|
||||
if (selfDestr != null && selfDestr.getHp() > -1) {// should work ;p
|
||||
if (monster.getHp() <= selfDestr.getHp()) {
|
||||
killMonster(monster, chr, true, selfDestr.getAction());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (killed) {
|
||||
killMonster(monster, chr, true);
|
||||
}
|
||||
return true;
|
||||
if (!monster.isAlive()) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
boolean killed = monster.damage(chr, damage, false);
|
||||
|
||||
selfDestruction selfDestr = monster.getStats().selfDestruction();
|
||||
if (selfDestr != null && selfDestr.getHp() > -1) {// should work ;p
|
||||
if (monster.getHp() <= selfDestr.getHp()) {
|
||||
killMonster(monster, chr, true, selfDestr.getAction());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (killed) {
|
||||
killMonster(monster, chr, true, delay);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void broadcastBalrogVictory(String leaderName) {
|
||||
@@ -1391,11 +1338,12 @@ public class MapleMap {
|
||||
}
|
||||
}
|
||||
|
||||
public void killMonster(final Monster monster, final Character chr, final boolean withDrops) {
|
||||
killMonster(monster, chr, withDrops, 1);
|
||||
public void killMonster(final Monster monster, final Character chr, final boolean withDrops, short dropDelay) {
|
||||
killMonster(monster, chr, withDrops, 1, dropDelay);
|
||||
}
|
||||
|
||||
public void killMonster(final Monster monster, final Character chr, final boolean withDrops, int animation) {
|
||||
public void killMonster(final Monster monster, final Character chr, final boolean withDrops, int animation,
|
||||
short dropDelay) {
|
||||
if (monster == null) {
|
||||
return;
|
||||
}
|
||||
@@ -1406,12 +1354,17 @@ public class MapleMap {
|
||||
broadcastMessage(PacketCreator.killMonster(monster.getObjectId(), animation), monster.getPosition());
|
||||
monster.aggroSwitchController(null, false);
|
||||
}
|
||||
} else {
|
||||
if (removeKilledMonsterObject(monster)) {
|
||||
try {
|
||||
if (monster.getStats().getLevel() >= chr.getLevel() + 30 && !chr.isGM()) {
|
||||
AutobanFactory.GENERAL.alert(chr, " for killing a " + monster.getName() + " which is over 30 levels higher.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!removeKilledMonsterObject(monster)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (monster.getStats().getLevel() >= chr.getLevel() + 30 && !chr.isGM()) {
|
||||
AutobanFactory.GENERAL.alert(chr, " for killing a " + monster.getName() + " which is over 30 levels higher.");
|
||||
}
|
||||
|
||||
/*if (chr.getQuest(Quest.getInstance(29400)).getStatus().equals(QuestStatus.Status.STARTED)) {
|
||||
if (chr.getLevel() >= 120 && monster.getStats().getLevel() >= 120) {
|
||||
@@ -1420,78 +1373,78 @@ public class MapleMap {
|
||||
}
|
||||
}*/
|
||||
|
||||
if (monster.getCP() > 0 && chr.getMap().isCPQMap()) {
|
||||
chr.gainCP(monster.getCP());
|
||||
}
|
||||
if (monster.getCP() > 0 && chr.getMap().isCPQMap()) {
|
||||
chr.gainCP(monster.getCP());
|
||||
}
|
||||
|
||||
int buff = monster.getBuffToGive();
|
||||
if (buff > -1) {
|
||||
ItemInformationProvider mii = ItemInformationProvider.getInstance();
|
||||
for (MapObject mmo : this.getPlayers()) {
|
||||
Character character = (Character) mmo;
|
||||
if (character.isAlive()) {
|
||||
StatEffect statEffect = mii.getItemEffect(buff);
|
||||
character.sendPacket(PacketCreator.showOwnBuffEffect(buff, 1));
|
||||
broadcastMessage(character, PacketCreator.showBuffEffect(character.getId(), buff, 1), false);
|
||||
statEffect.applyTo(character);
|
||||
}
|
||||
}
|
||||
int buff = monster.getBuffToGive();
|
||||
if (buff > -1) {
|
||||
ItemInformationProvider mii = ItemInformationProvider.getInstance();
|
||||
for (MapObject mmo : this.getPlayers()) {
|
||||
Character character = (Character) mmo;
|
||||
if (character.isAlive()) {
|
||||
StatEffect statEffect = mii.getItemEffect(buff);
|
||||
character.sendPacket(PacketCreator.showOwnBuffEffect(buff, 1));
|
||||
broadcastMessage(character, PacketCreator.showBuffEffect(character.getId(), buff, 1), false);
|
||||
statEffect.applyTo(character);
|
||||
}
|
||||
|
||||
if (MobId.isZakumArm(monster.getId())) {
|
||||
boolean makeZakReal = true;
|
||||
Collection<MapObject> objects = getMapObjects();
|
||||
for (MapObject object : objects) {
|
||||
Monster mons = getMonsterByOid(object.getObjectId());
|
||||
if (mons != null) {
|
||||
if (MobId.isZakumArm(mons.getId())) {
|
||||
makeZakReal = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (makeZakReal) {
|
||||
MapleMap map = chr.getMap();
|
||||
|
||||
for (MapObject object : objects) {
|
||||
Monster mons = map.getMonsterByOid(object.getObjectId());
|
||||
if (mons != null) {
|
||||
if (mons.getId() == MobId.ZAKUM_1) {
|
||||
makeMonsterReal(mons);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Character dropOwner = monster.killBy(chr);
|
||||
if (withDrops && !monster.dropsDisabled()) {
|
||||
if (dropOwner == null) {
|
||||
dropOwner = chr;
|
||||
}
|
||||
dropFromMonster(dropOwner, monster, false);
|
||||
}
|
||||
|
||||
if (monster.hasBossHPBar()) {
|
||||
for (Character mc : this.getAllPlayers()) {
|
||||
if (mc.getTargetHpBarHash() == monster.hashCode()) {
|
||||
mc.resetPlayerAggro();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally { // thanks resinate for pointing out a memory leak possibly from an exception thrown
|
||||
monster.dispatchMonsterKilled(true);
|
||||
broadcastMessage(PacketCreator.killMonster(monster.getObjectId(), animation), monster.getPosition());
|
||||
}
|
||||
}
|
||||
|
||||
if (MobId.isZakumArm(monster.getId())) {
|
||||
boolean makeZakReal = true;
|
||||
Collection<MapObject> objects = getMapObjects();
|
||||
for (MapObject object : objects) {
|
||||
Monster mons = getMonsterByOid(object.getObjectId());
|
||||
if (mons != null) {
|
||||
if (MobId.isZakumArm(mons.getId())) {
|
||||
makeZakReal = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (makeZakReal) {
|
||||
MapleMap map = chr.getMap();
|
||||
|
||||
for (MapObject object : objects) {
|
||||
Monster mons = map.getMonsterByOid(object.getObjectId());
|
||||
if (mons != null) {
|
||||
if (mons.getId() == MobId.ZAKUM_1) {
|
||||
makeMonsterReal(mons);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Character dropOwner = monster.killBy(chr);
|
||||
if (withDrops && !monster.dropsDisabled()) {
|
||||
if (dropOwner == null) {
|
||||
dropOwner = chr;
|
||||
}
|
||||
dropFromMonster(dropOwner, monster, false, dropDelay);
|
||||
}
|
||||
|
||||
if (monster.hasBossHPBar()) {
|
||||
for (Character mc : this.getAllPlayers()) {
|
||||
if (mc.getTargetHpBarHash() == monster.hashCode()) {
|
||||
mc.resetPlayerAggro();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally { // thanks resinate for pointing out a memory leak possibly from an exception thrown
|
||||
monster.dispatchMonsterKilled(true);
|
||||
broadcastMessage(PacketCreator.killMonster(monster.getObjectId(), animation), monster.getPosition());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void killFriendlies(Monster mob) {
|
||||
this.killMonster(mob, (Character) getPlayers().get(0), false);
|
||||
this.killMonster(mob, (Character) getPlayers().get(0), false, (short) 0);
|
||||
}
|
||||
|
||||
public void killMonster(int mobId) {
|
||||
@@ -1500,7 +1453,7 @@ public class MapleMap {
|
||||
|
||||
for (Monster mob : mobList) {
|
||||
if (mob.getId() == mobId) {
|
||||
this.killMonster(mob, chr, false);
|
||||
this.killMonster(mob, chr, false, (short) 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1519,7 +1472,7 @@ public class MapleMap {
|
||||
chr = defaultChr;
|
||||
}
|
||||
|
||||
this.killMonster(mob, chr, true);
|
||||
this.killMonster(mob, chr, true, (short) 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1549,7 +1502,7 @@ public class MapleMap {
|
||||
continue;
|
||||
}
|
||||
|
||||
killMonster(monster, null, false, 1);
|
||||
killMonster(monster, null, false, 1, (short) 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1559,7 +1512,7 @@ public class MapleMap {
|
||||
for (MapObject monstermo : getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapObjectType.MONSTER))) {
|
||||
Monster monster = (Monster) monstermo;
|
||||
|
||||
killMonster(monster, null, false, 1);
|
||||
killMonster(monster, null, false, 1, (short) 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1906,7 +1859,7 @@ public class MapleMap {
|
||||
Runnable removeAfterAction;
|
||||
|
||||
if (selfDestruction == null) {
|
||||
removeAfterAction = () -> killMonster(monster, null, false);
|
||||
removeAfterAction = () -> killMonster(monster, null, false, (short) 0);
|
||||
|
||||
registerMapSchedule(removeAfterAction, SECONDS.toMillis(monster.getStats().removeAfter()));
|
||||
} else {
|
||||
@@ -2154,11 +2107,13 @@ public class MapleMap {
|
||||
getWorldServer().registerTimedMapObject(expireKite, YamlConfig.config.server.KITE_EXPIRE_TIME);
|
||||
}
|
||||
|
||||
public final void spawnItemDrop(final MapObject dropper, final Character owner, final Item item, Point pos, final boolean ffaDrop, final boolean playerDrop) {
|
||||
public final void spawnItemDrop(final MapObject dropper, final Character owner, final Item item, Point pos,
|
||||
final boolean ffaDrop, final boolean playerDrop) {
|
||||
spawnItemDrop(dropper, owner, item, pos, (byte) (ffaDrop ? 2 : 0), playerDrop);
|
||||
}
|
||||
|
||||
public final void spawnItemDrop(final MapObject dropper, final Character owner, final Item item, Point pos, final byte dropType, final boolean playerDrop) {
|
||||
public final void spawnItemDrop(final MapObject dropper, final Character owner, final Item item, Point pos,
|
||||
final byte dropType, final boolean playerDrop) {
|
||||
if (FieldLimit.DROP_LIMIT.check(this.getFieldLimit())) { // thanks Conrad for noticing some maps shouldn't have loots available
|
||||
this.disappearingItemDrop(dropper, owner, item, pos);
|
||||
return;
|
||||
@@ -2171,7 +2126,8 @@ public class MapleMap {
|
||||
spawnAndAddRangedMapObject(mdrop, c -> {
|
||||
mdrop.lockItem();
|
||||
try {
|
||||
c.sendPacket(PacketCreator.dropItemFromMapObject(c.getPlayer(), mdrop, dropper.getPosition(), droppos, (byte) 1));
|
||||
c.sendPacket(PacketCreator.dropItemFromMapObject(c.getPlayer(), mdrop, dropper.getPosition(), droppos,
|
||||
(byte) 1, (short) 0));
|
||||
} finally {
|
||||
mdrop.unlockItem();
|
||||
}
|
||||
@@ -2179,7 +2135,7 @@ public class MapleMap {
|
||||
|
||||
mdrop.lockItem();
|
||||
try {
|
||||
broadcastItemDropMessage(mdrop, dropper.getPosition(), droppos, (byte) 0);
|
||||
broadcastItemDropMessage(mdrop, dropper.getPosition(), droppos, (byte) 0, (short) 0);
|
||||
} finally {
|
||||
mdrop.unlockItem();
|
||||
}
|
||||
@@ -2188,49 +2144,6 @@ public class MapleMap {
|
||||
activateItemReactors(mdrop, owner.getClient());
|
||||
}
|
||||
|
||||
public final void spawnItemDropList(List<Integer> list, final MapObject dropper, final Character owner, Point pos) {
|
||||
spawnItemDropList(list, 1, 1, dropper, owner, pos, true, false);
|
||||
}
|
||||
|
||||
public final void spawnItemDropList(List<Integer> list, int minCopies, int maxCopies, final MapObject dropper, final Character owner, Point pos) {
|
||||
spawnItemDropList(list, minCopies, maxCopies, dropper, owner, pos, true, false);
|
||||
}
|
||||
|
||||
// spawns item instances of all defined item ids on a list
|
||||
public final void spawnItemDropList(List<Integer> list, int minCopies, int maxCopies, final MapObject dropper, final Character owner, Point pos, final boolean ffaDrop, final boolean playerDrop) {
|
||||
int copies = (maxCopies - minCopies) + 1;
|
||||
if (copies < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
Collections.shuffle(list);
|
||||
|
||||
ItemInformationProvider ii = ItemInformationProvider.getInstance();
|
||||
Random rnd = new Random();
|
||||
|
||||
final Point dropPos = new Point(pos);
|
||||
dropPos.x -= (12 * list.size());
|
||||
|
||||
for (Integer integer : list) {
|
||||
if (integer == 0) {
|
||||
spawnMesoDrop(owner != null ? 10 * owner.getMesoRate() : 10, calcDropPos(dropPos, pos), dropper, owner, playerDrop, (byte) (ffaDrop ? 2 : 0));
|
||||
} else {
|
||||
final Item drop;
|
||||
int randomedId = integer;
|
||||
|
||||
if (ItemConstants.getInventoryType(randomedId) != InventoryType.EQUIP) {
|
||||
drop = new Item(randomedId, (short) 0, (short) (rnd.nextInt(copies) + minCopies));
|
||||
} else {
|
||||
drop = ii.randomizeStats((Equip) ii.getEquipById(randomedId));
|
||||
}
|
||||
|
||||
spawnItemDrop(dropper, owner, drop, calcDropPos(dropPos, pos), ffaDrop, playerDrop);
|
||||
}
|
||||
|
||||
dropPos.x += 25;
|
||||
}
|
||||
}
|
||||
|
||||
private void registerMapSchedule(Runnable r, long delay) {
|
||||
OverallService service = (OverallService) this.getChannelServer().getServiceAccess(ChannelServices.OVERALL);
|
||||
service.registerOverallAction(mapid, r, delay);
|
||||
@@ -2861,20 +2774,23 @@ public class MapleMap {
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcastItemDropMessage(MapItem mdrop, Point dropperPos, Point dropPos, byte mod, Point rangedFrom) {
|
||||
broadcastItemDropMessage(mdrop, dropperPos, dropPos, mod, getRangedDistance(), rangedFrom);
|
||||
private void broadcastItemDropMessage(MapItem mdrop, Point dropperPos, Point dropPos, byte mod, short delay,
|
||||
Point rangedFrom) {
|
||||
broadcastItemDropMessage(mdrop, dropperPos, dropPos, mod, delay, getRangedDistance(), rangedFrom);
|
||||
}
|
||||
|
||||
private void broadcastItemDropMessage(MapItem mdrop, Point dropperPos, Point dropPos, byte mod) {
|
||||
broadcastItemDropMessage(mdrop, dropperPos, dropPos, mod, Double.POSITIVE_INFINITY, null);
|
||||
private void broadcastItemDropMessage(MapItem mdrop, Point dropperPos, Point dropPos, byte mod, short delay) {
|
||||
broadcastItemDropMessage(mdrop, dropperPos, dropPos, mod, delay, Double.POSITIVE_INFINITY, null);
|
||||
}
|
||||
|
||||
private void broadcastItemDropMessage(MapItem mdrop, Point dropperPos, Point dropPos, byte mod, double rangeSq, Point rangedFrom) {
|
||||
private void broadcastItemDropMessage(MapItem mdrop, Point dropperPos, Point dropPos, byte mod, short delay,
|
||||
double rangeSq, Point rangedFrom) {
|
||||
chrRLock.lock();
|
||||
try {
|
||||
for (Character chr : characters) {
|
||||
Packet packet = PacketCreator.dropItemFromMapObject(chr, mdrop, dropperPos, dropPos, mod);
|
||||
Packet packet = PacketCreator.dropItemFromMapObject(chr, mdrop, dropperPos, dropPos, mod, delay);
|
||||
|
||||
// TODO: remove along with USE_MAXRANGE config
|
||||
if (rangeSq < Double.POSITIVE_INFINITY) {
|
||||
if (rangedFrom.distanceSq(chr.getPosition()) <= rangeSq) {
|
||||
chr.sendPacket(packet);
|
||||
@@ -3399,12 +3315,14 @@ public class MapleMap {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: no reason to implement runnable - this is not intended to be submitted to another thread
|
||||
private class MobLootEntry implements Runnable {
|
||||
|
||||
private final byte droptype;
|
||||
private final int mobpos;
|
||||
private final int chRate;
|
||||
private final Point pos;
|
||||
private final short delay;
|
||||
private final List<MonsterDropEntry> dropEntry;
|
||||
private final List<MonsterDropEntry> visibleQuestEntry;
|
||||
private final List<MonsterDropEntry> otherQuestEntry;
|
||||
@@ -3412,11 +3330,15 @@ public class MapleMap {
|
||||
private final Character chr;
|
||||
private final Monster mob;
|
||||
|
||||
protected MobLootEntry(byte droptype, int mobpos, int chRate, Point pos, List<MonsterDropEntry> dropEntry, List<MonsterDropEntry> visibleQuestEntry, List<MonsterDropEntry> otherQuestEntry, List<MonsterGlobalDropEntry> globalEntry, Character chr, Monster mob) {
|
||||
protected MobLootEntry(byte droptype, int mobpos, int chRate, Point pos, short delay,
|
||||
List<MonsterDropEntry> dropEntry, List<MonsterDropEntry> visibleQuestEntry,
|
||||
List<MonsterDropEntry> otherQuestEntry, List<MonsterGlobalDropEntry> globalEntry,
|
||||
Character chr, Monster mob) {
|
||||
this.droptype = droptype;
|
||||
this.mobpos = mobpos;
|
||||
this.chRate = chRate;
|
||||
this.pos = pos;
|
||||
this.delay = delay;
|
||||
this.dropEntry = dropEntry;
|
||||
this.visibleQuestEntry = visibleQuestEntry;
|
||||
this.otherQuestEntry = otherQuestEntry;
|
||||
@@ -3430,14 +3352,14 @@ public class MapleMap {
|
||||
byte d = 1;
|
||||
|
||||
// Normal Drops
|
||||
d = dropItemsFromMonsterOnMap(dropEntry, pos, d, chRate, droptype, mobpos, chr, mob);
|
||||
d = dropItemsFromMonsterOnMap(dropEntry, pos, d, chRate, droptype, mobpos, chr, mob, delay);
|
||||
|
||||
// Global Drops
|
||||
d = dropGlobalItemsFromMonsterOnMap(globalEntry, pos, d, droptype, mobpos, chr, mob);
|
||||
d = dropGlobalItemsFromMonsterOnMap(globalEntry, pos, d, droptype, mobpos, chr, mob, delay);
|
||||
|
||||
// Quest Drops
|
||||
d = dropItemsFromMonsterOnMap(visibleQuestEntry, pos, d, chRate, droptype, mobpos, chr, mob);
|
||||
dropItemsFromMonsterOnMap(otherQuestEntry, pos, d, chRate, droptype, mobpos, chr, mob);
|
||||
d = dropItemsFromMonsterOnMap(visibleQuestEntry, pos, d, chRate, droptype, mobpos, chr, mob, delay);
|
||||
dropItemsFromMonsterOnMap(otherQuestEntry, pos, d, chRate, droptype, mobpos, chr, mob, delay);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4457,11 +4379,6 @@ public class MapleMap {
|
||||
expireItemsTask = null;
|
||||
}
|
||||
|
||||
if (mobSpawnLootTask != null) {
|
||||
mobSpawnLootTask.cancel(false);
|
||||
mobSpawnLootTask = null;
|
||||
}
|
||||
|
||||
if (characterStatUpdateTask != null) {
|
||||
characterStatUpdateTask.cancel(false);
|
||||
characterStatUpdateTask = null;
|
||||
|
||||
@@ -56,6 +56,7 @@ import constants.id.MapId;
|
||||
import constants.id.NpcId;
|
||||
import constants.inventory.ItemConstants;
|
||||
import constants.skills.Buccaneer;
|
||||
import constants.skills.ChiefBandit;
|
||||
import constants.skills.Corsair;
|
||||
import constants.skills.ThunderBreaker;
|
||||
import net.encryption.InitializationVector;
|
||||
@@ -67,8 +68,9 @@ import net.packet.Packet;
|
||||
import net.server.PlayerCoolDownValueHolder;
|
||||
import net.server.Server;
|
||||
import net.server.channel.Channel;
|
||||
import net.server.channel.handlers.AbstractDealDamageHandler.AttackTarget;
|
||||
import net.server.channel.handlers.PlayerInteractionHandler;
|
||||
import net.server.channel.handlers.SummonDamageHandler.SummonAttackEntry;
|
||||
import net.server.channel.handlers.SummonDamageHandler.SummonAttackTarget;
|
||||
import net.server.channel.handlers.WhisperHandler;
|
||||
import net.server.guild.Alliance;
|
||||
import net.server.guild.Guild;
|
||||
@@ -77,6 +79,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;
|
||||
@@ -1813,7 +1816,8 @@ public class PacketCreator {
|
||||
return p;
|
||||
}
|
||||
|
||||
public static Packet dropItemFromMapObject(Character player, MapItem drop, Point dropfrom, Point dropto, byte mod) {
|
||||
public static Packet dropItemFromMapObject(Character player, MapItem drop, Point dropfrom, Point dropto, byte mod,
|
||||
short delay) {
|
||||
int dropType = drop.getDropType();
|
||||
if (drop.hasClientsideOwnership(player) && dropType < 3) {
|
||||
dropType = 2;
|
||||
@@ -1831,7 +1835,7 @@ public class PacketCreator {
|
||||
|
||||
if (mod != 2) {
|
||||
p.writePos(dropfrom);
|
||||
p.writeShort(0);//Fh?
|
||||
p.writeShort(delay);
|
||||
}
|
||||
if (drop.getMeso() == 0) {
|
||||
addExpirationTime(p, drop.getItem().getExpiration());
|
||||
@@ -2301,18 +2305,18 @@ public class PacketCreator {
|
||||
return p;
|
||||
}
|
||||
|
||||
public static Packet summonAttack(int cid, int summonOid, byte direction, List<SummonAttackEntry> allDamage) {
|
||||
public static Packet summonAttack(int cid, int summonOid, byte direction, List<SummonAttackTarget> targets) {
|
||||
OutPacket p = OutPacket.create(SendOpcode.SUMMON_ATTACK);
|
||||
//b2 00 29 f7 00 00 9a a3 04 00 c8 04 01 94 a3 04 00 06 ff 2b 00
|
||||
p.writeInt(cid);
|
||||
p.writeInt(summonOid);
|
||||
p.writeByte(0); // char level
|
||||
p.writeByte(direction);
|
||||
p.writeByte(allDamage.size());
|
||||
for (SummonAttackEntry attackEntry : allDamage) {
|
||||
p.writeInt(attackEntry.getMonsterOid()); // oid
|
||||
p.writeByte(targets.size());
|
||||
for (SummonAttackTarget target : targets) {
|
||||
p.writeInt(target.monsterOid()); // oid
|
||||
p.writeByte(6); // who knows
|
||||
p.writeInt(attackEntry.getDamage()); // damage
|
||||
p.writeInt(target.damage()); // damage
|
||||
}
|
||||
|
||||
return p;
|
||||
@@ -2337,29 +2341,40 @@ public class PacketCreator {
|
||||
}
|
||||
*/
|
||||
|
||||
public static Packet closeRangeAttack(Character chr, int skill, int skilllevel, int stance, int numAttackedAndDamage, Map<Integer, List<Integer>> damage, int speed, int direction, int display) {
|
||||
public static Packet closeRangeAttack(Character chr, int skill, int skilllevel, int stance,
|
||||
int numAttackedAndDamage, Map<Integer, AttackTarget> targets, int speed,
|
||||
int direction, int display) {
|
||||
final OutPacket p = OutPacket.create(SendOpcode.CLOSE_RANGE_ATTACK);
|
||||
addAttackBody(p, chr, skill, skilllevel, stance, numAttackedAndDamage, 0, damage, speed, direction, display);
|
||||
addAttackBody(p, chr, skill, skilllevel, stance, numAttackedAndDamage, 0, targets, speed, direction,
|
||||
display);
|
||||
return p;
|
||||
}
|
||||
|
||||
public static Packet rangedAttack(Character chr, int skill, int skilllevel, int stance, int numAttackedAndDamage, int projectile, Map<Integer, List<Integer>> damage, int speed, int direction, int display) {
|
||||
public static Packet rangedAttack(Character chr, int skill, int skilllevel, int stance, int numAttackedAndDamage,
|
||||
int projectile, Map<Integer, AttackTarget> targets, int speed, int direction,
|
||||
int display) {
|
||||
final OutPacket p = OutPacket.create(SendOpcode.RANGED_ATTACK);
|
||||
addAttackBody(p, chr, skill, skilllevel, stance, numAttackedAndDamage, projectile, damage, speed, direction, display);
|
||||
addAttackBody(p, chr, skill, skilllevel, stance, numAttackedAndDamage, projectile, targets, speed, direction,
|
||||
display);
|
||||
p.writeInt(0);
|
||||
return p;
|
||||
}
|
||||
|
||||
public static Packet magicAttack(Character chr, int skill, int skilllevel, int stance, int numAttackedAndDamage, Map<Integer, List<Integer>> damage, int charge, int speed, int direction, int display) {
|
||||
public static Packet magicAttack(Character chr, int skill, int skilllevel, int stance, int numAttackedAndDamage,
|
||||
Map<Integer, AttackTarget> targets, int charge, int speed, int direction,
|
||||
int display) {
|
||||
final OutPacket p = OutPacket.create(SendOpcode.MAGIC_ATTACK);
|
||||
addAttackBody(p, chr, skill, skilllevel, stance, numAttackedAndDamage, 0, damage, speed, direction, display);
|
||||
addAttackBody(p, chr, skill, skilllevel, stance, numAttackedAndDamage, 0, targets, speed, direction,
|
||||
display);
|
||||
if (charge != -1) {
|
||||
p.writeInt(charge);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
private static void addAttackBody(OutPacket p, Character chr, int skill, int skilllevel, int stance, int numAttackedAndDamage, int projectile, Map<Integer, List<Integer>> damage, int speed, int direction, int display) {
|
||||
private static void addAttackBody(OutPacket p, Character chr, int skill, int skilllevel, int stance,
|
||||
int numAttackedAndDamage, int projectile, Map<Integer, AttackTarget> targets,
|
||||
int speed, int direction, int display) {
|
||||
p.writeInt(chr.getId());
|
||||
p.writeByte(numAttackedAndDamage);
|
||||
p.writeByte(0x5B);//?
|
||||
@@ -2373,16 +2388,16 @@ public class PacketCreator {
|
||||
p.writeByte(speed);
|
||||
p.writeByte(0x0A);
|
||||
p.writeInt(projectile);
|
||||
for (Integer oned : damage.keySet()) {
|
||||
List<Integer> onedList = damage.get(oned);
|
||||
if (onedList != null) {
|
||||
p.writeInt(oned);
|
||||
for (Map.Entry<Integer, AttackTarget> target : targets.entrySet()) {
|
||||
AttackTarget value = target.getValue();
|
||||
if (value != null) {
|
||||
p.writeInt(target.getKey());
|
||||
p.writeByte(0x0);
|
||||
if (skill == 4211006) {
|
||||
p.writeByte(onedList.size());
|
||||
if (skill == ChiefBandit.MESO_EXPLOSION) {
|
||||
p.writeByte(value.damageLines().size());
|
||||
}
|
||||
for (Integer eachd : onedList) {
|
||||
p.writeInt(eachd);
|
||||
for (Integer damageLine : value.damageLines()) {
|
||||
p.writeInt(damageLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2593,6 +2608,14 @@ public class PacketCreator {
|
||||
return p;
|
||||
}
|
||||
|
||||
public static Packet removeExplodedMesoFromMap(int mapObjectId, short delay) {
|
||||
OutPacket p = OutPacket.create(SendOpcode.REMOVE_ITEM_FROM_MAP);
|
||||
p.writeByte(4);
|
||||
p.writeInt(mapObjectId);
|
||||
p.writeShort(delay);
|
||||
return p;
|
||||
}
|
||||
|
||||
public static Packet updateCharLook(Client target, Character chr) {
|
||||
OutPacket p = OutPacket.create(SendOpcode.UPDATE_CHAR_LOOK);
|
||||
p.writeInt(chr.getId());
|
||||
@@ -5580,8 +5603,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 +5712,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 +6502,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;
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
package tools.exceptions;
|
||||
|
||||
public class NotEnabledException extends RuntimeException {
|
||||
|
||||
public NotEnabledException() {
|
||||
super("Feature not enabled, please enable the feature in ServerConstant");
|
||||
}
|
||||
|
||||
public NotEnabledException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
237
src/test/java/client/processor/stat/AssignAPProcessorTest.java
Normal file
237
src/test/java/client/processor/stat/AssignAPProcessorTest.java
Normal file
@@ -0,0 +1,237 @@
|
||||
package client.processor.stat;
|
||||
|
||||
import client.Job;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class AssignAPProcessorTest {
|
||||
|
||||
@Test
|
||||
void getMinHp() {
|
||||
int max_level = 200;
|
||||
int cygnus_max_level = 120;
|
||||
|
||||
BiFunction<Job,Integer,Integer> f = AssignAPProcessor::getMinHp;
|
||||
|
||||
assertAll(
|
||||
// Beginners
|
||||
() -> assertEquals(2438, f.apply(Job.BEGINNER, max_level)),
|
||||
() -> assertEquals(1478, f.apply(Job.NOBLESSE, cygnus_max_level)),
|
||||
|
||||
// Warrior (Explorer)
|
||||
() -> assertEquals(4918, f.apply(Job.WARRIOR, max_level)),
|
||||
|
||||
() -> assertEquals(5218, f.apply(Job.FIGHTER, max_level)),
|
||||
() -> assertEquals(5218, f.apply(Job.CRUSADER, max_level)),
|
||||
() -> assertEquals(5218, f.apply(Job.HERO, max_level)),
|
||||
|
||||
() -> assertEquals(4918, f.apply(Job.PAGE, max_level)),
|
||||
() -> assertEquals(4918, f.apply(Job.WHITEKNIGHT, max_level)),
|
||||
() -> assertEquals(4918, f.apply(Job.PALADIN, max_level)),
|
||||
|
||||
() -> assertEquals(4918, f.apply(Job.SPEARMAN, max_level)),
|
||||
() -> assertEquals(4918, f.apply(Job.DRAGONKNIGHT, max_level)),
|
||||
() -> assertEquals(4918, f.apply(Job.DARKKNIGHT, max_level)),
|
||||
|
||||
// Warrior (Cygnus)
|
||||
() -> assertEquals(2998, f.apply(Job.DAWNWARRIOR1, cygnus_max_level)),
|
||||
() -> assertEquals(3298, f.apply(Job.DAWNWARRIOR2, cygnus_max_level)),
|
||||
() -> assertEquals(3298, f.apply(Job.DAWNWARRIOR3, cygnus_max_level)),
|
||||
() -> assertEquals(3298, f.apply(Job.DAWNWARRIOR4, cygnus_max_level)),
|
||||
|
||||
// Warrior (Aran)
|
||||
() -> assertEquals(4918, f.apply(Job.ARAN1, max_level)),
|
||||
() -> assertEquals(5218, f.apply(Job.ARAN2, max_level)),
|
||||
() -> assertEquals(5218, f.apply(Job.ARAN3, max_level)),
|
||||
() -> assertEquals(5218, f.apply(Job.ARAN4, max_level)),
|
||||
|
||||
// Magician (Explorer)
|
||||
() -> assertEquals(2054, f.apply(Job.MAGICIAN, max_level)),
|
||||
|
||||
() -> assertEquals(2054, f.apply(Job.FP_WIZARD, max_level)),
|
||||
() -> assertEquals(2054, f.apply(Job.FP_MAGE, max_level)),
|
||||
() -> assertEquals(2054, f.apply(Job.FP_ARCHMAGE, max_level)),
|
||||
|
||||
() -> assertEquals(2054, f.apply(Job.IL_WIZARD, max_level)),
|
||||
() -> assertEquals(2054, f.apply(Job.IL_MAGE, max_level)),
|
||||
() -> assertEquals(2054, f.apply(Job.IL_ARCHMAGE, max_level)),
|
||||
|
||||
() -> assertEquals(2054, f.apply(Job.CLERIC, max_level)),
|
||||
() -> assertEquals(2054, f.apply(Job.PRIEST, max_level)),
|
||||
() -> assertEquals(2054, f.apply(Job.BISHOP, max_level)),
|
||||
|
||||
// Magician (Cygnus)
|
||||
() -> assertEquals(1254, f.apply(Job.BLAZEWIZARD1, cygnus_max_level)),
|
||||
() -> assertEquals(1254, f.apply(Job.BLAZEWIZARD2, cygnus_max_level)),
|
||||
() -> assertEquals(1254, f.apply(Job.BLAZEWIZARD3, cygnus_max_level)),
|
||||
() -> assertEquals(1254, f.apply(Job.BLAZEWIZARD4, cygnus_max_level)),
|
||||
|
||||
// Bowman (Explorer)
|
||||
() -> assertEquals(4058, f.apply(Job.BOWMAN, max_level)),
|
||||
|
||||
() -> assertEquals(4358, f.apply(Job.HUNTER, max_level)),
|
||||
() -> assertEquals(4358, f.apply(Job.RANGER, max_level)),
|
||||
() -> assertEquals(4358, f.apply(Job.BOWMASTER, max_level)),
|
||||
|
||||
() -> assertEquals(4358, f.apply(Job.CROSSBOWMAN, max_level)),
|
||||
() -> assertEquals(4358, f.apply(Job.SNIPER, max_level)),
|
||||
() -> assertEquals(4358, f.apply(Job.MARKSMAN, max_level)),
|
||||
|
||||
// Bowman (Cygnus)
|
||||
() -> assertEquals(2458, f.apply(Job.WINDARCHER1, cygnus_max_level)),
|
||||
() -> assertEquals(2758, f.apply(Job.WINDARCHER2, cygnus_max_level)),
|
||||
() -> assertEquals(2758, f.apply(Job.WINDARCHER3, cygnus_max_level)),
|
||||
() -> assertEquals(2758, f.apply(Job.WINDARCHER4, cygnus_max_level)),
|
||||
|
||||
// Thief (Explorer)
|
||||
() -> assertEquals(4058, f.apply(Job.THIEF, max_level)),
|
||||
|
||||
() -> assertEquals(4358, f.apply(Job.ASSASSIN, max_level)),
|
||||
() -> assertEquals(4358, f.apply(Job.HERMIT, max_level)),
|
||||
() -> assertEquals(4358, f.apply(Job.NIGHTLORD, max_level)),
|
||||
|
||||
() -> assertEquals(4358, f.apply(Job.BANDIT, max_level)),
|
||||
() -> assertEquals(4358, f.apply(Job.CHIEFBANDIT, max_level)),
|
||||
() -> assertEquals(4358, f.apply(Job.SHADOWER, max_level)),
|
||||
|
||||
// Thief (Cygnus)
|
||||
() -> assertEquals(2458, f.apply(Job.NIGHTWALKER1, cygnus_max_level)),
|
||||
() -> assertEquals(2758, f.apply(Job.NIGHTWALKER2, cygnus_max_level)),
|
||||
() -> assertEquals(2758, f.apply(Job.NIGHTWALKER3, cygnus_max_level)),
|
||||
() -> assertEquals(2758, f.apply(Job.NIGHTWALKER4, cygnus_max_level)),
|
||||
|
||||
// Pirate (Explorer)
|
||||
() -> assertEquals(4438, f.apply(Job.PIRATE, max_level)),
|
||||
|
||||
() -> assertEquals(4738, f.apply(Job.BRAWLER, max_level)),
|
||||
() -> assertEquals(4738, f.apply(Job.MARAUDER, max_level)),
|
||||
() -> assertEquals(4738, f.apply(Job.BUCCANEER, max_level)),
|
||||
|
||||
() -> assertEquals(4738, f.apply(Job.GUNSLINGER, max_level)),
|
||||
() -> assertEquals(4738, f.apply(Job.OUTLAW, max_level)),
|
||||
() -> assertEquals(4738, f.apply(Job.CORSAIR, max_level)),
|
||||
|
||||
// Pirate (Cygnus)
|
||||
() -> assertEquals(2678, f.apply(Job.THUNDERBREAKER1, cygnus_max_level)),
|
||||
() -> assertEquals(2978, f.apply(Job.THUNDERBREAKER2, cygnus_max_level)),
|
||||
() -> assertEquals(2978, f.apply(Job.THUNDERBREAKER3, cygnus_max_level)),
|
||||
() -> assertEquals(2978, f.apply(Job.THUNDERBREAKER4, cygnus_max_level))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMinMp() {
|
||||
int max_level = 200;
|
||||
int cygnus_max_level = 120;
|
||||
|
||||
BiFunction<Job,Integer,Integer> f = AssignAPProcessor::getMinMp;
|
||||
|
||||
assertAll(
|
||||
// Beginners
|
||||
() -> assertEquals(1995, f.apply(Job.BEGINNER, max_level)),
|
||||
() -> assertEquals(1195, f.apply(Job.NOBLESSE, cygnus_max_level)),
|
||||
|
||||
// Warrior (Explorer)
|
||||
() -> assertEquals(855, f.apply(Job.WARRIOR, max_level)),
|
||||
|
||||
() -> assertEquals(855, f.apply(Job.FIGHTER, max_level)),
|
||||
() -> assertEquals(855, f.apply(Job.CRUSADER, max_level)),
|
||||
() -> assertEquals(855, f.apply(Job.HERO, max_level)),
|
||||
|
||||
() -> assertEquals(955, f.apply(Job.PAGE, max_level)),
|
||||
() -> assertEquals(955, f.apply(Job.WHITEKNIGHT, max_level)),
|
||||
() -> assertEquals(955, f.apply(Job.PALADIN, max_level)),
|
||||
|
||||
() -> assertEquals(955, f.apply(Job.SPEARMAN, max_level)),
|
||||
() -> assertEquals(955, f.apply(Job.DRAGONKNIGHT, max_level)),
|
||||
() -> assertEquals(955, f.apply(Job.DARKKNIGHT, max_level)),
|
||||
|
||||
// Warrior (Cygnus)
|
||||
() -> assertEquals(535, f.apply(Job.DAWNWARRIOR1, cygnus_max_level)),
|
||||
() -> assertEquals(535, f.apply(Job.DAWNWARRIOR2, cygnus_max_level)),
|
||||
() -> assertEquals(535, f.apply(Job.DAWNWARRIOR3, cygnus_max_level)),
|
||||
() -> assertEquals(535, f.apply(Job.DAWNWARRIOR4, cygnus_max_level)),
|
||||
|
||||
// Warrior (Aran)
|
||||
() -> assertEquals(855, f.apply(Job.ARAN1, max_level)),
|
||||
() -> assertEquals(855, f.apply(Job.ARAN2, max_level)),
|
||||
() -> assertEquals(855, f.apply(Job.ARAN3, max_level)),
|
||||
() -> assertEquals(855, f.apply(Job.ARAN4, max_level)),
|
||||
|
||||
// Magician (Explorer)
|
||||
() -> assertEquals(4399, f.apply(Job.MAGICIAN, max_level)),
|
||||
|
||||
() -> assertEquals(4849, f.apply(Job.FP_WIZARD, max_level)),
|
||||
() -> assertEquals(4849, f.apply(Job.FP_MAGE, max_level)),
|
||||
() -> assertEquals(4849, f.apply(Job.FP_ARCHMAGE, max_level)),
|
||||
|
||||
() -> assertEquals(4849, f.apply(Job.IL_WIZARD, max_level)),
|
||||
() -> assertEquals(4849, f.apply(Job.IL_MAGE, max_level)),
|
||||
() -> assertEquals(4849, f.apply(Job.IL_ARCHMAGE, max_level)),
|
||||
|
||||
() -> assertEquals(4849, f.apply(Job.CLERIC, max_level)),
|
||||
() -> assertEquals(4849, f.apply(Job.PRIEST, max_level)),
|
||||
() -> assertEquals(4849, f.apply(Job.BISHOP, max_level)),
|
||||
|
||||
// Magician (Cygnus)
|
||||
() -> assertEquals(2639, f.apply(Job.BLAZEWIZARD1, cygnus_max_level)),
|
||||
() -> assertEquals(3089, f.apply(Job.BLAZEWIZARD2, cygnus_max_level)),
|
||||
() -> assertEquals(3089, f.apply(Job.BLAZEWIZARD3, cygnus_max_level)),
|
||||
() -> assertEquals(3089, f.apply(Job.BLAZEWIZARD4, cygnus_max_level)),
|
||||
|
||||
// Bowman (Explorer)
|
||||
() -> assertEquals(2785, f.apply(Job.BOWMAN, max_level)),
|
||||
|
||||
() -> assertEquals(2935, f.apply(Job.HUNTER, max_level)),
|
||||
() -> assertEquals(2935, f.apply(Job.RANGER, max_level)),
|
||||
() -> assertEquals(2935, f.apply(Job.BOWMASTER, max_level)),
|
||||
|
||||
() -> assertEquals(2935, f.apply(Job.CROSSBOWMAN, max_level)),
|
||||
() -> assertEquals(2935, f.apply(Job.SNIPER, max_level)),
|
||||
() -> assertEquals(2935, f.apply(Job.MARKSMAN, max_level)),
|
||||
|
||||
// Bowman (Cygnus)
|
||||
() -> assertEquals(1665, f.apply(Job.WINDARCHER1, cygnus_max_level)),
|
||||
() -> assertEquals(1815, f.apply(Job.WINDARCHER2, cygnus_max_level)),
|
||||
() -> assertEquals(1815, f.apply(Job.WINDARCHER3, cygnus_max_level)),
|
||||
() -> assertEquals(1815, f.apply(Job.WINDARCHER4, cygnus_max_level)),
|
||||
|
||||
// Thief (Explorer)
|
||||
() -> assertEquals(2785, f.apply(Job.THIEF, max_level)),
|
||||
|
||||
() -> assertEquals(2935, f.apply(Job.ASSASSIN, max_level)),
|
||||
() -> assertEquals(2935, f.apply(Job.HERMIT, max_level)),
|
||||
() -> assertEquals(2935, f.apply(Job.NIGHTLORD, max_level)),
|
||||
|
||||
() -> assertEquals(2935, f.apply(Job.BANDIT, max_level)),
|
||||
() -> assertEquals(2935, f.apply(Job.CHIEFBANDIT, max_level)),
|
||||
() -> assertEquals(2935, f.apply(Job.SHADOWER, max_level)),
|
||||
|
||||
// Thief (Cygnus)
|
||||
() -> assertEquals(1665, f.apply(Job.NIGHTWALKER1, cygnus_max_level)),
|
||||
() -> assertEquals(1815, f.apply(Job.NIGHTWALKER2, cygnus_max_level)),
|
||||
() -> assertEquals(1815, f.apply(Job.NIGHTWALKER3, cygnus_max_level)),
|
||||
() -> assertEquals(1815, f.apply(Job.NIGHTWALKER4, cygnus_max_level)),
|
||||
|
||||
// Pirate (Explorer)
|
||||
() -> assertEquals(3545, f.apply(Job.PIRATE, max_level)),
|
||||
|
||||
() -> assertEquals(3695, f.apply(Job.BRAWLER, max_level)),
|
||||
() -> assertEquals(3695, f.apply(Job.MARAUDER, max_level)),
|
||||
() -> assertEquals(3695, f.apply(Job.BUCCANEER, max_level)),
|
||||
|
||||
() -> assertEquals(3695, f.apply(Job.GUNSLINGER, max_level)),
|
||||
() -> assertEquals(3695, f.apply(Job.OUTLAW, max_level)),
|
||||
() -> assertEquals(3695, f.apply(Job.CORSAIR, max_level)),
|
||||
|
||||
// Pirate (Cygnus)
|
||||
() -> assertEquals(2105, f.apply(Job.THUNDERBREAKER1, cygnus_max_level)),
|
||||
() -> assertEquals(2255, f.apply(Job.THUNDERBREAKER2, cygnus_max_level)),
|
||||
() -> assertEquals(2255, f.apply(Job.THUNDERBREAKER3, cygnus_max_level)),
|
||||
() -> assertEquals(2255, f.apply(Job.THUNDERBREAKER4, cygnus_max_level))
|
||||
);
|
||||
}
|
||||
}
|
||||
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()));
|
||||
}
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'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."/>
|
||||
|
||||
Reference in New Issue
Block a user