Compare commits

...

47 Commits

Author SHA1 Message Date
Ponk
431eced76a Merge pull request #307 from P0nk/chore/update-project #patch
Update project
2025-06-26 14:17:40 +02:00
P0nk
ac1f4c85b5 Refer to Cosmic-client for client setup 2025-06-26 14:11:57 +02:00
P0nk
ee8758e63b Upgrade dependencies 2025-06-26 13:53:40 +02:00
Lotus
76e114c9f2 [Handbook]: Add Skins, Jobs, Genders. Remove a few invalid "Pet" entries. (#285) #patch
* removed a few per entries that aren't in game, added Skin, Job, and Gender to handbook

* adjusted skin color + added to enum for db saves

* change skin names based on character creator's names for them.
2025-03-22 12:08:05 +01:00
Ponk
bb16165c51 Merge pull request #286 from v3921358/v3921358-npc-9120003-getmeso-check #patch
Fix incorrect method usage in NPC script (9120003.js)
2025-03-22 12:01:51 +01:00
Ponk
78e994402e Merge pull request #288 from P0nk/maintenance/maven-wrapper #patch
Add Maven Wrapper
2025-02-03 07:10:30 +01:00
P0nk
67ab800205 Add Maven Wrapper 2025-02-03 07:01:13 +01:00
v3921358
afb04b0d80 Fix incorrect method usage in NPC script (9120003.js)
Corrected the condition in NPC script 9120003.js by replacing cm.getMeso < price with cm.getMeso() < price to properly compare mesos against the price.
2024-12-12 19:55:48 +08:00
Ponk
a5b572023b Merge pull request #275 from nutnnut/fix-monster-crystal-level #patch
fix maker monster crystal level calculation
2024-10-19 13:52:08 +02:00
Ponk
380e4284ee Merge pull request #276 from JalenJaceX2/command-list #patch
Create Commands.txt
2024-10-19 13:44:27 +02:00
JalenJaceX2
16ff5a6f54 Create Commands.txt 2024-10-14 02:02:50 -07:00
Sirinut Euaungkanakul
cced80991f fix maker monster crystal level calculation
(cherry picked from commit 0f3611a41b449e195de6eceb686a97a40196b919)
2024-10-07 14:39:20 +07:00
Ponk
5fabbaf7ab Merge pull request #261 from pimittens/vanillafixes #patch
chaos scroll fix
2024-09-13 21:25:51 +02:00
pimittens
215dc42294 Create RandomizerTest.java 2024-09-10 13:49:16 -07:00
Ponk
314916279a Merge pull request #271 from P0nk/fix/global-drop-concurrency #patch
Fix global drops thread issue
2024-08-28 21:30:39 +02:00
P0nk
dbf1a1bb36 Fix global drops thread issue
This randomly caused global drops to stop working after a while.
Originally reported by Crabo of MapleHorizons.
2024-08-28 21:22:47 +02:00
Ponk
8f2b8dd013 Merge pull request #248 from HarkuLi/fix/vard-plastic-surgery #patch
NPC (Vard): Fix "Plastic Surgery" option
2024-08-28 19:57:04 +02:00
Ponk
8039852aa3 Merge pull request #247 from NoirReverie/master #patch
Fix minimum HP and MP checks on AP reset
2024-08-28 18:30:15 +02:00
Ponk
d3b567953d Merge pull request #270 from P0nk/fix/exploded-meso-delay #patch
Fix staggered exploding mesos
2024-08-27 07:47:31 +02:00
P0nk
5064ee936a Fix staggered exploding mesos
Thanks teto: "the staggering should only go up to 400ms [attack.attackDelay + (index++ % 5) * 100], and the total delay should be capped at 1000ms according to the BMS implementation"
2024-08-27 07:41:24 +02:00
Ponk
86da9b0b29 Merge pull request #262 from P0nk/fix/drop-delay #patch
Proper delay for item drops
2024-08-20 20:38:27 +02:00
P0nk
acac203e42 Scheduler free timing for reactor drops 2024-08-20 20:32:33 +02:00
P0nk
cdd1c8cb61 Proper timing on removing exploded meso
No longer using scheduling on server side but rather
a delay value inherent to the "remove item from map" packet.
2024-08-20 20:13:01 +02:00
P0nk
439753eb6d Fix drop delay for Meso Explosion 2024-08-20 19:44:16 +02:00
P0nk
e30700de66 Fix drop delay from summon attack 2024-08-17 21:27:43 +02:00
P0nk
994d1723b6 Remove unnecessary "spawn loot on animation" feature
No longer needed since item drop timing is now dictated by the client
since two commits back.
2024-08-17 19:14:04 +02:00
P0nk
2d40a89c55 Overload damageMonster for when no delay is needed 2024-08-17 19:13:38 +02:00
P0nk
802cc2b5f5 Use delay from packets for drop timing 2024-08-17 19:13:36 +02:00
P0nk
2ffca90d29 Add attack delay to AttackInfo
Meant to be used in item drop packet for timing handled by the client
rather than scheduling item drop packets on the server.
2024-08-14 07:45:06 +02:00
pimittens
ec90df9c58 chaos scroll fix
The intention of the rand method is to return a pseudorandom uniformly distributed int between lbound and ubound (both inclusive). The previous implementation did not work as intended when the lower bound was negative since java round up instead of down when casting a negative double to an int. Adding the lower bound after the cast rather than before fixes this.

Also changed the CHSCROLL_STAT_RANGE from 6 to 5 to be consistent with gms.
2024-08-06 20:42:23 -07:00
Ponk
8f2c2dc08f Merge pull request #253 from P0nk/cleanup/rebirth #minor
Remove rebirth system
2024-07-19 17:24:18 +02:00
P0nk
cad10c4d5c Remove rebirth system 2024-07-19 17:16:14 +02:00
Ponk
aa3686ed0b Merge pull request #251 from P0nk/cleanup/banish #minor
Remove custom banish scroll feature
2024-07-17 19:47:15 +02:00
P0nk
3850b63cec Add migration for <v0.14.0 to fix shop dc 2024-07-17 19:24:19 +02:00
P0nk
a7df8a4f49 Remove banish scroll from WZ files 2024-07-17 18:50:44 +02:00
P0nk
7071b13e41 Remove banish scroll from shops 2024-07-17 18:39:50 +02:00
P0nk
2324ae7f9e Refactor BanishInfo - make it a record 2024-07-17 18:11:50 +02:00
P0nk
402163c33d Remove "banishable town scroll" feature 2024-07-17 17:45:38 +02:00
P0nk
3356e42e71 Remove "spikes avoid banish" feature 2024-07-16 20:17:21 +02:00
P0nk
205e263255 Specify required Java version 2024-07-16 20:16:26 +02:00
Ponk
aab9823a06 Merge pull request #227 from HarkuLi/feat/fix-ariant-coliseum #patch
Ariant Coliseum: Fix score counting and trade conversation
2024-06-19 17:58:38 +02:00
HarkuLi
0245e7d1af NPC (Vard): Fix "Plastic Surgery" option
Correct typos for variable `fface_v`.
2024-06-19 18:56:14 +08:00
HarkuLi
3f800c4a68 Ignore .DS_Store files 2024-06-19 18:43:26 +08:00
HarkuLi
31e901ab6d Ariant Coliseum: Fix score counting and trade conversation 2024-06-19 18:42:31 +08:00
Noir
94a08d86a0 Min HP / MP needs to check against post-AP-reset value 2024-06-18 21:42:52 -04:00
NoirReverie
bcc7bedbc9 Merge branch 'P0nk:master' into master 2024-06-17 18:53:21 -04:00
Noir
a878a4f3f9 Fix minimum HP and MP checks on AP reset 2024-06-17 18:48:13 -04:00
62 changed files with 1631 additions and 946 deletions

3
.gitignore vendored
View File

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

19
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View File

@@ -0,0 +1,19 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
wrapperVersion=3.3.2
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip

View File

@@ -57,7 +57,7 @@ We will set up the following:
- Client - the client is the application used to _play the game_, i.e. MapleStory.exe.
### 1 - Database
You will start by installing the database server and client, and then run some scripts to prepare it for the server.
You will start by installing the database server and database client, and then run some scripts to prepare it for the server.
#### Steps
@@ -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
@@ -91,36 +91,21 @@ Making changes becomes a bit more tedious though as you have to rebuild the serv
On the first launch, the database container will run the scripts which may take so long that the server fails to start. In that case, just wait until the database is done running the scripts and then retry (Ctrl+C and re-run the command).
#### Jar
Another option is to start the server from a terminal by running a jar file. You first need to build the jar file from source which requires [Maven](https://maven.apache.org/).
Another option is to start the server from a terminal by running a jar file. You first need to build the jar file from source which requires [Maven](https://maven.apache.org/). Fortunately, [Maven Wrapper](https://maven.apache.org/wrapper/) is provided so you don't have to install Maven separately.
Building the jar file is as easy as running ``mvn clean package``. The project is configured to produce a "fat" jar which contains all dependencies (by utilizing the _maven-assembly-plugin_). Note that the WZ XML files are __not__ included in the jar.
Building the jar file is as easy as running ``./mvnw.cmd clean package``. The project is configured to produce a "fat" jar which contains all dependencies (by utilizing the _maven-assembly-plugin_). Note that the WZ XML files are __not__ included in the jar.
To run the jar, a ``launch.bat`` file is provided for convenience. Simply double-click it and the server will start in a new terminal window.
Alternatively, run the jar file from the terminal. Just remember to provide the `wz-path` system property pointing to your wz directory.
### 3 - Client
You will start by installing the game with the old installer, then overwrite some WZ files with our custom ones, and lastly get the localhost executable in place.
The client files are located in a separate repository: https://github.com/P0nk/Cosmic-client
#### Steps
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).
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".
7. Move _HeavenMS-localhost-WINDOW.exe_ into the installation directory.
8. Done! Double-click the exe and the game should start.
- The client may be a bit fiddly. Sometimes it won't start, but if you see "Client connected" in the server console it's a good indication. Try spam-clicking it like 10+ times, that usually works for me.
**Important note about localhost clients**: these executables are red-flagged by antivirus tools as potentially malicious software.
This happens due to the reverse engineering methods that were applied onto these software artifacts.
The one provided here has been in use for years already and posed no harm so far, so it is assumed to be safe.
Follow the installation guide in the README.
### 4 - Getting into the game
The client has started, and you're looking at the login screen.
You have successfully started the client, and you're looking at the login screen.
#### Logging in
At this point, you can log in to the admin account using the following credentials:
@@ -129,7 +114,7 @@ At this point, you can log in to the admin account using the following credentia
* Pin: "0000"
* Pic: "000000"
Or create a regular account by typing in your desired username & password and attempting to log in. This "automatic registration" feature lets you create new accounts to play around with. It is enabled by default (see _config.yaml_).
You can also create a new regular account by typing in your desired username & password and attempting to log in. This "automatic registration" feature lets you create new accounts to play around with. It is enabled by default (see _config.yaml_).
#### Entering the game
Create a new character as you normally would, and then select it to enter the game. Hooray, finally we're in!
@@ -144,21 +129,14 @@ That's it, have fun playing around in game!
Some slightly more advanced concepts that might be useful once you're up and running.
### Host on remote server
You don't have to host the server on your local machine to play. It's possible to host on a remote server such as a VPS or even a dedicated server.
You don't have to host the server on your local machine to play. It's possible to host on a remote server such as a VPS or a dedicated server.
I leave it to you to figure out the server hosting part, but once you have that running you'll need to edit the client exe to point to your remote server ip.
#### Edit client ip
1. Download and install a hex editor: [HxD](https://mh-nexus.de/en/hxd/)
2. Start HxD and open your client exe (I recommend making a copy of it first). At this point you should see a bunch of hex codes and a "Decoded text" column to the right of it.
3. Ctrl+f and search for Text-string "127.0.0.1". You should find three occurrences right above each other.
4. Place your cursor before the first "127" and start typing the desired ip, overwriting what is already there. Do the same on the other two and click on Save.
5. Done! Now the client will attempt to connect to that ip address instead when you launch it.
I leave it to you to figure out the server hosting part, but once you have that running you'll need to edit the client ip to point to your remote server ip.
### WZ files
WZ files are the asset/data files required by the client and server. Typically, [HaRepacker-resurrected](https://github.com/lastbattle/Harepacker-resurrected) is used to handle (view, edit, export) the .wz files.
WZ files are the asset/data files required by the client and server. Typically, the [HaRepacker-resurrected](https://github.com/lastbattle/Harepacker-resurrected) tool is used to manage (view, edit, export) the .wz files.
The client can read the .wz files directly, but the server requires them in XML format. The server also does not make use of the sprites, which is the motivation for different kinds of exporting.
The client can read the .wz files directly, but the server requires them to be in XML format. The server does not make use of the sprites, which is the motivation for different kinds of exporting.
HaRepacker allows you to export to "Private server", which is the .img files packaged in the .wz stripped of sprites and converted to XML. This takes much less disk space.
This server requires custom .wz files (unfortunately), as you may have noted during installation of the client. The intention is for these to be removed eventually and to solely run on vanilla .wz files.
@@ -172,10 +150,3 @@ This server requires custom .wz files (unfortunately), as you may have noted dur
Make sure to always export from the client .wz files to the server XML, and not the other way around.
Editing the client .wz without exporting to the server may lead to strange behavior.
### Client features
For more information about the client and its features, see [HeavenMS on GitHub](https://github.com/ronancpl/HeavenMS#download-items).
Some notable features:
* Opens in window mode by default
* Uncapped max speed

View File

@@ -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.
@@ -348,7 +344,7 @@ server:
USE_ENHANCED_CRAFTING: false #Apply chaos scroll on every equip crafted.
SCROLL_CHANCE_ROLLS: 1 #Number of rolls for success on a scroll, set 1 for default.
CHSCROLL_STAT_RATE: 1 #Number of rolls of stat upgrade on a successfully applied chaos scroll, set 1 for default.
CHSCROLL_STAT_RANGE: 6 #Stat upgrade range (-N, N) on chaos scrolls.
CHSCROLL_STAT_RANGE: 5 #Stat upgrade range (-N, N) on chaos scrolls.
#Beginner Skills Configuration
USE_ULTRA_NIMBLE_FEET: false #Massive speed & jump upgrade.
@@ -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.

View File

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

View File

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

View File

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

View File

@@ -480,9 +480,7 @@
5590000 - High-Five Stamp - Can equip items that are 5 levels above your current level.
5021026 - Gift Box Throwing Stars - A gift box that can be freely thrown around. Using the #cThrowing Star# will create an orbital effect.\n\nThis item cannot be deleted.
5010073 - Miss Popular - Well, lookie here. Someone<6E>s certainly become popular with the guys. Turn this effect on and off by assigning it to a shortcut key from the keyboard settings menu.
5010074 - Mr. Popular - Well, lookie here. Someone<6E>s certainly become popular with the girls. Turn this effect on and off by assigning it to a shortcut key from the keyboard settings menu.
5240027 - Golden Drumstick - A drumstick that can be consumed only by #cBaby Tiger#. It recovers hunger and #cincreases Closeness by 100.#
5390005 - Cute Tiger Messenger - Shout to everyone in the world your character is on with this megaphone. Now available with your avatar on the top of everyone's screen! Comes with a tiger background for your avatar.
5390006 - Roaring Tiger Messenger - Shout to everyone in the world your character is on with this megaphone. Now available with your avatar on the top of everyone's screen! Comes with a screen shaked.

200
handbook/Commands.txt Normal file
View File

@@ -0,0 +1,200 @@
Common Commands:
@help - Show available commands.
@commands - Show available commands.
@droplimit - Check drop limit of current map.
@time - Show current server time.
@credits - Show credits. These people made the server possible.
@uptime - Show server online time.
@gacha - Show gachapon rewards.
@dispose - Dispose to fix NPC chat.
@changel - Change language settings
@equiplv - Show levels of all equipped items.
@showrates - Show all world/character rates.
@rates - Show your rates.
@online - Show all online players.
@gm- Send a message to the game masters.
@reportbug - Send in a bug report.
@points - Show point total.
@joinevent - Join active event.
@leaveevent-Leave active event.
@ranks - Show player rankings.
@str - Assign AP into STR.
@dex - Assign AP into DEX.
@int - Assign AP into INT.
@luk - Assign AP into LUK.
@enableauth - Enable PIC code by resetting the cooldown.
@toggleexp - Toggle enable/disable all exp gain.
@mylawn - Claim ownership of the current map.
@bosshp - Show HP of bosses on current map.
@mobhp - Show HP of mobs on current map.
Donator Commands:
@whatdropsfrom - Show what items drop from a mob.
@whodrops - Show what drops an item.
@buffme - Activate GM buffs on self.
@goto - Warp to a predefined map.
JrGM Commands:
!recharge - Recharge and refill all USE items.
!whereami - Show info about objects on current map.
!hide - Hide from players.
!unhide - Toggle Hide.
!sp - Set available SP.
!ap - Set available AP.
!empowerme - Activate all useful buffs.
!buffmap - Give GM buffs to the whole map.
!buff - Activate a buff.
!bomb - Bomb a player, dealing damage.
!dc - Disconnect a player.
!cleardrops - Clear drops by player.
!clearslot - Clear all items in an inventory tab.
!clearsavelocs - Clear saved locations for a player.
!warp - Warp to a map.
!warphere - Move a player to your location.
!summon - Move a player to your location.
!warpto - Warp to a player.
!reach - Warp to a player.
!follow - Warp to a player.
!gmshop - Open the GM shop.
!heal - Fully heal your HP/MP.
!item - Spawn an item into your inventory.
!drop - Spawn an item onto the ground.
!level - Set your level.
!levelpro - Set your level, one by one.
!setslot - Set amount of inventory slots in all tabs.
!setstat - Set all primary stats to new value.
!maxstat - Max out all character stats.
!maxskill - Max out all job skills.
!resetskill Set all skill levels to 0.
!search - Search String.wz.
!jail - Move a player to the jail.
!unjail- Free a player from jail.
!job - Change job of a player.
!unbug - Unbug self.
!id - Search in handbook.
!gachalist - Show gachapon rewards.
!loot - Loots all items that belong to you.
!mobskill - Apply a mob skill to all mobs on the map.
Args: <mob skill id> <skill level>
GM Commands:
!debuff - Put a debuff on all nearby players.
!fly - Enable/disable fly feature.
!spawn - Spawn mob(s) on your location.
!mutemap - Toggle mute players in the map.
!checkdmg - Show stats and damage of a player.
!inmap - Show all players in the map.
!reloadevents - Reload all event data.
!reloaddrops - Reload all drop data.
!reloadportals - Reload all portal scripts.
!reloadmap - Reload the map.
!reloadshops - Reload popup shops and NPC shops.
!hpmp - Set HP/MP of a player.
!maxhpmp - Set base HP/MP of a player.
!music - Play a song.
!monitor - Toggle monitored packet logging of a character.
!monitors - Show all characters being monitored for packet logging.
!ignore - Toggle ignore a character from auto-ban alerts.
!ignored - Show all characters being ignored in auto-ban alerts.
!pos - Show current position and foothold.
!togglecoupon - Toggle enable/disable a coupon.
!togglewhitechat - Toggle white GM chat.
!fame - Set new fame value of a player.
!givenx - Give NX to a player.
!givevp - Give vote points to a player.
!givems - Give mesos to a player.
!giverp - Give reward points to a player.
!expeds - Show all ongoing boss expeditions.
!kill - Kill a player.
!seed - Drop all seeds inside Henesys PQ.
!maxenergy - Set dojo energy to max value.
!killall - Kill all mobs in the map.
!notice - Send a blue message to everyone on the server.
!rip - Send a RIP notice.
!openportal - Open a portal on the map.
!closeportal - Close a portal.
!pe - Handle synthesized packets from file, and handle them as if sent from a client.
!startevent - Start an event on current map.
!endevent - Close entry for ongoing event.
!startmapevent - Start a "classic" event on current map.
!stopmapevent - Stop ongoing "classic" event.
!online2 - Show all online players.
!ban - Ban a player.
!unban - Unban a player.
!healmap - Heal all HP/MP of all players in the map.
!healperson - Heal all HP/MP of a player.
!hurt - Nearly kill a player.
!killmap - Kill all players in the map.
!night - Set sky background to black.
!npc - Spawn an NPC on your location.
!face - Change face of a player.
!hair - Change hair of a player.
!startquest - Start a quest.
!completequest - Complete an active quest.
!resetquest - Reset a completed quest.
!timer - Set timer on a player in current map.
!timermap - Set timer on all players in current map.
!timerall Set a server wide timer.
!warpmap - Warp all characters on current map to a new map.
!warparea - Warp all nearby players to a new map.
SuperGM Commands:
!servermessage - Set scrolling server message.
!proitem - Spawn an item with custom stats.
!seteqstat-Set stats of all equips in inventory.
!exprate - Set world exp rate.
!mesorate - Set world meso rate.
!droprate - Set world drop rate.
!bossdroprate - Set world boss drop rate.
!questrate - Set world quest rate.
!travelrate - Set world travel rate.
!fishrate - Set fishing rate.
!itemvac - Loot all drops on the map.
!forcevac-Loot all drops on the map.
!zakum - Spawn Zakum on your location.
!horntail - Spawn Horntail on your location.
!pinkbean - Spawn Pink Bean on your location.
!pap - Spawn Papulatus on your location.
!pianus - Spawn Pianus (R) on your location.
!cake - Spawn Cake boss with specified HP.
!playernpc - Spawn a player NPC of an online player.
!playernpcremove - Remove a "Iv 200" player NPC.
!pnpc - Spawn a permanent NPC on your location.
!pnpcremove - Remove a permanent NPC on the map.
!pmob - Spawn a permanent mob on your location.
!pmobremove - Remove all permanent mobs of the same type on the map.
Developer Commands:
!debug - Show a debug message.
!set - Store value in an array, for testing.
!showpackets - Toggle show received packets in console.
!showmovelife - Toggle show move life in console.
!showsessions - Show online sessions.
!iplist - Show IP of all players.
Admin Commands:
!setgmlevel - Set GM level of a player.
!warpworld - Warp to a different world.
!saveall - Save all characters.
!dcall - Disconnect all players (online or logged in).
!mapplayers - Show all players on the map.
!getacc - Show account name of an online player.
!shutdown - Shut down the server.
!clearquestcache - Clear all quest cache.
!clearquest - Clear cache of a quest.
!supplyratecoupon - Set availability of coupons in Cash Shop.
!spawnallpnpcs - Spawn player NPCs of all existing players.
!eraseallpnpcs - Remove all player NPCs.
!addchannel - Add a new channel to a world.
!addworld - Add a new world.
!removechannel - Remove channel from a world.
!removeworld - Remove a world.
!devtest - Runs devtest.js. Developer utility - test stuff without restarting the server.

9
handbook/Equip/Skin.txt Normal file
View File

@@ -0,0 +1,9 @@
0 - Light - (no description)
1 - Tanned - (no description)
2 - Dark - (no description)
3 - Pale - (no description)
4 - Blue - (no description)
5 - Green - (no description)
9 - White - (no description)
10 - Pink - (no description)
11 - Brown - (no description)

2
handbook/Gender.txt Normal file
View File

@@ -0,0 +1,2 @@
0 - Male - (no description)
1 - Female - (no description)

70
handbook/Job.txt Normal file
View File

@@ -0,0 +1,70 @@
0 - Beginner - (no description)
100 - Warrior - (no description)
110 - Fighter - (no description)
111 - Crusader - (no description)
112 - Hero - (no description)
120 - Page - (no description)
121 - White Knight - (no description)
122 - Paladin - (no description)
130 - Spearman - (no description)
131 - Dragon Knight - (no description)
132 - Dark Knight - (no description)
200 - Magician - (no description)
210 - Fp Wizard - (no description)
211 - Fp Mage - (no description)
212 - Fp Archmage - (no description)
220 - Il Wizard - (no description)
221 - Il Mage - (no description)
222 - Il Archmage - (no description)
230 - Cleric - (no description)
231 - Priest - (no description)
232 - Bishop - (no description)
300 - Bowman - (no description)
310 - Hunter - (no description)
311 - Ranger - (no description)
312 - Bow Master - (no description)
320 - Crossbowman - (no description)
321 - Sniper - (no description)
322 - Crossbow Master - (no description)
400 - Thief - (no description)
410 - Assassin - (no description)
411 - Hermit - (no description)
412 - Night Lord - (no description)
420 - Bandit - (no description)
421 - Chief Bandit - (no description)
422 - Shadower - (no description)
500 - Pirate - (no description)
510 - Brawler - (no description)
511 - Marauder - (no description)
512 - Buccaneer - (no description)
520 - Gunslinger - (no description)
521 - Outlaw - (no description)
522 - Corsair - (no description)
800 - Maple Leaf Brigadier - (no description)
900 - Gm - (no description)
910 - Super Gm - (no description)
1000 - Noblesse - (no description)
1100 - Dawn Warrior 1 - (no description)
1110 - Dawn Warrior 2 - (no description)
1111 - Dawn Warrior 3 - (no description)
1200 - Blaze Wizard 1 - (no description)
1210 - Blaze Wizard 2 - (no description)
1211 - Blaze Wizard 3 - (no description)
1212 - Blaze Wizard 4 - (no description)
1300 - Wind Archer 1 - (no description)
1310 - Wind Archer 2 - (no description)
1311 - Wind Archer 3 - (no description)
1312 - Wind Archer 4 - (no description)
1400 - Night Walker 1 - (no description)
1410 - Night Walker 2 - (no description)
1411 - Night Walker 3 - (no description)
1412 - Night Walker 4 - (no description)
1500 - Thunder Breaker 1 - (no description)
1510 - Thunder Breaker 2 - (no description)
1511 - Thunder Breaker 3 - (no description)
1512 - Thunder Breaker 4 - (no description)
2000 - Legend - (no description)
2100 - Aran 1 - (no description)
2110 - Aran 2 - (no description)
2111 - Aran 3 - (no description)
2112 - Aran 4 - (no description)

View File

@@ -36,10 +36,7 @@
5000044 - Orange tiger - They are very gentle and obedient in nature, easily becoming friendly with the owner, which in turn speeds up the level-up process.
5000045 - Skunk - Calm and quiet by nature, the easy-going, eccentric Skunk has a rather humorous and pompous outlook on life, the finer things are what it enjoys.
5000026 - Sun Wu Kong - Sun Wu Kong ran out of Tang Shan Zhang. He is lazy and moody but can be changed with love.
5000040 - ??? ?? - ??? ??? ??? ??? ???????. ??? ????? ??? ?????, ??? ? ?? ??? ??? ???? ??? ?? ????. \n#c??: ?? ??, ??? ??, ??? ?? ???&?? ??, ?? ??, ???? ??, MP ????#
5000042 - Kino - A happy-go-lucky, orange mushroom that loves shiny stones.
5000043 - ???? - ??? ????, ?? ??? ??? ???????. ??? ?? ? ????, ??? ??? ???? ??? ?? ????. \n#c??: ????, ?????#
5000046 - ?? - ???? ?? ?? ???? ???? ??? ???????. \n#c??:????, ?????#
5000047 - Robo - A special egg that becomes a mythical Robo upon hatching.
5000048 - Baby Robo - A Baby Robo hatched out of the special Capsule. At Level 15, the pet can be evolved into a full-fledged Adult Robo with the help of Garnox the NPC using the Rock of Evolution.
5000049 - Blue Robo - Mysterious magical powers aligned and sucessfully evolved the Baby Robo into a Blue Robo!

View File

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

259
mvnw vendored Normal file
View File

@@ -0,0 +1,259 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup batch script, version 3.3.2
#
# Optional ENV vars
# -----------------
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
# MVNW_REPOURL - repo url base for downloading maven distribution
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
# ----------------------------------------------------------------------------
set -euf
[ "${MVNW_VERBOSE-}" != debug ] || set -x
# OS specific support.
native_path() { printf %s\\n "$1"; }
case "$(uname)" in
CYGWIN* | MINGW*)
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
native_path() { cygpath --path --windows "$1"; }
;;
esac
# set JAVACMD and JAVACCMD
set_java_home() {
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
if [ -n "${JAVA_HOME-}" ]; then
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACCMD="$JAVA_HOME/jre/sh/javac"
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACCMD="$JAVA_HOME/bin/javac"
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
return 1
fi
fi
else
JAVACMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v java
)" || :
JAVACCMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v javac
)" || :
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
return 1
fi
fi
}
# hash string like Java String::hashCode
hash_string() {
str="${1:-}" h=0
while [ -n "$str" ]; do
char="${str%"${str#?}"}"
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
str="${str#?}"
done
printf %x\\n $h
}
verbose() { :; }
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
die() {
printf %s\\n "$1" >&2
exit 1
}
trim() {
# MWRAPPER-139:
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
# Needed for removing poorly interpreted newline sequences when running in more
# exotic environments such as mingw bash on Windows.
printf "%s" "${1}" | tr -d '[:space:]'
}
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
while IFS="=" read -r key value; do
case "${key-}" in
distributionUrl) distributionUrl=$(trim "${value-}") ;;
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
esac
done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
case "${distributionUrl##*/}" in
maven-mvnd-*bin.*)
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
:Linux*x86_64*) distributionPlatform=linux-amd64 ;;
*)
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
distributionPlatform=linux-amd64
;;
esac
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
;;
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
esac
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
distributionUrlName="${distributionUrl##*/}"
distributionUrlNameMain="${distributionUrlName%.*}"
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
exec_maven() {
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
}
if [ -d "$MAVEN_HOME" ]; then
verbose "found existing MAVEN_HOME at $MAVEN_HOME"
exec_maven "$@"
fi
case "${distributionUrl-}" in
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
esac
# prepare tmp dir
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
trap clean HUP INT TERM EXIT
else
die "cannot create temp dir"
fi
mkdir -p -- "${MAVEN_HOME%/*}"
# Download and Install Apache Maven
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
verbose "Downloading from: $distributionUrl"
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
# select .zip or .tar.gz
if ! command -v unzip >/dev/null; then
distributionUrl="${distributionUrl%.zip}.tar.gz"
distributionUrlName="${distributionUrl##*/}"
fi
# verbose opt
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
# normalize http auth
case "${MVNW_PASSWORD:+has-password}" in
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
esac
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
verbose "Found wget ... using wget"
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
verbose "Found curl ... using curl"
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
elif set_java_home; then
verbose "Falling back to use Java to download"
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
cat >"$javaSource" <<-END
public class Downloader extends java.net.Authenticator
{
protected java.net.PasswordAuthentication getPasswordAuthentication()
{
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
}
public static void main( String[] args ) throws Exception
{
setDefault( new Downloader() );
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
}
}
END
# For Cygwin/MinGW, switch paths to Windows format before running javac and java
verbose " - Compiling Downloader.java ..."
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
verbose " - Running Downloader.java ..."
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
fi
# If specified, validate the SHA-256 sum of the Maven distribution zip file
if [ -n "${distributionSha256Sum-}" ]; then
distributionSha256Result=false
if [ "$MVN_CMD" = mvnd.sh ]; then
echo "Checksum validation is not supported for maven-mvnd." >&2
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
elif command -v sha256sum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
distributionSha256Result=true
fi
elif command -v shasum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
distributionSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
fi
if [ $distributionSha256Result = false ]; then
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
exit 1
fi
fi
# unzip and move
if command -v unzip >/dev/null; then
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
else
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
fi
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
clean || :
exec_maven "$@"

