Merge branch 'refs/heads/master' into feat/postgresql-database

# Conflicts:
#	config.yaml
#	docker-compose.yml
#	pom.xml
#	src/main/java/client/Character.java
#	src/main/java/client/Client.java
#	src/main/java/client/MonsterBook.java
#	src/main/java/client/command/commands/gm0/BuyBackCommand.java
#	src/main/java/client/processor/stat/AssignAPProcessor.java
#	src/main/java/config/ServerConfig.java
#	src/main/java/net/server/channel/Channel.java
#	src/main/java/net/server/channel/handlers/AbstractDealDamageHandler.java
#	src/main/java/net/server/channel/handlers/BuddylistModifyHandler.java
#	src/main/java/net/server/channel/handlers/CloseRangeDamageHandler.java
#	src/main/java/net/server/channel/handlers/EnterMTSHandler.java
#	src/main/java/net/server/channel/handlers/NPCTalkHandler.java
#	src/main/java/net/server/channel/handlers/RangedAttackHandler.java
#	src/main/java/net/server/channel/handlers/SummonDamageHandler.java
#	src/main/java/net/server/channel/handlers/UseCashItemHandler.java
#	src/main/java/net/server/handlers/login/CreateCharHandler.java
#	src/main/java/net/server/world/World.java
#	src/main/java/scripting/npc/NPCConversationManager.java
#	src/main/java/server/ItemInformationProvider.java
#	src/main/java/server/life/Monster.java
#	src/main/java/server/life/MonsterInformationProvider.java
#	src/main/java/server/maps/MapleMap.java
#	src/main/java/tools/PacketCreator.java
#	src/test/java/service/NoteServiceTest.java
#	src/test/java/testutil/Any.java
This commit is contained in:
P0nk
2024-09-02 20:43:55 +02:00
179 changed files with 3447 additions and 2090 deletions
+23
View File
@@ -0,0 +1,23 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
+15
View File
@@ -0,0 +1,15 @@
---
name: Feature request
about: Suggest an idea for this project, such as a missing gameplay feature
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
+11
View File
@@ -0,0 +1,11 @@
## Description
<!-- Describe your changes in detail -->
## Checklist before requesting a review
<!-- Mark with "x" inside the square brackets -->
- [ ] I have performed a self-review of my code
- [ ] I have tested my changes
- [ ] I have added unit tests that prove my changes work
## Screenshots
<!-- If applicable, add screenshots to help explain your changes -->
+3 -3
View File
@@ -11,10 +11,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Set up JDK 17 - name: Set up JDK 21
uses: actions/setup-java@v3 uses: actions/setup-java@v3
with: with:
java-version: '17' java-version: '21'
distribution: 'temurin' distribution: 'corretto'
- name: Build with Maven (compile -> test -> package) - name: Build with Maven (compile -> test -> package)
run: mvn -B package --file pom.xml run: mvn -B package --file pom.xml
+9 -1
View File
@@ -1,5 +1,6 @@
/logs/** /logs/**
.idea/ .idea/*
!.idea/codeStyles/
*.iml *.iml
/target /target
@@ -16,3 +17,10 @@
.project .project
# JDT-specific (Eclipse Java Development Tools) # JDT-specific (Eclipse Java Development Tools)
.classpath .classpath
# Database
database/docker-db-data
database/docker-pg-db-data
# macOS files
.DS_Store
+8
View File
@@ -0,0 +1,8 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JavaCodeStyleSettings>
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
</JavaCodeStyleSettings>
</code_scheme>
</component>
+5
View File
@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>
+10 -4
View File
@@ -4,15 +4,21 @@
# #
# Cosmic JAR creation stage # Cosmic JAR creation stage
# #
FROM maven:3.9.1-eclipse-temurin-17 AS jar FROM maven:3.9.6-amazoncorretto-21 AS jar
# Build in a separated location which won't have permissions issues. # Build in a separated location which won't have permissions issues.
WORKDIR /opt/cosmic WORKDIR /opt/cosmic
# Any changes to the pom will affect the entire build, so it should be copied first. # Any changes to the pom will affect the entire build, so it should be copied first.
COPY pom.xml ./pom.xml COPY pom.xml ./pom.xml
# Grab all the dependencies listed in the pom early, since it prevents changes to source code from requiring a complete re-download. # Grab all the dependencies listed in the pom early, since it prevents changes to source code from requiring a complete re-download.
# Skip compiling tests since we don't want all the dependecies to be downloaded. # Skip compiling tests since we don't want all the dependencies to be downloaded.
RUN mvn -f ./pom.xml clean dependency:go-offline -Dmaven.test.skip -T 1C # RUN mvn -f ./pom.xml clean dependency:go-offline -Dmaven.test.skip -T 1C
# TODO: The above command stopped working as of Java 21 upgrade due to:
# Failed to execute goal org.apache.maven.plugins:maven-dependency-plugin:3.6.1:go-offline (default-cli) on project Cosmic: org.eclipse.aether.resolution.DependencyResolutionException: The following artifacts could
# not be resolved: io.netty:netty-tcnative:jar:${os.detected.classifier}:2.0.65.Final (absent): Could not find artifact io.netty:netty-tcnative:jar:${os.detected.classifier}:2.0.65.Final in central (https://repo.maven.apache.org/maven2) -> [Help 1]
# Source code changes may not change dependencies, so it can go last. # Source code changes may not change dependencies, so it can go last.
# Skip compiling tests since we don't want all the dependecies to be downloaded for plugins. # Skip compiling tests since we don't want all the dependecies to be downloaded for plugins.
COPY src ./src COPY src ./src
@@ -21,7 +27,7 @@ RUN mvn -f ./pom.xml clean package -Dmaven.test.skip -T 1C
# #
# Server creation stage # Server creation stage
# #
FROM eclipse-temurin:17.0.6_10-jre FROM amazoncorretto:21
# Host the server in a location that won't have permissions issues. # Host the server in a location that won't have permissions issues.
WORKDIR /opt/server WORKDIR /opt/server
+121 -243
View File
@@ -1,303 +1,181 @@
# Cosmic - MapleStory v83 server # Cosmic
Cosmic is a server emulator for Global MapleStory (GMS) version 83.
## Introduction ## Introduction
Cosmic launched as a successor to HeavenMS on March 21st 2021.
HeavenMS is archived, ie. it receives no further updates. This project aims to continue its development; mainly by improving code quality and make getting into PS development as easy as possible. Cosmic launched on March 2021. It is based on code from a long line of server emulators spanning over a decade - starting with OdinMS (2008) and ending with HeavenMS (2019).
This is an open source project. Anyone may contribute by opening a pull request. This is mainly a Java based project, but there are also a bunch of scripts written in JavaScript.
Only the server side is maintained. The client is directly copied from HeavenMS. Head developer and maintainer: __Ponk__.\
Contributors: a lot of people over the years, and hopefully more to come. Big thanks to everyone who has contributed so far!
Join the Discord server where most of the discussions take place: https://discord.gg/JU5aQapVZK
Beware - ***This server emulator is not production ready.*** ### Goals
It can be useful for testing things locally or for trying out ideas, but launching a new private server based on this and opening it up to the public What we are working towards.
without knowing what you're doing is not recommended. * __Vanilla gameplay__ - stay as close to the original game as possible (within reason).
* __Ease of use__ - getting started should be frictionless and contributing to the project straightforward.
* __Reduce technical debt__ - making changes should be easy without causing unintended side effects.
* __Modern tools & technologies__ - stay appealing by continuously improving the code and the project as a whole.
### Non-goals
Explicitly excluded from the scope of the project.
* __Custom gameplay features__ - existing custom features will be removed over time and new ones are unlikely to be added.
* __Client development__ - this project is focused on the server. Please go elsewhere for client related questions.
* __Public server__ - there will not be an official Cosmic server open to the public. Feel free to launch your own server __at your own risk__. No support will be provided.
## Development information ## Project setup
### Status (updated 2022-10-16) ### Contribute
You may contribute to the project in various ways, mainly through GitHub:
* Providing improvements to the code through a [Pull Request](https://github.com/P0nk/Cosmic/pulls) from your own fork.
* Reporting a bug by creating an [Issue](https://github.com/P0nk/Cosmic/issues).
* Providing information to existing issues or reviewing pull requests that others have made.
* ...and in other ways that I haven't thought of!
Development is currently <span style="color:Yellow">**sporadic**</span>. ### Continuous integration
A GitHub Actions pipeline is set up to run the build automatically when a new pull request is opened or commits are pushed to an existing one. This ensures that the code compiles and all the tests pass.
My time is very limited nowadays, but I try to keep up with the submitted pull requests. I may submit some stuff of my own, once in a while. Once a pull request is merged, a tag with the new version is automatically created.
### Ways to contribute ### Discord integration
Most GitHub activity is pushed to a Discord channel for visibility. This works by leveraging a webhook. The activity includes (but is not limited to): merged commits, created PRs, comments, and new tags.
* Submit a Pull Request (fork -> commit -> PR). If you don't know where to start, have a look at the issues on GitHub.
* Report a bug (preferably as an Issue on GitHub, as reports on Discord may be forgotten or lost)
* Spread the word about Cosmic
### Working with GitHub
Anyone with a GitHub account can contribute by making some changes in a branch and opening up a PR.
All activity on the GitHub repo (opening PR, commenting, creating issue, etc.) is automatically pushed (via webhook) to a public Discord channel for visibility.
Issues is the main place where bugs, issues or general improvements are tracked. Feel free to submit a new issue, but please keep it in English. By providing a good description, you increase the chance of a bug being fixed.
Tasks (past, present and future) are kept in the Cosmic project, which you get to via the "Projects" tab. This gives you an idea of where the project is moving.
### Versioning ### Versioning
The project follows the [semantic versioning](https://semver.org/) scheme using git tags.
The project follows the [SemVer](https://semver.org/) versioning scheme using git tags. * *Bug fixes* are treated as PATCH: 1.2.__3__ -> 1.2.__4__
As a pull request gets merged, a new version is automatically created. * *General changes or improvements* are treated as MINOR: 1.__2__.3 -> 1.__3.0__
* *Major changes* are treated as MAJOR: __1__.2.3 -> __2.0.0__
Bug fixes result in bumped patch version: 1.2.__3__ -> 1.2.__4__
General improvements result in bumped minor version: 1.__2__.3 -> 1.__3__.3
Major changes result in bumped major version: __1__.2.3 -> __2__.2.3
### Cosmic
- GitHub: https://github.com/P0nk/Cosmic
- Discord: https://discord.gg/JU5aQapVZK
### HeavenMS
- GitHub: https://github.com/ronancpl/HeavenMS
- Discord: https://discord.gg/Q7wKxHX
## Tools / downloads
* **Java 17 SDK** - Needed to compile and run Java code. Install manually or through IntelliJ depending on how you prefer to launch the server. Not required for launching with Docker.
* Link: https://jdk.java.net/17/
* **IntelliJ IDEA** - Java IDE and your main tool for working with the source code. Community edition is good enough.
* Link: https://www.jetbrains.com/idea/
* **MySQL Community Server 8** - Database for game data.
* Link: https://dev.mysql.com/downloads/mysql/
* **MySQL Workbench 8** - Client for interacting with the database. Other clients do exist.
* Link: https://dev.mysql.com/downloads/workbench/
* **Docker Desktop** (optional) - For launching the game locally with less hassle.
* Link: https://www.docker.com/products/docker-desktop
* **Client files and general tools**
* Link: https://drive.google.com/drive/folders/1hgnb92MGL6xqEp9szEMBh0K9pSJcJ6IT?usp=sharing
* This is Ponk's own Google Drive, similar to how Ronan provides files for HeavenMS.
### MapleStory client
- Latest localhost client: https://hostr.co/amuX5SLeeVZx
**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.
Those depicted here have been put to use for years already and posed no harm so far, so they are soundly assumed to be safe.
## Getting started ## Getting started
The localhost MapleStory client needs to be installed, as well as the server that will host the game. Follow along as I go through the steps to play the game on your local computer from start to finish. I won't go into extreme detail, so if you don't have prior experience with Java or git, you might struggle.
### Installing the client We will set up the following:
- Database - the database is used by the server to store game data such as accounts, characters and inventory items.
- Server - the server is the "brain" and routes network traffic between the clients.
- Client - the client is the application used to _play the game_, i.e. MapleStory.exe.
1. Install MapleStory with "MapleGlobal-v83-setup.exe" in your folder of choice (e.g. "C:\Nexon\MapleStory") and follow their instructions. ### 1 - Database
2. Once done, erase these files: "HShield" (folder), "ASPLnchr.exe", "MapleStory.exe" and "Patcher.exe". You will start by installing the database server and client, and then run some scripts to prepare it for the server.
3. Extract into the client folder the "HeavenMS-localhost-WINDOW.exe" (from now on referred to as "localhost.exe") from the provided link.
4. Overwrite the original WZ files with the ones provided on the Google Drive: "CosmicWZ-v1-2021.05.10.zip"
- This is currently identical to the latest HeavenMS WZ files (except for the file name): "commit397_wz-20210321T173600Z-001.zip"
#### Editing localhost IP target #### Steps
If you are not using "localhost" as the target IP on the server's config file, you will need to HEX-EDIT localhost.exe to fetch your IP. Track down all IP locations by searching for "Type: String" "127.0.0.1", and applying the changes wherever it fits. 1. Download and install [MySQL Community Server 8+](https://dev.mysql.com/downloads/mysql/). You will have to set a root password, make sure you don't lose it because you will need it later.
2. Download and install [HeidiSQL](https://www.heidisql.com/download.php).
3. Open HeidiSQL and connect to the database ("New" -> "Session in root folder" -> fill in password -> "Open").
4. Run all four scripts located in database/sql in order. Starting with ``1-db_database.sql`` and ending with ``4-db-admin.sql``. In HeidiSQL: "File" -> "Run SQL File...".
5. The database is ready!
To hex-edit, install the Neo Hex Editor from "free-hex-editor-neo.exe" and follow their instructions. Once done, open localhost.exe for editing and overwrite the IP values under the 3 addresses. Save the changes and exit the editor. ### 2 - Server
You will start by cloning the repository, then configure the database properties and lastly start the server.
(TODO: find suitable alternative to Neo Hex Editor) #### Prerequisites
* Java 21 (I recommend [Amazon Corretto](https://aws.amazon.com/corretto))
* IDE (I recommend [IntelliJ IDEA](https://www.jetbrains.com/idea/))
#### Testing the localhost #### Steps
Open the "localhost.exe" client. 1. Clone Cosmic into a new project. In IntelliJ, you would create a new project from version control.
If by any means the program did not open, and checking the server log your ping has been listened by the server 2. Open _config.yaml_. Find "DB_PASS" and set it to your database root user password.
and you are using Windows 8, 10 or 11, it is probably some compatibility issue. 3. Start the server. The main method is located in `net.server.Server`.
4. If you see "Cosmic is now online" in the console, it means the server is online and ready to serve traffic. Yay!
In some cases it helps to spam click the exe a few times (2-3 times usually works for me on W10). Below, I list other ways of running the server which are completely optional.
In that case, extract "lolwut.exe" from "lolwut-v0.01.rar" and place it on the MapleStory client folder ("C:\Nexon\MapleStory"). #### Docker
Your "localhost.exe" property settings must follow these: Support for Docker is also provided out of the box, as an alternative to running straight in the IDE. If you have [Docker Desktop](https://www.docker.com/products/docker-desktop/) installed it's as easy as running `docker compose up`.
Note: "lolwut.exe" is currently not available in the Google Drive. Making changes becomes a bit more tedious though as you have to rebuild the server image via `docker compose up --build`.
* Run in compatibility mode: Windows 7; 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).
* Unchecked reduced color mode;
* 640 x 480 resolution;
* Unchecked disable display on high DPI settings;
* Run as an administrator;
* Opening "lolwut.exe", use Fraysa's method.
Important: should the client be refused a connection to the game server, it may be because of firewall issues. Head to the end of this file to proceed in allowing this connection through the computer's firewall. Alternatively, one can deactivate the firewall and try opening the client again. #### Jar
You can also search the server logs (/logs/cosmic-log.log) if any connection attempts have been made to ease debugging. 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/).
--- 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.
### Installing the server
1. Configure the project
2. Set up the database
3. Launch the server
If you are using Docker (quick start): 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.
1. Configure the project
2. Launch the server
#### Configuring the project Alternatively, run the jar file from the terminal. Just remember to provide the `wz-path` system property pointing to your wz directory.
The easiest way to set up your project is to clone the repository directly into a new IntelliJ project. ### 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.
1. Install IntelliJ #### Steps
2. Create a new "Project from Version Control..."
3. Enter the URL to this GitHub repository: "https://github.com/P0nk/Cosmic.git"
4. Click on "Clone". A new project will now be created with all the files from the repository.
#### Setting up the database 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-07-17-v0.14.0.zip_ from my [Google Drive](https://drive.google.com/drive/folders/1hgnb92MGL6xqEp9szEMBh0K9pSJcJ6IT).
5. Unzip it and copy all .wz-files into the installation directory. Replace the existing ones.
6. Download _HeavenMS-localhost-WINDOW.exe_ from [hostr.co](https://hostr.co/amuX5SLeeVZx). This is a client modified to connect to your localhost instead of Nexon's server (along with some fixes and custom changes).
- Your antivirus will likely detect the file as a trojan or similar and automatically delete it. To prevent this from happening, add your _Downloads_ directory and the installation directory as exclusions in your antivirus software. On W11, this is under "Virus & threat protection settings" -> "Add or remove exclusions".
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.
1. Install MySQL Server 8 and MySQL Workbench 8. **Important note about localhost clients**: these executables are red-flagged by antivirus tools as potentially malicious software.
2. Using Workbench, create a new user with username "cosmic_server" and password "snailshell". This happens due to the reverse engineering methods that were applied onto these software artifacts.
This the default configuration in Cosmic. The one provided here has been in use for years already and posed no harm so far, so it is assumed to be safe.
* (Optional) Restrict the Schema Privileges for this new user for improved security.
Add a new entry with "Schemas matching pattern: cosmic" and only select "SELECT", "INSERT", "UPDATE", "DELETE" under "Object Rights"
3. Run the sql scripts in the "database/sql" directory of the project in the order indicated by their names.
* Make sure you are connected to the database with the "root" user to be able to run the scripts.
* Run scripts one by one through the menu: "File" -> "Run SQL Script" -> select the script file to run -> "Run"
* The 3rd script "3-db_shopupdate" is optional. It adds custom shop items for certain NPCs.
* The 4th script "4-db_admin" is also optional, but recommended if you are new. It adds an admin account to simplify the setup.
Use this info when you connect to MySQL Server for the first time: ### 4 - Getting into the game
* Server Host: localhost The client has started, and you're looking at the login screen.
* Port: 3306
* Username: root
* Password: <whatever password you set during MySQL Server installation>
At the end of the execution of these sql scripts, you should have installed a database schema named "cosmic". #### Logging in
REGISTER YOUR FIRST ACCOUNT to be used in-game by **manually creating** an entry in the table "accounts" in the database with a username and password. At this point, you can log in to the admin account using the following credentials:
### Running the server
Configure the IP you want to use for your MapleStory server in "config.yaml" file, or set it as "localhost" if you want to run it only on your machine.
Alternatively, you can use the IP given by Hamachi to use on a Hamachi network, or you can use a non-Hamachi method of port-forwarding. Neither will be approached here.
To launch the server, you may either:
* Launch inside IntelliJ
* Launch a built jar file
* Launch with Docker
#### Run inside IntelliJ
1. Open the file src/main/java/net/server/Server.java.
2. Click the green arrow to the left of the class definition "public class Server", and then "Run Cosmic".
* Alternatively (recommended), create a new Configuration that points to "net.server.Server".
3. The server launches in a terminal window inside IntelliJ.
#### Run from a jar file
1. Create the jar file
* The jar file is created by the Maven assembly plugin in the package lifecycle.
* If you already have Maven installed, simply run the command "mvn clean install" to create the jar file.
* IntelliJ also comes with built-in Maven support. Open a new terminal window inside IntelliJ, type "mvn clean install" (your command should now be marked green), then Ctrl+Enter to build the jar file.
2. Launch the jar file
* Double click on "launch.bat" (need to have Java 17 installed)
#### Run as containers with Docker
1. Start Docker
2. Run the command "docker compose up" at the root of the project.
* If you make any changes to the code, make sure you append the "--build" option at the end of the command to force rebuild the server image.
---
### Getting into the game
If you ran the admin sql script, there already exists an account in the database with an admin character on it (GM level 6).
Log in using these credentials:
* Username: "admin" * Username: "admin"
* Password: "admin" * Password: "admin"
* Pin: "0000" * Pin: "0000"
* Pic: "000000" * Pic: "000000"
Admin characters have "hide" mode enabled by default. This means your character will be translucent on your screen, and completely invisible to others. 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_).
It will also prevent you from controlling mobs (making them stand still). To toggle this mode on and off, type "@hide" in the in-game chat.
By default, the server source is set to allow AUTO-REGISTERING. This means that, by simply typing in a "Login ID" and a "Password", you're able to create a new account. #### Entering the game
Create a new character as you normally would, and then select it to enter the game. Hooray, finally we're in!
After creating a character, experiment typing in all-chat "@commands". If you log in to the "Admin" character, you'll notice that the character looks almost invisible. This is hide mode, which is enabled by default when you log in to a GM character. You won't be visible to normal players and no mobs will move if you're alone on the map. Toggle hide mode on or off by typing "@hide" in the in-game chat.
This will display all available commands for the current GM level your character has.
To change a character's GM level, make sure that character is not logged in, then: Hide is one of many commands available to players, type "@commands" to see the full list. Higher ranked GMs have access to more powerful commands.
1. Open MySQL Workbench; That's it, have fun playing around in game!
2. Expand "cosmic" schema;
3. Expand "Tables";
4. Right-click "characters" and click "Select Rows"
5. Find your character in Result Grid. Scroll to the right and find the "gm" column.
6. Edit your character's gm value and click "Apply", and then "Apply" again in the window that appeared, then "Finish".
* 0 is what ordinary players start with, and 6 is the highest gm value. Higher level gms have access to more commands in game.
--- ## Advanced concepts
### Some notes about WZ/WZ.XML EDITING Some slightly more advanced concepts that might be useful once you're up and running.
Brief introduction to WZ files: they are the asset/data files required by the client and server. The client can read the .wz files directly, but the server requires them in XML format.
The server also does not make use of any of the sprites, which is where different kinds of exporting comes into the picture. HaRepacker allows you to export to Private server XML, which is the .img files packaged in the .wz stripped of sprites and converted to XML.
Link to HaRepacker-resurrected, the standard tool for handling WZ files: https://github.com/lastbattle/Harepacker-resurrected ### 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.
NOTE: Be extremely wary when using server-side's XMLs data being reimported into the client's WZ, as some means of synchronization between the server and client modules, this action COULD generate some kind of bugs afterwards. Client-to-server data reimporting seems to be fine, though. 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.
#### Editing the v83 WZ's: #### 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.
### 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.
* Use the HaRepacker-resurrected 4.2.4 editor, encryption "GMS (old)". 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.
* Open the desired WZ for editing and use the node hierarchy to make the desired changes (copy/pasting nodes may be unreliable in rare scenarios). 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.
* Save the changed WZ, **overwriting the original content** at the client folder.
* Finally, **RE-EXPORT (using the "Private Server..." exporting option) the changed XMLs into the server's WZ.XML files**, overwriting the old contents.
**These steps are IMPORTANT, to maintain synchronization** between the server and client modules. 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.
--- #### WZ editing
### Portforwarding the SERVER * Use the HaRepacker-resurrected editor, encryption "GMS (old)".
* Open the desired .wz for editing and use the node hierarchy to make the desired changes (copy/pasting nodes may be unreliable in rare scenarios).
* Save the changed .wz, overwriting the original content at the client folder.
* Finally, re-export (using the "Private Server" exporting option) the changed XMLs into the server's .wz XML files (found in the "wz" directory), overwriting the old contents.
To use portforward, you will need to have permission to change things on the LAN router. Access your router using the Internet browser. URLs vary accordingly with the manufacturer. To discover it, open the command prompt and type "ipconfig" and search for the "default gateway" field. The IP shown there is the URL needed to access the router. Also, look for the IP given to your machine (aka "IPv4 address" field), which will be the server one. Make sure to always export from the client .wz files to the server XML, and not the other way around.
The default login/password also varies, so use the link http://www.routerpasswords.com/ as reference. Usually, login as "admin" and password as "password" completes the task well. Editing the client .wz without exporting to the server may lead to strange behavior.
Now you have logged in the router system, search for anything related to portforwarding. Should the system prompt you between portforwarding and portriggering, pick the first, it is what we will be using. ### Client features
For more information about the client and its features, see [HeavenMS on GitHub](https://github.com/ronancpl/HeavenMS#download-items).
Now, it is needed to enable the right ports for the Internet. For Cosmic, it is basically needed to open ports 7575 to 7575 + (number of channels) and port 8484. Create a new custom service which enables that range of ports for the server's channel and opt to use TCP/UDP protocols. Finally, create a custom service now for using port 8484. Some notable features:
* Opens in window mode by default
Optionally, if you want to host a webpage, portforward the port 80 (the HTTP port) as well. * Uncapped max speed
It is not done yet, sometimes the firewalls will block connections between the LAN and the Internet. To overcome this, it is needed to create some rules for the firewall to permit these connections. Search for the advanced options with firewalls on your computer and, with it open, create two rules (one outbound and one inbound).
These rules must target "one application", "enable connections" and must target your MapleStory client (aka localhost).
After all these steps, the portforwarding process should now be complete.
---
### Client changelog
The following list, in bottom-up chronological order,
holds information regarding all changes that were applied from the starting localhost used in this development.
Some lines have a link attached, that will lead you to a snapshot of the localhost at that version of the artifact.
Naturally, later versions holds all previous changes along with the proposed changes.
**Change log:**
* Fixed Monster Magnet crashing the caster when trying to pull fixed mobs, credits to Shavit. https://gofile.io/?c=BW7dVM (dead link)
* Cleared need for administrator privileges (OS) to play the game, credits to Ubaware.
* Set a higher cap for AP assigning with AP Reset, credits to Ubaware.
* Fixed Monster Magnet crashing the caster when trying to pull bosses. Drawback: Dojo HPBar becomes unavailable. https://hostr.co/SvnSKrGzXhG0
* Fixed some 'rn' problems with quest icons & removed "tab" from party leader changed message. https://hostr.co/tsYsQzzV6xT0
* Removed block on applying attack-based strengthening gems on non-weapon equipments. https://hostr.co/m2bVtnizCtmD
* Set a higher cap for SPEED.
* Removed the AP assigning block for beginners below level 10. https://hostr.co/AHAHzneCti9B
* Removed block on party for beginners level 10 or below. https://hostr.co/JZq53mMtToCz
* Removed block on MTS entering in some maps, rendering the buyback option available.
* Removed "AP excess" popup and limited actions on Admin/MWLB, credits to kevintjuh93.
* Removed "You've gained a level!" popup, credits to PrinceReborn.
* Removed caps for WATK, WDEF, MDEF, ACC, AVOID.
* 'n' problem fixed.
* Fraysa's https://hostr.co/gJbLZITRVHmv
* Eric's MapleSilver starting on window-mode.
+3 -17
View File
@@ -161,8 +161,8 @@ server:
#Database Configuration #Database Configuration
DB_URL_FORMAT: "jdbc:mysql://%s:3306/cosmic" # If the docker ENV for DB_HOST is anything but "db", this string format should be changed from 3306 to 3307 (or whichever port it was changed to in docker) DB_URL_FORMAT: "jdbc:mysql://%s:3306/cosmic" # If the docker ENV for DB_HOST is anything but "db", this string format should be changed from 3306 to 3307 (or whichever port it was changed to in docker)
DB_HOST: "localhost" DB_HOST: "localhost"
DB_USER: "cosmic_server" DB_USER: "root"
DB_PASS: "snailshell" DB_PASS: ""
INIT_CONNECTION_POOL_TIMEOUT: 90 # Seconds INIT_CONNECTION_POOL_TIMEOUT: 90 # Seconds
PG_DB_NAME: "cosmic" PG_DB_NAME: "cosmic"
@@ -228,7 +228,6 @@ server:
USE_MTS: false USE_MTS: false
USE_CPQ: true #Renders the CPQ available or not. USE_CPQ: true #Renders the CPQ available or not.
USE_AUTOHIDE_GM: true #When enabled, GMs are automatically hidden when joining. Thanks to Steven Deblois (steven1152). USE_AUTOHIDE_GM: true #When enabled, GMs are automatically hidden when joining. Thanks to Steven Deblois (steven1152).
USE_BUYBACK_SYSTEM: false #Enables the HeavenMS-builtin buyback system, to be used by dead players when clicking the MTS button.
USE_FIXED_RATIO_HPMP_UPDATE: false #Enables the HeavenMS-builtin HPMP update based on the current pool to max pool ratio. USE_FIXED_RATIO_HPMP_UPDATE: false #Enables the HeavenMS-builtin HPMP update based on the current pool to max pool ratio.
USE_FAMILY_SYSTEM: true USE_FAMILY_SYSTEM: true
USE_DUEY: true USE_DUEY: true
@@ -256,17 +255,15 @@ server:
USE_ENFORCE_UNMERCHABLE_PET: true #Forces players to not sell pets via merchants, drops of it disappears. (since non-named pets gets dirty name and other possible DB-related issues) USE_ENFORCE_UNMERCHABLE_PET: true #Forces players to not sell pets via merchants, drops of it disappears. (since non-named pets gets dirty name and other possible DB-related issues)
USE_ENFORCE_MDOOR_POSITION: false #Forces mystic door to be spawned near spawnpoints. 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_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_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_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. USE_ERASE_UNTRADEABLE_DROP: true #Forces flagged untradeable items to disappear when dropped.
USE_ERASE_PET_ON_EXPIRATION: false #Forces pets to be removed from inventory when expire time comes, rather than converting it to a doll. USE_ERASE_PET_ON_EXPIRATION: false #Forces pets to be removed from inventory when expire time comes, rather than converting it to a doll.
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_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_BUFF_EVERLASTING: false #Every applied buff on players holds expiration time so high it'd be considered permanent. Suggestion thanks to Vcoc.
USE_BANISHABLE_TOWN_SCROLL: false #Enables town scrolls to act as if it's a "player banish", rendering the antibanish scroll effect available. 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_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_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_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_MAP_OWNERSHIP_SYSTEM: false #Flag to enable/disable map ownership system
USE_FISHING_SYSTEM: false #Flag to enable/disable custom fishing system USE_FISHING_SYSTEM: false #Flag to enable/disable custom fishing system
USE_NPCS_SCRIPTABLE: true #Flag to enable/disable serverside predefined script NPCs. USE_NPCS_SCRIPTABLE: true #Flag to enable/disable serverside predefined script NPCs.
@@ -324,7 +321,6 @@ server:
NAME_CHANGE_COOLDOWN: 2592000000 # (30*24*60*60*1000) Cooldown for name changes, default (GMS) is 30 days. 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). 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). 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 #Dangling Items/Locks Configuration
ITEM_EXPIRE_TIME: 180000 # (3 * 60 * 1000) Time before items start disappearing. Recommended to be set up to 3 minutes. ITEM_EXPIRE_TIME: 180000 # (3 * 60 * 1000) Time before items start disappearing. Recommended to be set up to 3 minutes.
@@ -352,7 +348,6 @@ server:
USE_PERFECT_SCROLLING: false #Scrolls doesn't use slots upon failure. USE_PERFECT_SCROLLING: false #Scrolls doesn't use slots upon failure.
USE_ENHANCED_CHSCROLL: false #Equips even more powerful with chaos upgrade. USE_ENHANCED_CHSCROLL: false #Equips even more powerful with chaos upgrade.
USE_ENHANCED_CRAFTING: false #Apply chaos scroll on every equip crafted. USE_ENHANCED_CRAFTING: false #Apply chaos scroll on every equip crafted.
USE_ENHANCED_CLNSLATE: false #Clean slates can be applied to recover successfully used slots as well.
SCROLL_CHANCE_ROLLS: 1 #Number of rolls for success on a scroll, set 1 for default. 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_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: 6 #Stat upgrade range (-N, N) on chaos scrolls.
@@ -401,7 +396,6 @@ server:
USE_EQUIPMNT_LVLUP_SLOTS: false #Equips can upgrade slots at level up. 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_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_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_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. 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. USE_EQUIPMNT_LVLUP: 1 #All equips lvlup at max level of N, set 1 to disable.
@@ -452,14 +446,6 @@ server:
WEDDING_GIFT_LIMIT: 1 #Max number of gifts per person to same wishlist on marriage instances. WEDDING_GIFT_LIMIT: 1 #Max number of gifts per person to same wishlist on marriage instances.
WEDDING_BLESSER_SHOWFX: true #Pops bubble sprite effect on players blessing the couple. Setting this false shows the blessing effect on the couple instead. WEDDING_BLESSER_SHOWFX: true #Pops bubble sprite effect on players blessing the couple. Setting this false shows the blessing effect on the couple instead.
#Buyback Configuration
USE_BUYBACK_WITH_MESOS: true #Enables usage of either mesos or NX for the buyback fee.
BUYBACK_FEE: 77.70 #Sets the base amount needed to buyback (level 30 or under will use the base value).
BUYBACK_LEVEL_STACK_FEE: 85.47 #Sets the level-stacking portion of the amount needed to buyback (fee will sum up linearly until level 120, when it reaches the peak).
BUYBACK_MESO_MULTIPLIER: 1000 #Sets a multiplier for the fee when using meso as the charge unit.
BUYBACK_RETURN_MINUTES: 1 #Sets the maximum amount of time the player can wait before decide to buyback.
BUYBACK_COOLDOWN_MINUTES: 7 #Sets the time the player must wait before using buyback again.
# Login timeout by shavit # Login timeout by shavit
TIMEOUT_DURATION: 3600000 # Kicks clients who don't send any packet to the game server in due time (in millisseconds). TIMEOUT_DURATION: 3600000 # Kicks clients who don't send any packet to the game server in due time (in millisseconds).
+2 -7
View File
@@ -1,9 +1,4 @@
#EXECUTE THIS FIRST, THEN NEXT SQL: 'db_drops.sql' /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */; /*!40101 SET NAMES utf8 */;
@@ -21510,4 +21505,4 @@ ALTER TABLE `skills`
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+1 -36
View File
@@ -131,39 +131,6 @@ INSERT IGNORE INTO `shopitems` (`shopid`, `itemid`, `price`, `pitch`, `position`
(9201101, 2012008, 4200000, 0, 164), (9201101, 2012008, 4200000, 0, 164),
(9201101, 2022251, 3800000, 0, 168); (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 -- Thanks to Vcoc
-- GMShop: Sacks, GmEquip, Cheese & Onyx, Utils, -- GMShop: Sacks, GmEquip, Cheese & Onyx, Utils,
-- Arrows, Bullets, Throwings and Capsules, -- Arrows, Bullets, Throwings and Capsules,
@@ -301,7 +268,6 @@ INSERT INTO `shopitems` ( `shopid`, `itemid`, `price`, `pitch`, `position`) VALU
(1200002, 2070000, 500, 0, 108), (1200002, 2070000, 500, 0, 108),
(1200002, 2061000, 1, 0, 120), (1200002, 2061000, 1, 0, 120),
(1200002, 2060000, 1, 0, 124), (1200002, 2060000, 1, 0, 124),
(1200002, 2030100, 400, 0, 128),
(1200002, 2030000, 400, 0, 132), (1200002, 2030000, 400, 0, 132),
(1200002, 2020028, 3000, 0, 136), (1200002, 2020028, 3000, 0, 136),
(1200002, 2010004, 310, 0, 140), (1200002, 2010004, 310, 0, 140),
@@ -323,7 +289,6 @@ INSERT INTO `shopitems` ( `shopid`, `itemid`, `price`, `pitch`, `position`) VALU
(1301000, 2070000, 500, 0, 108), (1301000, 2070000, 500, 0, 108),
(1301000, 2061000, 1, 0, 112), (1301000, 2061000, 1, 0, 112),
(1301000, 2060000, 1, 0, 116), (1301000, 2060000, 1, 0, 116),
(1301000, 2030100, 400, 0, 120),
(1301000, 2030000, 400, 0, 124), (1301000, 2030000, 400, 0, 124),
(1301000, 2022000, 1650, 0, 128), (1301000, 2022000, 1650, 0, 128),
(1301000, 2022003, 1100, 0, 132), (1301000, 2022003, 1100, 0, 132),
@@ -348,4 +313,4 @@ INSERT INTO `shopitems` ( `shopid`, `itemid`, `price`, `pitch`, `position`) VALU
(9270019, 1482004, 75000, 0, 100), (9270019, 1482004, 75000, 0, 100),
(9270020, 1052113, 120000, 0, 92), (9270020, 1052113, 120000, 0, 92),
(9270020, 1052110, 100000, 0, 96), (9270020, 1052110, 100000, 0, 96),
(9270020, 1002625, 75000, 0, 100); (9270020, 1002625, 75000, 0, 100);
+13
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.
+2
View File
@@ -0,0 +1,2 @@
DELETE FROM shopitems
WHERE itemid = 2030100; # Return Scroll - Banished Area (a custom item added in HeavenMS)
+5 -6
View File
@@ -23,14 +23,13 @@ services:
PG_DB_HOST: "pg_db" PG_DB_HOST: "pg_db"
db: db:
image: mysql:8.0.23 image: mysql:8.4.0
environment:
MYSQL_DATABASE: "cosmic"
MYSQL_ROOT_PASSWORD: ""
MYSQL_ALLOW_EMPTY_PASSWORD: yes
ports: ports:
- "3307:3306" - "3307:3306"
environment:
MYSQL_RANDOM_ROOT_PASSWORD: "true"
MYSQL_DATABASE: "cosmic"
MYSQL_USER: "cosmic_server"
MYSQL_PASSWORD: "snailshell"
volumes: volumes:
- ./database/docker-db-data:/var/lib/mysql - ./database/docker-db-data:/var/lib/mysql
- ./database/sql:/docker-entrypoint-initdb.d - ./database/sql:/docker-entrypoint-initdb.d
+2 -2
View File
@@ -302,10 +302,10 @@ Localhost:
* Removed caps for MATK, WDEF, MDEF, ACC and AVOID. * Removed caps for MATK, WDEF, MDEF, ACC and AVOID.
* Removed "AP excess" popup and "Admin/MWLB" action block, original credits to kevintjuh93. * Removed "AP excess" popup and "Admin/MWLB" action block, original credits to kevintjuh93.
* Removed "You've gained a level!" popup, original credits to PrinceReborn. * Removed "You've gained a level!" popup, original credits to PrinceReborn.
* Removed "Cannot enter MTS from this map." popup on maps that blocks transitions (such change channel, CS/MTS), rendering the buyback option now available for all maps. * Removed "Cannot enter MTS from this map." popup on maps that blocks transitions (such change channel, CS/MTS).
* Removed a check for players wishing to create/join a party being novices under level 10. * Removed a check for players wishing to create/join a party being novices under level 10.
* Set a new high cap for SPEED. * Set a new high cap for SPEED.
* Removed the AP assign block for novices. * Removed the AP assign block for novices.
* Removed a block that would show up when trying to apply an attack gem on equipments that aren't weapons. * Removed a block that would show up when trying to apply an attack gem on equipments that aren't weapons.
--------------------------- ---------------------------
-1
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. 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. 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! 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. 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". 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 2040000 - Scroll for Helmet for DEF - Improves the helmet's weapon def.\nSuccess rate:100%, weapon def. +1
+61 -21
View File
@@ -1,42 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>cosmic-maplestory</groupId> <groupId>cosmic-maplestory</groupId>
<artifactId>Cosmic</artifactId> <artifactId>Cosmic</artifactId>
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>Cosmic</name>
<description>Server emulator for Global MapleStory version 83</description>
<url>https://github.com/P0nk/Cosmic</url>
<inceptionYear>2021</inceptionYear>
<developers>
<developer>
<name>Ponk</name>
<email>[email protected]</email>
<url>https://github.com/P0nk</url>
<roles>
<role>maintainer</role>
<role>developer</role>
</roles>
<properties>
<discord>ponkcode</discord>
</properties>
</developer>
</developers>
<scm>
<connection>scm:git:https://github.com/P0nk/Cosmic.git</connection>
<developerConnection>scm:git:https://github.com/P0nk/Cosmic.git</developerConnection>
<url>https://github.com/P0nk/Cosmic</url>
</scm>
<issueManagement>
<system>GitHub Issues</system>
<url>https://github.com/P0nk/Cosmic/issues</url>
</issueManagement>
<ciManagement>
<system>GitHub Actions</system>
<url>https://github.com/P0nk/Cosmic/actions</url>
</ciManagement>
<properties> <properties>
<!-- Project --> <!-- Project -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>17</java.version> <java.version>21</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source> <maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target> <maven.compiler.target>${java.version}</maven.compiler.target>
<mainClass>net.server.Server</mainClass> <mainClass>net.server.Server</mainClass>
<!-- Maven plugins --> <!-- Maven plugins -->
<maven-surefire-plugin.version>3.0.0-M9</maven-surefire-plugin.version> <!-- For running unit tests --> <maven-surefire-plugin.version>3.2.5</maven-surefire-plugin.version> <!-- For running unit tests -->
<maven-jar-plugin.version>3.3.0</maven-jar-plugin.version> <!-- Disabled. (for building thin jar) --> <maven-jar-plugin.version>3.4.1</maven-jar-plugin.version> <!-- Disabled. (for building thin jar) -->
<maven-assembly-plugin.version>3.5.0</maven-assembly-plugin.version> <!-- For packaging the executable fat jar --> <maven-assembly-plugin.version>3.7.1
</maven-assembly-plugin.version> <!-- For packaging the executable fat jar -->
<!-- Dependencies --> <!-- Dependencies -->
<slf4j-api.version>1.7.36</slf4j-api.version> <!-- Logging facade --> <slf4j-api.version>2.0.13</slf4j-api.version> <!-- Logging facade -->
<log4j.version>2.20.0</log4j.version> <!-- Slf4j implementation --> <log4j.version>2.23.1</log4j.version> <!-- Slf4j implementation -->
<graalvm.version>22.3.1</graalvm.version> <!-- ScriptEngine implementation --> <graalvm-js.version>23.0.4</graalvm-js.version>
<netty.version>4.1.89.Final</netty.version> <!-- Networking --> <graalvm-js-scriptengine.version>24.0.1</graalvm-js-scriptengine.version> <!-- ScriptEngine implementation -->
<yamlbeans.version>1.15</yamlbeans.version> <!-- Config file --> <netty.version>4.1.109.Final</netty.version> <!-- Networking -->
<yamlbeans.version>1.17</yamlbeans.version> <!-- Config file -->
<jcip-annotations.version>1.0</jcip-annotations.version> <!-- Annotations for concurrency documentation --> <jcip-annotations.version>1.0</jcip-annotations.version> <!-- Annotations for concurrency documentation -->
<HikariCP.version>5.0.1</HikariCP.version> <!-- Database connection pool --> <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 -->
<postgresql.version>42.5.4</postgresql.version> <!-- PostgreSQL JDBC driver --> <postgresql.version>42.5.4</postgresql.version> <!-- PostgreSQL JDBC driver -->
<flyway.version>9.15.1</flyway.version> <!-- Database migration --> <flyway.version>9.15.1</flyway.version> <!-- Database migration -->
<mysql-connector-j.version>8.0.32</mysql-connector-j.version> <!-- MySQL JDBC driver -->
<jdbi-version>3.37.1</jdbi-version> <!-- Convenience wrapper around JDBC -->
<junit.version>5.9.2</junit.version> <!-- Unit test -->
<mockito.version>5.1.1</mockito.version> <!-- Unit test -->
<caffeine.version>3.1.4</caffeine.version> <!-- Caching --> <caffeine.version>3.1.4</caffeine.version> <!-- Caching -->
</properties> </properties>
@@ -125,7 +159,7 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId> <artifactId>log4j-slf4j2-impl</artifactId>
<version>${log4j.version}</version> <version>${log4j.version}</version>
</dependency> </dependency>
@@ -133,12 +167,12 @@
<dependency> <dependency>
<groupId>org.graalvm.js</groupId> <groupId>org.graalvm.js</groupId>
<artifactId>js</artifactId> <artifactId>js</artifactId>
<version>${graalvm.version}</version> <version>${graalvm-js.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.graalvm.js</groupId> <groupId>org.graalvm.js</groupId>
<artifactId>js-scriptengine</artifactId> <artifactId>js-scriptengine</artifactId>
<version>${graalvm.version}</version> <version>${graalvm-js-scriptengine.version}</version>
</dependency> </dependency>
<!-- Testing --> <!-- Testing -->
@@ -166,6 +200,12 @@
<version>${mockito.version}</version> <version>${mockito.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
+3
View File
@@ -0,0 +1,3 @@
function run(chr) {
chr.message("devtest.js")
}
-10
View File
@@ -36,12 +36,6 @@ function stopEntry() {
} }
function takeoff() { function takeoff() {
const PacketCreator = Java.type('tools.PacketCreator');
//sound src: https://www.soundjay.com/transportation/metro-door-close-01.mp3
KC_docked.broadcastMessage(PacketCreator.playSound("subway/whistle"));
NLC_docked.broadcastMessage(PacketCreator.playSound("subway/whistle"));
em.setProperty("docked", "false"); em.setProperty("docked", "false");
KC_Waiting.warpEveryone(Subway_to_NLC.getId()); KC_Waiting.warpEveryone(Subway_to_NLC.getId());
NLC_Waiting.warpEveryone(Subway_to_KC.getId()); NLC_Waiting.warpEveryone(Subway_to_KC.getId());
@@ -52,10 +46,6 @@ function arrived() {
Subway_to_KC.warpEveryone(KC_docked.getId(), 0); Subway_to_KC.warpEveryone(KC_docked.getId(), 0);
Subway_to_NLC.warpEveryone(NLC_docked.getId(), 0); Subway_to_NLC.warpEveryone(NLC_docked.getId(), 0);
scheduleNew(); scheduleNew();
const PacketCreator = Java.type('tools.PacketCreator');
KC_docked.broadcastMessage(PacketCreator.playSound("subway/whistle"));
NLC_docked.broadcastMessage(PacketCreator.playSound("subway/whistle"));
} }
function cancelSchedule() {} function cancelSchedule() {}
+2 -2
View File
@@ -58,8 +58,8 @@ function action(mode, type, selection) {
} }
} }
if (cm.getChar().getGender() == 1) { if (cm.getChar().getGender() == 1) {
for (var i = 0; i < fface.length; i++) { for (var i = 0; i < fface_v.length; i++) {
pushIfItemExists(facenew, fface[i] + cm.getChar().getFace() pushIfItemExists(facenew, fface_v[i] + cm.getChar().getFace()
% 1000 - (cm.getChar().getFace() % 1000 - (cm.getChar().getFace()
% 100)); % 100));
} }
+5 -5
View File
@@ -32,14 +32,14 @@ function action(mode, type, selection) {
} else if (status == 1) { } else if (status == 1) {
if (selection == 0) { if (selection == 0) {
apqpoints = cm.getPlayer().getAriantPoints(); apqpoints = cm.getPlayer().getAriantPoints();
if (apqpoints < 100) { 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.sendNext("Wow, it looks like you got the #b100#k points ready to trade, let's trade?!");
cm.dispose();
} else if (apqpoints + arena.getAriantRewardTier(cm.getPlayer()) >= 100) { } 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(); cm.dispose();
} else { } 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) { } 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."); 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.");
+2 -6
View File
@@ -24,7 +24,6 @@
var status; var status;
var anthemSong = "Field/anthem/brazil"; // sound src: https://c7.rbxcdn.com/f91060652a6e9fbfbf92cb1418435448
var ambientSong = "Bgm04/Shinin'Harbor"; var ambientSong = "Bgm04/Shinin'Harbor";
var feature_tree = []; var feature_tree = [];
@@ -217,7 +216,6 @@ function writeFeatureTab_Serverpotentials() {
addFeature("Poison damage value visible for other players."); addFeature("Poison damage value visible for other players.");
addFeature("M. book announcer displays info based on demand."); addFeature("M. book announcer displays info based on demand.");
addFeature("Custom jail system."); addFeature("Custom jail system.");
addFeature("Custom buyback system, uses mesos / NX, via MTS.");
addFeature("Custom fishing system having 'seasonal' catch times."); addFeature("Custom fishing system having 'seasonal' catch times.");
addFeature("Actual fishing handling w/ F. Net - thanks Dragohe4rt!"); addFeature("Actual fishing handling w/ F. Net - thanks Dragohe4rt!");
addFeature("Custom map leasing system."); addFeature("Custom map leasing system.");
@@ -260,7 +258,7 @@ function writeFeatureTab_CustomNPCs() {
function writeFeatureTab_Localhostedits() { function writeFeatureTab_Localhostedits() {
addFeature("Removed the 'n' NPC dialog issue."); addFeature("Removed the 'n' NPC dialog issue.");
addFeature("Removed caps for MATK, WMDEF, ACC and AVOID."); addFeature("Removed caps for MATK, WMDEF, ACC and AVOID.");
addFeature("Removed MTS block, buyback available anywhere."); addFeature("Removed MTS block.");
addFeature("Removed party blocks for novices under level 10."); addFeature("Removed party blocks for novices under level 10.");
addFeature("Set a much more higher cap for SPEED."); addFeature("Set a much more higher cap for SPEED.");
addFeature("Removed AP usage block for novices."); addFeature("Removed AP usage block for novices.");
@@ -308,8 +306,6 @@ function writeAllFeatures() {
} }
function start() { function start() {
const PacketCreator = Java.type('tools.PacketCreator');
cm.getPlayer().sendPacket(PacketCreator.musicChange(anthemSong));
status = -1; status = -1;
writeAllFeatures(); writeAllFeatures();
action(1, 0, 0); action(1, 0, 0);
@@ -369,4 +365,4 @@ function generateSelectionMenu(array) {
menu += "#L" + i + "#" + array[i] + "#l\r\n"; menu += "#L" + i + "#" + array[i] + "#l\r\n";
} }
return menu; return menu;
} }
-79
View File
@@ -1,79 +0,0 @@
/*
This file is part of the OdinMS Maple Story Server
Copyright (C) 2008 Patrick Huy <[email protected]>
Matthias Butz <[email protected]>
Jan Christian Meyer <[email protected]>
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();
}
}
+9 -3
View File
@@ -50,8 +50,14 @@ function end(mode, type, selection) {
status++; status++;
} }
// TODO: there are 10 different riffs; quest2288/0 through quest2288/9.
// One of the riffs should play randomly upon the death of Spirit of Rock, but there is currently no system in place to achieve that in a reasonable way.
// Spirit of Rock (4300013) spawns an invisible mob on death (Spirit of Rock's Soul, 4300017) which was likely used in some clever way in GMS.
// The map (103040430) has two scripts which could be useful: onFirstUserEnter=Depart_Boss_F_Enter and onUserEnter=Depart_BossEnter
// Currently, the best hypothesis is that one of the map scripts registers some form of "mob spawn" action/script that runs once the invisible mob spawns.
// The script would randomly pick one of the 10 riffs and then register it with all chrs on the map (to later be used by this quest 2293) and play it.
if (status == 0) { if (status == 0) {
qm.sendSimple("Here, I'll give you some samples. Please listen to them and choose one. Please listen carefully before making your choide.\r\n\ qm.sendSimple("Here, I'll give you some samples. Please listen to them and choose one. Please listen carefully before making your choice.\r\n\
\t#b#L1# Listen to song No. 1#l \r\n\ \t#b#L1# Listen to song No. 1#l \r\n\
\t#L2# Listen to Song No. 2#l \r\n\ \t#L2# Listen to Song No. 2#l \r\n\
\t#L3# Listen to Song No. 3#l \r\n\ \t#L3# Listen to Song No. 3#l \r\n\
@@ -67,7 +73,7 @@ function end(mode, type, selection) {
qm.sendOk("Was it this?"); qm.sendOk("Was it this?");
status = -1; status = -1;
} else if (selection == 3) { } else if (selection == 3) {
qm.playSound("quest2293/Die"); qm.playSound("quest2288/6");
qm.sendOk("You heard that?"); qm.sendOk("You heard that?");
status = -1; status = -1;
} else if (selection == 4) { } else if (selection == 4) {
@@ -88,4 +94,4 @@ function end(mode, type, selection) {
} else if (status == 3) { } else if (status == 3) {
qm.dispose(); qm.dispose();
} }
} }
+6 -1
View File
@@ -31,7 +31,12 @@ import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.*; import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
public class BuddyList { public class BuddyList {
public enum BuddyOperation { public enum BuddyOperation {
+137 -205
View File
@@ -24,8 +24,17 @@ package client;
import client.autoban.AutobanManager; import client.autoban.AutobanManager;
import client.creator.CharacterFactoryRecipe; import client.creator.CharacterFactoryRecipe;
import client.inventory.*; import client.inventory.Equip;
import client.inventory.Equip.StatUpgrade; import client.inventory.Equip.StatUpgrade;
import client.inventory.Inventory;
import client.inventory.InventoryProof;
import client.inventory.InventoryType;
import client.inventory.Item;
import client.inventory.ItemFactory;
import client.inventory.ModifyInventory;
import client.inventory.Pet;
import client.inventory.PetDataFactory;
import client.inventory.WeaponType;
import client.inventory.manipulator.CashIdGenerator; import client.inventory.manipulator.CashIdGenerator;
import client.inventory.manipulator.InventoryManipulator; import client.inventory.manipulator.InventoryManipulator;
import client.keybind.KeyBinding; import client.keybind.KeyBinding;
@@ -40,7 +49,35 @@ import constants.id.ItemId;
import constants.id.MapId; import constants.id.MapId;
import constants.id.MobId; import constants.id.MobId;
import constants.inventory.ItemConstants; import constants.inventory.ItemConstants;
import constants.skills.*; import constants.skills.Aran;
import constants.skills.Beginner;
import constants.skills.Bishop;
import constants.skills.BlazeWizard;
import constants.skills.Bowmaster;
import constants.skills.Brawler;
import constants.skills.Buccaneer;
import constants.skills.Corsair;
import constants.skills.Crusader;
import constants.skills.DarkKnight;
import constants.skills.DawnWarrior;
import constants.skills.Evan;
import constants.skills.FPArchMage;
import constants.skills.Hermit;
import constants.skills.Hero;
import constants.skills.ILArchMage;
import constants.skills.Legend;
import constants.skills.Magician;
import constants.skills.Marauder;
import constants.skills.Marksman;
import constants.skills.NightLord;
import constants.skills.Noblesse;
import constants.skills.Paladin;
import constants.skills.Priest;
import constants.skills.Ranger;
import constants.skills.Shadower;
import constants.skills.Sniper;
import constants.skills.ThunderBreaker;
import constants.skills.Warrior;
import model.CharacterIdentity; import model.CharacterIdentity;
import net.netty.GameViolationException; import net.netty.GameViolationException;
import net.packet.Packet; import net.packet.Packet;
@@ -52,22 +89,59 @@ import net.server.guild.Alliance;
import net.server.guild.Guild; import net.server.guild.Guild;
import net.server.guild.GuildCharacter; import net.server.guild.GuildCharacter;
import net.server.guild.GuildPackets; import net.server.guild.GuildPackets;
import net.server.world.*; import net.server.world.Messenger;
import net.server.world.MessengerCharacter;
import net.server.world.Party;
import net.server.world.PartyCharacter;
import net.server.world.PartyOperation;
import net.server.world.World;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import scripting.AbstractPlayerInteraction; import scripting.AbstractPlayerInteraction;
import scripting.event.EventInstanceManager; import scripting.event.EventInstanceManager;
import scripting.item.ItemScriptManager; import scripting.item.ItemScriptManager;
import server.*; import server.CashShop;
import server.ExpLogger;
import server.ExpLogger.ExpLogRecord; import server.ExpLogger.ExpLogRecord;
import server.ItemInformationProvider;
import server.ItemInformationProvider.ScriptedItem; import server.ItemInformationProvider.ScriptedItem;
import server.Marriage;
import server.StatEffect;
import server.Storage;
import server.ThreadManager;
import server.TimerManager;
import server.Trade;
import server.events.Events; import server.events.Events;
import server.events.RescueGaga; import server.events.RescueGaga;
import server.events.gm.Fitness; import server.events.gm.Fitness;
import server.events.gm.Ola; import server.events.gm.Ola;
import server.life.*; import server.life.BanishInfo;
import server.maps.*; import server.life.MobSkill;
import server.life.MobSkillFactory;
import server.life.MobSkillId;
import server.life.MobSkillType;
import server.life.Monster;
import server.life.PlayerNPC;
import server.maps.AbstractAnimatedMapObject;
import server.maps.Door;
import server.maps.DoorObject;
import server.maps.Dragon;
import server.maps.FieldLimit;
import server.maps.HiredMerchant;
import server.maps.MapEffect;
import server.maps.MapItem;
import server.maps.MapManager;
import server.maps.MapObject;
import server.maps.MapObjectType;
import server.maps.MapleMap;
import server.maps.MiniGame;
import server.maps.MiniGame.MiniGameResult; import server.maps.MiniGame.MiniGameResult;
import server.maps.PlayerShop;
import server.maps.PlayerShopItem;
import server.maps.Portal;
import server.maps.SavedLocation;
import server.maps.SavedLocationType;
import server.maps.Summon;
import server.minigame.RockPaperScissor; import server.minigame.RockPaperScissor;
import server.partyquest.AriantColiseum; import server.partyquest.AriantColiseum;
import server.partyquest.MonsterCarnival; import server.partyquest.MonsterCarnival;
@@ -75,17 +149,38 @@ import server.partyquest.MonsterCarnivalParty;
import server.partyquest.PartyQuest; import server.partyquest.PartyQuest;
import server.quest.Quest; import server.quest.Quest;
import server.shop.Shop; import server.shop.Shop;
import tools.*; import tools.DatabaseConnection;
import tools.exceptions.NotEnabledException; import tools.LongTool;
import tools.PacketCreator;
import tools.Pair;
import tools.Randomizer;
import tools.packets.WeddingPackets; import tools.packets.WeddingPackets;
import java.awt.*; import java.awt.*;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.sql.*; import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.*; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -95,7 +190,9 @@ import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static java.util.concurrent.TimeUnit.*; import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
public class Character extends AbstractCharacterObject { public class Character extends AbstractCharacterObject {
private static final Logger log = LoggerFactory.getLogger(Character.class); private static final Logger log = LoggerFactory.getLogger(Character.class);
@@ -129,7 +226,7 @@ public class Character extends AbstractCharacterObject {
private int expRate = 1, mesoRate = 1, dropRate = 1, expCoupon = 1, mesoCoupon = 1, dropCoupon = 1; private int expRate = 1, mesoRate = 1, dropRate = 1, expCoupon = 1, mesoCoupon = 1, dropCoupon = 1;
private int omokwins, omokties, omoklosses, matchcardwins, matchcardties, matchcardlosses; private int omokwins, omokties, omoklosses, matchcardwins, matchcardties, matchcardlosses;
private int owlSearch; private int owlSearch;
private long lastfametime, lastUsedCashItem, lastExpression = 0, lastHealed, lastBuyback = 0, lastDeathtime, jailExpiration = -1; private long lastfametime, lastUsedCashItem, lastExpression = 0, lastHealed, lastDeathtime, jailExpiration = -1;
private transient int localstr, localdex, localluk, localint_, localmagic, localwatk; private transient int localstr, localdex, localluk, localint_, localmagic, localwatk;
private transient int equipmaxhp, equipmaxmp, equipstr, equipdex, equipluk, equipint_, equipmagic, equipwatk, localchairhp, localchairmp; private transient int equipmaxhp, equipmaxmp, equipstr, equipdex, equipluk, equipint_, equipmagic, equipwatk, localchairhp, localchairmp;
private int localchairrate; private int localchairrate;
@@ -255,9 +352,6 @@ public class Character extends AbstractCharacterObject {
private int targetHpBarHash = 0; private int targetHpBarHash = 0;
private long targetHpBarTime = 0; private long targetHpBarTime = 0;
private long nextWarningTime = 0; private long nextWarningTime = 0;
private int banishMap = -1;
private int banishSp = -1;
private long banishTime = 0;
private long lastExpGainTime; private long lastExpGainTime;
private boolean pendingNameChange; //only used to change name on logout, not to be relied upon elsewhere private boolean pendingNameChange; //only used to change name on logout, not to be relied upon elsewhere
private long loginTime; private long loginTime;
@@ -1259,48 +1353,14 @@ public class Character extends AbstractCharacterObject {
} }
} }
public boolean canRecoverLastBanish() { public void changeMapBanish(BanishInfo banishInfo) {
return System.currentTimeMillis() - this.banishTime < MINUTES.toMillis(5); if (banishInfo.msg() != null) {
} dropMessage(5, banishInfo.msg());
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);
} }
MapleMap map_ = getWarpMap(mapid); MapleMap map_ = getWarpMap(mapid);
Portal portal_ = map_.getPortal(portal); Portal portal_ = map_.getPortal(banishInfo.portal());
changeMap(map_, portal_ != null ? portal_ : map_.getRandomPlayerSpawnpoint()); changeMap(map_, portal_ != null ? portal_ : map_.getRandomPlayerSpawnpoint());
setBanishPlayerData(banMap, banSp, banTime);
} }
public void changeMap(int map) { public void changeMap(int map) {
@@ -1684,7 +1744,6 @@ public class Character extends AbstractCharacterObject {
this.mapTransitioning.set(true); this.mapTransitioning.set(true);
this.unregisterChairBuff(); this.unregisterChairBuff();
this.clearBanishPlayerData();
Trade.cancelTrade(this, Trade.TradeResult.UNSUCCESSFUL_ANOTHER_MAP); Trade.cancelTrade(this, Trade.TradeResult.UNSUCCESSFUL_ANOTHER_MAP);
this.closePlayerInteractions(); this.closePlayerInteractions();
@@ -1972,7 +2031,7 @@ public class Character extends AbstractCharacterObject {
this.getCashShop().gainCash(1, nxGain); this.getCashShop().gainCash(1, nxGain);
if (YamlConfig.config.server.USE_ANNOUNCE_NX_COUPON_LOOT) { if (YamlConfig.config.server.USE_ANNOUNCE_NX_COUPON_LOOT) {
showHint("You have earned #e#b" + nxGain + " NX#k#n. (" + this.getCashShop().getCash(1) + " NX)", 300); showHint("You have earned #e#b" + nxGain + " NX#k#n. (" + this.getCashShop().getCash(CashShop.NX_CREDIT) + " NX)", 300);
} }
this.getMap().pickItemDrop(pickupPacket, mapitem); this.getMap().pickItemDrop(pickupPacket, mapitem);
@@ -2024,7 +2083,7 @@ public class Character extends AbstractCharacterObject {
this.getCashShop().gainCash(1, nxGain); this.getCashShop().gainCash(1, nxGain);
if (YamlConfig.config.server.USE_ANNOUNCE_NX_COUPON_LOOT) { if (YamlConfig.config.server.USE_ANNOUNCE_NX_COUPON_LOOT) {
showHint("You have earned #e#b" + nxGain + " NX#k#n. (" + this.getCashShop().getCash(1) + " NX)", 300); showHint("You have earned #e#b" + nxGain + " NX#k#n. (" + this.getCashShop().getCash(CashShop.NX_CREDIT) + " NX)", 300);
} }
} else if (applyConsumeOnPickup(mItem.getItemId())) { } else if (applyConsumeOnPickup(mItem.getItemId())) {
} else if (InventoryManipulator.addFromDrop(client, mItem, true)) { } else if (InventoryManipulator.addFromDrop(client, mItem, true)) {
@@ -5967,7 +6026,8 @@ public class Character extends AbstractCharacterObject {
sendPacket(PacketCreator.giveBuff(energybar, 0, stat)); sendPacket(PacketCreator.giveBuff(energybar, 0, stat));
sendPacket(PacketCreator.showOwnBuffEffect(energycharge.getId(), 2)); sendPacket(PacketCreator.showOwnBuffEffect(energycharge.getId(), 2));
getMap().broadcastPacket(this, PacketCreator.showBuffEffect(id, energycharge.getId(), 2)); getMap().broadcastPacket(this, PacketCreator.showBuffEffect(id, energycharge.getId(), 2));
getMap().broadcastPacket(this, PacketCreator.giveForeignBuff(energybar, stat)); getMap().broadcastPacket(this, PacketCreator.giveForeignPirateBuff(id, energycharge.getId(),
ceffect.getDuration(), stat));
} }
if (energybar >= 10000 && energybar < 11000) { if (energybar >= 10000 && energybar < 11000) {
energybar = 15000; energybar = 15000;
@@ -6056,65 +6116,6 @@ public class Character extends AbstractCharacterObject {
} }
} }
private boolean canBuyback(int fee, boolean usingMesos) {
return (usingMesos ? this.getMeso() : cashshop.getCash(1)) >= fee;
}
private void applyBuybackFee(int fee, boolean usingMesos) {
if (usingMesos) {
this.gainMeso(-fee);
} else {
cashshop.gainCash(1, -fee);
}
}
private long getNextBuybackTime() {
return lastBuyback + MINUTES.toMillis(YamlConfig.config.server.BUYBACK_COOLDOWN_MINUTES);
}
private boolean isBuybackInvincible() {
return Server.getInstance().getCurrentTime() - lastBuyback < 4200;
}
private int getBuybackFee() {
float fee = YamlConfig.config.server.BUYBACK_FEE;
int grade = Math.min(Math.max(level, 30), 120) - 30;
fee += (grade * YamlConfig.config.server.BUYBACK_LEVEL_STACK_FEE);
if (YamlConfig.config.server.USE_BUYBACK_WITH_MESOS) {
fee *= YamlConfig.config.server.BUYBACK_MESO_MULTIPLIER;
}
return (int) Math.floor(fee);
}
public void showBuybackInfo() {
String s = "#eBUYBACK STATUS#n\r\n\r\nCurrent buyback fee: #b" + getBuybackFee() + " " + (YamlConfig.config.server.USE_BUYBACK_WITH_MESOS ? "mesos" : "NX") + "#k\r\n\r\n";
long timeNow = Server.getInstance().getCurrentTime();
boolean avail = true;
if (!isAlive()) {
long timeLapsed = timeNow - lastDeathtime;
long timeRemaining = MINUTES.toMillis(YamlConfig.config.server.BUYBACK_RETURN_MINUTES) - (timeLapsed + Math.max(0, getNextBuybackTime() - timeNow));
if (timeRemaining < 1) {
s += "Buyback #e#rUNAVAILABLE#k#n";
avail = false;
} else {
s += "Buyback countdown: #e#b" + getTimeRemaining(MINUTES.toMillis(YamlConfig.config.server.BUYBACK_RETURN_MINUTES) - timeLapsed) + "#k#n";
}
s += "\r\n";
}
if (timeNow < getNextBuybackTime() && avail) {
s += "Buyback available in #r" + getTimeRemaining(getNextBuybackTime() - timeNow) + "#k";
s += "\r\n";
} else {
s += "Buyback #bavailable#k";
}
this.showHint(s);
}
private static String getTimeRemaining(long timeLeft) { private static String getTimeRemaining(long timeLeft) {
int seconds = (int) Math.floor(timeLeft / SECONDS.toMillis(1)) % 60; int seconds = (int) Math.floor(timeLeft / SECONDS.toMillis(1)) % 60;
int minutes = (int) Math.floor(timeLeft / MINUTES.toMillis(1)) % 60; int minutes = (int) Math.floor(timeLeft / MINUTES.toMillis(1)) % 60;
@@ -6122,34 +6123,6 @@ public class Character extends AbstractCharacterObject {
return (minutes > 0 ? (String.format("%02d", minutes) + " minutes, ") : "") + String.format("%02d", seconds) + " seconds"; return (minutes > 0 ? (String.format("%02d", minutes) + " minutes, ") : "") + String.format("%02d", seconds) + " seconds";
} }
public boolean couldBuyback() { // Ronan's buyback system
long timeNow = Server.getInstance().getCurrentTime();
if (timeNow - lastDeathtime > MINUTES.toMillis(YamlConfig.config.server.BUYBACK_RETURN_MINUTES)) {
this.dropMessage(5, "The period of time to decide has expired, therefore you are unable to buyback.");
return false;
}
long nextBuybacktime = getNextBuybackTime();
if (timeNow < nextBuybacktime) {
long timeLeft = nextBuybacktime - timeNow;
this.dropMessage(5, "Next buyback available in " + getTimeRemaining(timeLeft) + ".");
return false;
}
boolean usingMesos = YamlConfig.config.server.USE_BUYBACK_WITH_MESOS;
int fee = getBuybackFee();
if (!canBuyback(fee, usingMesos)) {
this.dropMessage(5, "You don't have " + fee + " " + (usingMesos ? "mesos" : "NX") + " to buyback.");
return false;
}
lastBuyback = timeNow;
applyBuybackFee(fee, usingMesos);
return true;
}
public boolean isBuffFrom(BuffStat stat, Skill skill) { public boolean isBuffFrom(BuffStat stat, Skill skill) {
effLock.lock(); effLock.lock();
chrLock.lock(); chrLock.lock();
@@ -8878,11 +8851,7 @@ public class Character extends AbstractCharacterObject {
boolean playerDied = false; boolean playerDied = false;
if (hp <= 0) { if (hp <= 0) {
if (oldHp > hp) { if (oldHp > hp) {
if (!isBuybackInvincible()) { playerDied = true;
playerDied = true;
} else {
hp = 1;
}
} }
} }
@@ -10913,70 +10882,33 @@ public class Character extends AbstractCharacterObject {
this.commandtext = text; this.commandtext = text;
} }
public void setReborns(int value) { public int getRewardPoints() {
if (!YamlConfig.config.server.USE_REBIRTH_SYSTEM) {
yellowMessage("Rebirth system is not enabled!");
throw new NotEnabledException();
}
try (Connection con = DatabaseConnection.getConnection(); try (Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("UPDATE characters SET reborns=? WHERE id=?;")) { PreparedStatement ps = con.prepareStatement("SELECT rewardpoints FROM accounts WHERE id=?;")) {
ps.setInt(1, accountid);
ResultSet resultSet = ps.executeQuery();
int point = -1;
if (resultSet.next()) {
point = resultSet.getInt(1);
}
return point;
} catch (SQLException e) {
e.printStackTrace();
}
return -1;
}
public void setRewardPoints(int value) {
try (Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("UPDATE accounts SET rewardpoints=? WHERE id=?;")) {
ps.setInt(1, value); ps.setInt(1, value);
ps.setInt(2, id); ps.setInt(2, accountid);
ps.executeUpdate(); ps.executeUpdate();
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace(); 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 //EVENTS
private byte team = 0; private byte team = 0;
private Fitness fitness; private Fitness fitness;
+22 -3
View File
@@ -44,7 +44,11 @@ import net.server.coordinator.session.SessionCoordinator.AntiMulticlientResult;
import net.server.guild.Guild; import net.server.guild.Guild;
import net.server.guild.GuildCharacter; import net.server.guild.GuildCharacter;
import net.server.guild.GuildPackets; import net.server.guild.GuildPackets;
import net.server.world.*; import net.server.world.MessengerCharacter;
import net.server.world.Party;
import net.server.world.PartyCharacter;
import net.server.world.PartyOperation;
import net.server.world.World;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import scripting.AbstractPlayerInteraction; import scripting.AbstractPlayerInteraction;
@@ -69,8 +73,23 @@ import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.sql.*; import java.sql.Connection;
import java.util.*; import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
+1 -1
View File
@@ -7,9 +7,9 @@ import tools.PacketCreator;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Map;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
+5 -1
View File
@@ -24,7 +24,11 @@ package client;
import server.quest.Quest; import server.quest.Quest;
import tools.StringUtil; import tools.StringUtil;
import java.util.*; import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/** /**
* @author Matze * @author Matze
+57 -2
View File
@@ -21,8 +21,63 @@
*/ */
package client; package client;
import constants.skills.*; import constants.skills.Aran;
import provider.*; import constants.skills.Archer;
import constants.skills.Assassin;
import constants.skills.Bandit;
import constants.skills.Beginner;
import constants.skills.Bishop;
import constants.skills.BlazeWizard;
import constants.skills.Bowmaster;
import constants.skills.Buccaneer;
import constants.skills.ChiefBandit;
import constants.skills.Cleric;
import constants.skills.Corsair;
import constants.skills.Crossbowman;
import constants.skills.Crusader;
import constants.skills.DarkKnight;
import constants.skills.DawnWarrior;
import constants.skills.DragonKnight;
import constants.skills.Evan;
import constants.skills.FPArchMage;
import constants.skills.FPMage;
import constants.skills.FPWizard;
import constants.skills.Fighter;
import constants.skills.GM;
import constants.skills.Gunslinger;
import constants.skills.Hermit;
import constants.skills.Hero;
import constants.skills.Hunter;
import constants.skills.ILArchMage;
import constants.skills.ILMage;
import constants.skills.ILWizard;
import constants.skills.Legend;
import constants.skills.Magician;
import constants.skills.Marauder;
import constants.skills.Marksman;
import constants.skills.NightLord;
import constants.skills.NightWalker;
import constants.skills.Noblesse;
import constants.skills.Page;
import constants.skills.Paladin;
import constants.skills.Pirate;
import constants.skills.Priest;
import constants.skills.Ranger;
import constants.skills.Rogue;
import constants.skills.Shadower;
import constants.skills.Sniper;
import constants.skills.Spearman;
import constants.skills.SuperGM;
import constants.skills.ThunderBreaker;
import constants.skills.Warrior;
import constants.skills.WhiteKnight;
import constants.skills.WindArcher;
import provider.Data;
import provider.DataDirectoryEntry;
import provider.DataFileEntry;
import provider.DataProvider;
import provider.DataProviderFactory;
import provider.DataTool;
import provider.wz.WZFiles; import provider.wz.WZFiles;
import server.StatEffect; import server.StatEffect;
import server.life.Element; import server.life.Element;
@@ -24,13 +24,177 @@
package client.command; package client.command;
import client.Client; import client.Client;
import client.command.commands.gm0.*; import client.command.commands.gm0.ChangeLanguageCommand;
import client.command.commands.gm1.*; import client.command.commands.gm0.DisposeCommand;
import client.command.commands.gm2.*; import client.command.commands.gm0.DropLimitCommand;
import client.command.commands.gm3.*; import client.command.commands.gm0.EnableAuthCommand;
import client.command.commands.gm4.*; import client.command.commands.gm0.EquipLvCommand;
import client.command.commands.gm5.*; import client.command.commands.gm0.GachaCommand;
import client.command.commands.gm6.*; import client.command.commands.gm0.GmCommand;
import client.command.commands.gm0.HelpCommand;
import client.command.commands.gm0.JoinEventCommand;
import client.command.commands.gm0.LeaveEventCommand;
import client.command.commands.gm0.MapOwnerClaimCommand;
import client.command.commands.gm0.OnlineCommand;
import client.command.commands.gm0.RanksCommand;
import client.command.commands.gm0.RatesCommand;
import client.command.commands.gm0.ReportBugCommand;
import client.command.commands.gm0.ShowRatesCommand;
import client.command.commands.gm0.StaffCommand;
import client.command.commands.gm0.StatDexCommand;
import client.command.commands.gm0.StatIntCommand;
import client.command.commands.gm0.StatLukCommand;
import client.command.commands.gm0.StatStrCommand;
import client.command.commands.gm0.TimeCommand;
import client.command.commands.gm0.ToggleExpCommand;
import client.command.commands.gm0.UptimeCommand;
import client.command.commands.gm1.BossHpCommand;
import client.command.commands.gm1.BuffMeCommand;
import client.command.commands.gm1.GotoCommand;
import client.command.commands.gm1.MobHpCommand;
import client.command.commands.gm1.WhatDropsFromCommand;
import client.command.commands.gm1.WhoDropsCommand;
import client.command.commands.gm2.ApCommand;
import client.command.commands.gm2.BombCommand;
import client.command.commands.gm2.BuffCommand;
import client.command.commands.gm2.BuffMapCommand;
import client.command.commands.gm2.ClearDropsCommand;
import client.command.commands.gm2.ClearSavedLocationsCommand;
import client.command.commands.gm2.ClearSlotCommand;
import client.command.commands.gm2.DcCommand;
import client.command.commands.gm2.EmpowerMeCommand;
import client.command.commands.gm2.GachaListCommand;
import client.command.commands.gm2.GmShopCommand;
import client.command.commands.gm2.HealCommand;
import client.command.commands.gm2.HideCommand;
import client.command.commands.gm2.IdCommand;
import client.command.commands.gm2.ItemCommand;
import client.command.commands.gm2.ItemDropCommand;
import client.command.commands.gm2.JailCommand;
import client.command.commands.gm2.JobCommand;
import client.command.commands.gm2.LevelCommand;
import client.command.commands.gm2.LevelProCommand;
import client.command.commands.gm2.LootCommand;
import client.command.commands.gm2.MaxSkillCommand;
import client.command.commands.gm2.MaxStatCommand;
import client.command.commands.gm2.MobSkillCommand;
import client.command.commands.gm2.ReachCommand;
import client.command.commands.gm2.RechargeCommand;
import client.command.commands.gm2.ResetSkillCommand;
import client.command.commands.gm2.SearchCommand;
import client.command.commands.gm2.SetSlotCommand;
import client.command.commands.gm2.SetStatCommand;
import client.command.commands.gm2.SpCommand;
import client.command.commands.gm2.SummonCommand;
import client.command.commands.gm2.UnBugCommand;
import client.command.commands.gm2.UnHideCommand;
import client.command.commands.gm2.UnJailCommand;
import client.command.commands.gm2.WarpAreaCommand;
import client.command.commands.gm2.WarpCommand;
import client.command.commands.gm2.WarpMapCommand;
import client.command.commands.gm2.WhereaMiCommand;
import client.command.commands.gm3.BanCommand;
import client.command.commands.gm3.ChatCommand;
import client.command.commands.gm3.CheckDmgCommand;
import client.command.commands.gm3.ClosePortalCommand;
import client.command.commands.gm3.DebuffCommand;
import client.command.commands.gm3.EndEventCommand;
import client.command.commands.gm3.ExpedsCommand;
import client.command.commands.gm3.FaceCommand;
import client.command.commands.gm3.FameCommand;
import client.command.commands.gm3.FlyCommand;
import client.command.commands.gm3.GiveMesosCommand;
import client.command.commands.gm3.GiveNxCommand;
import client.command.commands.gm3.HairCommand;
import client.command.commands.gm3.HealMapCommand;
import client.command.commands.gm3.HealPersonCommand;
import client.command.commands.gm3.HpMpCommand;
import client.command.commands.gm3.HurtCommand;
import client.command.commands.gm3.IgnoreCommand;
import client.command.commands.gm3.IgnoredCommand;
import client.command.commands.gm3.InMapCommand;
import client.command.commands.gm3.KillAllCommand;
import client.command.commands.gm3.KillCommand;
import client.command.commands.gm3.KillMapCommand;
import client.command.commands.gm3.MaxEnergyCommand;
import client.command.commands.gm3.MaxHpMpCommand;
import client.command.commands.gm3.MonitorCommand;
import client.command.commands.gm3.MonitorsCommand;
import client.command.commands.gm3.MusicCommand;
import client.command.commands.gm3.MuteMapCommand;
import client.command.commands.gm3.NightCommand;
import client.command.commands.gm3.NoticeCommand;
import client.command.commands.gm3.NpcCommand;
import client.command.commands.gm3.OnlineTwoCommand;
import client.command.commands.gm3.OpenPortalCommand;
import client.command.commands.gm3.PeCommand;
import client.command.commands.gm3.PosCommand;
import client.command.commands.gm3.QuestCompleteCommand;
import client.command.commands.gm3.QuestResetCommand;
import client.command.commands.gm3.QuestStartCommand;
import client.command.commands.gm3.ReloadDropsCommand;
import client.command.commands.gm3.ReloadEventsCommand;
import client.command.commands.gm3.ReloadMapCommand;
import client.command.commands.gm3.ReloadPortalsCommand;
import client.command.commands.gm3.ReloadShopsCommand;
import client.command.commands.gm3.RipCommand;
import client.command.commands.gm3.SeedCommand;
import client.command.commands.gm3.SpawnCommand;
import client.command.commands.gm3.StartEventCommand;
import client.command.commands.gm3.StartMapEventCommand;
import client.command.commands.gm3.StopMapEventCommand;
import client.command.commands.gm3.TimerAllCommand;
import client.command.commands.gm3.TimerCommand;
import client.command.commands.gm3.TimerMapCommand;
import client.command.commands.gm3.ToggleCouponCommand;
import client.command.commands.gm3.UnBanCommand;
import client.command.commands.gm4.BossDropRateCommand;
import client.command.commands.gm4.CakeCommand;
import client.command.commands.gm4.DropRateCommand;
import client.command.commands.gm4.ExpRateCommand;
import client.command.commands.gm4.FishingRateCommand;
import client.command.commands.gm4.ForceVacCommand;
import client.command.commands.gm4.HorntailCommand;
import client.command.commands.gm4.ItemVacCommand;
import client.command.commands.gm4.MesoRateCommand;
import client.command.commands.gm4.PapCommand;
import client.command.commands.gm4.PianusCommand;
import client.command.commands.gm4.PinkbeanCommand;
import client.command.commands.gm4.PlayerNpcCommand;
import client.command.commands.gm4.PlayerNpcRemoveCommand;
import client.command.commands.gm4.PmobCommand;
import client.command.commands.gm4.PmobRemoveCommand;
import client.command.commands.gm4.PnpcCommand;
import client.command.commands.gm4.PnpcRemoveCommand;
import client.command.commands.gm4.ProItemCommand;
import client.command.commands.gm4.QuestRateCommand;
import client.command.commands.gm4.ServerMessageCommand;
import client.command.commands.gm4.SetEqStatCommand;
import client.command.commands.gm4.TravelRateCommand;
import client.command.commands.gm4.ZakumCommand;
import client.command.commands.gm5.DebugCommand;
import client.command.commands.gm5.IpListCommand;
import client.command.commands.gm5.SetCommand;
import client.command.commands.gm5.ShowMoveLifeCommand;
import client.command.commands.gm5.ShowPacketsCommand;
import client.command.commands.gm5.ShowSessionsCommand;
import client.command.commands.gm6.ClearQuestCacheCommand;
import client.command.commands.gm6.ClearQuestCommand;
import client.command.commands.gm6.DCAllCommand;
import client.command.commands.gm6.DevtestCommand;
import client.command.commands.gm6.EraseAllPNpcsCommand;
import client.command.commands.gm6.GetAccCommand;
import client.command.commands.gm6.MapPlayersCommand;
import client.command.commands.gm6.SaveAllCommand;
import client.command.commands.gm6.ServerAddChannelCommand;
import client.command.commands.gm6.ServerAddWorldCommand;
import client.command.commands.gm6.ServerRemoveChannelCommand;
import client.command.commands.gm6.ServerRemoveWorldCommand;
import client.command.commands.gm6.SetGmLevelCommand;
import client.command.commands.gm6.ShutdownCommand;
import client.command.commands.gm6.SpawnAllPNpcsCommand;
import client.command.commands.gm6.SupplyRateCouponCommand;
import client.command.commands.gm6.WarpWorldCommand;
import constants.id.MapId; import constants.id.MapId;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -178,7 +342,6 @@ public class CommandsExecutor {
addCommand("droplimit", DropLimitCommand.class); addCommand("droplimit", DropLimitCommand.class);
addCommand("time", TimeCommand.class); addCommand("time", TimeCommand.class);
addCommand("credits", StaffCommand.class); addCommand("credits", StaffCommand.class);
addCommand("buyback", BuyBackCommand.class);
addCommand("uptime", UptimeCommand.class); addCommand("uptime", UptimeCommand.class);
addCommand("gacha", GachaCommand.class); addCommand("gacha", GachaCommand.class);
addCommand("dispose", DisposeCommand.class); addCommand("dispose", DisposeCommand.class);
@@ -389,6 +552,7 @@ public class CommandsExecutor {
addCommand("addworld", 6, ServerAddWorldCommand.class); addCommand("addworld", 6, ServerAddWorldCommand.class);
addCommand("removechannel", 6, ServerRemoveChannelCommand.class); addCommand("removechannel", 6, ServerRemoveChannelCommand.class);
addCommand("removeworld", 6, ServerRemoveWorldCommand.class); addCommand("removeworld", 6, ServerRemoveWorldCommand.class);
addCommand("devtest", 6, DevtestCommand.class);
commandsNameDesc.add(levelCommandsCursor); commandsNameDesc.add(levelCommandsCursor);
} }
@@ -1,49 +0,0 @@
/*
This file is part of the HeavenMS MapleStory Server, commands OdinMS-based
Copyleft (L) 2016 - 2019 RonanLana
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/>.
*/
/*
@Author: Arthur L - Refactored command content into modules
*/
package client.command.commands.gm0;
import client.Client;
import client.command.Command;
import client.command.CommandContext;
import client.processor.action.BuybackProcessor;
public class BuyBackCommand extends Command {
{
setDescription("Revive yourself after a death.");
}
@Override
public void execute(Client c, String[] params, CommandContext ctx) {
if (params.length < 1) {
c.getPlayer().yellowMessage("Syntax: @buyback <info|now>");
return;
}
if (params[0].contentEquals("now")) {
BuybackProcessor.processBuyback(c);
} else {
c.getPlayer().showBuybackInfo();
}
}
}
@@ -28,7 +28,10 @@ import client.command.Command;
import client.command.CommandContext; import client.command.CommandContext;
import net.server.Server; import net.server.Server;
import static java.util.concurrent.TimeUnit.*; import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
public class UptimeCommand extends Command { public class UptimeCommand extends Command {
{ {
@@ -29,7 +29,11 @@ import client.command.Command;
import client.command.CommandContext; import client.command.CommandContext;
import constants.game.GameConstants; import constants.game.GameConstants;
import constants.id.NpcId; import constants.id.NpcId;
import server.maps.*; import server.maps.FieldLimit;
import server.maps.MapFactory;
import server.maps.MapleMap;
import server.maps.MiniDungeonInfo;
import server.maps.Portal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@@ -12,7 +12,11 @@ import tools.exceptions.IdTypeNotSupportedException;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.*; import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -24,7 +24,10 @@
package client.command.commands.gm2; package client.command.commands.gm2;
import client.Character; import client.Character;
import client.*; import client.Client;
import client.Job;
import client.Skill;
import client.SkillFactory;
import client.command.Command; import client.command.Command;
import client.command.CommandContext; import client.command.CommandContext;
import provider.Data; import provider.Data;
@@ -24,7 +24,10 @@
package client.command.commands.gm2; package client.command.commands.gm2;
import client.Character; import client.Character;
import client.*; import client.Client;
import client.Job;
import client.Skill;
import client.SkillFactory;
import client.command.Command; import client.command.Command;
import client.command.CommandContext; import client.command.CommandContext;
import provider.Data; import provider.Data;
@@ -0,0 +1,41 @@
package client.command.commands.gm6;
import client.Client;
import client.command.Command;
import client.command.CommandContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import scripting.AbstractScriptManager;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
public class DevtestCommand extends Command {
{
setDescription("Runs devtest.js. Developer utility - test stuff without restarting the server.");
}
private static final Logger log = LoggerFactory.getLogger(DevtestCommand.class);
private static class DevtestScriptManager extends AbstractScriptManager {
@Override
public ScriptEngine getInvocableScriptEngine(String path) {
return super.getInvocableScriptEngine(path);
}
}
@Override
public void execute(Client client, String[] params, CommandContext context) {
DevtestScriptManager scriptManager = new DevtestScriptManager();
ScriptEngine scriptEngine = scriptManager.getInvocableScriptEngine("devtest.js");
try {
Invocable invocable = (Invocable) scriptEngine;
invocable.invokeFunction("run", client.getPlayer());
} catch (ScriptException | NoSuchMethodException e) {
log.info("devtest.js run() threw an exception", e);
}
}
}
@@ -31,7 +31,10 @@ import net.server.Server;
import net.server.world.World; import net.server.world.World;
import server.TimerManager; import server.TimerManager;
import static java.util.concurrent.TimeUnit.*; import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
public class ShutdownCommand extends Command { public class ShutdownCommand extends Command {
{ {
@@ -47,19 +47,19 @@ public abstract class CharacterFactory {
return -1; return -1;
} }
Character newchar = Character.getDefault(c); Character newCharacter = Character.getDefault(c);
newchar.setWorld(c.getWorld()); newCharacter.setWorld(c.getWorld());
newchar.setSkinColor(SkinColor.getById(skin)); newCharacter.setSkinColor(SkinColor.getById(skin));
newchar.setGender(gender); newCharacter.setGender(gender);
newchar.setName(name); newCharacter.setName(name);
newchar.setHair(hair); newCharacter.setHair(hair);
newchar.setFace(face); newCharacter.setFace(face);
newchar.setLevel(recipe.getLevel()); newCharacter.setLevel(recipe.getLevel());
newchar.setJob(recipe.getJob()); newCharacter.setJob(recipe.getJob());
newchar.setMapId(recipe.getMap()); newCharacter.setMapId(recipe.getMap());
Inventory equipped = newchar.getInventory(InventoryType.EQUIPPED); Inventory equipped = newCharacter.getInventory(InventoryType.EQUIPPED);
ItemInformationProvider ii = ItemInformationProvider.getInstance(); ItemInformationProvider ii = ItemInformationProvider.getInstance();
int top = recipe.getTop(), bottom = recipe.getBottom(), shoes = recipe.getShoes(), weapon = recipe.getWeapon(); int top = recipe.getTop(), bottom = recipe.getBottom(), shoes = recipe.getShoes(), weapon = recipe.getWeapon();
@@ -88,12 +88,17 @@ public abstract class CharacterFactory {
equipped.addItemFromDB(eq_weapon.copy()); equipped.addItemFromDB(eq_weapon.copy());
} }
if (!newchar.insertNewChar(recipe)) { if (!MakeCharInfoValidator.isNewCharacterValid(newCharacter)) {
log.warn("Owner from account {} tried to packet edit in character creation", c.getAccountName());
return -2; return -2;
} }
c.sendPacket(PacketCreator.addNewCharEntry(newchar));
Server.getInstance().createCharacterEntry(newchar); if (!newCharacter.insertNewChar(recipe)) {
return -2;
}
c.sendPacket(PacketCreator.addNewCharEntry(newCharacter));
Server.getInstance().createCharacterEntry(newCharacter);
Server.getInstance().broadcastGMMessage(c.getWorld(), PacketCreator.sendYellowTip("[New Char]: " + c.getAccountName() + " has created a new character with IGN " + name)); Server.getInstance().broadcastGMMessage(c.getWorld(), PacketCreator.sendYellowTip("[New Char]: " + c.getAccountName() + " has created a new character with IGN " + name));
log.info("Account {} created chr with name {}", c.getAccountName(), name); log.info("Account {} created chr with name {}", c.getAccountName(), name);
@@ -0,0 +1,140 @@
package client.creator;
import client.Character;
import client.Job;
import client.inventory.InventoryType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import provider.Data;
import provider.DataTool;
import java.util.HashSet;
import java.util.Set;
public class MakeCharInfo {
private static final Logger log = LoggerFactory.getLogger(MakeCharInfo.class);
private static final String FACE_ID = "0";
private static final String HAIR_ID = "1";
private static final String HAIR_COLOR_ID = "2";
private static final String SKIN_ID = "3";
private static final String TOP_ID = "4";
private static final String BOTTOM_ID = "5";
private static final String SHOE_ID = "6";
private static final String WEAPON_ID = "7";
private final Set<Integer> charFaces = new HashSet<>();
private final Set<Integer> charHairs = new HashSet<>();
private final Set<Integer> charHairColors = new HashSet<>();
private final Set<Integer> charSkins = new HashSet<>();
private final Set<Integer> charTops = new HashSet<>();
private final Set<Integer> charBottoms = new HashSet<>();
private final Set<Integer> charShoes = new HashSet<>();
private final Set<Integer> charWeapons = new HashSet<>();
public MakeCharInfo(Data charInfoData) {
for (Data data : charInfoData.getChildren()) {
switch (data.getName()) {
case FACE_ID -> {
for (Data faceData : data) {
charFaces.add(DataTool.getInt(faceData));
}
}
case HAIR_ID -> {
for (Data hairData : data) {
charHairs.add(DataTool.getInt(hairData));
}
}
case HAIR_COLOR_ID -> {
for (Data hairColorData : data) {
charHairColors.add(DataTool.getInt(hairColorData));
}
}
case SKIN_ID -> {
for (Data skinData : data) {
charSkins.add(DataTool.getInt(skinData));
}
}
case TOP_ID -> {
for (Data topData : data) {
charTops.add(DataTool.getInt(topData));
}
}
case BOTTOM_ID -> {
for (Data bottomData : data) {
charBottoms.add(DataTool.getInt(bottomData));
}
}
case SHOE_ID -> {
for (Data shoeData : data) {
charShoes.add(DataTool.getInt(shoeData));
}
}
case WEAPON_ID -> {
for (Data weaponData : data) {
charWeapons.add(DataTool.getInt(weaponData));
}
}
default -> log.error("Unhandled node inside MakeCharInfo.img.xml: '" + data.getName() + "'");
}
}
}
public boolean verifyFaceId(int id) {
return this.charFaces.contains(id);
}
public boolean verifyHairId(int id) {
if (id % 10 != 0) {
return this.charHairs.contains(id - (id % 10));
}
return this.charHairs.contains(id);
}
public boolean verifyHairColorId(int id) {
return this.charHairColors.contains(id % 10);
}
public boolean verifySkinId(int id) {
return this.charSkins.contains(id);
}
public boolean verifyTopId(int id) {
return this.charTops.contains(id);
}
public boolean verifyBottomId(int id) {
return this.charBottoms.contains(id);
}
public boolean verifyShoeId(int id) {
return this.charShoes.contains(id);
}
public boolean verifyWeaponId(int id) {
return this.charWeapons.contains(id);
}
public boolean verifyCharacter(Character character) {
if (!verifyFaceId(character.getFace())) return false;
if (!verifyHairId(character.getHair())) return false;
if (!verifyHairColorId(character.getHair())) return false;
if (!verifySkinId(character.getSkinColor().getId())) return false;
// Here we only verify the equipment if the character that's being created is of type 'Beginner'
// This is because when the Maple Life A or Maple Life B items are used, the client does not send any data
// regarding what equipment the character should be wearing (as it's all handled server-side)
Job characterJob = character.getJob();
if (characterJob == Job.BEGINNER || characterJob == Job.NOBLESSE || characterJob == Job.LEGEND) {
if (!verifyTopId(character.getInventory(InventoryType.EQUIPPED).getItem((short) -5).getItemId()))
return false;
if (!verifyBottomId(character.getInventory(InventoryType.EQUIPPED).getItem((short) -6).getItemId()))
return false;
if (!verifyShoeId(character.getInventory(InventoryType.EQUIPPED).getItem((short) -7).getItemId()))
return false;
if (!verifyWeaponId(character.getInventory(InventoryType.EQUIPPED).getItem((short) -11).getItemId()))
return false;
}
return true;
}
}
@@ -0,0 +1,41 @@
package client.creator;
import client.Character;
import provider.Data;
import provider.DataProviderFactory;
import provider.wz.WZFiles;
public class MakeCharInfoValidator {
private static final MakeCharInfo charFemale;
private static final MakeCharInfo charMale;
private static final MakeCharInfo orientCharFemale;
private static final MakeCharInfo orientCharMale;
private static final MakeCharInfo premiumCharFemale;
private static final MakeCharInfo premiumCharMale;
static {
Data data = DataProviderFactory.getDataProvider(WZFiles.ETC).getData("MakeCharInfo.img");
charFemale = new MakeCharInfo(data.getChildByPath("Info/CharFemale"));
charMale = new MakeCharInfo(data.getChildByPath("Info/CharMale"));
orientCharFemale = new MakeCharInfo(data.getChildByPath("OrientCharFemale"));
orientCharMale = new MakeCharInfo(data.getChildByPath("OrientCharMale"));
premiumCharFemale = new MakeCharInfo(data.getChildByPath("PremiumCharFemale"));
premiumCharMale = new MakeCharInfo(data.getChildByPath("PremiumCharMale"));
}
private static MakeCharInfo getMakeCharInfo(Character character) {
return switch (character.getJob()) {
case BEGINNER, WARRIOR, MAGICIAN, BOWMAN, THIEF, PIRATE -> character.isMale() ? charMale : charFemale;
case NOBLESSE -> character.isMale() ? premiumCharMale : premiumCharFemale;
case LEGEND -> character.isMale() ? orientCharMale : orientCharFemale;
default -> null;
};
}
public static boolean isNewCharacterValid(Character character) {
MakeCharInfo makeCharInfo = getMakeCharInfo(character);
if (makeCharInfo == null) return false;
return makeCharInfo.verifyCharacter(character);
}
}
@@ -43,7 +43,6 @@ public class BeginnerCreator extends CharacterFactory {
} }
public static int createCharacter(Client c, String name, int face, int hair, int skin, int top, int bottom, int shoes, int weapon, int gender) { public static int createCharacter(Client c, String name, int face, int hair, int skin, int top, int bottom, int shoes, int weapon, int gender) {
int status = createNewCharacter(c, name, face, hair, skin, gender, createRecipe(Job.BEGINNER, 1, MapId.MUSHROOM_TOWN, top, bottom, shoes, weapon)); return createNewCharacter(c, name, face, hair, skin, gender, createRecipe(Job.BEGINNER, 1, MapId.MUSHROOM_TOWN, top, bottom, shoes, weapon));
return status;
} }
} }
@@ -43,7 +43,6 @@ public class LegendCreator extends CharacterFactory {
} }
public static int createCharacter(Client c, String name, int face, int hair, int skin, int top, int bottom, int shoes, int weapon, int gender) { public static int createCharacter(Client c, String name, int face, int hair, int skin, int top, int bottom, int shoes, int weapon, int gender) {
int status = createNewCharacter(c, name, face, hair, skin, gender, createRecipe(Job.LEGEND, 1, MapId.ARAN_TUTORIAL_START, top, bottom, shoes, weapon)); return createNewCharacter(c, name, face, hair, skin, gender, createRecipe(Job.LEGEND, 1, MapId.ARAN_TUTORIAL_START, top, bottom, shoes, weapon));
return status;
} }
} }
@@ -43,7 +43,6 @@ public class NoblesseCreator extends CharacterFactory {
} }
public static int createCharacter(Client c, String name, int face, int hair, int skin, int top, int bottom, int shoes, int weapon, int gender) { public static int createCharacter(Client c, String name, int face, int hair, int skin, int top, int bottom, int shoes, int weapon, int gender) {
int status = createNewCharacter(c, name, face, hair, skin, gender, createRecipe(Job.NOBLESSE, 1, MapId.STARTING_MAP_NOBLESSE, top, bottom, shoes, weapon)); return createNewCharacter(c, name, face, hair, skin, gender, createRecipe(Job.NOBLESSE, 1, MapId.STARTING_MAP_NOBLESSE, top, bottom, shoes, weapon));
return status;
} }
} }
@@ -31,7 +31,14 @@ import server.ItemInformationProvider;
import server.ThreadManager; import server.ThreadManager;
import tools.Pair; import tools.Pair;
import java.util.*; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@@ -652,4 +659,4 @@ public class Inventory implements Iterable<Item> {
public void dispose() { public void dispose() {
owner = null; owner = null;
} }
} }
@@ -23,7 +23,11 @@ package client.inventory;
import tools.DatabaseConnection; import tools.DatabaseConnection;
import tools.Pair; import tools.Pair;
import java.sql.*; import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
@@ -418,4 +422,4 @@ public enum ItemFactory {
lock.unlock(); lock.unlock();
} }
} }
} }
@@ -24,7 +24,12 @@ package client.inventory.manipulator;
import client.BuffStat; import client.BuffStat;
import client.Character; import client.Character;
import client.Client; import client.Client;
import client.inventory.*; import client.inventory.Equip;
import client.inventory.Inventory;
import client.inventory.InventoryType;
import client.inventory.Item;
import client.inventory.ModifyInventory;
import client.inventory.Pet;
import client.newyear.NewYearCardRecord; import client.newyear.NewYearCardRecord;
import config.YamlConfig; import config.YamlConfig;
import constants.id.ItemId; import constants.id.ItemId;
@@ -1,83 +0,0 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2019 RonanLana
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/>.
*/
package client.processor.action; // thanks Alex for pointing out some package structures containing broad modules
import client.Character;
import client.Client;
import server.maps.MapleMap;
import tools.PacketCreator;
/**
* @author RonanLana
*/
public class BuybackProcessor {
public static void processBuyback(Client c) {
Character chr = c.getPlayer();
boolean buyback;
c.lockClient();
try {
buyback = !chr.isAlive() && chr.couldBuyback();
} finally {
c.unlockClient();
}
if (buyback) {
String jobString;
switch (chr.getJobStyle()) {
case WARRIOR:
jobString = "warrior";
break;
case MAGICIAN:
jobString = "magician";
break;
case BOWMAN:
jobString = "bowman";
break;
case THIEF:
jobString = "thief";
break;
case BRAWLER:
case GUNSLINGER:
jobString = "pirate";
break;
default:
jobString = "beginner";
}
chr.healHpMp();
chr.purgeDebuffs();
chr.broadcastStance(chr.isFacingLeft() ? 5 : 4);
MapleMap map = chr.getMap();
map.broadcastMessage(PacketCreator.playSound("Buyback/" + jobString));
map.broadcastMessage(PacketCreator.earnTitleMessage(chr.getName() + " just bought back into the game!"));
chr.sendPacket(PacketCreator.showBuybackEffect());
map.broadcastMessage(chr, PacketCreator.showForeignBuybackEffect(chr.getId()), false);
}
}
}
@@ -46,7 +46,12 @@ import tools.DatabaseConnection;
import tools.PacketCreator; import tools.PacketCreator;
import tools.Pair; import tools.Pair;
import java.sql.*; import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedList; import java.util.LinkedList;
@@ -41,7 +41,11 @@ import tools.DatabaseConnection;
import tools.PacketCreator; import tools.PacketCreator;
import tools.Pair; import tools.Pair;
import java.sql.*; import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@@ -24,7 +24,11 @@
package client.processor.stat; package client.processor.stat;
import client.Character; import client.Character;
import client.*; import client.Client;
import client.Job;
import client.Skill;
import client.SkillFactory;
import client.Stat;
import client.autoban.AutobanFactory; import client.autoban.AutobanFactory;
import client.inventory.Equip; import client.inventory.Equip;
import client.inventory.InventoryType; import client.inventory.InventoryType;
@@ -32,6 +36,12 @@ import client.inventory.Item;
import config.YamlConfig; import config.YamlConfig;
import constants.skills.*; import constants.skills.*;
import net.netty.GameViolationException; import net.netty.GameViolationException;
import constants.skills.BlazeWizard;
import constants.skills.Brawler;
import constants.skills.DawnWarrior;
import constants.skills.Magician;
import constants.skills.ThunderBreaker;
import constants.skills.Warrior;
import net.packet.InPacket; import net.packet.InPacket;
import tools.PacketCreator; import tools.PacketCreator;
import tools.Randomizer; import tools.Randomizer;
@@ -542,16 +552,14 @@ public class AssignAPProcessor {
return false; return false;
} }
int hp = player.getMaxHp(); int hplose = -takeHp(player.getJob());
int level_ = player.getLevel(); if (player.getMaxHp() + hplose < getMinHp(player.getJob(), player.getLevel())) {
if (hp < level_ * 14 + 148) {
player.message("You don't have the minimum HP pool required to swap."); player.message("You don't have the minimum HP pool required to swap.");
c.sendPacket(PacketCreator.enableActions()); c.sendPacket(PacketCreator.enableActions());
return false; return false;
} }
int curHp = player.getHp(); int curHp = player.getHp();
int hplose = -takeHp(player.getJob());
player.assignHP(hplose, -1); player.assignHP(hplose, -1);
if (!YamlConfig.config.server.USE_FIXED_RATIO_HPMP_UPDATE) { if (!YamlConfig.config.server.USE_FIXED_RATIO_HPMP_UPDATE) {
player.updateHp(Math.max(1, curHp + hplose)); player.updateHp(Math.max(1, curHp + hplose));
@@ -573,29 +581,14 @@ public class AssignAPProcessor {
return false; return false;
} }
int mp = player.getMaxMp(); int mplose = -takeMp(player.getJob());
int level = player.getLevel(); if (player.getMaxMp() + mplose < getMinMp(player.getJob(), 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) {
player.message("You don't have the minimum MP pool required to swap."); player.message("You don't have the minimum MP pool required to swap.");
c.sendPacket(PacketCreator.enableActions()); c.sendPacket(PacketCreator.enableActions());
return false; return false;
} }
int curMp = player.getMp(); int curMp = player.getMp();
int mplose = -takeMp(job);
player.assignMP(mplose, -1); player.assignMP(mplose, -1);
if (!YamlConfig.config.server.USE_FIXED_RATIO_HPMP_UPDATE) { if (!YamlConfig.config.server.USE_FIXED_RATIO_HPMP_UPDATE) {
player.updateMp(Math.max(0, curMp + mplose)); player.updateMp(Math.max(0, curMp + mplose));
@@ -886,4 +879,109 @@ public class AssignAPProcessor {
return MaxMP; 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;
}
} }
+1 -15
View File
@@ -77,7 +77,6 @@ public class ServerConfig {
public boolean USE_MTS; public boolean USE_MTS;
public boolean USE_CPQ; public boolean USE_CPQ;
public boolean USE_AUTOHIDE_GM; public boolean USE_AUTOHIDE_GM;
public boolean USE_BUYBACK_SYSTEM;
public boolean USE_FIXED_RATIO_HPMP_UPDATE; public boolean USE_FIXED_RATIO_HPMP_UPDATE;
public boolean USE_FAMILY_SYSTEM; public boolean USE_FAMILY_SYSTEM;
public boolean USE_DUEY; public boolean USE_DUEY;
@@ -105,17 +104,15 @@ public class ServerConfig {
public boolean USE_ENFORCE_UNMERCHABLE_PET; public boolean USE_ENFORCE_UNMERCHABLE_PET;
public boolean USE_ENFORCE_MDOOR_POSITION; public boolean USE_ENFORCE_MDOOR_POSITION;
public boolean USE_SPAWN_CLEAN_MDOOR; public boolean USE_SPAWN_CLEAN_MDOOR;
public boolean USE_SPAWN_LOOT_ON_ANIMATION;
public boolean USE_SPAWN_RELEVANT_LOOT; public boolean USE_SPAWN_RELEVANT_LOOT;
public boolean USE_ERASE_PERMIT_ON_OPENSHOP; public boolean USE_ERASE_PERMIT_ON_OPENSHOP;
public boolean USE_ERASE_UNTRADEABLE_DROP; public boolean USE_ERASE_UNTRADEABLE_DROP;
public boolean USE_ERASE_PET_ON_EXPIRATION; public boolean USE_ERASE_PET_ON_EXPIRATION;
public boolean USE_BUFF_MOST_SIGNIFICANT; public boolean USE_BUFF_MOST_SIGNIFICANT;
public boolean USE_BUFF_EVERLASTING; public boolean USE_BUFF_EVERLASTING;
public boolean USE_BANISHABLE_TOWN_SCROLL; public boolean USE_MULTIPLE_SAME_EQUIP_DROP;
public boolean USE_ENABLE_FULL_RESPAWN; public boolean USE_ENABLE_FULL_RESPAWN;
public boolean USE_ENABLE_CHAT_LOG; public boolean USE_ENABLE_CHAT_LOG;
public boolean USE_REBIRTH_SYSTEM;
public boolean USE_MAP_OWNERSHIP_SYSTEM; public boolean USE_MAP_OWNERSHIP_SYSTEM;
public boolean USE_FISHING_SYSTEM; public boolean USE_FISHING_SYSTEM;
public boolean USE_NPCS_SCRIPTABLE; public boolean USE_NPCS_SCRIPTABLE;
@@ -173,7 +170,6 @@ public class ServerConfig {
public long NAME_CHANGE_COOLDOWN; 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 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 boolean INSTANT_NAME_CHANGE;
public int REBIRTH_NPC_ID;
//Dangling Items/Locks Configuration //Dangling Items/Locks Configuration
public int ITEM_EXPIRE_TIME; public int ITEM_EXPIRE_TIME;
@@ -201,7 +197,6 @@ public class ServerConfig {
public boolean USE_PERFECT_SCROLLING; public boolean USE_PERFECT_SCROLLING;
public boolean USE_ENHANCED_CHSCROLL; public boolean USE_ENHANCED_CHSCROLL;
public boolean USE_ENHANCED_CRAFTING; public boolean USE_ENHANCED_CRAFTING;
public boolean USE_ENHANCED_CLNSLATE;
public int SCROLL_CHANCE_ROLLS; public int SCROLL_CHANCE_ROLLS;
public int CHSCROLL_STAT_RATE; public int CHSCROLL_STAT_RATE;
public int CHSCROLL_STAT_RANGE; public int CHSCROLL_STAT_RANGE;
@@ -250,7 +245,6 @@ public class ServerConfig {
public boolean USE_EQUIPMNT_LVLUP_SLOTS; public boolean USE_EQUIPMNT_LVLUP_SLOTS;
public boolean USE_EQUIPMNT_LVLUP_POWER; public boolean USE_EQUIPMNT_LVLUP_POWER;
public boolean USE_EQUIPMNT_LVLUP_CASH; public boolean USE_EQUIPMNT_LVLUP_CASH;
public boolean USE_SPIKES_AVOID_BANISH;
public int MAX_EQUIPMNT_LVLUP_STAT_UP; public int MAX_EQUIPMNT_LVLUP_STAT_UP;
public int MAX_EQUIPMNT_STAT; public int MAX_EQUIPMNT_STAT;
public int USE_EQUIPMNT_LVLUP; public int USE_EQUIPMNT_LVLUP;
@@ -301,14 +295,6 @@ public class ServerConfig {
public int WEDDING_GIFT_LIMIT; public int WEDDING_GIFT_LIMIT;
public boolean WEDDING_BLESSER_SHOWFX; public boolean WEDDING_BLESSER_SHOWFX;
//Buyback Configuration
public boolean USE_BUYBACK_WITH_MESOS;
public float BUYBACK_FEE;
public float BUYBACK_LEVEL_STACK_FEE;
public int BUYBACK_MESO_MULTIPLIER;
public int BUYBACK_RETURN_MINUTES;
public int BUYBACK_COOLDOWN_MINUTES;
// Login timeout by shavit // Login timeout by shavit
public long TIMEOUT_DURATION; public long TIMEOUT_DURATION;
+4 -41
View File
@@ -101,46 +101,6 @@ public class ItemId {
public static final int BEGINNERS_GUIDE = 4161001; public static final int BEGINNERS_GUIDE = 4161001;
public static final int LEGENDS_GUIDE = 4161048; public static final int LEGENDS_GUIDE = 4161048;
public static final int NOBLESSE_GUIDE = 4161047; public static final int NOBLESSE_GUIDE = 4161047;
public static final int SWORD = 1302000; // Weapon
public static final int HAND_AXE = 1312004;
public static final int WOODEN_CLUB = 1322005;
public static final int BASIC_POLEARM = 1442079;
public static final int WHITE_UNDERSHIRT = 1040002; // Top
public static final int UNDERSHIRT = 1040006;
public static final int GREY_TSHIRT = 1040010;
public static final int WHITE_TUBETOP = 1041002;
public static final int YELLOW_TSHIRT = 1041006;
public static final int GREEN_TSHIRT = 1041010;
public static final int RED_STRIPED_TOP = 1041011;
public static final int SIMPLE_WARRIOR_TOP = 1042167;
public static final int BLUE_JEAN_SHORTS = 1060002; // Bottom
public static final int BROWN_COTTON_SHORTS = 1060006;
public static final int RED_MINISKIRT = 1061002;
public static final int INDIGO_MINISKIRT = 1061008;
public static final int SIMPLE_WARRIOR_PANTS = 1062115;
public static final int RED_RUBBER_BOOTS = 1072001;
public static final int LEATHER_SANDALS = 1072005;
public static final int YELLOW_RUBBER_BOOTS = 1072037;
public static final int BLUE_RUBBER_BOOTS = 1072038;
public static final int AVERAGE_MUSASHI_SHOES = 1072383;
public static final int BLACK_TOBEN = 30000; // Hair
public static final int ZETA = 30010;
public static final int BLACK_REBEL = 30020;
public static final int BLACK_BUZZ = 30030;
public static final int BLACK_SAMMY = 31000;
public static final int BLACK_EDGY = 31040;
public static final int BLACK_CONNIE = 31050;
public static final int MOTIVATED_LOOK_M = 20000; // Face
public static final int PERPLEXED_STARE = 20001;
public static final int LEISURE_LOOK_M = 20002;
public static final int MOTIVATED_LOOK_F = 21000;
public static final int FEARFUL_STARE_M = 21001;
public static final int LEISURE_LOOK_F = 21002;
public static final int FEARFUL_STARE_F = 21201;
public static final int PERPLEXED_STARE_HAZEL = 20401;
public static final int LEISURE_LOOK_HAZEL = 20402;
public static final int MOTIVATED_LOOK_AMETHYST = 21700;
public static final int MOTIVATED_LOOK_BLUE = 20100;
// Warrior // Warrior
public static final int RED_HWARANG_SHIRT = 1040021; public static final int RED_HWARANG_SHIRT = 1040021;
@@ -211,7 +171,6 @@ public class ItemId {
public static final int EYEDROP = 2050001; public static final int EYEDROP = 2050001;
public static final int TONIC = 2050002; public static final int TONIC = 2050002;
public static final int HOLY_WATER = 2050003; 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 DOJO_PARTY_ALL_CURE = 2022433;
private static final int CARNIVAL_PARTY_ALL_CURE = 2022163; private static final int CARNIVAL_PARTY_ALL_CURE = 2022163;
public static final int WHITE_ELIXIR = 2022544; public static final int WHITE_ELIXIR = 2022544;
@@ -362,6 +321,10 @@ public class ItemId {
return itemId == NX_CARD_100 || itemId == NX_CARD_250; return itemId == NX_CARD_100 || itemId == NX_CARD_250;
} }
public static boolean isCashPackage(int itemId) {
return itemId / 10000 == 910;
}
// Face expression // Face expression
private static final int FACE_EXPRESSION_MIN = 5160000; private static final int FACE_EXPRESSION_MIN = 5160000;
private static final int FACE_EXPRESSION_MAX = 5160014; private static final int FACE_EXPRESSION_MAX = 5160014;
@@ -134,11 +134,7 @@ public final class ItemConstants {
} }
public static boolean isTownScroll(int itemId) { public static boolean isTownScroll(int itemId) {
return itemId >= 2030000 && itemId < ItemId.ANTI_BANISH_SCROLL; return itemId >= 2030000;
}
public static boolean isAntibanishScroll(int itemId) {
return itemId == ItemId.ANTI_BANISH_SCROLL;
} }
public static boolean isCleanSlate(int scrollId) { public static boolean isCleanSlate(int scrollId) {
+165 -2
View File
@@ -23,11 +23,174 @@ package net;
import net.netty.LoginServer; import net.netty.LoginServer;
import net.opcodes.RecvOpcode; import net.opcodes.RecvOpcode;
import net.server.channel.handlers.*; import net.server.channel.handlers.AcceptFamilyHandler;
import net.server.channel.handlers.AdminChatHandler;
import net.server.channel.handlers.AdminCommandHandler;
import net.server.channel.handlers.AdminLogHandler;
import net.server.channel.handlers.AllianceOperationHandler;
import net.server.channel.handlers.AranComboHandler;
import net.server.channel.handlers.AutoAggroHandler;
import net.server.channel.handlers.AutoAssignHandler;
import net.server.channel.handlers.BBSOperationHandler;
import net.server.channel.handlers.BeholderHandler;
import net.server.channel.handlers.BuddylistModifyHandler;
import net.server.channel.handlers.CancelBuffHandler;
import net.server.channel.handlers.CancelChairHandler;
import net.server.channel.handlers.CancelDebuffHandler;
import net.server.channel.handlers.CancelItemEffectHandler;
import net.server.channel.handlers.CashOperationHandler;
import net.server.channel.handlers.CashShopSurpriseHandler;
import net.server.channel.handlers.ChangeChannelHandler;
import net.server.channel.handlers.ChangeMapHandler;
import net.server.channel.handlers.ChangeMapSpecialHandler;
import net.server.channel.handlers.CharInfoRequestHandler;
import net.server.channel.handlers.ClickGuideHandler;
import net.server.channel.handlers.CloseChalkboardHandler;
import net.server.channel.handlers.CloseRangeDamageHandler;
import net.server.channel.handlers.CoconutHandler;
import net.server.channel.handlers.CouponCodeHandler;
import net.server.channel.handlers.DamageSummonHandler;
import net.server.channel.handlers.DenyAllianceRequestHandler;
import net.server.channel.handlers.DenyGuildRequestHandler;
import net.server.channel.handlers.DenyPartyRequestHandler;
import net.server.channel.handlers.DistributeAPHandler;
import net.server.channel.handlers.DistributeSPHandler;
import net.server.channel.handlers.DoorHandler;
import net.server.channel.handlers.DueyHandler;
import net.server.channel.handlers.EnterCashShopHandler;
import net.server.channel.handlers.EnterMTSHandler;
import net.server.channel.handlers.FaceExpressionHandler;
import net.server.channel.handlers.FamilyAddHandler;
import net.server.channel.handlers.FamilyPreceptsHandler;
import net.server.channel.handlers.FamilySeparateHandler;
import net.server.channel.handlers.FamilySummonResponseHandler;
import net.server.channel.handlers.FamilyUseHandler;
import net.server.channel.handlers.FieldDamageMobHandler;
import net.server.channel.handlers.FredrickHandler;
import net.server.channel.handlers.GeneralChatHandler;
import net.server.channel.handlers.GiveFameHandler;
import net.server.channel.handlers.GrenadeEffectHandler;
import net.server.channel.handlers.GuildOperationHandler;
import net.server.channel.handlers.HealOvertimeHandler;
import net.server.channel.handlers.HiredMerchantRequest;
import net.server.channel.handlers.InnerPortalHandler;
import net.server.channel.handlers.InventoryMergeHandler;
import net.server.channel.handlers.InventorySortHandler;
import net.server.channel.handlers.ItemMoveHandler;
import net.server.channel.handlers.ItemPickupHandler;
import net.server.channel.handlers.ItemRewardHandler;
import net.server.channel.handlers.KeymapChangeHandler;
import net.server.channel.handlers.LeftKnockbackHandler;
import net.server.channel.handlers.MTSHandler;
import net.server.channel.handlers.MagicDamageHandler;
import net.server.channel.handlers.MakerSkillHandler;
import net.server.channel.handlers.MesoDropHandler;
import net.server.channel.handlers.MessengerHandler;
import net.server.channel.handlers.MobBanishPlayerHandler;
import net.server.channel.handlers.MobDamageMobFriendlyHandler;
import net.server.channel.handlers.MobDamageMobHandler;
import net.server.channel.handlers.MonsterBombHandler;
import net.server.channel.handlers.MonsterBookCoverHandler;
import net.server.channel.handlers.MonsterCarnivalHandler;
import net.server.channel.handlers.MoveDragonHandler;
import net.server.channel.handlers.MoveLifeHandler;
import net.server.channel.handlers.MovePetHandler;
import net.server.channel.handlers.MovePlayerHandler;
import net.server.channel.handlers.MoveSummonHandler;
import net.server.channel.handlers.MultiChatHandler;
import net.server.channel.handlers.NPCAnimationHandler;
import net.server.channel.handlers.NPCMoreTalkHandler;
import net.server.channel.handlers.NPCShopHandler;
import net.server.channel.handlers.NPCTalkHandler;
import net.server.channel.handlers.NewYearCardHandler;
import net.server.channel.handlers.NoteActionHandler;
import net.server.channel.handlers.OpenFamilyHandler;
import net.server.channel.handlers.OpenFamilyPedigreeHandler;
import net.server.channel.handlers.OwlWarpHandler;
import net.server.channel.handlers.PartyOperationHandler;
import net.server.channel.handlers.PartySearchRegisterHandler;
import net.server.channel.handlers.PartySearchStartHandler;
import net.server.channel.handlers.PartySearchUpdateHandler;
import net.server.channel.handlers.PetAutoPotHandler;
import net.server.channel.handlers.PetChatHandler;
import net.server.channel.handlers.PetCommandHandler;
import net.server.channel.handlers.PetExcludeItemsHandler;
import net.server.channel.handlers.PetFoodHandler;
import net.server.channel.handlers.PetLootHandler;
import net.server.channel.handlers.PlayerInteractionHandler;
import net.server.channel.handlers.PlayerLoggedinHandler;
import net.server.channel.handlers.PlayerMapTransitionHandler;
import net.server.channel.handlers.QuestActionHandler;
import net.server.channel.handlers.QuickslotKeyMappedModifiedHandler;
import net.server.channel.handlers.RPSActionHandler;
import net.server.channel.handlers.RaiseIncExpHandler;
import net.server.channel.handlers.RaiseUIStateHandler;
import net.server.channel.handlers.RangedAttackHandler;
import net.server.channel.handlers.ReactorHitHandler;
import net.server.channel.handlers.RemoteGachaponHandler;
import net.server.channel.handlers.RemoteStoreHandler;
import net.server.channel.handlers.ReportHandler;
import net.server.channel.handlers.RingActionHandler;
import net.server.channel.handlers.ScriptedItemHandler;
import net.server.channel.handlers.ScrollHandler;
import net.server.channel.handlers.SkillBookHandler;
import net.server.channel.handlers.SkillEffectHandler;
import net.server.channel.handlers.SkillMacroHandler;
import net.server.channel.handlers.SnowballHandler;
import net.server.channel.handlers.SpawnPetHandler;
import net.server.channel.handlers.SpecialMoveHandler;
import net.server.channel.handlers.SpouseChatHandler;
import net.server.channel.handlers.StorageHandler;
import net.server.channel.handlers.SummonDamageHandler;
import net.server.channel.handlers.TakeDamageHandler;
import net.server.channel.handlers.TouchMonsterDamageHandler;
import net.server.channel.handlers.TouchReactorHandler;
import net.server.channel.handlers.TouchingCashShopHandler;
import net.server.channel.handlers.TransferNameHandler;
import net.server.channel.handlers.TransferNameResultHandler;
import net.server.channel.handlers.TransferWorldHandler;
import net.server.channel.handlers.TrockAddMapHandler;
import net.server.channel.handlers.UseCashItemHandler;
import net.server.channel.handlers.UseCatchItemHandler;
import net.server.channel.handlers.UseChairHandler;
import net.server.channel.handlers.UseDeathItemHandler;
import net.server.channel.handlers.UseGachaExpHandler;
import net.server.channel.handlers.UseHammerHandler;
import net.server.channel.handlers.UseItemEffectHandler;
import net.server.channel.handlers.UseItemHandler;
import net.server.channel.handlers.UseMapleLifeHandler;
import net.server.channel.handlers.UseMountFoodHandler;
import net.server.channel.handlers.UseOwlOfMinervaHandler;
import net.server.channel.handlers.UseSolomonHandler;
import net.server.channel.handlers.UseSummonBagHandler;
import net.server.channel.handlers.UseWaterOfLifeHandler;
import net.server.channel.handlers.WeddingHandler;
import net.server.channel.handlers.WeddingTalkHandler;
import net.server.channel.handlers.WeddingTalkMoreHandler;
import net.server.channel.handlers.WhisperHandler;
import net.server.handlers.CustomPacketHandler; import net.server.handlers.CustomPacketHandler;
import net.server.handlers.KeepAliveHandler; import net.server.handlers.KeepAliveHandler;
import net.server.handlers.LoginRequiringNoOpHandler; import net.server.handlers.LoginRequiringNoOpHandler;
import net.server.handlers.login.*; import net.server.handlers.login.AcceptToSHandler;
import net.server.handlers.login.AfterLoginHandler;
import net.server.handlers.login.CharSelectedHandler;
import net.server.handlers.login.CharSelectedWithPicHandler;
import net.server.handlers.login.CharlistRequestHandler;
import net.server.handlers.login.CheckCharNameHandler;
import net.server.handlers.login.CreateCharHandler;
import net.server.handlers.login.DeleteCharHandler;
import net.server.handlers.login.GuestLoginHandler;
import net.server.handlers.login.LoginPasswordHandler;
import net.server.handlers.login.RegisterPicHandler;
import net.server.handlers.login.RegisterPinHandler;
import net.server.handlers.login.RelogRequestHandler;
import net.server.handlers.login.ServerStatusRequestHandler;
import net.server.handlers.login.ServerlistRequestHandler;
import net.server.handlers.login.SetGenderHandler;
import net.server.handlers.login.ViewAllCharHandler;
import net.server.handlers.login.ViewAllCharRegisterPicHandler;
import net.server.handlers.login.ViewAllCharSelectedHandler;
import net.server.handlers.login.ViewAllCharSelectedWithPicHandler;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -82,6 +82,11 @@ public class ByteBufInPacket implements InPacket {
return byteBuf.readerIndex(); return byteBuf.readerIndex();
} }
@Override
public boolean equals(Object o) {
return o instanceof ByteBufInPacket other && byteBuf.equals(other.byteBuf);
}
@Override @Override
public String toString() { public String toString() {
final int readerIndex = byteBuf.readerIndex(); final int readerIndex = byteBuf.readerIndex();
@@ -91,4 +91,9 @@ public class ByteBufOutPacket implements OutPacket {
public void skip(int numberOfBytes) { public void skip(int numberOfBytes) {
writeBytes(new byte[numberOfBytes]); writeBytes(new byte[numberOfBytes]);
} }
@Override
public boolean equals(Object o) {
return o instanceof ByteBufOutPacket other && byteBuf.equals(other.byteBuf);
}
} }
+5 -1
View File
@@ -25,7 +25,11 @@ import client.Character;
import client.Client; import client.Client;
import service.TransitionService; import service.TransitionService;
import java.util.*; import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
+28 -3
View File
@@ -63,7 +63,17 @@ import net.server.coordinator.session.SessionCoordinator;
import net.server.guild.Alliance; import net.server.guild.Alliance;
import net.server.guild.Guild; import net.server.guild.Guild;
import net.server.guild.GuildCharacter; import net.server.guild.GuildCharacter;
import net.server.task.*; import net.server.task.BossLogTask;
import net.server.task.CharacterDiseaseTask;
import net.server.task.CouponTask;
import net.server.task.DueyFredrickTask;
import net.server.task.EventRecallCoordinatorTask;
import net.server.task.InvitationTask;
import net.server.task.LoginCoordinatorTask;
import net.server.task.LoginStorageTask;
import net.server.task.RankingCommandTask;
import net.server.task.RankingLoginTask;
import net.server.task.RespawnTask;
import net.server.world.World; import net.server.world.World;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -88,8 +98,20 @@ import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.*; import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.SortedMap;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
@@ -99,7 +121,10 @@ import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import static java.util.concurrent.TimeUnit.*; import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
public class Server { public class Server {
private static final Logger log = LoggerFactory.getLogger(Server.class); private static final Logger log = LoggerFactory.getLogger(Server.class);
+18 -3
View File
@@ -43,7 +43,11 @@ import server.TimerManager;
import server.events.gm.Event; import server.events.gm.Event;
import server.expeditions.Expedition; import server.expeditions.Expedition;
import server.expeditions.ExpeditionType; import server.expeditions.ExpeditionType;
import server.maps.*; import server.maps.HiredMerchant;
import server.maps.MapManager;
import server.maps.MapleMap;
import server.maps.MiniDungeon;
import server.maps.MiniDungeonInfo;
import service.TransitionService; import service.TransitionService;
import tools.PacketCreator; import tools.PacketCreator;
import tools.Pair; import tools.Pair;
@@ -52,14 +56,25 @@ import java.io.IOException;
import java.nio.file.DirectoryStream; import java.nio.file.DirectoryStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.*; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import static java.util.concurrent.TimeUnit.*; import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
public final class Channel { public final class Channel {
private static final Logger log = LoggerFactory.getLogger(Channel.class); private static final Logger log = LoggerFactory.getLogger(Channel.class);
@@ -42,7 +42,6 @@ import server.TimerManager;
import server.life.*; import server.life.*;
import server.maps.MapItem; import server.maps.MapItem;
import server.maps.MapObject; import server.maps.MapObject;
import server.maps.MapObjectType;
import server.maps.MapleMap; import server.maps.MapleMap;
import service.BanService; import service.BanService;
import tools.PacketCreator; import tools.PacketCreator;
@@ -58,6 +57,8 @@ import static java.util.concurrent.TimeUnit.SECONDS;
public abstract class AbstractDealDamageHandler extends AbstractPacketHandler { public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
private final DropProvider dropProvider; private final DropProvider dropProvider;
private final BanService banService; private final BanService banService;
private static final int EXPLODED_MESO_SPREAD_DELAY = 100;
private static final int EXPLODED_MESO_MAX_DELAY = 1000;
public AbstractDealDamageHandler(DropProvider dropProvider, BanService banService) { public AbstractDealDamageHandler(DropProvider dropProvider, BanService banService) {
this.dropProvider = dropProvider; this.dropProvider = dropProvider;
@@ -67,10 +68,12 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
public class AttackInfo { public class AttackInfo {
public int numAttacked, numDamage, numAttackedAndDamage, skill, skilllevel, stance, direction, rangedirection, charge, display; 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 boolean ranged, magic;
public int speed = 4; public int speed = 4;
public Point position = new Point(); public Point position = new Point();
public List<Integer> explodedMesos;
public Short attackDelay;
public StatEffect getAttackEffect(Character chr, Skill theSkill) { public StatEffect getAttackEffect(Character chr, Skill theSkill) {
Skill mySkill = theSkill; Skill mySkill = theSkill;
@@ -96,6 +99,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) { protected void applyAttack(AttackInfo attack, final Character player, int attackCount) {
final MapleMap map = player.getMap(); final MapleMap map = player.getMap();
if (map.isOwnershipRestricted(player)) { if (map.isOwnershipRestricted(player)) {
@@ -157,50 +163,14 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
return; 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; int totDamage = 0;
if (attack.skill == ChiefBandit.MESO_EXPLOSION) { if (attack.skill == ChiefBandit.MESO_EXPLOSION) {
int delay = 0; removeExplodedMesos(map, attack);
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;
}
}
} }
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) { if (monster != null) {
double distance = player.getPosition().distanceSq(monster.getPosition()); double distance = player.getPosition().distanceSq(monster.getPosition());
double distanceToDetect = 200000.0; double distanceToDetect = 200000.0;
@@ -235,7 +205,7 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
} }
int totDamageToOneMonster = 0; 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 (attack.magic) { // thanks BHB, Alex (CanIGetaPR) for noticing no immunity status check here
if (monster.isBuffed(MonsterStatus.MAGIC_IMMUNITY)) { if (monster.isBuffed(MonsterStatus.MAGIC_IMMUNITY)) {
@@ -271,7 +241,7 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
Skill pickpocket = SkillFactory.getSkill(ChiefBandit.PICKPOCKET); Skill pickpocket = SkillFactory.getSkill(ChiefBandit.PICKPOCKET);
int picklv = (player.isGM()) ? pickpocket.getMaxLevel() : player.getSkillLevel(pickpocket); int picklv = (player.isGM()) ? pickpocket.getMaxLevel() : player.getSkillLevel(pickpocket);
if (picklv > 0) { if (picklv > 0) {
int delay = 0; short delay = 0;
final int maxmeso = player.getBuffedValue(BuffStat.PICKPOCKET); final int maxmeso = player.getBuffedValue(BuffStat.PICKPOCKET);
for (Integer eachd : onedList) { for (Integer eachd : onedList) {
eachd += Integer.MAX_VALUE; eachd += Integer.MAX_VALUE;
@@ -284,7 +254,9 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
eachdf = eachd; 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; delay += 100;
} }
} }
@@ -296,7 +268,7 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
if (steal.getEffect(player.getSkillLevel(steal)).makeChanceResult() && monster.trySteal()) { if (steal.getEffect(player.getSkillLevel(steal)).makeChanceResult() && monster.trySteal()) {
Optional<MonsterDropEntry> stolenItem = dropProvider.getRandomStealDrop(monster.getId()); Optional<MonsterDropEntry> stolenItem = dropProvider.getRandomStealDrop(monster.getId());
stolenItem.ifPresent(item -> map.dropItemsFromMonster(Collections.singletonList(item), player, monster)); stolenItem.ifPresent(item -> map.dropItemsFromMonster(Collections.singletonList(item), player, monster, attack.attackDelay));
} }
} else if (attack.skill == FPArchMage.FIRE_DEMON) { } else if (attack.skill == FPArchMage.FIRE_DEMON) {
long duration = SECONDS.toMillis(SkillFactory.getSkill(FPArchMage.FIRE_DEMON).getEffect(player.getSkillLevel(SkillFactory.getSkill(FPArchMage.FIRE_DEMON))).getDuration()); long duration = SECONDS.toMillis(SkillFactory.getSkill(FPArchMage.FIRE_DEMON).getEffect(player.getSkillLevel(SkillFactory.getSkill(FPArchMage.FIRE_DEMON))).getDuration());
@@ -413,7 +385,7 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
StatEffect mortal = mortalBlow.getEffect(skillLevel); StatEffect mortal = mortalBlow.getEffect(skillLevel);
if (monster.getHp() <= (monster.getStats().getHp() * mortal.getX()) / 100) { if (monster.getHp() <= (monster.getStats().getHp() * mortal.getX()) / 100) {
if (Randomizer.rand(1, 100) <= mortal.getY()) { 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
} }
} }
} }
@@ -479,7 +451,7 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
map.broadcastMessage(PacketCreator.damageMonster(monster.getObjectId(), totDamageToOneMonster)); 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) { if (monster.isBuffed(MonsterStatus.WEAPON_REFLECT) && !attack.magic) {
for (MobSkillId msId : monster.getSkills()) { for (MobSkillId msId : monster.getSkills()) {
@@ -506,7 +478,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; int animationTime;
if (fixedTime == 0) { if (fixedTime == 0) {
@@ -533,7 +506,7 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
ret.numAttackedAndDamage = p.readByte(); ret.numAttackedAndDamage = p.readByte();
ret.numAttacked = (ret.numAttackedAndDamage >>> 4) & 0xF; ret.numAttacked = (ret.numAttackedAndDamage >>> 4) & 0xF;
ret.numDamage = ret.numAttackedAndDamage & 0xF; ret.numDamage = ret.numAttackedAndDamage & 0xF;
ret.allDamage = new HashMap<>(); ret.targets = new HashMap<>();
ret.skill = p.readInt(); ret.skill = p.readInt();
ret.ranged = ranged; ret.ranged = ranged;
ret.magic = magic; ret.magic = magic;
@@ -556,41 +529,9 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
ret.direction = p.readByte(); ret.direction = p.readByte();
ret.stance = p.readByte(); ret.stance = p.readByte();
if (ret.skill == ChiefBandit.MESO_EXPLOSION) { if (ret.skill == ChiefBandit.MESO_EXPLOSION) {
if (ret.numAttackedAndDamage == 0) { return parseMesoExplosion(p, ret);
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;
} }
if (ranged) { if (ranged) {
p.readByte(); p.readByte();
ret.speed = p.readByte(); ret.speed = p.readByte();
@@ -622,9 +563,10 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
calcDmgMax = chr.calculateMaxBaseDamage(chr.getTotalWatk()); calcDmgMax = chr.calculateMaxBaseDamage(chr.getTotalWatk());
} }
StatEffect effect = null;
if (ret.skill != 0) { if (ret.skill != 0) {
Skill skill = SkillFactory.getSkill(ret.skill); Skill skill = SkillFactory.getSkill(ret.skill);
StatEffect effect = skill.getEffect(ret.skilllevel); effect = skill.getEffect(ret.skilllevel);
if (magic) { if (magic) {
// Since the skill is magic based, use the magic formula // Since the skill is magic based, use the magic formula
@@ -746,10 +688,12 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
} }
for (int i = 0; i < ret.numAttacked; i++) { for (int i = 0; i < ret.numAttacked; i++) {
int oid = p.readInt(); int oid = p.readInt();
p.skip(14); p.skip(4);
List<Integer> allDamageNumbers = new ArrayList<>(); Point curPos = p.readPos();
Monster monster = chr.getMap().getMonsterByOid(oid); 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) { if (chr.getBuffEffect(BuffStat.WK_CHARGE) != null) {
// Charge, so now we need to check elemental effectiveness // Charge, so now we need to check elemental effectiveness
int sourceID = chr.getBuffSource(BuffStat.WK_CHARGE); int sourceID = chr.getBuffSource(BuffStat.WK_CHARGE);
@@ -863,12 +807,22 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
damage = -Integer.MAX_VALUE + damage - 1; damage = -Integer.MAX_VALUE + damage - 1;
} }
allDamageNumbers.add(damage); if(effect != null) {
int maxattack = Math.max(effect.getBulletCount(), effect.getAttackCount());
if (shadowPartner) {
maxattack = maxattack * 2;
}
if (ret.numDamage > maxattack) {
banService.addPoint(chr, AutobanFactory.DAMAGE_HACK, "Too many lines: " + ret.numDamage + " Max lines: " + maxattack + " SID: " + ret.skill + " MobID: " + (monster != null ? monster.getId() : "null") + " Map: " + chr.getMap().getMapName() + " (" + chr.getMapId() + ")");
}
}
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) { 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); p.skip(4);
} }
ret.allDamage.put(oid, allDamageNumbers); ret.targets.put(oid, new AttackTarget(delay, damageLines));
} }
if (ret.skill == NightWalker.POISON_BOMB) { // Poison Bomb if (ret.skill == NightWalker.POISON_BOMB) { // Poison Bomb
p.skip(4); p.skip(4);
@@ -877,7 +831,67 @@ public abstract class AbstractDealDamageHandler extends AbstractPacketHandler {
return ret; return ret;
} }
private static int rand(int l, int u) { private AttackInfo parseMesoExplosion(InPacket p, AttackInfo attackInfo) {
return (int) ((Math.random() * (u - l + 1)) + l); 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();
}
}
} }
} }
@@ -26,7 +26,12 @@ import net.packet.InPacket;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import server.maps.AnimatedMapObject; import server.maps.AnimatedMapObject;
import server.movement.*; import server.movement.AbsoluteLifeMovement;
import server.movement.ChangeEquip;
import server.movement.JumpDownMovement;
import server.movement.LifeMovementFragment;
import server.movement.RelativeLifeMovement;
import server.movement.TeleportMovement;
import tools.exceptions.EmptyMovementException; import tools.exceptions.EmptyMovementException;
import java.awt.*; import java.awt.*;
@@ -141,7 +141,7 @@ public final class AdminCommandHandler extends AbstractPacketHandler {
for (int x = 0; x < amount; x++) { for (int x = 0; x < amount; x++) {
Monster monster = (Monster) monsterx.get(x); Monster monster = (Monster) monsterx.get(x);
if (monster.getId() == mobToKill) { if (monster.getId() == mobToKill) {
c.getPlayer().getMap().killMonster(monster, c.getPlayer(), true); c.getPlayer().getMap().killMonster(monster, c.getPlayer(), true, (short) 0);
} }
} }
break; break;
@@ -23,7 +23,14 @@ package net.server.channel.handlers;
import client.Client; import client.Client;
import client.SkillFactory; import client.SkillFactory;
import constants.skills.*; import constants.skills.Bishop;
import constants.skills.Bowmaster;
import constants.skills.Corsair;
import constants.skills.Evan;
import constants.skills.FPArchMage;
import constants.skills.ILArchMage;
import constants.skills.Marksman;
import constants.skills.WindArcher;
import net.AbstractPacketHandler; import net.AbstractPacketHandler;
import net.PacketHandler; import net.PacketHandler;
import net.packet.InPacket; import net.packet.InPacket;
@@ -53,4 +60,4 @@ public final class CancelBuffHandler extends AbstractPacketHandler implements Pa
break; break;
} }
} }
} }
@@ -117,7 +117,7 @@ public final class CashOperationHandler extends AbstractPacketHandler {
CashItem cItem = CashItemFactory.getItem(p.readInt()); CashItem cItem = CashItemFactory.getItem(p.readInt());
Map<String, String> recipient = Character.getCharacterFromDatabase(p.readString()); Map<String, String> recipient = Character.getCharacterFromDatabase(p.readString());
String message = p.readString(); String message = p.readString();
if (!canBuy(chr, cItem, cs.getCash(4)) || message.length() < 1 || message.length() > 73) { if (!canBuy(chr, cItem, cs.getCash(CashShop.NX_PREPAID)) || message.isEmpty() || message.length() > 73) {
c.enableCSActions(); c.enableCSActions();
return; return;
} }
@@ -405,7 +405,7 @@ public final class CashOperationHandler extends AbstractPacketHandler {
c.sendPacket(PacketCreator.showCash(c.getPlayer())); c.sendPacket(PacketCreator.showCash(c.getPlayer()));
} else if (action == 0x2E) { //name change } else if (action == 0x2E) { //name change
CashItem cItem = CashItemFactory.getItem(p.readInt()); CashItem cItem = CashItemFactory.getItem(p.readInt());
if (cItem == null || !canBuy(chr, cItem, cs.getCash(4))) { if (cItem == null || !canBuy(chr, cItem, cs.getCash(CashShop.NX_PREPAID))) {
c.sendPacket(PacketCreator.showCashShopMessage((byte) 0)); c.sendPacket(PacketCreator.showCashShopMessage((byte) 0));
c.enableCSActions(); c.enableCSActions();
return; return;
@@ -434,7 +434,7 @@ public final class CashOperationHandler extends AbstractPacketHandler {
c.enableCSActions(); c.enableCSActions();
} else if (action == 0x31) { //world transfer } else if (action == 0x31) { //world transfer
CashItem cItem = CashItemFactory.getItem(p.readInt()); CashItem cItem = CashItemFactory.getItem(p.readInt());
if (cItem == null || !canBuy(chr, cItem, cs.getCash(4))) { if (cItem == null || !canBuy(chr, cItem, cs.getCash(CashShop.NX_PREPAID))) {
c.sendPacket(PacketCreator.showCashShopMessage((byte) 0)); c.sendPacket(PacketCreator.showCashShopMessage((byte) 0));
c.enableCSActions(); c.enableCSActions();
return; return;
@@ -24,26 +24,34 @@ import client.inventory.Item;
import net.AbstractPacketHandler; import net.AbstractPacketHandler;
import net.packet.InPacket; import net.packet.InPacket;
import server.CashShop; import server.CashShop;
import server.CashShop.CashShopSurpriseResult;
import tools.PacketCreator; import tools.PacketCreator;
import tools.Pair;
import java.util.Optional;
/** /**
* @author RonanLana * @author RonanLana
* @author Ponk
*/ */
public class CashShopSurpriseHandler extends AbstractPacketHandler { public class CashShopSurpriseHandler extends AbstractPacketHandler {
@Override @Override
public final void handlePacket(InPacket p, Client c) { public final void handlePacket(InPacket p, Client c) {
CashShop cs = c.getPlayer().getCashShop(); CashShop cs = c.getPlayer().getCashShop();
if (!cs.isOpened()) {
if (cs.isOpened()) { return;
Pair<Item, Item> cssResult = cs.openCashShopSurprise();
if (cssResult != null) {
Item cssItem = cssResult.getLeft(), cssBox = cssResult.getRight();
c.sendPacket(PacketCreator.onCashGachaponOpenSuccess(c.getAccID(), cssBox.getSN(), cssBox.getQuantity(), cssItem, cssItem.getItemId(), cssItem.getQuantity(), true));
} else {
c.sendPacket(PacketCreator.onCashItemGachaponOpenFailed());
}
} }
long cashId = p.readLong();
Optional<CashShopSurpriseResult> result = cs.openCashShopSurprise(cashId);
if (result.isEmpty()) {
c.sendPacket(PacketCreator.onCashItemGachaponOpenFailed());
return;
}
Item usedCashShopSurprise = result.get().usedCashShopSurprise();
Item reward = result.get().reward();
c.sendPacket(PacketCreator.onCashGachaponOpenSuccess(c.getAccID(), usedCashShopSurprise.getCashId(),
usedCashShopSurprise.getQuantity(), reward, reward.getItemId(), reward.getQuantity(), true));
} }
} }
@@ -21,14 +21,24 @@
*/ */
package net.server.channel.handlers; package net.server.channel.handlers;
import client.BuffStat;
import client.Character; import client.Character;
import client.*; import client.Client;
import client.Job;
import client.Skill;
import client.SkillFactory;
import config.YamlConfig; import config.YamlConfig;
import constants.game.GameConstants; import constants.game.GameConstants;
import constants.id.MapId; import constants.id.MapId;
import constants.skills.*;
import database.drop.DropProvider; import database.drop.DropProvider;
import net.netty.GameViolationException; import net.netty.GameViolationException;
import constants.skills.Crusader;
import constants.skills.DawnWarrior;
import constants.skills.DragonKnight;
import constants.skills.Hero;
import constants.skills.NightWalker;
import constants.skills.Rogue;
import constants.skills.WindArcher;
import net.packet.InPacket; import net.packet.InPacket;
import server.StatEffect; import server.StatEffect;
import service.BanService; import service.BanService;
@@ -71,7 +81,9 @@ public final class CloseRangeDamageHandler extends AbstractDealDamageHandler {
c.sendPacket(PacketCreator.getEnergy("energy", chr.getDojoEnergy())); 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; int numFinisherOrbs = 0;
Integer comboBuff = chr.getBuffedValue(BuffStat.COMBO); Integer comboBuff = chr.getBuffedValue(BuffStat.COMBO);
if (GameConstants.isFinisherSkill(attack.skill)) { if (GameConstants.isFinisherSkill(attack.skill)) {
@@ -132,9 +144,9 @@ public final class CloseRangeDamageHandler extends AbstractDealDamageHandler {
} }
if (attack.numAttacked > 0 && attack.skill == DragonKnight.SACRIFICE) { if (attack.numAttacked > 0 && attack.skill == DragonKnight.SACRIFICE) {
int totDamageToOneMonster = 0; // sacrifice attacks only 1 mob with 1 attack 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()) { if (dmgIt.hasNext()) {
totDamageToOneMonster = dmgIt.next().get(0); totDamageToOneMonster = dmgIt.next().damageLines().getFirst();
} }
chr.safeAddHP(-1 * totDamageToOneMonster * attack.getAttackEffect(chr, null).getX() / 100); chr.safeAddHP(-1 * totDamageToOneMonster * attack.getAttackEffect(chr, null).getX() / 100);
@@ -42,7 +42,11 @@ import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.*; import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
/** /**
@@ -25,7 +25,6 @@ import client.Character;
import client.Client; import client.Client;
import client.inventory.Equip; import client.inventory.Equip;
import client.inventory.Item; import client.inventory.Item;
import client.processor.action.BuybackProcessor;
import config.YamlConfig; import config.YamlConfig;
import database.character.CharacterSaver; import database.character.CharacterSaver;
import net.AbstractPacketHandler; import net.AbstractPacketHandler;
@@ -52,136 +51,132 @@ public final class EnterMTSHandler extends AbstractPacketHandler {
this.chrSaver = chrSaver; this.chrSaver = chrSaver;
} }
@Override @Override
public final void handlePacket(InPacket p, Client c) { public void handlePacket(InPacket p, Client c) {
Character chr = c.getPlayer(); Character chr = c.getPlayer();
if (!chr.isAlive() && YamlConfig.config.server.USE_BUYBACK_SYSTEM) { if (!YamlConfig.config.server.USE_MTS) {
BuybackProcessor.processBuyback(c);
c.sendPacket(PacketCreator.enableActions()); c.sendPacket(PacketCreator.enableActions());
} else { return;
if (!YamlConfig.config.server.USE_MTS) { }
c.sendPacket(PacketCreator.enableActions());
return;
}
if (chr.getEventInstance() != null) { if (chr.getEventInstance() != null) {
c.sendPacket(PacketCreator.serverNotice(5, "Entering Cash Shop or MTS are disabled when registered on an event.")); c.sendPacket(PacketCreator.serverNotice(5, "Entering Cash Shop or MTS are disabled when registered on an event."));
c.sendPacket(PacketCreator.enableActions()); c.sendPacket(PacketCreator.enableActions());
return; return;
} }
if (MiniDungeonInfo.isDungeonMap(chr.getMapId())) { if (MiniDungeonInfo.isDungeonMap(chr.getMapId())) {
c.sendPacket(PacketCreator.serverNotice(5, "Changing channels or entering Cash Shop or MTS are disabled when inside a Mini-Dungeon.")); c.sendPacket(PacketCreator.serverNotice(5, "Changing channels or entering Cash Shop or MTS are disabled when inside a Mini-Dungeon."));
c.sendPacket(PacketCreator.enableActions()); c.sendPacket(PacketCreator.enableActions());
return; return;
} }
if (FieldLimit.CANNOTMIGRATE.check(chr.getMap().getFieldLimit())) { if (FieldLimit.CANNOTMIGRATE.check(chr.getMap().getFieldLimit())) {
chr.dropMessage(1, "You can't do it here in this map."); chr.dropMessage(1, "You can't do it here in this map.");
c.sendPacket(PacketCreator.enableActions()); c.sendPacket(PacketCreator.enableActions());
return; return;
} }
if (!chr.isAlive()) { if (!chr.isAlive()) {
c.sendPacket(PacketCreator.enableActions()); c.sendPacket(PacketCreator.enableActions());
return; return;
} }
if (chr.getLevel() < 10) { if (chr.getLevel() < 10) {
c.sendPacket(PacketCreator.blockedMessage2(5)); c.sendPacket(PacketCreator.blockedMessage2(5));
c.sendPacket(PacketCreator.enableActions()); c.sendPacket(PacketCreator.enableActions());
return; return;
} }
chr.closePlayerInteractions(); chr.closePlayerInteractions();
chr.closePartySearchInteractions(); chr.closePartySearchInteractions();
chr.unregisterChairBuff(); chr.unregisterChairBuff();
Server.getInstance().getPlayerBuffStorage().addBuffsToStorage(chr.getId(), chr.getAllBuffs()); Server.getInstance().getPlayerBuffStorage().addBuffsToStorage(chr.getId(), chr.getAllBuffs());
Server.getInstance().getPlayerBuffStorage().addDiseasesToStorage(chr.getId(), chr.getAllDiseases()); Server.getInstance().getPlayerBuffStorage().addDiseasesToStorage(chr.getId(), chr.getAllDiseases());
chr.setAwayFromChannelWorld(); chr.setAwayFromChannelWorld();
chr.notifyMapTransferToPartner(-1); chr.notifyMapTransferToPartner(-1);
chr.removeIncomingInvites(); chr.removeIncomingInvites();
chr.cancelAllBuffs(true); chr.cancelAllBuffs(true);
chr.cancelAllDebuffs(); chr.cancelAllDebuffs();
chr.cancelBuffExpireTask(); chr.cancelBuffExpireTask();
chr.cancelDiseaseExpireTask(); chr.cancelDiseaseExpireTask();
chr.cancelSkillCooldownTask(); chr.cancelSkillCooldownTask();
chr.cancelExpirationTask(); chr.cancelExpirationTask();
chr.forfeitExpirableQuests(); chr.forfeitExpirableQuests();
chr.cancelQuestExpirationTask(); chr.cancelQuestExpirationTask();
chrSaver.save(chr); chrSaver.save(chr);
c.getChannelServer().removePlayer(chr); c.getChannelServer().removePlayer(chr);
chr.getMap().removePlayer(c.getPlayer()); chr.getMap().removePlayer(c.getPlayer());
try { try {
c.sendPacket(PacketCreator.openCashShop(c, true)); c.sendPacket(PacketCreator.openCashShop(c, true));
} catch (Exception ex) { } catch (Exception ex) {
ex.printStackTrace(); ex.printStackTrace();
}
chr.getCashShop().open(true);// xD
c.enableCSActions();
c.sendPacket(PacketCreator.MTSWantedListingOver(0, 0));
c.sendPacket(PacketCreator.showMTSCash(c.getPlayer()));
List<MTSItemInfo> items = new ArrayList<>();
int pages = 0;
try (Connection con = DatabaseConnection.getConnection()) {
try (PreparedStatement ps = con.prepareStatement("SELECT * FROM mts_items WHERE tab = 1 AND transfer = 0 ORDER BY id DESC LIMIT 16, 16");
ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
if (rs.getInt("type") != 1) {
Item i = new Item(rs.getInt("itemid"), (short) 0, (short) rs.getInt("quantity"));
i.setOwner(rs.getString("owner"));
items.add(new MTSItemInfo(i, rs.getInt("price") + 100 + (int) (rs.getInt("price") * 0.1), rs.getInt("id"), rs.getInt("seller"), rs.getString("sellername"), rs.getString("sell_ends")));
} else {
Equip equip = new Equip(rs.getInt("itemid"), (byte) rs.getInt("position"), -1);
equip.setOwner(rs.getString("owner"));
equip.setQuantity((short) 1);
equip.setAcc((short) rs.getInt("acc"));
equip.setAvoid((short) rs.getInt("avoid"));
equip.setDex((short) rs.getInt("dex"));
equip.setHands((short) rs.getInt("hands"));
equip.setHp((short) rs.getInt("hp"));
equip.setInt((short) rs.getInt("int"));
equip.setJump((short) rs.getInt("jump"));
equip.setVicious((short) rs.getInt("vicious"));
equip.setFlag((short) rs.getInt("flag"));
equip.setLuk((short) rs.getInt("luk"));
equip.setMatk((short) rs.getInt("matk"));
equip.setMdef((short) rs.getInt("mdef"));
equip.setMp((short) rs.getInt("mp"));
equip.setSpeed((short) rs.getInt("speed"));
equip.setStr((short) rs.getInt("str"));
equip.setWatk((short) rs.getInt("watk"));
equip.setWdef((short) rs.getInt("wdef"));
equip.setUpgradeSlots((byte) rs.getInt("upgradeslots"));
equip.setLevel((byte) rs.getInt("level"));
equip.setItemLevel(rs.getByte("itemlevel"));
equip.setItemExp(rs.getInt("itemexp"));
equip.setRingId(rs.getInt("ringid"));
equip.setExpiration(rs.getLong("expiration"));
equip.setGiftFrom(rs.getString("giftFrom"));
items.add(new MTSItemInfo(equip, rs.getInt("price") + 100 + (int) (rs.getInt("price") * 0.1), rs.getInt("id"), rs.getInt("seller"), rs.getString("sellername"), rs.getString("sell_ends")));
}
}
}
try (PreparedStatement ps = con.prepareStatement("SELECT COUNT(*) FROM mts_items");
ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
pages = (int) Math.ceil(rs.getInt(1) / 16);
}
}
} catch (SQLException e) {
e.printStackTrace();
}
c.sendPacket(PacketCreator.sendMTS(items, 1, 0, 0, pages));
c.sendPacket(PacketCreator.transferInventory(getTransfer(chr.getId())));
c.sendPacket(PacketCreator.notYetSoldInv(getNotYetSold(chr.getId())));
} }
chr.getCashShop().open(true);// xD
c.enableCSActions();
c.sendPacket(PacketCreator.MTSWantedListingOver(0, 0));
c.sendPacket(PacketCreator.showMTSCash(c.getPlayer()));
List<MTSItemInfo> items = new ArrayList<>();
int pages = 0;
try (Connection con = DatabaseConnection.getConnection()) {
try (PreparedStatement ps = con.prepareStatement("SELECT * FROM mts_items WHERE tab = 1 AND transfer = 0 ORDER BY id DESC LIMIT 16, 16");
ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
if (rs.getInt("type") != 1) {
Item i = new Item(rs.getInt("itemid"), (short) 0, (short) rs.getInt("quantity"));
i.setOwner(rs.getString("owner"));
items.add(new MTSItemInfo(i, rs.getInt("price") + 100 + (int) (rs.getInt("price") * 0.1), rs.getInt("id"), rs.getInt("seller"), rs.getString("sellername"), rs.getString("sell_ends")));
} else {
Equip equip = new Equip(rs.getInt("itemid"), (byte) rs.getInt("position"), -1);
equip.setOwner(rs.getString("owner"));
equip.setQuantity((short) 1);
equip.setAcc((short) rs.getInt("acc"));
equip.setAvoid((short) rs.getInt("avoid"));
equip.setDex((short) rs.getInt("dex"));
equip.setHands((short) rs.getInt("hands"));
equip.setHp((short) rs.getInt("hp"));
equip.setInt((short) rs.getInt("int"));
equip.setJump((short) rs.getInt("jump"));
equip.setVicious((short) rs.getInt("vicious"));
equip.setFlag((short) rs.getInt("flag"));
equip.setLuk((short) rs.getInt("luk"));
equip.setMatk((short) rs.getInt("matk"));
equip.setMdef((short) rs.getInt("mdef"));
equip.setMp((short) rs.getInt("mp"));
equip.setSpeed((short) rs.getInt("speed"));
equip.setStr((short) rs.getInt("str"));
equip.setWatk((short) rs.getInt("watk"));
equip.setWdef((short) rs.getInt("wdef"));
equip.setUpgradeSlots((byte) rs.getInt("upgradeslots"));
equip.setLevel((byte) rs.getInt("level"));
equip.setItemLevel(rs.getByte("itemlevel"));
equip.setItemExp(rs.getInt("itemexp"));
equip.setRingId(rs.getInt("ringid"));
equip.setExpiration(rs.getLong("expiration"));
equip.setGiftFrom(rs.getString("giftFrom"));
items.add(new MTSItemInfo(equip, rs.getInt("price") + 100 + (int) (rs.getInt("price") * 0.1), rs.getInt("id"), rs.getInt("seller"), rs.getString("sellername"), rs.getString("sell_ends")));
}
}
}
try (PreparedStatement ps = con.prepareStatement("SELECT COUNT(*) FROM mts_items");
ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
pages = (int) Math.ceil(rs.getInt(1) / 16);
}
}
} catch (SQLException e) {
e.printStackTrace();
}
c.sendPacket(PacketCreator.sendMTS(items, 1, 0, 0, pages));
c.sendPacket(PacketCreator.transferInventory(getTransfer(chr.getId())));
c.sendPacket(PacketCreator.notYetSoldInv(getNotYetSold(chr.getId())));
} }
private List<MTSItemInfo> getNotYetSold(int cid) { private List<MTSItemInfo> getNotYetSold(int cid) {
@@ -23,7 +23,11 @@ package net.server.channel.handlers;
import client.Character; import client.Character;
import client.Client; import client.Client;
import client.inventory.*; import client.inventory.Equip;
import client.inventory.Inventory;
import client.inventory.InventoryType;
import client.inventory.Item;
import client.inventory.ModifyInventory;
import config.YamlConfig; import config.YamlConfig;
import net.AbstractPacketHandler; import net.AbstractPacketHandler;
import net.netty.GameViolationException; import net.netty.GameViolationException;
@@ -35,6 +35,7 @@ import net.server.Server;
import net.server.channel.Channel; import net.server.channel.Channel;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import server.CashShop;
import server.ItemInformationProvider; import server.ItemInformationProvider;
import server.MTSItemInfo; import server.MTSItemInfo;
import tools.DatabaseConnection; import tools.DatabaseConnection;
@@ -48,7 +49,6 @@ import java.sql.SQLException;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar;
import java.util.List; import java.util.List;
public final class MTSHandler extends AbstractPacketHandler { public final class MTSHandler extends AbstractPacketHandler {
@@ -403,7 +403,7 @@ public final class MTSHandler extends AbstractPacketHandler {
ResultSet rs = ps.executeQuery(); ResultSet rs = ps.executeQuery();
if (rs.next()) { if (rs.next()) {
int price = rs.getInt("price") + 100 + (int) (rs.getInt("price") * 0.1); // taxes int price = rs.getInt("price") + 100 + (int) (rs.getInt("price") * 0.1); // taxes
if (c.getPlayer().getCashShop().getCash(4) >= price) { // FIX if (c.getPlayer().getCashShop().getCash(CashShop.NX_PREPAID) >= price) { // FIX
boolean alwaysnull = true; boolean alwaysnull = true;
for (Channel cserv : Server.getInstance().getAllChannels()) { for (Channel cserv : Server.getInstance().getAllChannels()) {
Character victim = cserv.getPlayerStorage().getCharacterById(rs.getInt("seller")); Character victim = cserv.getPlayerStorage().getCharacterById(rs.getInt("seller"));
@@ -460,11 +460,11 @@ public final class MTSHandler extends AbstractPacketHandler {
ResultSet rs = ps.executeQuery(); ResultSet rs = ps.executeQuery();
if (rs.next()) { if (rs.next()) {
int price = rs.getInt("price") + 100 + (int) (rs.getInt("price") * 0.1); int price = rs.getInt("price") + 100 + (int) (rs.getInt("price") * 0.1);
if (c.getPlayer().getCashShop().getCash(4) >= price) { if (c.getPlayer().getCashShop().getCash(CashShop.NX_PREPAID) >= price) {
for (Channel cserv : Server.getInstance().getAllChannels()) { for (Channel cserv : Server.getInstance().getAllChannels()) {
Character victim = cserv.getPlayerStorage().getCharacterById(rs.getInt("seller")); Character victim = cserv.getPlayerStorage().getCharacterById(rs.getInt("seller"));
if (victim != null) { if (victim != null) {
victim.getCashShop().gainCash(4, rs.getInt("price")); victim.getCashShop().gainCash(CashShop.NX_PREPAID, rs.getInt("price"));
} else { } else {
try (PreparedStatement pse = con.prepareStatement("SELECT accountid FROM characters WHERE id = ?")) { try (PreparedStatement pse = con.prepareStatement("SELECT accountid FROM characters WHERE id = ?")) {
pse.setInt(1, rs.getInt("seller")); pse.setInt(1, rs.getInt("seller"));
@@ -21,8 +21,11 @@
*/ */
package net.server.channel.handlers; package net.server.channel.handlers;
import client.BuffStat;
import client.Character; import client.Character;
import client.*; import client.Client;
import client.Skill;
import client.SkillFactory;
import config.YamlConfig; import config.YamlConfig;
import constants.id.MapId; import constants.id.MapId;
import constants.skills.Bishop; import constants.skills.Bishop;
@@ -68,7 +71,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; 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); chr.getMap().broadcastMessage(chr, packet, false, true);
StatEffect effect = attack.getAttackEffect(chr, null); StatEffect effect = attack.getAttackEffect(chr, null);
@@ -86,8 +90,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 Skill eaterSkill = SkillFactory.getSkill((chr.getJob().getId() - (chr.getJob().getId() % 10)) * 10000);// MP Eater, works with right job
int eaterLevel = chr.getSkillLevel(eaterSkill); int eaterLevel = chr.getSkillLevel(eaterSkill);
if (eaterLevel > 0) { if (eaterLevel > 0) {
for (Integer singleDamage : attack.allDamage.keySet()) { for (Integer oid : attack.targets.keySet()) {
eaterSkill.getEffect(eaterLevel).applyPassive(chr, chr.getMap().getMapObject(singleDamage), 0); eaterSkill.getEffect(eaterLevel).applyPassive(chr, chr.getMap().getMapObject(oid), 0);
} }
} }
} }
@@ -67,7 +67,8 @@ public final class MesoDropHandler extends AbstractPacketHandler {
if (player.attemptCatchFish(meso)) { if (player.attemptCatchFish(meso)) {
player.getMap().disappearingMesoDrop(meso, player, player, player.getPosition()); player.getMap().disappearingMesoDrop(meso, player, player, player.getPosition());
} else { } else {
player.getMap().spawnMesoDrop(meso, player.getPosition(), player, player, true, (byte) 2); player.getMap().spawnMesoDrop(meso, player.getPosition(), player, player, true, (byte) 2,
(short) 0);
} }
} }
} }
@@ -23,23 +23,25 @@ import client.Character;
import client.Client; import client.Client;
import net.AbstractPacketHandler; import net.AbstractPacketHandler;
import net.packet.InPacket; import net.packet.InPacket;
import server.life.LifeFactory.BanishInfo; import server.life.BanishInfo;
import server.life.Monster; import server.life.Monster;
public final class MobBanishPlayerHandler extends AbstractPacketHandler { public final class MobBanishPlayerHandler extends AbstractPacketHandler {
@Override @Override
public final void handlePacket(InPacket p, Client c) { public void handlePacket(InPacket p, Client c) {
int mobid = p.readInt(); // mob banish handling detected thanks to MedicOP int mobId = p.readInt(); // mob banish handling detected thanks to MedicOP
Character chr = c.getPlayer(); Character chr = c.getPlayer();
Monster mob = chr.getMap().getMonsterById(mobid); Monster mob = chr.getMap().getMonsterById(mobId);
if (mob == null) {
if (mob != null) { return;
BanishInfo banishInfo = mob.getBanish();
if (banishInfo != null) {
chr.changeMapBanish(banishInfo.getMap(), banishInfo.getPortal(), banishInfo.getMsg());
}
} }
BanishInfo banishInfo = mob.getBanish();
if (banishInfo == null) {
return;
}
chr.changeMapBanish(banishInfo);
} }
} }
@@ -57,21 +57,24 @@ public final class MobDamageMobHandler extends AbstractPacketHandler {
Monster attacker = map.getMonsterByOid(from); Monster attacker = map.getMonsterByOid(from);
Monster damaged = map.getMonsterByOid(to); Monster damaged = map.getMonsterByOid(to);
if (attacker != null && damaged != null) { if (attacker == null || damaged == null) {
int maxDmg = calcMaxDamage(attacker, damaged, magic); // thanks Darter (YungMoozi) for reporting unchecked dmg return;
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);
} }
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) { private static int calcMaxDamage(Monster attacker, Monster damaged, boolean magic) {
@@ -27,7 +27,12 @@ import config.YamlConfig;
import net.packet.InPacket; import net.packet.InPacket;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import server.life.*; import server.life.MobSkill;
import server.life.MobSkillFactory;
import server.life.MobSkillId;
import server.life.MobSkillType;
import server.life.Monster;
import server.life.MonsterInformationProvider;
import server.maps.MapObject; import server.maps.MapObject;
import server.maps.MapObjectType; import server.maps.MapObjectType;
import server.maps.MapleMap; import server.maps.MapleMap;
@@ -168,7 +173,7 @@ public final class MoveLifeHandler extends AbstractMovementPacketHandler {
if (banishPlayers != null) { if (banishPlayers != null) {
for (Character chr : banishPlayers) { for (Character chr : banishPlayers) {
chr.changeMapBanish(monster.getBanish().getMap(), monster.getBanish().getPortal(), monster.getBanish().getMsg()); chr.changeMapBanish(monster.getBanish());
} }
} }
} }
@@ -176,4 +181,4 @@ public final class MoveLifeHandler extends AbstractMovementPacketHandler {
private static boolean inRangeInclusive(Byte pVal, Integer pMin, Integer pMax) { private static boolean inRangeInclusive(Byte pVal, Integer pMin, Integer pMax) {
return !(pVal < pMin) || (pVal > pMax); return !(pVal < pMin) || (pVal > pMax);
} }
} }
@@ -76,8 +76,6 @@ public final class NPCTalkHandler extends AbstractPacketHandler {
NPCScriptManager.getInstance().start(c, npc.getId(), "gachapon"); NPCScriptManager.getInstance().start(c, npc.getId(), "gachapon");
} else if (npc.getName().endsWith("Maple TV")) { } else if (npc.getName().endsWith("Maple TV")) {
NPCScriptManager.getInstance().start(c, npc.getId(), "mapleTV"); NPCScriptManager.getInstance().start(c, npc.getId(), "mapleTV");
} else if (YamlConfig.config.server.USE_REBIRTH_SYSTEM && npc.getId() == YamlConfig.config.server.REBIRTH_NPC_ID) {
NPCScriptManager.getInstance().start(c, npc.getId(), "rebirth");
} else { } else {
boolean hasNpcScript = NPCScriptManager.getInstance().start(c, npc.getId(), oid, null); boolean hasNpcScript = NPCScriptManager.getInstance().start(c, npc.getId(), oid, null);
if (!hasNpcScript) { if (!hasNpcScript) {
@@ -41,8 +41,15 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import server.ItemInformationProvider; import server.ItemInformationProvider;
import server.Trade; import server.Trade;
import server.maps.*; import server.maps.FieldLimit;
import server.maps.HiredMerchant;
import server.maps.MapObject;
import server.maps.MapObjectType;
import server.maps.MiniGame;
import server.maps.MiniGame.MiniGameType; import server.maps.MiniGame.MiniGameType;
import server.maps.PlayerShop;
import server.maps.PlayerShopItem;
import server.maps.Portal;
import tools.PacketCreator; import tools.PacketCreator;
import java.awt.*; import java.awt.*;
@@ -21,9 +21,20 @@
*/ */
package net.server.channel.handlers; package net.server.channel.handlers;
import client.BuddyList;
import client.BuddylistEntry;
import client.Character; import client.Character;
import client.*; import client.Client;
import client.inventory.*; import client.Disease;
import client.Family;
import client.FamilyEntry;
import client.Mount;
import client.SkillFactory;
import client.inventory.Equip;
import client.inventory.Inventory;
import client.inventory.InventoryType;
import client.inventory.Item;
import client.inventory.Pet;
import client.keybind.KeyBinding; import client.keybind.KeyBinding;
import config.YamlConfig; import config.YamlConfig;
import constants.game.GameConstants; import constants.game.GameConstants;
@@ -59,8 +70,14 @@ import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.*; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public final class PlayerLoggedinHandler extends AbstractPacketHandler { public final class PlayerLoggedinHandler extends AbstractPacketHandler {
@@ -423,11 +440,6 @@ public final class PlayerLoggedinHandler extends AbstractPacketHandler {
Entry::getValue 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)); c.sendPacket(PacketCreator.setNPCScriptable(npcsIds));
} }
@@ -21,8 +21,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package net.server.channel.handlers; package net.server.channel.handlers;
import client.BuffStat;
import client.Character; import client.Character;
import client.*; import client.Client;
import client.Skill;
import client.SkillFactory;
import client.inventory.Inventory; import client.inventory.Inventory;
import client.inventory.InventoryType; import client.inventory.InventoryType;
import client.inventory.Item; import client.inventory.Item;
@@ -32,7 +35,13 @@ import config.YamlConfig;
import constants.id.ItemId; import constants.id.ItemId;
import constants.id.MapId; import constants.id.MapId;
import constants.inventory.ItemConstants; import constants.inventory.ItemConstants;
import constants.skills.*; import constants.skills.Aran;
import constants.skills.Buccaneer;
import constants.skills.NightLord;
import constants.skills.NightWalker;
import constants.skills.Shadower;
import constants.skills.ThunderBreaker;
import constants.skills.WindArcher;
import database.drop.DropProvider; import database.drop.DropProvider;
import net.netty.GameViolationException; import net.netty.GameViolationException;
import net.packet.InPacket; import net.packet.InPacket;
@@ -79,17 +88,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) { 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); applyAttack(attack, chr, 1);
} else if (attack.skill == ThunderBreaker.SHARK_WAVE && chr.getSkillLevel(ThunderBreaker.SHARK_WAVE) > 0) { } 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); applyAttack(attack, chr, 1);
for (int i = 0; i < attack.numAttacked; i++) { for (int i = 0; i < attack.numAttacked; i++) {
chr.handleEnergyChargeGain(); chr.handleEnergyChargeGain();
} }
} else if (attack.skill == Aran.COMBO_SMASH || attack.skill == Aran.COMBO_FENRIR || attack.skill == Aran.COMBO_TEMPEST) { } 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) { if (attack.skill == Aran.COMBO_SMASH && chr.getCombo() >= 30) {
chr.setCombo((short) 0); chr.setCombo((short) 0);
applyAttack(attack, chr, 1); applyAttack(attack, chr, 1);
@@ -209,10 +224,14 @@ public final class RangedAttackHandler extends AbstractDealDamageHandler {
case 3221001: // Pierce case 3221001: // Pierce
case 5221004: // Rapid Fire case 5221004: // Rapid Fire
case 13111002: // KoC Hurricane 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; break;
default: 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; break;
} }
chr.getMap().broadcastMessage(chr, packet, false, true); chr.getMap().broadcastMessage(chr, packet, false, true);
@@ -25,8 +25,12 @@ import client.Character;
import client.Client; import client.Client;
import client.Skill; import client.Skill;
import client.SkillFactory; import client.SkillFactory;
import client.inventory.*; import client.inventory.Equip;
import client.inventory.Equip.ScrollResult; import client.inventory.Equip.ScrollResult;
import client.inventory.Inventory;
import client.inventory.InventoryType;
import client.inventory.Item;
import client.inventory.ModifyInventory;
import client.inventory.manipulator.InventoryManipulator; import client.inventory.manipulator.InventoryManipulator;
import constants.id.ItemId; import constants.id.ItemId;
import constants.inventory.ItemConstants; import constants.inventory.ItemConstants;
@@ -37,7 +41,6 @@ import tools.PacketCreator;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* @author Matze * @author Matze
@@ -50,8 +53,8 @@ public final class ScrollHandler extends AbstractPacketHandler {
if (c.tryacquireClient()) { if (c.tryacquireClient()) {
try { try {
p.readInt(); // whatever... p.readInt(); // whatever...
short slot = p.readShort(); short scrollSlot = p.readShort();
short dst = p.readShort(); short equipSlot = p.readShort();
byte ws = (byte) p.readShort(); byte ws = (byte) p.readShort();
boolean whiteScroll = false; // white scroll being used? boolean whiteScroll = false; // white scroll being used?
boolean legendarySpirit = false; // legendary spirit skill boolean legendarySpirit = false; // legendary spirit skill
@@ -61,24 +64,21 @@ public final class ScrollHandler extends AbstractPacketHandler {
ItemInformationProvider ii = ItemInformationProvider.getInstance(); ItemInformationProvider ii = ItemInformationProvider.getInstance();
Character chr = c.getPlayer(); Character chr = c.getPlayer();
Equip toScroll = (Equip) chr.getInventory(InventoryType.EQUIPPED).getItem(dst); Equip toScroll = (Equip) chr.getInventory(InventoryType.EQUIPPED).getItem(equipSlot);
Skill LegendarySpirit = SkillFactory.getSkill(1003); Skill LegendarySpirit = SkillFactory.getSkill(1003);
if (chr.getSkillLevel(LegendarySpirit) > 0 && dst >= 0) { if (chr.getSkillLevel(LegendarySpirit) > 0 && equipSlot >= 0) {
legendarySpirit = true; legendarySpirit = true;
toScroll = (Equip) chr.getInventory(InventoryType.EQUIP).getItem(dst); toScroll = (Equip) chr.getInventory(InventoryType.EQUIP).getItem(equipSlot);
} }
byte oldLevel = toScroll.getLevel(); byte oldLevel = toScroll.getLevel();
byte oldSlots = toScroll.getUpgradeSlots(); byte oldSlots = toScroll.getUpgradeSlots();
Inventory useInventory = chr.getInventory(InventoryType.USE); Inventory useInventory = chr.getInventory(InventoryType.USE);
Item scroll = useInventory.getItem(slot); Item scroll = useInventory.getItem(scrollSlot);
Item wscroll = null; Item wscroll = null;
if (ItemConstants.isCleanSlate(scroll.getItemId())) { if (ItemConstants.isCleanSlate(scroll.getItemId()) && !ii.canUseCleanSlate(toScroll)) {
Map<String, Integer> eqStats = ii.getEquipStats(toScroll.getItemId()); // clean slate issue found thanks to Masterrulax announceCannotScroll(c, legendarySpirit);
if (eqStats == null || eqStats.get("tuc") == 0) { return;
announceCannotScroll(c, legendarySpirit);
return;
}
} else if (!ItemConstants.isModifierScroll(scroll.getItemId()) && toScroll.getUpgradeSlots() < 1) { } else if (!ItemConstants.isModifierScroll(scroll.getItemId()) && toScroll.getUpgradeSlots() < 1) {
announceCannotScroll(c, legendarySpirit); // thanks onechord for noticing zero upgrade slots freezing Legendary Scroll UI announceCannotScroll(c, legendarySpirit); // thanks onechord for noticing zero upgrade slots freezing Legendary Scroll UI
return; return;
@@ -103,11 +103,6 @@ public final class ScrollHandler extends AbstractPacketHandler {
} }
} }
if (ItemConstants.isCleanSlate(scroll.getItemId()) && !ii.canUseCleanSlate(toScroll)) {
announceCannotScroll(c, legendarySpirit);
return;
}
Equip scrolled = (Equip) ii.scrollEquipWithId(toScroll, scroll.getItemId(), whiteScroll, 0, chr.isGM()); Equip scrolled = (Equip) ii.scrollEquipWithId(toScroll, scroll.getItemId(), whiteScroll, 0, chr.isGM());
ScrollResult scrollSuccess = Equip.ScrollResult.FAIL; // fail ScrollResult scrollSuccess = Equip.ScrollResult.FAIL; // fail
if (scrolled == null) { if (scrolled == null) {
@@ -141,7 +136,7 @@ public final class ScrollHandler extends AbstractPacketHandler {
if (scrollSuccess == Equip.ScrollResult.CURSE) { if (scrollSuccess == Equip.ScrollResult.CURSE) {
if (!ItemId.isWeddingRing(toScroll.getItemId())) { if (!ItemId.isWeddingRing(toScroll.getItemId())) {
mods.add(new ModifyInventory(3, toScroll)); mods.add(new ModifyInventory(3, toScroll));
if (dst < 0) { if (equipSlot < 0) {
Inventory inv = chr.getInventory(InventoryType.EQUIPPED); Inventory inv = chr.getInventory(InventoryType.EQUIPPED);
inv.lockInventory(); inv.lockInventory();
@@ -174,7 +169,7 @@ public final class ScrollHandler extends AbstractPacketHandler {
} }
c.sendPacket(PacketCreator.modifyInventory(true, mods)); c.sendPacket(PacketCreator.modifyInventory(true, mods));
chr.getMap().broadcastMessage(PacketCreator.getScrollEffect(chr.getId(), scrollSuccess, legendarySpirit, whiteScroll)); chr.getMap().broadcastMessage(PacketCreator.getScrollEffect(chr.getId(), scrollSuccess, legendarySpirit, whiteScroll));
if (dst < 0 && (scrollSuccess == Equip.ScrollResult.SUCCESS || scrollSuccess == Equip.ScrollResult.CURSE)) { if (equipSlot < 0 && (scrollSuccess == Equip.ScrollResult.SUCCESS || scrollSuccess == Equip.ScrollResult.CURSE)) {
chr.equipChanged(); chr.equipChanged();
} }
} finally { } finally {
@@ -22,7 +22,23 @@
package net.server.channel.handlers; package net.server.channel.handlers;
import client.Client; import client.Client;
import constants.skills.*; import constants.skills.Bishop;
import constants.skills.Bowmaster;
import constants.skills.Brawler;
import constants.skills.ChiefBandit;
import constants.skills.Corsair;
import constants.skills.DarkKnight;
import constants.skills.Evan;
import constants.skills.FPArchMage;
import constants.skills.FPMage;
import constants.skills.Gunslinger;
import constants.skills.Hero;
import constants.skills.ILArchMage;
import constants.skills.Marksman;
import constants.skills.NightWalker;
import constants.skills.Paladin;
import constants.skills.ThunderBreaker;
import constants.skills.WindArcher;
import net.AbstractPacketHandler; import net.AbstractPacketHandler;
import net.packet.InPacket; import net.packet.InPacket;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -65,4 +81,4 @@ public final class SkillEffectHandler extends AbstractPacketHandler {
return; return;
} }
} }
} }
@@ -26,7 +26,13 @@ import client.Client;
import client.Skill; import client.Skill;
import client.SkillFactory; import client.SkillFactory;
import config.YamlConfig; import config.YamlConfig;
import constants.skills.*; import constants.skills.Brawler;
import constants.skills.Corsair;
import constants.skills.DarkKnight;
import constants.skills.Hero;
import constants.skills.Paladin;
import constants.skills.Priest;
import constants.skills.SuperGM;
import net.AbstractPacketHandler; import net.AbstractPacketHandler;
import net.packet.InPacket; import net.packet.InPacket;
import net.server.Server; import net.server.Server;
@@ -153,4 +159,4 @@ public final class SpecialMoveHandler extends AbstractPacketHandler {
c.sendPacket(PacketCreator.enableActions()); c.sendPacket(PacketCreator.enableActions());
} }
} }
} }
@@ -43,6 +43,7 @@ import server.maps.Summon;
import service.BanService; import service.BanService;
import tools.PacketCreator; import tools.PacketCreator;
import java.awt.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -53,25 +54,7 @@ public final class SummonDamageHandler extends AbstractDealDamageHandler {
super(dropProvider, banService); super(dropProvider, banService);
} }
public final class SummonAttackEntry { public record SummonAttackTarget(int monsterOid, int damage, short delay) {}
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;
}
}
@Override @Override
public void handlePacket(InPacket p, Client c) { public void handlePacket(InPacket p, Client c) {
@@ -92,17 +75,21 @@ public final class SummonDamageHandler extends AbstractDealDamageHandler {
Skill summonSkill = SkillFactory.getSkill(summon.getSkill()); Skill summonSkill = SkillFactory.getSkill(summon.getSkill());
StatEffect summonEffect = summonSkill.getEffect(summon.getSkillLevel()); StatEffect summonEffect = summonSkill.getEffect(summon.getSkillLevel());
p.skip(4); p.skip(4);
List<SummonAttackEntry> allDamage = new ArrayList<>(); List<SummonAttackTarget> targets = new ArrayList<>();
byte direction = p.readByte(); byte direction = p.readByte();
int numAttacked = p.readByte(); int numAttacked = p.readByte();
p.skip(8); // I failed lol (mob x,y and summon x,y), Thanks Gerald p.skip(8); // I failed lol (mob x,y and summon x,y), Thanks Gerald
for (int x = 0; x < numAttacked; x++) { for (int x = 0; x < numAttacked; x++) {
int monsterOid = p.readInt(); // attacked oid 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(); 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)) { if (player.getMap().isOwnershipRestricted(player)) {
return; return;
@@ -110,25 +97,28 @@ public final class SummonDamageHandler extends AbstractDealDamageHandler {
boolean magic = summonEffect.getWatk() == 0; boolean magic = summonEffect.getWatk() == 0;
int maxDmg = calcMaxDamage(summonEffect, player, magic); // thanks Darter (YungMoozi) for reporting unchecked max dmg int maxDmg = calcMaxDamage(summonEffect, player, magic); // thanks Darter (YungMoozi) for reporting unchecked max dmg
for (SummonAttackEntry attackEntry : allDamage) { for (SummonAttackTarget target : targets) {
int damage = attackEntry.getDamage(); int damage = target.damage();
Monster target = player.getMap().getMonsterByOid(attackEntry.getMonsterOid()); Monster mob = player.getMap().getMonsterByOid(target.monsterOid());
if (target != null) { if (mob == null) {
if (damage > maxDmg) { continue;
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);
} }
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 if (summon.getSkill() == Outlaw.GAVIOTA) { // thanks Periwinks for noticing Gaviota not cancelling after grenade toss
@@ -21,8 +21,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package net.server.channel.handlers; package net.server.channel.handlers;
import client.BuffStat;
import client.Character; import client.Character;
import client.*; import client.Client;
import client.Skill;
import client.SkillFactory;
import client.inventory.Inventory; import client.inventory.Inventory;
import client.inventory.InventoryType; import client.inventory.InventoryType;
import client.inventory.Item; import client.inventory.Item;
@@ -39,7 +42,12 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import server.StatEffect; import server.StatEffect;
import server.life.LifeFactory.loseItem; import server.life.LifeFactory.loseItem;
import server.life.*; import server.life.MobAttackInfo;
import server.life.MobAttackInfoFactory;
import server.life.MobSkill;
import server.life.MobSkillFactory;
import server.life.MobSkillType;
import server.life.Monster;
import server.maps.MapObject; import server.maps.MapObject;
import server.maps.MapleMap; import server.maps.MapleMap;
import tools.PacketCreator; import tools.PacketCreator;
@@ -281,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 :) 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());
} }
} }
} }
@@ -28,7 +28,11 @@ import net.packet.InPacket;
import tools.DatabaseConnection; import tools.DatabaseConnection;
import tools.PacketCreator; import tools.PacketCreator;
import java.sql.*; import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Calendar; import java.util.Calendar;
import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.DAYS;
@@ -81,4 +85,4 @@ public final class TransferNameHandler extends AbstractPacketHandler {
} }
c.sendPacket(PacketCreator.sendNameTransferRules(0)); c.sendPacket(PacketCreator.sendNameTransferRules(0));
} }
} }
@@ -29,7 +29,11 @@ import net.server.Server;
import tools.DatabaseConnection; import tools.DatabaseConnection;
import tools.PacketCreator; import tools.PacketCreator;
import java.sql.*; import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
/** /**
* @author Ronan * @author Ronan
@@ -76,4 +80,4 @@ public final class TransferWorldHandler extends AbstractPacketHandler {
} }
c.sendPacket(PacketCreator.sendWorldTransferRules(0, c)); c.sendPacket(PacketCreator.sendWorldTransferRules(0, c));
} }
} }
@@ -22,10 +22,22 @@
package net.server.channel.handlers; package net.server.channel.handlers;
import client.Character; import client.Character;
import client.*; import client.Client;
import client.creator.veteran.*; import client.Skill;
import client.inventory.*; import client.SkillFactory;
import client.SkillMacro;
import client.creator.veteran.BowmanCreator;
import client.creator.veteran.MagicianCreator;
import client.creator.veteran.PirateCreator;
import client.creator.veteran.ThiefCreator;
import client.creator.veteran.WarriorCreator;
import client.inventory.Equip;
import client.inventory.Equip.ScrollResult; import client.inventory.Equip.ScrollResult;
import client.inventory.Inventory;
import client.inventory.InventoryType;
import client.inventory.Item;
import client.inventory.ModifyInventory;
import client.inventory.Pet;
import client.inventory.manipulator.InventoryManipulator; import client.inventory.manipulator.InventoryManipulator;
import client.inventory.manipulator.KarmaManipulator; import client.inventory.manipulator.KarmaManipulator;
import client.processor.npc.DueyProcessor; import client.processor.npc.DueyProcessor;
@@ -44,7 +56,12 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import server.ItemInformationProvider; import server.ItemInformationProvider;
import server.TimerManager; import server.TimerManager;
import server.maps.*; import server.maps.AbstractMapObject;
import server.maps.FieldLimit;
import server.maps.Kite;
import server.maps.MapleMap;
import server.maps.MapleTVEffect;
import server.maps.PlayerShopItem;
import server.shop.Shop; import server.shop.Shop;
import server.shop.ShopFactory; import server.shop.ShopFactory;
import service.NoteService; import service.NoteService;
@@ -61,7 +61,7 @@ public final class UseCatchItemHandler extends AbstractPacketHandler {
case ItemId.PHEROMONE_PERFUME: case ItemId.PHEROMONE_PERFUME:
if (mob.getId() == MobId.TAMABLE_HOG) { if (mob.getId() == MobId.TAMABLE_HOG) {
chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1)); 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.removeById(c, InventoryType.USE, itemId, 1, true, true);
InventoryManipulator.addById(c, ItemId.HOG, (short) 1, "", -1); 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 ((abm.getLastSpam(10) + 1000) < currentServerTime()) {
if (mob.getHp() < ((mob.getMaxHp() / 10) * 4)) { if (mob.getHp() < ((mob.getMaxHp() / 10) * 4)) {
chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1)); 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.removeById(c, InventoryType.USE, itemId, 1, true, true);
InventoryManipulator.addById(c, ItemId.GHOST_SACK, (short) 1, "", -1); InventoryManipulator.addById(c, ItemId.GHOST_SACK, (short) 1, "", -1);
} else { } else {
@@ -90,9 +90,10 @@ public final class UseCatchItemHandler extends AbstractPacketHandler {
if (chr.canHold(ItemId.ARPQ_SPIRIT_JEWEL, 1)) { if (chr.canHold(ItemId.ARPQ_SPIRIT_JEWEL, 1)) {
if (Math.random() < 0.5) { // 50% chance if (Math.random() < 0.5) { // 50% chance
chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1)); 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.removeById(c, InventoryType.USE, itemId, 1, true, true);
InventoryManipulator.addById(c, ItemId.ARPQ_SPIRIT_JEWEL, (short) 1, "", -1); InventoryManipulator.addById(c, ItemId.ARPQ_SPIRIT_JEWEL, (short) 1, "", -1);
chr.updateAriantScore();
} else { } else {
chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 0)); 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.getId() == MobId.LOST_RUDOLPH) {
if (mob.getHp() < ((mob.getMaxHp() / 10) * 4)) { if (mob.getHp() < ((mob.getMaxHp() / 10) * 4)) {
chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1)); 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.removeById(c, InventoryType.USE, itemId, 1, true, true);
InventoryManipulator.addById(c, ItemId.TAMED_RUDOLPH, (short) 1, "", -1); InventoryManipulator.addById(c, ItemId.TAMED_RUDOLPH, (short) 1, "", -1);
} else { } else {
@@ -125,7 +126,7 @@ public final class UseCatchItemHandler extends AbstractPacketHandler {
if (mob.getId() == MobId.KING_SLIME_DOJO) { if (mob.getId() == MobId.KING_SLIME_DOJO) {
if (mob.getHp() < ((mob.getMaxHp() / 10) * 3)) { if (mob.getHp() < ((mob.getMaxHp() / 10) * 3)) {
chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1)); 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.removeById(c, InventoryType.USE, itemId, 1, true, true);
InventoryManipulator.addById(c, ItemId.MONSTER_MARBLE_1, (short) 1, "", -1); InventoryManipulator.addById(c, ItemId.MONSTER_MARBLE_1, (short) 1, "", -1);
} else { } else {
@@ -138,7 +139,7 @@ public final class UseCatchItemHandler extends AbstractPacketHandler {
if (mob.getId() == MobId.FAUST_DOJO) { if (mob.getId() == MobId.FAUST_DOJO) {
if (mob.getHp() < ((mob.getMaxHp() / 10) * 3)) { if (mob.getHp() < ((mob.getMaxHp() / 10) * 3)) {
chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1)); 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.removeById(c, InventoryType.USE, itemId, 1, true, true);
InventoryManipulator.addById(c, ItemId.MONSTER_MARBLE_2, (short) 1, "", -1); InventoryManipulator.addById(c, ItemId.MONSTER_MARBLE_2, (short) 1, "", -1);
} else { } else {
@@ -151,7 +152,7 @@ public final class UseCatchItemHandler extends AbstractPacketHandler {
if (mob.getId() == MobId.MUSHMOM_DOJO) { if (mob.getId() == MobId.MUSHMOM_DOJO) {
if (mob.getHp() < ((mob.getMaxHp() / 10) * 3)) { if (mob.getHp() < ((mob.getMaxHp() / 10) * 3)) {
chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1)); 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.removeById(c, InventoryType.USE, itemId, 1, true, true);
InventoryManipulator.addById(c, ItemId.MONSTER_MARBLE_3, (short) 1, "", -1); InventoryManipulator.addById(c, ItemId.MONSTER_MARBLE_3, (short) 1, "", -1);
} else { } else {
@@ -164,7 +165,7 @@ public final class UseCatchItemHandler extends AbstractPacketHandler {
if (mob.getId() == MobId.POISON_FLOWER) { if (mob.getId() == MobId.POISON_FLOWER) {
if (mob.getHp() < ((mob.getMaxHp() / 10) * 4)) { if (mob.getHp() < ((mob.getMaxHp() / 10) * 4)) {
chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1)); 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.removeById(c, InventoryType.USE, itemId, 1, true, true);
InventoryManipulator.addById(c, ItemId.EPQ_MONSTER_MARBLE, (short) 1, "", -1); InventoryManipulator.addById(c, ItemId.EPQ_MONSTER_MARBLE, (short) 1, "", -1);
} else { } else {
@@ -178,7 +179,7 @@ public final class UseCatchItemHandler extends AbstractPacketHandler {
if ((abm.getLastSpam(10) + 3000) < currentServerTime()) { if ((abm.getLastSpam(10) + 3000) < currentServerTime()) {
abm.spam(10); abm.spam(10);
chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1)); 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.removeById(c, InventoryType.USE, itemId, 1, true, true);
InventoryManipulator.addById(c, ItemId.FISH_NET_WITH_A_CATCH, (short) 1, "", -1); InventoryManipulator.addById(c, ItemId.FISH_NET_WITH_A_CATCH, (short) 1, "", -1);
} else { } else {
@@ -201,7 +202,7 @@ public final class UseCatchItemHandler extends AbstractPacketHandler {
if (timeCatch != 0 && (abm.getLastSpam(10) + timeCatch) < currentServerTime()) { if (timeCatch != 0 && (abm.getLastSpam(10) + timeCatch) < currentServerTime()) {
if (mobHp != 0 && mob.getHp() < ((mob.getMaxHp() / 100) * mobHp)) { if (mobHp != 0 && mob.getHp() < ((mob.getMaxHp() / 100) * mobHp)) {
chr.getMap().broadcastMessage(PacketCreator.catchMonster(monsterid, itemId, (byte) 1)); 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.removeById(c, InventoryType.USE, itemId, 1, true, true);
InventoryManipulator.addById(c, itemGanho, (short) 1, "", -1); InventoryManipulator.addById(c, itemGanho, (short) 1, "", -1);
} else if (mob.getId() != MobId.P_JUNIOR) { } 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()); // System.out.println("UseCatchItemHandler: \r\n" + slea.toString());
} }
} }
private static void killMonster(Monster mob) {
mob.getMap().killMonster(mob, null, false, (short) 0);
}
} }
@@ -27,7 +27,6 @@ import client.Disease;
import client.inventory.InventoryType; import client.inventory.InventoryType;
import client.inventory.Item; import client.inventory.Item;
import client.inventory.manipulator.InventoryManipulator; import client.inventory.manipulator.InventoryManipulator;
import config.YamlConfig;
import constants.id.ItemId; import constants.id.ItemId;
import constants.inventory.ItemConstants; import constants.inventory.ItemConstants;
import net.AbstractPacketHandler; import net.AbstractPacketHandler;
@@ -73,23 +72,8 @@ public final class UseItemHandler extends AbstractPacketHandler {
remove(c, slot); remove(c, slot);
return; return;
} else if (ItemConstants.isTownScroll(itemId)) { } 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)) { if (ii.getItemEffect(toUse.getItemId()).applyTo(chr)) {
remove(c, slot); remove(c, slot);
} else {
chr.dropMessage(5, "You cannot recover from a banish state at the moment.");
} }
return; return;
} }
@@ -25,8 +25,12 @@ import net.server.Server;
import net.server.coordinator.matchchecker.MatchCheckerListenerFactory.MatchCheckerType; import net.server.coordinator.matchchecker.MatchCheckerListenerFactory.MatchCheckerType;
import net.server.world.World; import net.server.world.World;
import java.util.*; import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
/** /**
@@ -33,8 +33,14 @@ import provider.wz.WZFiles;
import tools.PacketCreator; import tools.PacketCreator;
import tools.Pair; import tools.Pair;
import java.util.*; import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -22,7 +22,11 @@ package net.server.coordinator.partysearch;
import client.Character; import client.Character;
import tools.IntervalBuilder; import tools.IntervalBuilder;
import java.util.*; import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -32,8 +32,14 @@ import tools.DatabaseConnection;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import java.time.Instant; import java.time.Instant;
import java.util.*; import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -4,7 +4,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import tools.DatabaseConnection; import tools.DatabaseConnection;
import java.sql.*; import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -27,8 +27,14 @@ import server.life.Monster;
import server.maps.MapleMap; import server.maps.MapleMap;
import tools.Pair; import tools.Pair;
import java.util.*; import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
+7 -1
View File
@@ -42,7 +42,13 @@ import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.*; import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@@ -25,44 +25,14 @@ import client.Client;
import client.creator.novice.BeginnerCreator; import client.creator.novice.BeginnerCreator;
import client.creator.novice.LegendCreator; import client.creator.novice.LegendCreator;
import client.creator.novice.NoblesseCreator; import client.creator.novice.NoblesseCreator;
import constants.id.ItemId;
import net.AbstractPacketHandler; import net.AbstractPacketHandler;
import net.netty.GameViolationException;
import net.packet.InPacket; import net.packet.InPacket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tools.PacketCreator; import tools.PacketCreator;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public final class CreateCharHandler extends AbstractPacketHandler { public final class CreateCharHandler extends AbstractPacketHandler {
private static final Logger log = LoggerFactory.getLogger(CreateCharHandler.class);
private final static Set<Integer> IDs = new HashSet<>(Arrays.asList(
ItemId.SWORD, ItemId.HAND_AXE, ItemId.WOODEN_CLUB, ItemId.BASIC_POLEARM,// weapons
ItemId.WHITE_UNDERSHIRT, ItemId.UNDERSHIRT, ItemId.GREY_TSHIRT, ItemId.WHITE_TUBETOP, ItemId.YELLOW_TSHIRT,
ItemId.GREEN_TSHIRT, ItemId.RED_STRIPED_TOP, ItemId.SIMPLE_WARRIOR_TOP,// bottom
ItemId.BLUE_JEAN_SHORTS, ItemId.BROWN_COTTON_SHORTS, ItemId.RED_MINISKIRT, ItemId.INDIGO_MINISKIRT,
ItemId.SIMPLE_WARRIOR_PANTS, // top
ItemId.RED_RUBBER_BOOTS, ItemId.LEATHER_SANDALS, ItemId.YELLOW_RUBBER_BOOTS, ItemId.BLUE_RUBBER_BOOTS,
ItemId.AVERAGE_MUSASHI_SHOES,// shoes
ItemId.BLACK_TOBEN, ItemId.ZETA, ItemId.BLACK_REBEL, ItemId.BLACK_BUZZ, ItemId.BLACK_SAMMY,
ItemId.BLACK_EDGY, ItemId.BLACK_CONNIE,// hair
ItemId.MOTIVATED_LOOK_M, ItemId.PERPLEXED_STARE, ItemId.LEISURE_LOOK_M, ItemId.MOTIVATED_LOOK_F,
ItemId.FEARFUL_STARE_M, ItemId.LEISURE_LOOK_F, ItemId.FEARFUL_STARE_F, ItemId.PERPLEXED_STARE_HAZEL,
ItemId.LEISURE_LOOK_HAZEL, ItemId.MOTIVATED_LOOK_AMETHYST, ItemId.MOTIVATED_LOOK_BLUE //face
//#NeverTrustStevenCode
));
private static boolean isLegal(Integer toCompare) {
return IDs.contains(toCompare);
}
@Override @Override
public final void handlePacket(InPacket p, Client c) { public void handlePacket(InPacket p, Client c) {
String name = p.readString(); String name = p.readString();
int job = p.readInt(); int job = p.readInt();
int face = p.readInt(); int face = p.readInt();
@@ -77,14 +47,6 @@ public final class CreateCharHandler extends AbstractPacketHandler {
int weapon = p.readInt(); int weapon = p.readInt();
int gender = p.readByte(); int gender = p.readByte();
int[] items = new int[]{weapon, top, bottom, shoes, hair, face};
for (int item : items) {
if (!isLegal(item)) {
log.warn("Owner from account {} tried to packet edit in chr creation", c.getAccountName());
throw new GameViolationException("Create character with invalid equip");
}
}
int status; int status;
switch (job) { switch (job) {
case 0: // Knights of Cygnus case 0: // Knights of Cygnus

Some files were not shown because too many files have changed in this diff Show More