149
mvnw.cmd vendored Normal file
View File

@@ -0,0 +1,149 @@
<# : batch portion
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Apache Maven Wrapper startup batch script, version 3.3.2
@REM
@REM Optional ENV vars
@REM MVNW_REPOURL - repo url base for downloading maven distribution
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
@REM ----------------------------------------------------------------------------
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
@SET __MVNW_CMD__=
@SET __MVNW_ERROR__=
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
@SET PSModulePath=
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
)
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
@SET __MVNW_PSMODULEP_SAVE=
@SET __MVNW_ARG0_NAME__=
@SET MVNW_USERNAME=
@SET MVNW_PASSWORD=
@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
@echo Cannot start maven from wrapper >&2 && exit /b 1
@GOTO :EOF
: end batch / begin powershell #>
$ErrorActionPreference = "Stop"
if ($env:MVNW_VERBOSE -eq "true") {
$VerbosePreference = "Continue"
}
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
if (!$distributionUrl) {
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
}
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
"maven-mvnd-*" {
$USE_MVND = $true
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
$MVN_CMD = "mvnd.cmd"
break
}
default {
$USE_MVND = $false
$MVN_CMD = $script -replace '^mvnw','mvn'
break
}
}
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
if ($env:MVNW_REPOURL) {
$MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
}
$distributionUrlName = $distributionUrl -replace '^.*/',''
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
if ($env:MAVEN_USER_HOME) {
$MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
}
$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
exit $?
}
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
}
# prepare tmp dir
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
trap {
if ($TMP_DOWNLOAD_DIR.Exists) {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
}
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
# Download and Install Apache Maven
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
Write-Verbose "Downloading from: $distributionUrl"
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
$webclient = New-Object System.Net.WebClient
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
# If specified, validate the SHA-256 sum of the Maven distribution zip file
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
if ($distributionSha256Sum) {
if ($USE_MVND) {
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
}
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
}
}
# unzip and move
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
try {
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
} catch {
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
Write-Error "fail to move MAVEN_HOME"
}
} finally {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"

23
pom.xml
View File

@@ -1,6 +1,5 @@
<?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>
@@ -51,24 +50,24 @@
<mainClass>net.server.Server</mainClass>
<!-- 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-surefire-plugin.version>3.5.3</maven-surefire-plugin.version> <!-- For running unit tests -->
<maven-jar-plugin.version>3.4.2</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 -->
<!-- Dependencies -->
<slf4j-api.version>2.0.13</slf4j-api.version> <!-- Logging facade -->
<log4j.version>2.23.1</log4j.version> <!-- Slf4j implementation -->
<slf4j-api.version>2.0.17</slf4j-api.version> <!-- Logging facade -->
<log4j.version>2.25.0</log4j.version> <!-- Slf4j implementation -->
<graalvm-js.version>23.0.4</graalvm-js.version>
<graalvm-js-scriptengine.version>24.0.1</graalvm-js-scriptengine.version> <!-- ScriptEngine implementation -->
<netty.version>4.1.109.Final</netty.version> <!-- Networking -->
<netty.version>4.2.2.Final</netty.version> <!-- Networking -->
<yamlbeans.version>1.17</yamlbeans.version> <!-- Config file -->
<jcip-annotations.version>1.0</jcip-annotations.version> <!-- Annotations for concurrency documentation -->
<HikariCP.version>5.1.0</HikariCP.version> <!-- Database connection pool -->
<mysql-connector-j.version>8.4.0</mysql-connector-j.version> <!-- MySQL JDBC driver -->
<jdbi-version>3.45.1</jdbi-version> <!-- Convenience wrapper around JDBC -->
<junit.version>5.10.2</junit.version> <!-- Unit test -->
<mockito.version>5.11.0</mockito.version> <!-- Unit test -->
<HikariCP.version>6.3.0</HikariCP.version> <!-- Database connection pool -->
<mysql-connector-j.version>9.3.0</mysql-connector-j.version> <!-- MySQL JDBC driver -->
<jdbi-version>3.49.5</jdbi-version> <!-- Convenience wrapper around JDBC -->
<junit.version>5.13.1</junit.version> <!-- Unit test -->
<mockito.version>5.18.0</mockito.version> <!-- Unit test -->
</properties>
<dependencies>

View File

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

View File

@@ -32,14 +32,14 @@ function action(mode, type, selection) {
} else if (status == 1) {
if (selection == 0) {
apqpoints = cm.getPlayer().getAriantPoints();
if (apqpoints < 100) {
cm.sendOk("Your Battle Arena score: #b" + apqpoints + "#k points. You need to surpass #b100 points#k so that I can give you the #bPalm Tree Beach Chair#k. Talk to me again when you have enough points.");
cm.dispose();
if (apqpoints >= 100) {
cm.sendNext("Wow, it looks like you got the #b100#k points ready to trade, let's trade?!");
} else if (apqpoints + arena.getAriantRewardTier(cm.getPlayer()) >= 100) {
cm.sendOk("Your Battle Arena score: #b" + apqpoints + "#k points and you pratically already have that score! Talk to my wife, #p2101016#to get them and then re-chat with me!");
cm.sendOk("Your Battle Arena score: #b" + apqpoints + "#k points and you pratically already have that score! Talk to my wife, #p2101016# to get them and then re-chat with me!");
cm.dispose();
} else {
cm.sendNext("Wow, it looks like you got the #b100#k points ready to trade, let's trade?!");
cm.sendOk("Your Battle Arena score: #b" + apqpoints + "#k points. You need to surpass #b100 points#k so that I can give you the #bPalm Tree Beach Chair#k. Talk to me again when you have enough points.");
cm.dispose();
}
} else if (selection == 1) {
cm.sendOk("The main objective of the Battle Arena is to allow the player to accumulate points so that they can be traded honorably for the highest prize: the #bPalm Tree Beach Chair#k. Collect points during the battles and talk to me when it's time to get the prize. In each battle, the player is given the opportunity to score points based on the amount of jewelry that the player has at the end. But be careful! If your points distance from other players #ris too high#k, this will have been all for nothing and you will earn mere #r1 point#k only.");

View File

@@ -46,7 +46,7 @@ function action(mode, type, selection) {
cm.dispose();
return;
}
if (cm.getMeso < price) {
if (cm.getMeso() < price) {
cm.sendOk("Please check and see if you have " + price + " mesos to enter this place.");
} else {
cm.gainMeso(-price);

View File

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

View File

@@ -116,6 +116,7 @@ import server.events.Events;
import server.events.RescueGaga;
import server.events.gm.Fitness;
import server.events.gm.Ola;
import server.life.BanishInfo;
import server.life.MobSkill;
import server.life.MobSkillFactory;
import server.life.MobSkillId;
@@ -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.*;
@@ -267,7 +267,7 @@ public class Character extends AbstractCharacterObject {
private final Pet[] pets = new Pet[3];
private PlayerShop playerShop = null;
private Shop shop = null;
private SkinColor skinColor = SkinColor.NORMAL;
private SkinColor skinColor = SkinColor.LIGHT;
private Storage storage = null;
private Trade trade = null;
private MonsterBook monsterbook;
@@ -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();
@@ -10999,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;

View File

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

View File

@@ -22,14 +22,15 @@
package client;
public enum SkinColor {
NORMAL(0),
DARK(1),
BLACK(2),
LIGHT(0),
TANNED(1),
DARK(2),
PALE(3),
BLUE(4),
GREEN(5),
WHITE(9),
PINK(10);
PINK(10),
BROWN(11);
final int id;

View File

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

View File

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

View File

@@ -165,7 +165,6 @@ public class ItemId {
public static final int EYEDROP = 2050001;
public static final int TONIC = 2050002;
public static final int HOLY_WATER = 2050003;
public static final int ANTI_BANISH_SCROLL = 2030100;
private static final int DOJO_PARTY_ALL_CURE = 2022433;
private static final int CARNIVAL_PARTY_ALL_CURE = 2022163;
public static final int WHITE_ELIXIR = 2022544;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2063,7 +2063,7 @@ public class ItemInformationProvider {
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
int dropperid = rs.getInt("dropperid");
itemid = getCrystalForLevel(LifeFactory.getMonsterLevel(dropperid) - 1);
itemid = getCrystalForLevel(LifeFactory.getMonsterLevel(dropperid));
}
}
}

View File

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

View File

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

View File

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

View File

@@ -56,7 +56,6 @@ import org.slf4j.LoggerFactory;
import scripting.event.EventInstanceManager;
import server.StatEffect;
import server.TimerManager;
import server.life.LifeFactory.BanishInfo;
import server.loot.LootManager;
import server.maps.AbstractAnimatedMapObject;
import server.maps.MapObjectType;
@@ -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);

View File

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

View File

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

View File

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

View File

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

View File

@@ -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;
@@ -1814,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;
@@ -1832,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());
@@ -2302,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;
@@ -2338,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);//?
@@ -2374,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);
}
}
}
@@ -2594,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());

View File

@@ -35,6 +35,6 @@ public class Randomizer {
}
public static int rand(final int lbound, final int ubound) {
return (int) ((rand.nextDouble() * (ubound - lbound + 1)) + lbound);
return ((int) (rand.nextDouble() * (ubound - lbound + 1))) + lbound;
}
}

View File

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

View File

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

View File

@@ -0,0 +1,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))
);
}
}

View File

@@ -0,0 +1,37 @@
package tools;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class RandomizerTest {
@Test
void randShouldIncludeEntireRange() {
Map<Integer, Integer> rands = new HashMap<>();
final int rounds = 100_000;
for (int i = 0; i < rounds; i++) {
int randomValue = Randomizer.rand(-5, 5);
rands.compute(randomValue, (k, v) -> v == null ? 0 : v + 1);
}
assertFalse(rands.containsKey(-6));
assertTrue(rands.containsKey(-5));
assertTrue(rands.containsKey(-4));
assertTrue(rands.containsKey(-3));
assertTrue(rands.containsKey(-2));
assertTrue(rands.containsKey(-1));
assertTrue(rands.containsKey(0));
assertTrue(rands.containsKey(1));
assertTrue(rands.containsKey(2));
assertTrue(rands.containsKey(3));
assertTrue(rands.containsKey(4));
assertTrue(rands.containsKey(5));
assertFalse(rands.containsKey(6));
}
}

View File

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

View File

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

View File

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