Compare commits

...
Sign in to create a new pull request.

44 commits

Author SHA1 Message Date
14a6c330d2 v1.6.6: 1.21.5 support (#50)
Reviewed-on: #50
Co-authored-by: SBDeveloper <support@sbdevelopment.tech>
Co-committed-by: SBDeveloper <support@sbdevelopment.tech>
2025-03-27 19:55:31 +01:00
aaecbf447d Delete .github/dependabot.yml 2025-03-27 19:54:36 +01:00
e142050b7a Delete .github/workflows/maven.yml 2025-03-27 19:54:31 +01:00
Stijn Bannink
cc59b911a5
Merge pull request #36 from SBDPlugins/dependabot/maven/org.bstats-bstats-bukkit-3.1.0
Bump org.bstats:bstats-bukkit from 3.0.2 to 3.1.0
2024-11-07 19:44:14 +01:00
Stijn Bannink
7651be3f14
Merge pull request #38 from SBDPlugins/dependabot/maven/io.netty-netty-transport-4.1.114.Final
Bump io.netty:netty-transport from 4.1.97.Final to 4.1.114.Final
2024-11-07 19:44:05 +01:00
Stijn Bannink
c74684a7a8
Merge pull request #39 from SBDPlugins/dependabot/maven/com.github.cryptomorin-XSeries-11.3.0
Bump com.github.cryptomorin:XSeries from 11.2.0 to 11.3.0
2024-11-07 19:43:57 +01:00
Stijn Bannink
0c61f5c517
Merge pull request #40 from SBDPlugins/dependabot/maven/org.apache.maven.plugins-maven-javadoc-plugin-3.11.1
Bump org.apache.maven.plugins:maven-javadoc-plugin from 3.7.0 to 3.11.1
2024-11-07 19:43:45 +01:00
dependabot[bot]
fa5e26ffd8
Bump org.apache.maven.plugins:maven-javadoc-plugin from 3.7.0 to 3.11.1
Bumps [org.apache.maven.plugins:maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) from 3.7.0 to 3.11.1.
- [Release notes](https://github.com/apache/maven-javadoc-plugin/releases)
- [Commits](https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.7.0...maven-javadoc-plugin-3.11.1)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-javadoc-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-04 16:29:25 +00:00
dependabot[bot]
7e392eb0ec
Bump com.github.cryptomorin:XSeries from 11.2.0 to 11.3.0
Bumps [com.github.cryptomorin:XSeries](https://github.com/CryptoMorin/XSeries) from 11.2.0 to 11.3.0.
- [Release notes](https://github.com/CryptoMorin/XSeries/releases)
- [Commits](https://github.com/CryptoMorin/XSeries/compare/v11.2.0...v11.3.0)

---
updated-dependencies:
- dependency-name: com.github.cryptomorin:XSeries
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-03 16:50:37 +00:00
dependabot[bot]
48762f9703
Bump io.netty:netty-transport from 4.1.97.Final to 4.1.114.Final
Bumps [io.netty:netty-transport](https://github.com/netty/netty) from 4.1.97.Final to 4.1.114.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.1.97.Final...netty-4.1.114.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-transport
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-03 16:50:30 +00:00
dependabot[bot]
59d4f84625
Bump org.bstats:bstats-bukkit from 3.0.2 to 3.1.0
Bumps [org.bstats:bstats-bukkit](https://github.com/Bastian/bStats-Metrics) from 3.0.2 to 3.1.0.
- [Release notes](https://github.com/Bastian/bStats-Metrics/releases)
- [Commits](https://github.com/Bastian/bStats-Metrics/compare/v3.0.2...v3.1.0)

---
updated-dependencies:
- dependency-name: org.bstats:bstats-bukkit
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-23 16:54:17 +00:00
Stijn Bannink
c2074e4504
Update README.md 2024-06-30 22:56:04 +02:00
Stijn Bannink
7586ba5502
Merge pull request #30 from SBDPlugins/development
v1.6.4: Added support for 1.20.5, 1.20.6 and 1.21
2024-06-30 22:46:01 +02:00
3f382583a7
Fixed all stupid NMS changes for 1.20.6+ 2024-06-30 22:37:24 +02:00
bd6a24a242
Bumped XSeries, moved to new reflection API of XSeries 2024-06-30 20:49:09 +02:00
603f144d26
Merge remote-tracking branch 'origin/development' into development
# Conflicts:
#	.idea/misc.xml
#	pom.xml
#	src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java
#	src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java
#	src/main/java/tech/sbdevelopment/mapreflectionapi/utils/XMaterial.java
2024-06-30 20:29:50 +02:00
445dc1d2e9
Added 1.21 support (untested) 2024-06-30 20:26:53 +02:00
Stijn Bannink
cba7cbf6e5 Fixed compilation 2024-04-29 18:11:13 +02:00
Stijn Bannink
f25c727a15 Added 1.20.5 support finished 2024-04-29 18:01:28 +02:00
Stijn Bannink
ef49048ee1 Started with 1.20.5 support 2024-04-29 16:50:10 +02:00
Stijn Bannink
89d4234a8c Cleanup 2023-12-24 15:39:25 +01:00
Stijn Bannink
b5df2bb80c Fixed multiple events, still WIP for Interact event 2023-12-24 15:38:40 +01:00
Stijn Bannink
5557a76976 Fixed typo in event name 2023-12-22 20:45:36 +01:00
Stijn Bannink
89cbc9b8be Added Spigot 1.20.4 support 2023-12-22 20:37:28 +01:00
Stijn Bannink
8206cb403b Added render distance info to README 2023-12-09 20:19:49 +01:00
Stijn Bannink
293c239fb8 v1.6.2: More version fixes (1.20.2 and 1.19.2) 2023-11-13 21:30:32 +01:00
Stijn Bannink
2af12469be
Update README.md 2023-10-23 19:05:25 +02:00
Stijn Bannink
74635f22e3
Merge pull request #29 from StanByes/master
Patch reflection item getter at PacketPlayInSetCreativeSlot
2023-10-23 19:04:19 +02:00
StanByes
eff2fb016f Patch reflection item getter at PacketPlayInSetCreativeSlot 2023-10-23 18:52:14 +02:00
Stijn Bannink
886fba1822
Merge pull request #28 from StanByes/master
Patch reflection channel getter at PlayerJoinEvent (1.20.2)
2023-10-23 13:26:33 +02:00
StanByes
e9039eb56d Patch reflection channel getter at PlayerJoinEvent 2023-10-23 13:18:40 +02:00
Stijn Bannink
a5e7a4afdc Updated deps 2023-10-02 13:15:55 +02:00
Stijn Bannink
fa08e1b5ff Bumped to 1.20.2 2023-09-22 16:37:43 +02:00
Stijn Bannink
d7429c301e
Merge pull request #25 from SBDPlugins/dependabot/maven/org.bstats-bstats-bukkit-3.0.2
⬆️ Bump org.bstats:bstats-bukkit from 3.0.0 to 3.0.2
2023-09-22 14:37:59 +02:00
dependabot[bot]
8a2daf1580
⬆️ Bump org.bstats:bstats-bukkit from 3.0.0 to 3.0.2
Bumps [org.bstats:bstats-bukkit](https://github.com/Bastian/bStats-Metrics) from 3.0.0 to 3.0.2.
- [Release notes](https://github.com/Bastian/bStats-Metrics/releases)
- [Commits](https://github.com/Bastian/bStats-Metrics/compare/v3.0.0...v3.0.2)

---
updated-dependencies:
- dependency-name: org.bstats:bstats-bukkit
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-22 12:37:50 +00:00
Stijn Bannink
4bb68cc5d2
Create dependabot.yml 2023-09-22 14:37:01 +02:00
Stijn Bannink
616e215797 Implemented Reflection caching, closes #8 2023-08-13 12:44:39 +02:00
Stijn Bannink
8d11483afe Fixed multi example for v1.6 2023-08-12 21:41:11 +02:00
Stijn Bannink
31ff370dc0 Bumped to v1.6 2023-08-12 21:35:52 +02:00
Stijn Bannink
19fdd6ff66 Merge remote-tracking branch 'origin/legacy/reflection' into legacy/reflection 2023-08-12 21:35:24 +02:00
Stijn Bannink
63ac9bb38e [BREAKING]: Improved [row][col] naming; now always accepts [row][col], breaks old multi-map plugins 2023-08-12 21:35:18 +02:00
Stijn Bannink
a5acef0666
Fixed typo in README 2023-08-10 19:32:12 +02:00
Stijn Bannink
c390ebbd5e Hotfix: Fixed supports() function detecting invalid major/minor version combination for newer versions; closes #22 2023-08-09 10:48:37 +02:00
Stijn Bannink
15640e5886 Hotfix for the show in hand feature, closes #20 2023-07-05 19:36:41 +02:00
25 changed files with 3071 additions and 682 deletions

View file

@ -1,26 +0,0 @@
name: Java CI
on: [push]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Build with Maven
run: mvn -B package --file pom.xml
- run: mkdir -p target
- uses: actions/upload-artifact@master
with:
name: MapReflectionAPI
path: target

2
.idea/misc.xml generated
View file

@ -30,7 +30,7 @@
</set> </set>
</option> </option>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="temurin-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" /> <output url="file://$PROJECT_DIR$/out" />
</component> </component>
</project> </project>

2
.idea/vcs.xml generated
View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" /> <mapping directory="" vcs="Git" />
</component> </component>
</project> </project>

104
README.md
View file

@ -1,9 +1,11 @@
# MapReflectionAPI # MapReflectionAPI
This plugin helps developer with viewing images on maps. It supports Spigot 1.12 - 1.19. This plugin helps developer with displaying images on maps. It supports Spigot 1.12 - 1.21.
## Usage: ## Usage:
### Using the API:
First, include the API using Maven: First, include the API using Maven:
```xml ```xml
@ -16,7 +18,7 @@ First, include the API using Maven:
<dependency> <dependency>
<groupId>tech.sbdevelopment</groupId> <groupId>tech.sbdevelopment</groupId>
<artifactId>MapReflectionAPI</artifactId> <artifactId>MapReflectionAPI</artifactId>
<version>1.4.3</version> <version>1.6.4</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
``` ```
@ -50,17 +52,8 @@ controller.showInHand(p, true);
It's also possible to split one image onto multiple itemframes. For example using the following code. It's also possible to split one image onto multiple itemframes. For example using the following code.
```java ```java
BufferedImage leftTopFrame = ...; //--- Wrap image (into 2 rows and 2 columns) ---
BufferedImage leftBottomFrame = ...; MultiMapWrapper wrapper = MapReflectionAPI.getMapManager().wrapMultiImage(ImageIO.read(new File("image.png")), 2, 2);
BufferedImage rightTopFrame = ...;
BufferedImage rightBottomFrame = ...;
BufferedImage[][] images = {
{leftBottomFrame, leftTopFrame},
{rightBottomFrame, rightTopFrame}
};
//--- Wrap image ---
MultiMapWrapper wrapper = MapReflectionAPI.getMapManager().wrapMultiImage(images);
MultiMapController controller = wrapper.getController(); MultiMapController controller = wrapper.getController();
final Player p = Bukkit.getPlayer("SBDeveloper"); final Player p = Bukkit.getPlayer("SBDeveloper");
@ -81,17 +74,94 @@ ItemFrame leftBottomFrame = ...;
ItemFrame rightTopFrame = ...; ItemFrame rightTopFrame = ...;
ItemFrame rightBottomFrame = ...; ItemFrame rightBottomFrame = ...;
ItemFrame[][] frames = { ItemFrame[][] frames = {
{leftBottomFrame, leftTopFrame}, {leftTopFrame, rightTopFrame},
{rightBottomFrame, rightTopFrame} {leftBottomFrame, rightBottomFrame}
}; };
controller.showInFrames(p, frames, true); controller.showInFrames(p, frames, true);
``` ```
More information can be found on the [JavaDoc](https://sbdevelopment.tech/javadoc/mapreflectionapi/). More information can be found on the [JavaDoc](https://sbdevelopment.tech/javadoc/mapreflectionapi/).
### Notes on map render distance:
MapReflectionAPI does not implement render distance to images shown on maps. This should be implemented by yourself. An example of this is below.
```java
import org.bukkit.event.EventHandler;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
public class MapRenderDistanceListener implements Listener {
private static final int MAP_RENDER_DISTANCE_SQUARED = 1024;
@EventHandler
public void onPlayerJoin(PlayerJoinEvent e) {
Bukkit.getScheduler().runTaskLaterAsynchronously(MyPluginInstance, () -> {
//Show the maps to the player which are within distance
});
}
@EventHandler
public void onPlayerLeave(PlayerQuitEvent e) {
Bukkit.getScheduler().runTaskLaterAsynchronously(MyPluginInstance, () -> {
//Hide the maps to the player which are within distance
});
}
@EventHandler
public void onPlayerChangeWorld(PlayerChangedWorldEvent e) {
//Hide all the maps in the e.getFrom() world
Bukkit.getScheduler().runTaskLaterAsynchronously(MyPluginInstance, () -> {
//Show the maps to the player which are within distance in e.getPlayer().getWorld()
}, 20);
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerDeath(PlayerDeathEvent e) {
//Hide all the maps in the e.getEntity().getWorld()
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerRespawn(PlayerRespawnEvent e) {
Bukkit.getScheduler().runTaskLaterAsynchronously(MyPluginInstance, () -> {
//Show the maps to the player which are within distance in e.getPlayer().getWorld()
}, 20);
}
@EventHandler
public void onPlayerMove(PlayerMoveEvent e) {
//Hide all the maps in the e.getFrom() world
//Show the maps to the player which are within distance in e.getTo().getWorld()
//FOR EXAMPLE:
if (e.getTo() == null) return;
if (e.getFrom().getChunk().equals(e.getTo().getChunk())) return;
for (Frame frame : API.getFramesInWorld(e.getPlayer().getWorld())) {
double distanceSquared = e.getTo().distanceSquared(frame.getLocation());
if (distanceSquared > MAP_RENDER_DISTANCE_SQUARED) {
API.hideFrame(e.getPlayer(), frame);
} else {
API.showFrame(e.getPlayer(), frame);
}
}
}
@EventHandler
public void onPlayerTeleport(PlayerTeleportEvent e) {
//Hide all the maps in the e.getFrom() world
//Show the maps to the player which are within distance in e.getTo().getWorld()
//SEE EXAMPLE ABOVE
}
}
```
## Credits: ## Credits:
This is a fork of [MapManager](https://github.com/InventivetalentDev/MapManager). It updates the API to 1.19 and uses This is a fork of [MapManager](https://github.com/InventivetalentDev/MapManager). It updates the API to the latest version of Minecraft and uses other dependencies.
other dependencies.
This plugin includes classes from BKCommonLib. Please checkout the README in that package for more information. This plugin includes classes from BKCommonLib. Please checkout the README in that package for more information.

43
pom.xml
View file

@ -24,7 +24,7 @@
<groupId>tech.sbdevelopment</groupId> <groupId>tech.sbdevelopment</groupId>
<artifactId>MapReflectionAPI</artifactId> <artifactId>MapReflectionAPI</artifactId>
<version>1.4.4</version> <version>1.6.6</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>MapReflectionAPI</name> <name>MapReflectionAPI</name>
@ -48,14 +48,14 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version> <version>3.14.0</version>
<configuration> <configuration>
<release>11</release> <release>11</release>
<annotationProcessorPaths> <annotationProcessorPaths>
<path> <path>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>1.18.24</version> <version>1.18.34</version>
</path> </path>
</annotationProcessorPaths> </annotationProcessorPaths>
</configuration> </configuration>
@ -63,7 +63,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId> <artifactId>maven-shade-plugin</artifactId>
<version>3.3.0</version> <version>3.6.0</version>
<executions> <executions>
<execution> <execution>
<phase>package</phase> <phase>package</phase>
@ -81,6 +81,10 @@
<pattern>org.bstats</pattern> <pattern>org.bstats</pattern>
<shadedPattern>tech.sbdevelopment.mapreflectionapi.libs.bstats</shadedPattern> <shadedPattern>tech.sbdevelopment.mapreflectionapi.libs.bstats</shadedPattern>
</relocation> </relocation>
<relocation>
<pattern>com.cryptomorin.xseries</pattern>
<shadedPattern>tech.sbdevelopment.mapreflectionapi.libs.xseries</shadedPattern>
</relocation>
</relocations> </relocations>
</configuration> </configuration>
</execution> </execution>
@ -103,12 +107,19 @@
</goals> </goals>
</execution> </execution>
</executions> </executions>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
</dependency>
</dependencies>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId> <artifactId>maven-javadoc-plugin</artifactId>
<version>3.4.0</version> <version>3.11.2</version>
<configuration> <configuration>
<release>11</release> <release>11</release>
<sourcepath>${maven.lombok.delombok-target}</sourcepath> <sourcepath>${maven.lombok.delombok-target}</sourcepath>
@ -155,40 +166,48 @@
<id>dmulloy2-repo</id> <id>dmulloy2-repo</id>
<url>https://repo.dmulloy2.net/repository/public/</url> <url>https://repo.dmulloy2.net/repository/public/</url>
</repository> </repository>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories> </repositories>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.spigotmc</groupId> <groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId> <artifactId>spigot-api</artifactId>
<version>1.19.4-R0.1-SNAPSHOT</version> <version>1.21.5-R0.1-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>1.18.24</version> <version>1.18.34</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.bstats</groupId> <groupId>org.bstats</groupId>
<artifactId>bstats-bukkit</artifactId> <artifactId>bstats-bukkit</artifactId>
<version>3.0.0</version> <version>3.1.0</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>com.github.cryptomorin</groupId>
<artifactId>XSeries</artifactId>
<version>13.1.0</version>
</dependency>
<!-- Libraries below are provided by Spigot --> <!-- Libraries below are provided by CraftBukkit -->
<dependency> <dependency>
<groupId>org.jetbrains</groupId> <groupId>org.jetbrains</groupId>
<artifactId>annotations-java5</artifactId> <artifactId>annotations-java5</artifactId>
<version>23.0.0</version> <version>24.1.0</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.netty</groupId> <groupId>io.netty</groupId>
<artifactId>netty-transport</artifactId> <artifactId>netty-transport</artifactId>
<version>4.1.77.Final</version> <version>4.1.118.Final</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
</dependencies> </dependencies>

View file

@ -27,6 +27,8 @@ import java.awt.*;
import java.io.InputStream; import java.io.InputStream;
import java.util.Arrays; import java.util.Arrays;
import static com.cryptomorin.xseries.reflection.XReflection.supports;
/** /**
* Additional functionality on top of Bukkit's MapPalette * Additional functionality on top of Bukkit's MapPalette
*/ */
@ -50,14 +52,12 @@ public class MapColorPalette {
MCSDBubbleFormat bubbleData = new MCSDBubbleFormat(); MCSDBubbleFormat bubbleData = new MCSDBubbleFormat();
try { try {
String bub_path_postfix; String bub_path_postfix;
if (ReflectionUtil.supports(17)) { if (supports(17)) {
bub_path_postfix = "map_1_17.bub"; bub_path_postfix = "map_1_17.bub";
} else if (ReflectionUtil.supports(16)) { } else if (supports(16)) {
bub_path_postfix = "map_1_16.bub"; bub_path_postfix = "map_1_16.bub";
} else if (ReflectionUtil.supports(12)) {
bub_path_postfix = "map_1_12.bub";
} else { } else {
bub_path_postfix = "map_1_8_8.bub"; bub_path_postfix = "map_1_12.bub";
} }
String bub_path = "/tech/sbdevelopment/mapreflectionapi/libs/bkcommonlib/internal/resources/map/" + bub_path_postfix; String bub_path = "/tech/sbdevelopment/mapreflectionapi/libs/bkcommonlib/internal/resources/map/" + bub_path_postfix;
InputStream input = MapColorPalette.class.getResourceAsStream(bub_path); InputStream input = MapColorPalette.class.getResourceAsStream(bub_path);

View file

@ -30,11 +30,12 @@ import tech.sbdevelopment.mapreflectionapi.listeners.MapListener;
import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener;
import tech.sbdevelopment.mapreflectionapi.managers.Configuration; import tech.sbdevelopment.mapreflectionapi.managers.Configuration;
import tech.sbdevelopment.mapreflectionapi.utils.MainUtil; import tech.sbdevelopment.mapreflectionapi.utils.MainUtil;
import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil;
import tech.sbdevelopment.mapreflectionapi.utils.UpdateManager; import tech.sbdevelopment.mapreflectionapi.utils.UpdateManager;
import java.util.logging.Level; import java.util.logging.Level;
import static com.cryptomorin.xseries.reflection.XReflection.supports;
public class MapReflectionAPI extends JavaPlugin { public class MapReflectionAPI extends JavaPlugin {
private static MapReflectionAPI instance; private static MapReflectionAPI instance;
private static MapManager mapManager; private static MapManager mapManager;
@ -67,8 +68,8 @@ public class MapReflectionAPI extends JavaPlugin {
getLogger().info("MapReflectionAPI v" + getDescription().getVersion()); getLogger().info("MapReflectionAPI v" + getDescription().getVersion());
getLogger().info("Made by © Copyright SBDevelopment 2023"); getLogger().info("Made by © Copyright SBDevelopment 2023");
if (!ReflectionUtil.supports(12)) { if (!supports(12)) {
getLogger().severe("MapReflectionAPI only supports Minecraft 1.12 - 1.19.4!"); getLogger().severe("MapReflectionAPI only supports Minecraft 1.12 - 1.20.5!");
Bukkit.getPluginManager().disablePlugin(this); Bukkit.getPluginManager().disablePlugin(this);
return; return;
} }

View file

@ -19,7 +19,6 @@
package tech.sbdevelopment.mapreflectionapi.api; package tech.sbdevelopment.mapreflectionapi.api;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.entity.ItemFrame; import org.bukkit.entity.ItemFrame;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -117,13 +116,4 @@ public interface MapController extends IMapController {
* @param frame {@link ItemFrame} to clear * @param frame {@link ItemFrame} to clear
*/ */
void clearFrame(Player player, ItemFrame frame); void clearFrame(Player player, ItemFrame frame);
/**
* Get an {@link ItemFrame} by its entity ID
*
* @param world The world the {@link ItemFrame} is in
* @param entityId Entity-ID of the {@link ItemFrame}
* @return The found {@link ItemFrame}, or <code>null</code>
*/
ItemFrame getItemFrameById(World world, int entityId);
} }

View file

@ -19,10 +19,13 @@
package tech.sbdevelopment.mapreflectionapi.api; package tech.sbdevelopment.mapreflectionapi.api;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.entity.ItemFrame;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException;
import tech.sbdevelopment.mapreflectionapi.managers.Configuration; import tech.sbdevelopment.mapreflectionapi.managers.Configuration;
import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.util.HashSet; import java.util.HashSet;
@ -30,6 +33,8 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import static com.cryptomorin.xseries.reflection.XReflection.*;
/** /**
* The {@link MapManager} manages all the maps. It also contains functions for wrapping. * The {@link MapManager} manages all the maps. It also contains functions for wrapping.
*/ */
@ -172,6 +177,30 @@ public class MapManager {
return null; return null;
} }
/**
* Get an {@link ItemFrame} by its entity ID
*
* @param world The world the {@link ItemFrame} is in
* @param entityId Entity-ID of the {@link ItemFrame}
* @return The found {@link ItemFrame}, or <code>null</code>
*/
public ItemFrame getItemFrameById(World world, int entityId) {
Object worldHandle = ReflectionUtil.getHandle(world);
Object nmsEntity = ReflectionUtil.callMethod(worldHandle, supports(18) ? "a" : "getEntity", entityId);
if (nmsEntity == null) return null;
Object craftEntity = ReflectionUtil.callMethod(nmsEntity, "getBukkitEntity");
if (craftEntity == null) return null;
Class<?> itemFrameClass = getNMSClass("world.entity.decoration", "EntityItemFrame");
if (itemFrameClass == null) return null;
if (craftEntity.getClass().isAssignableFrom(itemFrameClass))
return (ItemFrame) itemFrameClass.cast(craftEntity);
return null;
}
/** /**
* Register an occupied map ID * Register an occupied map ID
* *

View file

@ -27,6 +27,9 @@ import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static com.cryptomorin.xseries.reflection.XReflection.*;
import static com.cryptomorin.xseries.reflection.minecraft.MinecraftConnection.sendPacket;
/** /**
* The {@link MapSender} sends the Map packets to players. * The {@link MapSender} sends the Map packets to players.
*/ */
@ -82,8 +85,9 @@ public class MapSender {
}, 0, 2); }, 0, 2);
} }
private static final Class<?> packetPlayOutMapClass = ReflectionUtil.getNMSClass("network.protocol.game", "PacketPlayOutMap"); private static final Class<?> packetPlayOutMapClass = getNMSClass("network.protocol.game", "PacketPlayOutMap");
private static final Class<?> worldMapData = ReflectionUtil.supports(17) ? ReflectionUtil.getNMSClass("world.level.saveddata.maps", "WorldMap$b") : null; private static final Class<?> worldMapData = supports(17) ? getNMSClass("world.level.saveddata.maps", supports(21, 2) ? "WorldMap$c" : "WorldMap$b") : null; //1.21.2+ uses WorldMap$c, 1.17+ uses WorldMap$b
private static final Class<?> mapIdClazz = supports(21) ? getNMSClass("world.level.saveddata.maps", "MapId") : null;
/** /**
* Send a map to a player * Send a map to a player
@ -108,9 +112,28 @@ public class MapSender {
return; return;
} }
final int id = -id0; int id = -id0;
Object packet; Object packet;
if (ReflectionUtil.supports(17)) { //1.17+ if (supports(20, 4)) { //1.20.5+
Object updateData = ReflectionUtil.callConstructor(worldMapData,
content.minX, //X pos
content.minY, //Y pos
content.maxX, //X size (2nd X pos)
content.maxY, //Y size (2nd Y pos)
content.array //Data
);
Object mapId = ReflectionUtil.callConstructor(mapIdClazz, id);
packet = ReflectionUtil.callConstructor(packetPlayOutMapClass,
mapId, //ID
(byte) 0, //Scale, 0 = 1 block per pixel
false, //Show icons
new ReflectionUtil.CollectionParam<>(), //Icons
updateData
);
} else if (supports(17)) { //1.17+
Object updateData = ReflectionUtil.callConstructor(worldMapData, Object updateData = ReflectionUtil.callConstructor(worldMapData,
content.minX, //X pos content.minX, //X pos
content.minY, //Y pos content.minY, //Y pos
@ -126,7 +149,7 @@ public class MapSender {
new ReflectionUtil.CollectionParam<>(), //Icons new ReflectionUtil.CollectionParam<>(), //Icons
updateData updateData
); );
} else if (ReflectionUtil.supports(14)) { //1.16-1.14 } else if (supports(14)) { //1.16-1.14
packet = ReflectionUtil.callConstructor(packetPlayOutMapClass, packet = ReflectionUtil.callConstructor(packetPlayOutMapClass,
id, //ID id, //ID
(byte) 0, //Scale, 0 = 1 block per pixel (byte) 0, //Scale, 0 = 1 block per pixel
@ -153,7 +176,7 @@ public class MapSender {
); );
} }
ReflectionUtil.sendPacket(player, packet); sendPacket(player, packet);
} }
@Data @Data

View file

@ -18,7 +18,10 @@
package tech.sbdevelopment.mapreflectionapi.api; package tech.sbdevelopment.mapreflectionapi.api;
import org.bukkit.*; import lombok.Getter;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.ItemFrame; import org.bukkit.entity.ItemFrame;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
@ -30,6 +33,7 @@ import tech.sbdevelopment.mapreflectionapi.api.events.MapContentUpdateEvent;
import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException;
import tech.sbdevelopment.mapreflectionapi.managers.Configuration; import tech.sbdevelopment.mapreflectionapi.managers.Configuration;
import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil; import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil;
import tech.sbdevelopment.mapreflectionapi.utils.XMaterial;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@ -37,19 +41,18 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import static com.cryptomorin.xseries.reflection.XReflection.*;
import static com.cryptomorin.xseries.reflection.minecraft.MinecraftConnection.getHandle;
import static com.cryptomorin.xseries.reflection.minecraft.MinecraftConnection.sendPacket;
/** /**
* A {@link MapWrapper} wraps one image. * A {@link MapWrapper} wraps one image.
*/ */
@Getter
public class MapWrapper extends AbstractMapWrapper { public class MapWrapper extends AbstractMapWrapper {
private static final String REFERENCE_METADATA = "MAP_WRAPPER_REF"; public static final String REFERENCE_METADATA = "MAP_WRAPPER_REF";
protected ArrayImage content; protected ArrayImage content;
private static final Material MAP_MATERIAL;
static {
MAP_MATERIAL = ReflectionUtil.supports(13) ? Material.FILLED_MAP : Material.MAP;
}
/** /**
* Construct a new {@link MapWrapper} * Construct a new {@link MapWrapper}
* *
@ -59,13 +62,13 @@ public class MapWrapper extends AbstractMapWrapper {
this.content = image; this.content = image;
} }
private static final Class<?> craftStackClass = ReflectionUtil.getCraftClass("inventory.CraftItemStack"); private static final Class<?> craftStackClass = getCraftClass("inventory.CraftItemStack");
private static final Class<?> setSlotPacketClass = ReflectionUtil.getNMSClass("network.protocol.game", "PacketPlayOutSetSlot"); private static final Class<?> setSlotPacketClass = getNMSClass("network.protocol.game", "PacketPlayOutSetSlot");
private static final Class<?> entityClass = ReflectionUtil.getNMSClass("world.entity", "Entity"); private static final Class<?> entityClass = getNMSClass("world.entity", "Entity");
private static final Class<?> dataWatcherClass = ReflectionUtil.getNMSClass("network.syncher", "DataWatcher"); private static final Class<?> dataWatcherClass = getNMSClass("network.syncher", "DataWatcher");
private static final Class<?> entityMetadataPacketClass = ReflectionUtil.getNMSClass("network.protocol.game", "PacketPlayOutEntityMetadata"); private static final Class<?> entityMetadataPacketClass = getNMSClass("network.protocol.game", "PacketPlayOutEntityMetadata");
private static final Class<?> entityItemFrameClass = ReflectionUtil.getNMSClass("world.entity.decoration", "EntityItemFrame"); private static final Class<?> entityItemFrameClass = getNMSClass("world.entity.decoration", "EntityItemFrame");
private static final Class<?> dataWatcherItemClass = ReflectionUtil.getNMSClass("network.syncher", "DataWatcher$Item"); private static final Class<?> dataWatcherItemClass = getNMSClass("network.syncher", "DataWatcher$Item");
protected MapController controller = new MapController() { protected MapController controller = new MapController() {
private final Map<UUID, Integer> viewers = new HashMap<>(); private final Map<UUID, Integer> viewers = new HashMap<>();
@ -105,7 +108,8 @@ public class MapWrapper extends AbstractMapWrapper {
@Override @Override
public void update(@NotNull ArrayImage content) { public void update(@NotNull ArrayImage content) {
MapContentUpdateEvent event = new MapContentUpdateEvent(MapWrapper.this, content); boolean async = !MapReflectionAPI.getInstance().getServer().isPrimaryThread();
MapContentUpdateEvent event = new MapContentUpdateEvent(MapWrapper.this, content, async);
Bukkit.getPluginManager().callEvent(event); Bukkit.getPluginManager().callEvent(event);
if (Configuration.getInstance().isImageCache()) { if (Configuration.getInstance().isImageCache()) {
@ -162,31 +166,32 @@ public class MapWrapper extends AbstractMapWrapper {
} }
String inventoryMenuName; String inventoryMenuName;
if (ReflectionUtil.supports(20)) { //1.20 if (supports(21)) {
inventoryMenuName = "bQ"; //1.21.5 = bQ, 1.21 - 1.21.4 = cc
} else if (ReflectionUtil.supports(19)) { //1.19 inventoryMenuName = supports(21, 4) ? "bQ" : "cc";
inventoryMenuName = ReflectionUtil.VER_MINOR == 3 ? "bO" : "bT"; //1.19.4 = bO, >= 1.19.3 = bT } else if (supports(20)) {
} else if (ReflectionUtil.supports(18)) { //1.18 //1.20.5 = cb, 1.20.2 - 1.20.4 = bR, 1.20(.1) = bQ
inventoryMenuName = ReflectionUtil.VER_MINOR == 1 ? "bV" : "bU"; //1.18.1 = ap, 1.18(.2) = ao inventoryMenuName = supports(20, 4) ? "cb" : supports(20, 2) ? "bR" : "bQ";
} else if (ReflectionUtil.supports(17)) { //1.17, same as 1.18(.2) } else if (supports(19)) {
//1.19.4 = bO, >= 1.19.3 = bT
inventoryMenuName = supports(19, 3) ? "bO" : "bT";
} else if (supports(18)) {
//1.18.1 = ap, 1.18(.2) = ao
inventoryMenuName = supports(18, 1) ? "bV" : "bU";
} else if (supports(17)) {
//1.17, same as 1.18(.2)
inventoryMenuName = "bU"; inventoryMenuName = "bU";
} else { //1.12-1.16 } else {
//1.12-1.16
inventoryMenuName = "defaultContainer"; inventoryMenuName = "defaultContainer";
} }
Object inventoryMenu = ReflectionUtil.getField(ReflectionUtil.getHandle(player), inventoryMenuName); Object inventoryMenu = ReflectionUtil.getField(getHandle(player), inventoryMenuName);
ItemStack stack; Object nmsStack = asCraftItemStack(player);
if (ReflectionUtil.supports(13)) {
stack = new ItemStack(MAP_MATERIAL, 1);
} else {
stack = new ItemStack(MAP_MATERIAL, 1, (short) getMapId(player));
}
Object nmsStack = createCraftItemStack(stack, (short) getMapId(player));
Object packet; Object packet;
if (ReflectionUtil.supports(17)) { //1.17+ if (supports(17)) { //1.17+
int stateId = (int) ReflectionUtil.callMethod(inventoryMenu, ReflectionUtil.supports(18) ? "j" : "getStateId"); int stateId = (int) ReflectionUtil.callMethod(inventoryMenu, supports(18) ? "j" : "getStateId");
packet = ReflectionUtil.callConstructor(setSlotPacketClass, packet = ReflectionUtil.callConstructor(setSlotPacketClass,
0, //0 = Player inventory 0, //0 = Player inventory
@ -202,7 +207,7 @@ public class MapWrapper extends AbstractMapWrapper {
); );
} }
ReflectionUtil.sendPacketSync(player, packet); sendPacket(player, packet);
} }
@Override @Override
@ -212,7 +217,7 @@ public class MapWrapper extends AbstractMapWrapper {
@Override @Override
public void showInHand(Player player, boolean force) { public void showInHand(Player player, boolean force) {
if (player.getInventory().getItemInMainHand().getType() != MAP_MATERIAL && !force) if (player.getInventory().getItemInMainHand().getType() != XMaterial.FILLED_MAP.parseMaterial() && !force)
return; return;
showInInventory(player, player.getInventory().getHeldItemSlot(), force); showInInventory(player, player.getInventory().getHeldItemSlot(), force);
@ -230,7 +235,7 @@ public class MapWrapper extends AbstractMapWrapper {
@Override @Override
public void showInFrame(Player player, ItemFrame frame, boolean force) { public void showInFrame(Player player, ItemFrame frame, boolean force) {
if (frame.getItem().getType() != MAP_MATERIAL && !force) if (frame.getItem().getType() != XMaterial.FILLED_MAP.parseMaterial() && !force)
return; return;
showInFrame(player, frame.getEntityId()); showInFrame(player, frame.getEntityId());
@ -245,7 +250,7 @@ public class MapWrapper extends AbstractMapWrapper {
public void showInFrame(Player player, int entityId, String debugInfo) { public void showInFrame(Player player, int entityId, String debugInfo) {
if (!isViewing(player)) return; if (!isViewing(player)) return;
ItemStack stack = new ItemStack(MAP_MATERIAL, 1); ItemStack stack = new ItemStack(XMaterial.FILLED_MAP.parseMaterial(), 1);
if (debugInfo != null) { if (debugInfo != null) {
ItemMeta itemMeta = stack.getItemMeta(); ItemMeta itemMeta = stack.getItemMeta();
itemMeta.setDisplayName(debugInfo); itemMeta.setDisplayName(debugInfo);
@ -253,7 +258,7 @@ public class MapWrapper extends AbstractMapWrapper {
} }
Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> {
ItemFrame frame = getItemFrameById(player.getWorld(), entityId); ItemFrame frame = MapReflectionAPI.getMapManager().getItemFrameById(player.getWorld(), entityId);
if (frame != null) { if (frame != null) {
frame.removeMetadata(REFERENCE_METADATA, MapReflectionAPI.getInstance()); frame.removeMetadata(REFERENCE_METADATA, MapReflectionAPI.getInstance());
frame.setMetadata(REFERENCE_METADATA, new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper.this)); frame.setMetadata(REFERENCE_METADATA, new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper.this));
@ -267,7 +272,7 @@ public class MapWrapper extends AbstractMapWrapper {
public void clearFrame(Player player, int entityId) { public void clearFrame(Player player, int entityId) {
sendItemFramePacket(player, entityId, null, -1); sendItemFramePacket(player, entityId, null, -1);
Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> {
ItemFrame frame = getItemFrameById(player.getWorld(), entityId); ItemFrame frame = MapReflectionAPI.getMapManager().getItemFrameById(player.getWorld(), entityId);
if (frame != null) frame.removeMetadata(REFERENCE_METADATA, MapReflectionAPI.getInstance()); if (frame != null) frame.removeMetadata(REFERENCE_METADATA, MapReflectionAPI.getInstance());
}); });
} }
@ -277,22 +282,8 @@ public class MapWrapper extends AbstractMapWrapper {
clearFrame(player, frame.getEntityId()); clearFrame(player, frame.getEntityId());
} }
@Override private Object asCraftItemStack(Player player) {
public ItemFrame getItemFrameById(World world, int entityId) { return createCraftItemStack(new ItemStack(XMaterial.FILLED_MAP.parseMaterial(), 1, (short) getMapId(player)), (short) getMapId(player));
Object worldHandle = ReflectionUtil.getHandle(world);
Object nmsEntity = ReflectionUtil.callMethod(worldHandle, ReflectionUtil.supports(18) ? "a" : "getEntity", entityId);
if (nmsEntity == null) return null;
Object craftEntity = ReflectionUtil.callMethod(nmsEntity, "getBukkitEntity");
if (craftEntity == null) return null;
Class<?> itemFrameClass = ReflectionUtil.getNMSClass("world.entity.decoration", "EntityItemFrame");
if (itemFrameClass == null) return null;
if (craftEntity.getClass().isAssignableFrom(itemFrameClass))
return (ItemFrame) itemFrameClass.cast(craftEntity);
return null;
} }
private Object createCraftItemStack(@NotNull ItemStack stack, int mapId) { private Object createCraftItemStack(@NotNull ItemStack stack, int mapId) {
@ -300,17 +291,34 @@ public class MapWrapper extends AbstractMapWrapper {
Object nmsStack = ReflectionUtil.callMethod(craftStackClass, "asNMSCopy", stack); Object nmsStack = ReflectionUtil.callMethod(craftStackClass, "asNMSCopy", stack);
if (ReflectionUtil.supports(13)) { //1.20.5 uses new NBT compound system
if (supports(20, 4)) {
Object mapIdComponent = ReflectionUtil.getDeclaredField(getNMSClass("core.component", "DataComponents"), supports(21, 4) ? "M" : supports(21, 2) ? "L" : "B"); //1.21.2+ uses L, otherwise B
Object mapId1 = ReflectionUtil.callConstructor(getNMSClass("world.level.saveddata.maps", "MapId"), mapId);
// Use generic reflection because of generics
// <T> T ItemStack#b(DataComponentType<? super T> dataComponentType, T t)
try {
Method m = nmsStack.getClass().getMethod("b", getNMSClass("core.component", "DataComponentType"), Object.class);
m.setAccessible(true);
m.invoke(nmsStack, mapIdComponent, mapId1);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
ex.printStackTrace();
return null;
}
} else if (supports(13)) {
String nbtObjectName; String nbtObjectName;
if (ReflectionUtil.supports(19)) { //1.19 if (supports(20)) { //1.20
nbtObjectName = "w";
} else if (supports(19)) { //1.19
nbtObjectName = "v"; nbtObjectName = "v";
} else if (ReflectionUtil.supports(18)) { //1.18 } else if (supports(18)) { //1.18
nbtObjectName = ReflectionUtil.VER_MINOR == 1 ? "t" : "u"; //1.18.1 = t, 1.18(.2) = u nbtObjectName = supports(18, 1) ? "t" : "u"; //1.18.1 = t, 1.18(.2) = u
} else { //1.13 - 1.17 } else { //1.13 - 1.17
nbtObjectName = "getOrCreateTag"; nbtObjectName = "getOrCreateTag";
} }
Object nbtObject = ReflectionUtil.callMethod(nmsStack, nbtObjectName); Object nbtObject = ReflectionUtil.callMethod(nmsStack, nbtObjectName);
ReflectionUtil.callMethod(nbtObject, ReflectionUtil.supports(18) ? "a" : "setInt", "map", mapId); ReflectionUtil.callMethod(nbtObject, supports(18) ? "a" : "setInt", "map", mapId);
} }
return nmsStack; return nmsStack;
} }
@ -319,26 +327,30 @@ public class MapWrapper extends AbstractMapWrapper {
Object nmsStack = createCraftItemStack(stack, mapId); Object nmsStack = createCraftItemStack(stack, mapId);
String dataWatcherObjectName; String dataWatcherObjectName;
if (ReflectionUtil.supports(19)) { //1.19 if (supports(21)) { //1.21
dataWatcherObjectName = ReflectionUtil.VER_MINOR == 3 ? "g" : "ao"; //1.19.4 = g, >= 1.19.3 = ao dataWatcherObjectName = supports(21, 2) ? "e" : "f"; //1.21.2+ = e, 1.21(.1) = f
} else if (ReflectionUtil.supports(18)) { //1.18 } else if (supports(19, 3)) { //1.19.3 and 1.20(.1)
dataWatcherObjectName = ReflectionUtil.VER_MINOR == 1 ? "ap" : "ao"; //1.18.1 = ap, 1.18(.2) = ao dataWatcherObjectName = "g";
} else if (ReflectionUtil.supports(17)) { //1.17 } else if (supports(19)) { //1.19-1.19.2
dataWatcherObjectName = "ao"; dataWatcherObjectName = "ao";
} else if (ReflectionUtil.supports(14)) { //1.14 - 1.16 } else if (supports(18)) { //1.18
dataWatcherObjectName = supports(18, 1) ? "ap" : "ao"; //1.18.1 = ap, 1.18(.2) = ao
} else if (supports(17)) { //1.17
dataWatcherObjectName = "ao";
} else if (supports(14)) { //1.14 - 1.16
dataWatcherObjectName = "ITEM"; dataWatcherObjectName = "ITEM";
} else if (ReflectionUtil.supports(13)) { //1.13 } else if (supports(13)) { //1.13
dataWatcherObjectName = "e"; dataWatcherObjectName = "e";
} else { //1.12 } else { //1.12
dataWatcherObjectName = "c"; dataWatcherObjectName = "c";
} }
Object dataWatcherObject = ReflectionUtil.getDeclaredField(entityItemFrameClass, dataWatcherObjectName); Object dataWatcherObject = ReflectionUtil.getDeclaredField(entityItemFrameClass, dataWatcherObjectName);
ReflectionUtil.ListParam list = new ReflectionUtil.ListParam<>(); ReflectionUtil.ListParam<Object> list = new ReflectionUtil.ListParam<>();
Object packet; Object packet;
if (ReflectionUtil.supports(19, 2)) { //1.19.3 if (supports(19, 3)) { //1.19.3
Class<?> dataWatcherRecordClass = ReflectionUtil.getNMSClass("network.syncher", "DataWatcher$b"); Class<?> dataWatcherRecordClass = getNMSClass("network.syncher", "DataWatcher$" + (supports(20, 4) ? "c" : "b"));
// Sadly not possible to use ReflectionUtil (in its current state), because of the Object parameter // Sadly not possible to use ReflectionUtil (in its current state), because of the Object parameter
Object dataWatcherItem; Object dataWatcherItem;
try { try {
@ -369,16 +381,7 @@ public class MapWrapper extends AbstractMapWrapper {
ReflectionUtil.setDeclaredField(packet, "b", list); ReflectionUtil.setDeclaredField(packet, "b", list);
} }
ReflectionUtil.sendPacketSync(player, packet); sendPacket(player, packet);
} }
}; };
public ArrayImage getContent() {
return content;
}
@Override
public MapController getController() {
return controller;
}
} }

View file

@ -86,7 +86,7 @@ public interface MultiMapController extends IMapController {
* Show this {@link MultiMapController} in {@link ItemFrame}s * Show this {@link MultiMapController} in {@link ItemFrame}s
* *
* @param player {@link Player} that will be able to see the maps * @param player {@link Player} that will be able to see the maps
* @param entityIdMatrix 2D-Array of entity-IDs of the {@link ItemFrame}s (<code>int[width][height]</code>) * @param entityIdMatrix 2D-Array of entity-IDs of the {@link ItemFrame}s (<code>int[rows][columns]</code>)
* @see MapController#showInFrame(Player, int) * @see MapController#showInFrame(Player, int)
*/ */
void showInFrames(Player player, Integer[][] entityIdMatrix); void showInFrames(Player player, Integer[][] entityIdMatrix);
@ -95,7 +95,7 @@ public interface MultiMapController extends IMapController {
* Show this {@link MultiMapController} in {@link ItemFrame}s * Show this {@link MultiMapController} in {@link ItemFrame}s
* *
* @param player {@link Player} that will be able to see the maps * @param player {@link Player} that will be able to see the maps
* @param entityIdMatrix 2D-Array of entity-IDs of the {@link ItemFrame}s (<code>int[width][height]</code>) * @param entityIdMatrix 2D-Array of entity-IDs of the {@link ItemFrame}s (<code>int[rows][columns]</code>)
* @param callable {@link DebugCallable} which will be called to display debug information, or <code>null</code> * @param callable {@link DebugCallable} which will be called to display debug information, or <code>null</code>
* @see MapController#showInFrame(Player, int, String) * @see MapController#showInFrame(Player, int, String)
*/ */
@ -105,7 +105,7 @@ public interface MultiMapController extends IMapController {
* Show this {@link MultiMapController} in {@link ItemFrame}s * Show this {@link MultiMapController} in {@link ItemFrame}s
* *
* @param player {@link Player} that will be able to see the maps * @param player {@link Player} that will be able to see the maps
* @param itemFrameMatrix 2D-Array of {@link ItemFrame}s (<code>ItemFrame[width][height]</code>) * @param itemFrameMatrix 2D-Array of {@link ItemFrame}s (<code>ItemFrame[rows][columns]</code>)
* @param force if <code>false</code>, the map will not be shown if there is not Map-Item in the ItemFrames * @param force if <code>false</code>, the map will not be shown if there is not Map-Item in the ItemFrames
* @see MapController#showInFrame(Player, ItemFrame, boolean) * @see MapController#showInFrame(Player, ItemFrame, boolean)
*/ */
@ -115,7 +115,7 @@ public interface MultiMapController extends IMapController {
* Show this {@link MultiMapController} in {@link ItemFrame}s * Show this {@link MultiMapController} in {@link ItemFrame}s
* *
* @param player {@link Player} that will be able to see the maps * @param player {@link Player} that will be able to see the maps
* @param itemFrameMatrix 2D-Array of {@link ItemFrame}s (<code>ItemFrame[width][height]</code>) * @param itemFrameMatrix 2D-Array of {@link ItemFrame}s (<code>ItemFrame[rows][columns]</code>)
* @see MapController#showInFrame(Player, ItemFrame) * @see MapController#showInFrame(Player, ItemFrame)
*/ */
void showInFrames(Player player, ItemFrame[][] itemFrameMatrix); void showInFrames(Player player, ItemFrame[][] itemFrameMatrix);
@ -124,7 +124,7 @@ public interface MultiMapController extends IMapController {
* Clear the frames * Clear the frames
* *
* @param player {@link Player} that will be able to see the cleared frames * @param player {@link Player} that will be able to see the cleared frames
* @param entityIdMatrix 2D-Array of entity-IDs of the {@link ItemFrame}s (<code>int[width][height]</code>) * @param entityIdMatrix 2D-Array of entity-IDs of the {@link ItemFrame}s (<code>int[rows][columns]</code>)
*/ */
void clearFrames(Player player, Integer[][] entityIdMatrix); void clearFrames(Player player, Integer[][] entityIdMatrix);
@ -132,7 +132,7 @@ public interface MultiMapController extends IMapController {
* Clear the frames * Clear the frames
* *
* @param player {@link Player} that will be able to see the cleared frames * @param player {@link Player} that will be able to see the cleared frames
* @param itemFrameMatrix 2D-Array of {@link ItemFrame}s (<code>ItemFrame[width][height]</code>) * @param itemFrameMatrix 2D-Array of {@link ItemFrame}s (<code>ItemFrame[rows][columns]</code>)
*/ */
void clearFrames(Player player, ItemFrame[][] itemFrameMatrix); void clearFrames(Player player, ItemFrame[][] itemFrameMatrix);
@ -144,11 +144,11 @@ public interface MultiMapController extends IMapController {
* Called to get debug information for a frame * Called to get debug information for a frame
* *
* @param controller the {@link MapController} * @param controller the {@link MapController}
* @param x X-Position of the current frame * @param row Row of the current frame
* @param y Y-Position of the current frame * @param column Column of the current frame
* @return {@link String} to show when a player looks at the map, or <code>null</code> * @return {@link String} to show when a player looks at the map, or <code>null</code>
* @see MapController#showInFrame(Player, int, String) * @see MapController#showInFrame(Player, int, String)
*/ */
String call(MapController controller, int x, int y); String call(MapController controller, int row, int column);
} }
} }

View file

@ -25,52 +25,75 @@ import org.jetbrains.annotations.NotNull;
import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI;
import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException;
import java.awt.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import static tech.sbdevelopment.mapreflectionapi.utils.MainUtil.validateArrayDimensions;
/** /**
* A {@link MultiMapWrapper} wraps one image split in pieces. * A {@link MultiMapWrapper} wraps one image split in pieces.
*/ */
public class MultiMapWrapper extends AbstractMapWrapper { public class MultiMapWrapper extends AbstractMapWrapper {
private final MapWrapper[][] wrapperMatrix; private final MapWrapper[][] wrapperMatrix;
/**
* Creates a new {@link MultiMapWrapper} from the given image.
* The image will be split into the given amount of rows and columns.
*
* @param image The image to wrap
* @param rows The amount of rows
* @param columns The amount of columns
*/
public MultiMapWrapper(BufferedImage image, int rows, int columns) { public MultiMapWrapper(BufferedImage image, int rows, int columns) {
this(splitImage(image, columns, rows)); this(splitImage(image, rows, columns));
} }
/**
* Creates a new {@link MultiMapWrapper} from the given image.
* The image will be split into the given amount of rows and columns.
*
* @param image The image to wrap
* @param rows The amount of rows
* @param columns The amount of columns
*/
public MultiMapWrapper(ArrayImage image, int rows, int columns) { public MultiMapWrapper(ArrayImage image, int rows, int columns) {
this(splitImage(image.toBuffered(), columns, rows)); this(splitImage(image.toBuffered(), rows, columns));
} }
public MultiMapWrapper(ArrayImage[][] imageMatrix) { /**
* Creates a new {@link MultiMapWrapper} from the given image.
*
* @param imageMatrix The image matrix to wrap
*/
protected MultiMapWrapper(ArrayImage[][] imageMatrix) {
wrapperMatrix = new MapWrapper[imageMatrix.length][imageMatrix[0].length]; wrapperMatrix = new MapWrapper[imageMatrix.length][imageMatrix[0].length];
for (int x = 0; x < imageMatrix.length; x++) { for (int row = 0; row < imageMatrix.length; row++) {
if (imageMatrix[x].length != imageMatrix[0].length) { if (imageMatrix[row].length != imageMatrix[0].length) {
throw new IllegalArgumentException("An image in a MultiMapWrapper is not rectangular!"); throw new IllegalArgumentException("An image in a MultiMapWrapper is not rectangular!");
} }
for (int y = 0; y < imageMatrix[x].length; y++) { for (int column = 0; column < imageMatrix[row].length; column++) {
wrapperMatrix[x][y] = MapReflectionAPI.getMapManager().wrapImage(imageMatrix[x][y]); wrapperMatrix[row][column] = MapReflectionAPI.getMapManager().wrapImage(imageMatrix[row][column]);
} }
} }
} }
public MultiMapWrapper(BufferedImage[][] imageMatrix) { /**
* Creates a new {@link MultiMapWrapper} from the given image.
*
* @param imageMatrix The image matrix to wrap
*/
protected MultiMapWrapper(BufferedImage[][] imageMatrix) {
wrapperMatrix = new MapWrapper[imageMatrix.length][imageMatrix[0].length]; wrapperMatrix = new MapWrapper[imageMatrix.length][imageMatrix[0].length];
for (int x = 0; x < imageMatrix.length; x++) { for (int row = 0; row < imageMatrix.length; row++) {
if (imageMatrix[x].length != imageMatrix[0].length) { if (imageMatrix[row].length != imageMatrix[0].length) {
throw new IllegalArgumentException("An image in a MultiMapWrapper is not rectangular!"); throw new IllegalArgumentException("An image in a MultiMapWrapper is not rectangular!");
} }
for (int y = 0; y < imageMatrix[x].length; y++) { for (int column = 0; column < imageMatrix[row].length; column++) {
wrapperMatrix[x][y] = MapReflectionAPI.getMapManager().wrapImage(imageMatrix[x][y]); wrapperMatrix[row][column] = MapReflectionAPI.getMapManager().wrapImage(imageMatrix[row][column]);
} }
} }
} }
@ -117,10 +140,10 @@ public class MultiMapWrapper extends AbstractMapWrapper {
@Override @Override
public void update(@NotNull ArrayImage content) { public void update(@NotNull ArrayImage content) {
ArrayImage[][] split = splitImage(content.toBuffered(), wrapperMatrix[0].length, wrapperMatrix.length); ArrayImage[][] split = splitImage(content.toBuffered(), wrapperMatrix.length, wrapperMatrix[0].length);
for (int x = 0; x < wrapperMatrix.length; x++) { for (int row = 0; row < wrapperMatrix.length; row++) {
for (int y = 0; y < wrapperMatrix[x].length; y++) { for (int column = 0; column < wrapperMatrix[row].length; column++) {
wrapperMatrix[x][y].getController().update(split[x][y]); wrapperMatrix[row][column].getController().update(split[row][column]);
} }
} }
} }
@ -150,33 +173,27 @@ public class MultiMapWrapper extends AbstractMapWrapper {
@Override @Override
public void showInFrames(Player player, Integer[][] entityIdMatrix) { public void showInFrames(Player player, Integer[][] entityIdMatrix) {
validateArrayDimensions(wrapperMatrix, entityIdMatrix); for (int row = 0; row < entityIdMatrix.length; row++) {
for (int column = 0; column < entityIdMatrix[row].length; column++) {
for (int x = 0; x < entityIdMatrix.length; x++) { wrapperMatrix[row][column].getController().showInFrame(player, entityIdMatrix[row][column]);
for (int y = 0; y < entityIdMatrix[x].length; y++) {
wrapperMatrix[y][x].getController().showInFrame(player, entityIdMatrix[x][wrapperMatrix.length - 1 - y]);
} }
} }
} }
@Override @Override
public void showInFrames(Player player, Integer[][] entityIdMatrix, DebugCallable callable) { public void showInFrames(Player player, Integer[][] entityIdMatrix, DebugCallable callable) {
validateArrayDimensions(wrapperMatrix, entityIdMatrix); for (int row = 0; row < entityIdMatrix.length; row++) {
for (int column = 0; column < entityIdMatrix[row].length; column++) {
for (int x = 0; x < entityIdMatrix.length; x++) { wrapperMatrix[row][column].getController().showInFrame(player, entityIdMatrix[row][column], callable.call(wrapperMatrix[row][column].getController(), row, column));
for (int y = 0; y < entityIdMatrix[x].length; y++) {
wrapperMatrix[y][x].getController().showInFrame(player, entityIdMatrix[x][wrapperMatrix.length - 1 - y], callable.call(wrapperMatrix[y][x].getController(), x, y));
} }
} }
} }
@Override @Override
public void showInFrames(Player player, ItemFrame[][] itemFrameMatrix, boolean force) { public void showInFrames(Player player, ItemFrame[][] itemFrameMatrix, boolean force) {
validateArrayDimensions(wrapperMatrix, itemFrameMatrix); for (int row = 0; row < itemFrameMatrix.length; row++) {
for (int column = 0; column < itemFrameMatrix[row].length; column++) {
for (int x = 0; x < itemFrameMatrix.length; x++) { wrapperMatrix[row][column].getController().showInFrame(player, itemFrameMatrix[row][column], force);
for (int y = 0; y < itemFrameMatrix[x].length; y++) {
wrapperMatrix[y][x].getController().showInFrame(player, itemFrameMatrix[x][wrapperMatrix.length - 1 - y], force);
} }
} }
} }
@ -188,47 +205,47 @@ public class MultiMapWrapper extends AbstractMapWrapper {
@Override @Override
public void clearFrames(Player player, Integer[][] entityIdMatrix) { public void clearFrames(Player player, Integer[][] entityIdMatrix) {
validateArrayDimensions(wrapperMatrix, entityIdMatrix); for (int row = 0; row < entityIdMatrix.length; row++) {
for (int column = 0; column < entityIdMatrix[row].length; column++) {
for (int x = 0; x < entityIdMatrix.length; x++) { wrapperMatrix[row][column].getController().clearFrame(player, entityIdMatrix[row][column]);
for (int y = 0; y < entityIdMatrix[x].length; y++) {
wrapperMatrix[y][x].getController().clearFrame(player, entityIdMatrix[x][y]);
} }
} }
} }
@Override @Override
public void clearFrames(Player player, ItemFrame[][] itemFrameMatrix) { public void clearFrames(Player player, ItemFrame[][] itemFrameMatrix) {
validateArrayDimensions(wrapperMatrix, itemFrameMatrix); for (int row = 0; row < itemFrameMatrix.length; row++) {
for (int column = 0; column < itemFrameMatrix[row].length; column++) {
for (int x = 0; x < itemFrameMatrix.length; x++) { wrapperMatrix[row][column].getController().clearFrame(player, itemFrameMatrix[row][column]);
for (int y = 0; y < itemFrameMatrix[x].length; y++) {
wrapperMatrix[y][x].getController().clearFrame(player, itemFrameMatrix[x][y]);
} }
} }
} }
}; };
/* /**
* Modified Method from http://kalanir.blogspot.de/2010/02/how-to-split-image-into-chunks-java.html * Splits a BufferedImage into a matrix of ArrayImages.
*
* @param image The image to split
* @param rows The number of rows
* @param columns The number of columns
* @return The matrix of ArrayImages
*/ */
private static ArrayImage[][] splitImage(final BufferedImage image, final int columns, final int rows) { private static ArrayImage[][] splitImage(final BufferedImage image, final int rows, final int columns) {
int chunkWidth = image.getWidth() / columns; int chunkWidth = image.getWidth() / columns;
int chunkHeight = image.getHeight() / rows; int chunkHeight = image.getHeight() / rows;
ArrayImage[][] images = new ArrayImage[rows][columns]; ArrayImage[][] images = new ArrayImage[rows][columns];
for (int x = 0; x < rows; x++) {
for (int y = 0; y < columns; y++) {
BufferedImage raw = new BufferedImage(chunkWidth, chunkHeight, image.getType());
Graphics2D gr = raw.createGraphics(); for (int i = 0; i < rows; i++) {
gr.drawImage(image, 0, 0, chunkWidth, chunkHeight, chunkWidth * y, chunkHeight * x, chunkWidth * y + chunkWidth, chunkHeight * x + chunkHeight, null); for (int j = 0; j < columns; j++) {
gr.dispose(); int x = j * chunkWidth;
int y = i * chunkHeight;
images[x][y] = new ArrayImage(raw); BufferedImage raw = image.getSubimage(x, y, chunkWidth, chunkHeight);
raw.flush(); images[i][j] = new ArrayImage(raw);
} }
} }
return images; return images;
} }

View file

@ -1,6 +1,6 @@
/* /*
* This file is part of MapReflectionAPI. * This file is part of MapReflectionAPI.
* Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -20,53 +20,40 @@ package tech.sbdevelopment.mapreflectionapi.api.events;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI;
import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; import tech.sbdevelopment.mapreflectionapi.api.MapWrapper;
import tech.sbdevelopment.mapreflectionapi.api.events.types.CancellableEvent;
import tech.sbdevelopment.mapreflectionapi.utils.XMaterial;
/** /**
* This event gets fired when a map in the creative inventory gets updated * This event gets fired when a map in the creative inventory gets updated
*/ */
@RequiredArgsConstructor @RequiredArgsConstructor
@Getter @Getter
public class CreateInventoryMapUpdateEvent extends Event implements Cancellable { public class CreativeInventoryMapUpdateEvent extends CancellableEvent {
private static final HandlerList handlerList = new HandlerList();
@Setter
private boolean cancelled;
private final Player player; private final Player player;
private final int slot; private final int slot;
private final ItemStack item; private final ItemStack item;
private MapWrapper mapWrapper; private MapWrapper mapWrapper;
/** /**
* Construct a new {@link CreateInventoryMapUpdateEvent} * Construct a new {@link CreativeInventoryMapUpdateEvent}
* *
* @param player The player whose inventory is updated * @param player The player whose inventory is updated
* @param slot The new slot * @param slot The new slot
* @param item The item in the new slot * @param item The item in the new slot
* @param isAsync Is this event called async? * @param isAsync Is this event called async?
*/ */
public CreateInventoryMapUpdateEvent(Player player, int slot, ItemStack item, boolean isAsync) { public CreativeInventoryMapUpdateEvent(Player player, int slot, ItemStack item, boolean isAsync) {
super(isAsync); super(isAsync);
this.player = player; this.player = player;
this.slot = slot; this.slot = slot;
this.item = item; this.item = item;
} }
@Override
public @NotNull HandlerList getHandlers() {
return handlerList;
}
/** /**
* Get the {@link MapWrapper} of the map of this event * Get the {@link MapWrapper} of the map of this event
* *
@ -76,7 +63,7 @@ public class CreateInventoryMapUpdateEvent extends Event implements Cancellable
public MapWrapper getMapWrapper() { public MapWrapper getMapWrapper() {
if (mapWrapper == null) { if (mapWrapper == null) {
if (item == null) return null; if (item == null) return null;
if (item.getType() != Material.MAP) return null; if (!XMaterial.FILLED_MAP.isSimilar(item)) return null;
mapWrapper = MapReflectionAPI.getMapManager().getWrapperForId(player, item.getDurability()); mapWrapper = MapReflectionAPI.getMapManager().getWrapperForId(player, item.getDurability());
} }

View file

@ -1,6 +1,6 @@
/* /*
* This file is part of MapReflectionAPI. * This file is part of MapReflectionAPI.
* Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -20,23 +20,15 @@ package tech.sbdevelopment.mapreflectionapi.api.events;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable; import tech.sbdevelopment.mapreflectionapi.api.events.types.CancellableEvent;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/** /**
* This event gets fired when a map creation is cancelled * This event gets fired when a map creation is cancelled
*/ */
@RequiredArgsConstructor @RequiredArgsConstructor
@Getter @Getter
public class MapCancelEvent extends Event implements Cancellable { public class MapCancelEvent extends CancellableEvent {
private static final HandlerList handlerList = new HandlerList();
@Setter
private boolean cancelled;
private final Player player; private final Player player;
private final int id; private final int id;
@ -52,9 +44,4 @@ public class MapCancelEvent extends Event implements Cancellable {
this.player = player; this.player = player;
this.id = id; this.id = id;
} }
@Override
public @NotNull HandlerList getHandlers() {
return handlerList;
}
} }

View file

@ -1,6 +1,6 @@
/* /*
* This file is part of MapReflectionAPI. * This file is part of MapReflectionAPI.
* Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -21,11 +21,9 @@ package tech.sbdevelopment.mapreflectionapi.api.events;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Setter; import lombok.Setter;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; import tech.sbdevelopment.mapreflectionapi.api.ArrayImage;
import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; import tech.sbdevelopment.mapreflectionapi.api.MapWrapper;
import tech.sbdevelopment.mapreflectionapi.api.events.types.Event;
/** /**
* This event gets fired when the content of a {@link MapWrapper} is updated * This event gets fired when the content of a {@link MapWrapper} is updated
@ -33,8 +31,6 @@ import tech.sbdevelopment.mapreflectionapi.api.MapWrapper;
@RequiredArgsConstructor @RequiredArgsConstructor
@Getter @Getter
public class MapContentUpdateEvent extends Event { public class MapContentUpdateEvent extends Event {
private static final HandlerList handlerList = new HandlerList();
private final MapWrapper wrapper; private final MapWrapper wrapper;
private final ArrayImage content; private final ArrayImage content;
@Setter @Setter
@ -52,9 +48,4 @@ public class MapContentUpdateEvent extends Event {
this.wrapper = wrapper; this.wrapper = wrapper;
this.content = content; this.content = content;
} }
@Override
public @NotNull HandlerList getHandlers() {
return handlerList;
}
} }

View file

@ -1,6 +1,6 @@
/* /*
* This file is part of MapReflectionAPI. * This file is part of MapReflectionAPI.
* Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -20,28 +20,20 @@ package tech.sbdevelopment.mapreflectionapi.api.events;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.bukkit.entity.ItemFrame; import org.bukkit.entity.ItemFrame;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI;
import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; import tech.sbdevelopment.mapreflectionapi.api.MapWrapper;
import tech.sbdevelopment.mapreflectionapi.api.events.types.CancellableEvent;
/** /**
* This event gets fired when a player interact with a map * This event gets fired when a player interact with a map
*/ */
@RequiredArgsConstructor @RequiredArgsConstructor
@Getter @Getter
public class MapInteractEvent extends Event implements Cancellable { public class MapInteractEvent extends CancellableEvent {
private static final HandlerList handlerList = new HandlerList();
@Setter
private boolean cancelled;
private final Player player; private final Player player;
private final int entityID; private final int entityID;
private final int action; private final int action;
@ -69,11 +61,6 @@ public class MapInteractEvent extends Event implements Cancellable {
this.hand = hand; this.hand = hand;
} }
@Override
public @NotNull HandlerList getHandlers() {
return handlerList;
}
/** /**
* Get the {@link ItemFrame} the map is in * Get the {@link ItemFrame} the map is in
* *
@ -81,10 +68,8 @@ public class MapInteractEvent extends Event implements Cancellable {
*/ */
@Nullable @Nullable
public ItemFrame getFrame() { public ItemFrame getFrame() {
if (getMapWrapper() == null) return null;
if (frame == null) { if (frame == null) {
frame = getMapWrapper().getController().getItemFrameById(player.getWorld(), entityID); frame = MapReflectionAPI.getMapManager().getItemFrameById(player.getWorld(), entityID);
} }
return frame; return frame;
} }
@ -96,10 +81,11 @@ public class MapInteractEvent extends Event implements Cancellable {
*/ */
@Nullable @Nullable
public MapWrapper getMapWrapper() { public MapWrapper getMapWrapper() {
if (getFrame() == null) return null;
if (mapWrapper == null) { if (mapWrapper == null) {
mapWrapper = MapReflectionAPI.getMapManager().getWrapperForId(player, entityID); if (!frame.hasMetadata(MapWrapper.REFERENCE_METADATA)) return null;
mapWrapper = (MapWrapper) frame.getMetadata(MapWrapper.REFERENCE_METADATA).get(0).value();
} }
return mapWrapper; return mapWrapper;
} }
} }

View file

@ -0,0 +1,54 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2023 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package tech.sbdevelopment.mapreflectionapi.api.events.types;
import lombok.NoArgsConstructor;
import org.bukkit.event.Cancellable;
@NoArgsConstructor
public class CancellableEvent extends Event implements Cancellable {
/**
* If this event gets cancelled.
*/
private boolean cancelled;
public CancellableEvent(boolean isAsync) {
super(isAsync);
}
/**
* Check if this event gets cancelled.
*
* @return true if cancelled, false if not
*/
@Override
public boolean isCancelled() {
return cancelled;
}
/**
* Set if this event gets cancelled.
*
* @param cancelled true if you wish to cancel this event
*/
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
}

View file

@ -0,0 +1,46 @@
/*
* This file is part of MapReflectionAPI.
* Copyright (c) 2023 inventivetalent / SBDevelopment - All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package tech.sbdevelopment.mapreflectionapi.api.events.types;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.bukkit.event.HandlerList;
@NoArgsConstructor
public class Event extends org.bukkit.event.Event {
public Event(boolean isAsync) {
super(isAsync);
}
/**
* A list of EventHandlers listening to this event.
*/
@Getter
private static final HandlerList handlerList = new HandlerList();
/**
* Get the EventHandlers listening to this event.
*
* @return The EventHandlers listening to this event.
*/
@Override
public HandlerList getHandlers() {
return handlerList;
}
}

View file

@ -31,14 +31,16 @@ import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI;
import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; import tech.sbdevelopment.mapreflectionapi.api.events.CreativeInventoryMapUpdateEvent;
import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent;
import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent;
import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil; import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static com.cryptomorin.xseries.reflection.minecraft.MinecraftConnection.getConnection;
import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*; import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*;
import static com.cryptomorin.xseries.reflection.XReflection.*;
public class PacketListener implements Listener { public class PacketListener implements Listener {
private static final Class<?> packetPlayOutMapClass = getNMSClass("network.protocol.game", "PacketPlayOutMap"); private static final Class<?> packetPlayOutMapClass = getNMSClass("network.protocol.game", "PacketPlayOutMap");
@ -46,6 +48,16 @@ public class PacketListener implements Listener {
private static final Class<?> packetPlayInSetCreativeSlotClass = getNMSClass("network.protocol.game", "PacketPlayInSetCreativeSlot"); private static final Class<?> packetPlayInSetCreativeSlotClass = getNMSClass("network.protocol.game", "PacketPlayInSetCreativeSlot");
private static final Class<?> vec3DClass = getNMSClass("world.phys", "Vec3D"); private static final Class<?> vec3DClass = getNMSClass("world.phys", "Vec3D");
private static final Class<?> craftStackClass = getCraftClass("inventory.CraftItemStack"); private static final Class<?> craftStackClass = getCraftClass("inventory.CraftItemStack");
private static final Class<?> playerCommonConnection;
static {
if (supports(20, 2)) {
// The packet send method has been abstracted from ServerGamePacketListenerImpl to ServerCommonPacketListenerImpl in 1.20.2
playerCommonConnection = getNMSClass("server.network", "ServerCommonPacketListenerImpl");
} else {
playerCommonConnection = getNMSClass("server.network", "PlayerConnection");
}
}
@EventHandler @EventHandler
public void onJoin(PlayerJoinEvent e) { public void onJoin(PlayerJoinEvent e) {
@ -61,46 +73,83 @@ public class PacketListener implements Listener {
ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() {
@Override @Override
public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception {
boolean cancel = false;
if (packet.getClass().isAssignableFrom(packetPlayOutMapClass)) { if (packet.getClass().isAssignableFrom(packetPlayOutMapClass)) {
Object packetPlayOutMap = packetPlayOutMapClass.cast(packet); Object packetPlayOutMap = packetPlayOutMapClass.cast(packet);
int id = (int) getDeclaredField(packetPlayOutMap, "a"); int id;
if (id < 0) { boolean inv = false;
int newId = -id; if (supports(20, 4)) { //1.20.4 uses MapId class and record classes (final fields...)
setDeclaredField(packetPlayOutMap, "a", newId); Object mapId = getDeclaredField(packetPlayOutMap, "b");
id = (int) getDeclaredField(mapId, "c");
if (id < 0) {
Object newMapid = callConstructor(mapId.getClass(), -id);
Object c = getDeclaredField(packetPlayOutMap, "c");
Object d = getDeclaredField(packetPlayOutMap, "d");
Object e = getDeclaredField(packetPlayOutMap, "e");
Object f = getDeclaredField(packetPlayOutMap, "f");
packetPlayOutMap = callConstructor(packetPlayOutMapClass, newMapid, c, d, e, f);
packet = packetPlayOutMap;
inv = true;
}
} else { } else {
id = (int) getDeclaredField(packetPlayOutMap, "a");
if (id < 0) {
setDeclaredField(packetPlayOutMap, "a", -id);
inv = true;
}
}
if (!inv) {
boolean async = !MapReflectionAPI.getInstance().getServer().isPrimaryThread(); boolean async = !MapReflectionAPI.getInstance().getServer().isPrimaryThread();
MapCancelEvent event = new MapCancelEvent(player, id, async); MapCancelEvent event = new MapCancelEvent(player, id, async);
if (MapReflectionAPI.getMapManager().isIdUsedBy(player, id)) event.setCancelled(true); if (MapReflectionAPI.getMapManager().isIdUsedBy(player, id)) event.setCancelled(true);
if (event.getHandlers().getRegisteredListeners().length > 0) Bukkit.getPluginManager().callEvent(event);
Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled()) return; if (event.isCancelled()) cancel = true;
} }
} }
super.write(ctx, packet, promise); if (!cancel) super.write(ctx, packet, promise);
} }
@Override @Override
public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception {
boolean cancel = false;
if (packet.getClass().isAssignableFrom(packetPlayInUseEntityClass)) { if (packet.getClass().isAssignableFrom(packetPlayInUseEntityClass)) {
Object packetPlayInEntity = packetPlayInUseEntityClass.cast(packet); Object packetPlayInEntity = packetPlayInUseEntityClass.cast(packet);
int entityId = (int) getDeclaredField(packetPlayInEntity, "a"); int entityId = (int) getDeclaredField(packetPlayInEntity, supports(20, 4) ? "b" : "a");
Enum<?> actionEnum; Enum<?> actionEnum;
Enum<?> hand; Enum<?> hand;
Object pos; Object pos;
if (ReflectionUtil.supports(17)) { if (supports(17)) {
Object action = getDeclaredField(packetPlayInEntity, "b"); Object action = getDeclaredField(packetPlayInEntity, supports(20, 4) ? "c" : "b");
actionEnum = (Enum<?>) callDeclaredMethod(action, "a"); //action type actionEnum = (Enum<?>) callDeclaredMethod(action, "a");
hand = hasField(action, "a") ? (Enum<?>) getDeclaredField(action, "a") : null; Class<?> d = getNMSClass("network.protocol.game", "PacketPlayInUseEntity$d");
pos = hasField(action, "b") ? getDeclaredField(action, "b") : null; Class<?> e = getNMSClass("network.protocol.game", "PacketPlayInUseEntity$e");
if (action.getClass().isAssignableFrom(e)) {
hand = (Enum<?>) getDeclaredField(action, "a");
pos = getDeclaredField(action, "b");
} else {
pos = null;
if (action.getClass().isAssignableFrom(d)) {
hand = (Enum<?>) getDeclaredField(action, "a");
} else {
hand = null;
}
}
} else { } else {
actionEnum = (Enum<?>) callDeclaredMethod(packetPlayInEntity, ReflectionUtil.supports(13) ? "b" : "a"); //1.13 = b, 1.12 = a actionEnum = (Enum<?>) callDeclaredMethod(packetPlayInEntity, supports(13) ? "b" : "a"); //1.13 = b, 1.12 = a
hand = (Enum<?>) callDeclaredMethod(packetPlayInEntity, ReflectionUtil.supports(13) ? "c" : "b"); //1.13 = c, 1.12 = b hand = (Enum<?>) callDeclaredMethod(packetPlayInEntity, supports(13) ? "c" : "b"); //1.13 = c, 1.12 = b
pos = callDeclaredMethod(packetPlayInEntity, ReflectionUtil.supports(13) ? "d" : "c"); //1.13 = d, 1.12 = c pos = callDeclaredMethod(packetPlayInEntity, supports(13) ? "d" : "c"); //1.13 = d, 1.12 = c
} }
if (Bukkit.getScheduler().callSyncMethod(MapReflectionAPI.getInstance(), () -> { if (Bukkit.getScheduler().callSyncMethod(MapReflectionAPI.getInstance(), () -> {
@ -111,23 +160,28 @@ public class PacketListener implements Listener {
return event.isCancelled(); return event.isCancelled();
} }
return false; return false;
}).get(1, TimeUnit.SECONDS)) return; }).get(1, TimeUnit.SECONDS)) cancel = true;
} else if (packet.getClass().isAssignableFrom(packetPlayInSetCreativeSlotClass)) { } else if (packet.getClass().isAssignableFrom(packetPlayInSetCreativeSlotClass)) {
Object packetPlayInSetCreativeSlot = packetPlayInSetCreativeSlotClass.cast(packet); Object packetPlayInSetCreativeSlot = packetPlayInSetCreativeSlotClass.cast(packet);
int slot = (int) ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, ReflectionUtil.supports(19, 3) ? "a" : ReflectionUtil.supports(13) ? "b" : "a"); //1.19.4 = a, 1.19.3 - 1.13 = b, 1.12 = a int slot;
Object nmsStack = ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, ReflectionUtil.supports(18) ? "c" : "getItemStack"); //1.18 = c, 1.17 = getItemStack if (supports(20, 4)) { //1.20.4+ uses short
slot = (short) ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, "b");
} else { //1.20.3 and lower uses int
slot = (int) ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, supports(19, 4) ? "a" : supports(13) ? "b" : "a"); //1.20.4 - 1.19.4 = a, 1.19.3 - 1.13 and 1.20.5 = b, 1.12 = a
}
Object nmsStack = ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, supports(20, 4) ? "e" : supports(20, 2) ? "d" : supports(18) ? "c" : "getItemStack"); //1.20.5 = e, 1.20.2-1.20.4 = d, >= 1.18 = c, 1.17 = getItemStack
ItemStack craftStack = (ItemStack) ReflectionUtil.callMethod(craftStackClass, "asBukkitCopy", nmsStack); ItemStack craftStack = (ItemStack) ReflectionUtil.callMethod(craftStackClass, "asBukkitCopy", nmsStack);
boolean async = !MapReflectionAPI.getInstance().getServer().isPrimaryThread(); boolean async = !MapReflectionAPI.getInstance().getServer().isPrimaryThread();
CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(player, slot, craftStack, async); CreativeInventoryMapUpdateEvent event = new CreativeInventoryMapUpdateEvent(player, slot, craftStack, async);
if (event.getMapWrapper() != null) { if (event.getMapWrapper() != null) {
Bukkit.getPluginManager().callEvent(event); Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled()) return; if (event.isCancelled()) cancel = true;
} }
} }
super.channelRead(ctx, packet); if (!cancel) super.channelRead(ctx, packet);
} }
}; };
@ -141,19 +195,17 @@ public class PacketListener implements Listener {
} }
private Channel getChannel(Player player) { private Channel getChannel(Player player) {
Object playerHandle = getHandle(player); Object networkManager = getDeclaredField(playerCommonConnection, getConnection(player), supports(21) ? "e" : supports(20, 2) ? "c" : supports(19, 4) ? "h" : supports(19) ? "b" : supports(17) ? "a" : "networkManager"); //1.20.2 = ServerCommonPacketListenerImpl#c, 1.20(.1) & 1.19.4 = h, >= 1.19.3 = b, 1.18 - 1.17 = a, 1.16 = networkManager
Object playerConnection = getDeclaredField(playerHandle, ReflectionUtil.supports(20) ? "c" : ReflectionUtil.supports(17) ? "b" : "playerConnection"); //1.20 = c, 1.17-1.19 = b, 1.16 = playerConnection return (Channel) getDeclaredField(networkManager, supports(20, 2) ? "n" : supports(18) ? "m" : supports(17) ? "k" : "channel"); //1.20.2 = n, 1.20(.1), 1.19 & 1.18 = m, 1.17 = k, 1.16 = channel
Object networkManager = getDeclaredField(playerConnection, ReflectionUtil.supports(19, 3) ? "h" : ReflectionUtil.supports(19) ? "b" : ReflectionUtil.supports(17) ? "a" : "networkManager"); //1.19.4 = h, >= 1.19.3 = b, 1.18 = a, 1.16 = networkManager
return (Channel) getDeclaredField(networkManager, ReflectionUtil.supports(18) ? "m" : ReflectionUtil.supports(17) ? "k" : "channel"); //1.19 & 1.18 = m, 1.17 = k, 1.16 = channel
} }
private Vector vec3DToVector(Object vec3d) { private Vector vec3DToVector(Object vec3d) {
if (!(vec3d.getClass().isAssignableFrom(vec3DClass))) return new Vector(0, 0, 0); if (!(vec3d.getClass().isAssignableFrom(vec3DClass))) return new Vector(0, 0, 0);
Object vec3dNMS = vec3DClass.cast(vec3d); Object vec3dNMS = vec3DClass.cast(vec3d);
double x = (double) ReflectionUtil.getDeclaredField(vec3dNMS, ReflectionUtil.supports(19) ? "c" : ReflectionUtil.supports(17) ? "b" : "x"); //1.19 = c, 1.18 = b, 1.16 = x double x = (double) getDeclaredField(vec3dNMS, supports(21, 2) ? "d" : supports(19) ? "c" : supports(17) ? "b" : "x"); //1.21.2+ = d, 1.19 = c, 1.18 = b, 1.16 = x
double y = (double) ReflectionUtil.getDeclaredField(vec3dNMS, ReflectionUtil.supports(19) ? "d" : ReflectionUtil.supports(17) ? "c" : "y"); //1.19 = d, 1.18 = c, 1.16 = y double y = (double) getDeclaredField(vec3dNMS, supports(21, 2) ? "e" : supports(19) ? "d" : supports(17) ? "c" : "y"); //1.21.2+ = e, 1.19 = d, 1.18 = c, 1.16 = y
double z = (double) ReflectionUtil.getDeclaredField(vec3dNMS, ReflectionUtil.supports(19) ? "e" : ReflectionUtil.supports(17) ? "d" : "z"); //1.19 = e, 1.18 = d, 1.16 = z double z = (double) getDeclaredField(vec3dNMS, supports(21, 2) ? "f" : supports(19) ? "e" : supports(17) ? "d" : "z"); //1.21.2+ = f, 1.19 = e, 1.18 = d, 1.16 = z
return new Vector(x, y, z); return new Vector(x, y, z);
} }

View file

@ -18,6 +18,9 @@
package tech.sbdevelopment.mapreflectionapi.utils; package tech.sbdevelopment.mapreflectionapi.utils;
import java.util.Map;
import java.util.function.Supplier;
public class MainUtil { public class MainUtil {
private MainUtil() { private MainUtil() {
} }
@ -35,10 +38,4 @@ public class MainUtil {
return true; return true;
} }
} }
public static <A, B> void validateArrayDimensions(A[][] arrayOne, B[][] arrayTwo) {
if (arrayOne.length != arrayTwo.length || arrayOne[0].length != arrayTwo[0].length) {
throw new IllegalArgumentException("The dimensions of two provided arrays (" + arrayOne.getClass().getName() + ", " + arrayTwo.getClass().getName() + ") do not match!");
}
}
} }

View file

@ -19,186 +19,23 @@
package tech.sbdevelopment.mapreflectionapi.utils; package tech.sbdevelopment.mapreflectionapi.utils;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import javax.annotation.Nonnull;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture;
/** import static com.cryptomorin.xseries.reflection.XReflection.getCraftClass;
* <b>ReflectionUtil</b> - Reflection handler for NMS and CraftBukkit.<br> import static com.cryptomorin.xseries.reflection.XReflection.getNMSClass;
* Caches the packet related methods and is asynchronous.
* <p>
* This class does not handle null checks as most of the requests are from the
* other utility classes that already handle null checks.
* <p>
* <a href="https://wiki.vg/Protocol">Clientbound Packets</a> are considered fake
* updates to the client without changing the actual data. Since all the data is handled
* by the server.
*
* @author Crypto Morin, Stijn Bannink
* @version 2.1
*/
public class ReflectionUtil { public class ReflectionUtil {
/** private static final Map<String, Constructor<?>> constructorCache = new HashMap<>();
* We use reflection mainly to avoid writing a new class for version barrier. private static final Map<String, Method> methodCache = new HashMap<>();
* The version barrier is for NMS that uses the Minecraft version as the main package name. private static final Map<String, Field> fieldCache = new HashMap<>();
* <p> private static final Class<?> craftWorld = getCraftClass("CraftWorld");
* E.g. EntityPlayer in 1.15 is in the class {@code net.minecraft.server.v1_15_R1}
* but in 1.14 it's in {@code net.minecraft.server.v1_14_R1}
* In order to maintain cross-version compatibility we cannot import these classes.
* <p>
* Performance is not a concern for these specific statically initialized values.
*/
public static final String VERSION;
static { // This needs to be right below VERSION because of initialization order.
// This package loop is used to avoid implementation-dependant strings like Bukkit.getVersion() or Bukkit.getBukkitVersion()
// which allows easier testing as well.
String found = null;
for (Package pack : Package.getPackages()) {
String name = pack.getName();
// .v because there are other packages.
if (name.startsWith("org.bukkit.craftbukkit.v")) {
found = pack.getName().split("\\.")[3];
// Just a final guard to make sure it finds this important class.
// As a protection for forge+bukkit implementation that tend to mix versions.
// The real CraftPlayer should exist in the package.
// Note: Doesn't seem to function properly. Will need to separate the version
// handler for NMS and CraftBukkit for softwares like catmc.
try {
Class.forName("org.bukkit.craftbukkit." + found + ".entity.CraftPlayer");
break;
} catch (ClassNotFoundException e) {
found = null;
}
}
}
if (found == null)
throw new IllegalArgumentException("Failed to parse server version. Could not find any package starting with name: 'org.bukkit.craftbukkit.v'");
VERSION = found;
}
/**
* The raw minor version number.
* E.g. {@code v1_17_R1} to {@code 17}
*
* @since 4.0.0
*/
public static final int VER = Integer.parseInt(VERSION.substring(1).split("_")[1]);
/**
* The raw minor version number.
* E.g. {@code v1_18_R2} to {@code 2}
*
* @since 4.0.0
*/
public static final int VER_MINOR = toInt(VERSION.substring(1).split("_")[2].substring(1), 0);
/**
* Mojang remapped their NMS in 1.17 https://www.spigotmc.org/threads/spigot-bungeecord-1-17.510208/#post-4184317
*/
public static final String
CRAFTBUKKIT = "org.bukkit.craftbukkit." + VERSION + '.',
NMS = v(17, "net.minecraft.").orElse("net.minecraft.server." + VERSION + '.');
/**
* A nullable public accessible field only available in {@code EntityPlayer}.
* This can be null if the player is offline.
*/
private static final MethodHandle PLAYER_CONNECTION;
/**
* Responsible for getting the NMS handler {@code EntityPlayer} object for the player.
* {@code CraftPlayer} is simply a wrapper for {@code EntityPlayer}.
* Used mainly for handling packet related operations.
* <p>
* This is also where the famous player {@code ping} field comes from!
*/
private static final MethodHandle GET_HANDLE;
private static final MethodHandle GET_HANDLE_WORLD;
/**
* Sends a packet to the player's client through a {@code NetworkManager} which
* is where {@code ProtocolLib} controls packets by injecting channels!
*/
private static final MethodHandle SEND_PACKET;
static {
Class<?> entityPlayer = getNMSClass("server.level", "EntityPlayer");
Class<?> worldServer = getNMSClass("server.level", "WorldServer");
Class<?> craftPlayer = getCraftClass("entity.CraftPlayer");
Class<?> craftWorld = getCraftClass("CraftWorld");
Class<?> playerConnection = getNMSClass("server.network", "PlayerConnection");
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle sendPacket = null;
MethodHandle getHandle = null;
MethodHandle getHandleWorld = null;
MethodHandle connection = null;
try {
connection = lookup.findGetter(entityPlayer,
supports(20) ? "c" : supports(17) ? "b" : "playerConnection", playerConnection);
getHandle = lookup.findVirtual(craftPlayer, "getHandle", MethodType.methodType(entityPlayer));
getHandleWorld = lookup.findVirtual(craftWorld, "getHandle", MethodType.methodType(worldServer));
sendPacket = lookup.findVirtual(playerConnection,
v(18, "a").orElse("sendPacket"),
MethodType.methodType(void.class, getNMSClass("network.protocol", "Packet")));
} catch (NoSuchMethodException | NoSuchFieldException | IllegalAccessException ex) {
ex.printStackTrace();
}
PLAYER_CONNECTION = connection;
SEND_PACKET = sendPacket;
GET_HANDLE = getHandle;
GET_HANDLE_WORLD = getHandleWorld;
}
private ReflectionUtil() {
}
/**
* This method is purely for readability.
* No performance is gained.
*
* @since 5.0.0
*/
public static <T> VersionHandler<T> v(int version, T handle) {
return new VersionHandler<>(version, handle);
}
/**
* Checks whether the server version is equal or greater than the given version.
*
* @param version the version to compare the server version with.
* @return true if the version is equal or newer, otherwise false.
* @since 4.0.0
*/
public static boolean supports(int version) {
return VER >= version;
}
/**
* Checks whether the server version is equal or greater than the given version.
* <p>
* PAY ATTENTION! The minor version is based on the NMS version.
* This means that v1_19_R3 has major version 19 and minor version 3.
*
* @param major the major version to compare the server version with.
* @param minor the minor version to compare the server version with.
* @return true if the version is equal or newer, otherwise false.
* @since 4.0.0
*/
public static boolean supports(int major, int minor) {
return VER >= major && VER_MINOR >= minor;
}
/** /**
* Helper class converted to {@link List} * Helper class converted to {@link List}
@ -206,7 +43,6 @@ public class ReflectionUtil {
* @param <E> The storage type * @param <E> The storage type
*/ */
public static class ListParam<E> extends ArrayList<E> { public static class ListParam<E> extends ArrayList<E> {
} }
/** /**
@ -215,7 +51,6 @@ public class ReflectionUtil {
* @param <E> The storage type * @param <E> The storage type
*/ */
public static class CollectionParam<E> extends ArrayList<E> { public static class CollectionParam<E> extends ArrayList<E> {
} }
private static Class<?> wrapperToPrimitive(Class<?> clazz) { private static Class<?> wrapperToPrimitive(Class<?> clazz) {
@ -241,6 +76,11 @@ public class ReflectionUtil {
.toArray(Class<?>[]::new); .toArray(Class<?>[]::new);
} }
@Nullable
public static Object getHandle(@NotNull World world) {;
return callDeclaredMethod(craftWorld, world, "getHandle");
}
@Nullable @Nullable
public static Class<?> getClass(@NotNull String name) { public static Class<?> getClass(@NotNull String name) {
try { try {
@ -254,9 +94,17 @@ public class ReflectionUtil {
@Nullable @Nullable
public static Object callConstructorNull(Class<?> clazz, Class<?> paramClass) { public static Object callConstructorNull(Class<?> clazz, Class<?> paramClass) {
try { try {
Constructor<?> con = clazz.getConstructor(paramClass); String cacheKey = "ConstructorNull:" + clazz.getName() + ":" + paramClass.getName();
con.setAccessible(true);
return con.newInstance(clazz.cast(null)); if (constructorCache.containsKey(cacheKey)) {
Constructor<?> cachedConstructor = constructorCache.get(cacheKey);
return cachedConstructor.newInstance(clazz.cast(null));
} else {
Constructor<?> con = clazz.getConstructor(paramClass);
con.setAccessible(true);
constructorCache.put(cacheKey, con);
return con.newInstance(clazz.cast(null));
}
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException | } catch (NoSuchMethodException | IllegalAccessException | InstantiationException |
InvocationTargetException ex) { InvocationTargetException ex) {
ex.printStackTrace(); ex.printStackTrace();
@ -267,9 +115,17 @@ public class ReflectionUtil {
@Nullable @Nullable
public static Object callFirstConstructor(Class<?> clazz, Object... params) { public static Object callFirstConstructor(Class<?> clazz, Object... params) {
try { try {
Constructor<?> con = clazz.getConstructors()[0]; String cacheKey = "FirstConstructor:" + clazz.getName();
con.setAccessible(true);
return con.newInstance(params); if (constructorCache.containsKey(cacheKey)) {
Constructor<?> cachedConstructor = constructorCache.get(cacheKey);
return cachedConstructor.newInstance(params);
} else {
Constructor<?> con = clazz.getConstructors()[0];
con.setAccessible(true);
constructorCache.put(cacheKey, con);
return con.newInstance(params);
}
} catch (IllegalAccessException | InstantiationException | } catch (IllegalAccessException | InstantiationException |
InvocationTargetException ex) { InvocationTargetException ex) {
ex.printStackTrace(); ex.printStackTrace();
@ -280,9 +136,17 @@ public class ReflectionUtil {
@Nullable @Nullable
public static Object callConstructor(Class<?> clazz, Object... params) { public static Object callConstructor(Class<?> clazz, Object... params) {
try { try {
Constructor<?> con = clazz.getConstructor(toParamTypes(params)); String cacheKey = "Constructor:" + clazz.getName() + ":" + Arrays.hashCode(params);
con.setAccessible(true);
return con.newInstance(params); if (constructorCache.containsKey(cacheKey)) {
Constructor<?> cachedConstructor = constructorCache.get(cacheKey);
return cachedConstructor.newInstance(params);
} else {
Constructor<?> con = clazz.getConstructor(toParamTypes(params));
con.setAccessible(true);
constructorCache.put(cacheKey, con);
return con.newInstance(params);
}
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException | } catch (NoSuchMethodException | IllegalAccessException | InstantiationException |
InvocationTargetException ex) { InvocationTargetException ex) {
ex.printStackTrace(); ex.printStackTrace();
@ -293,9 +157,17 @@ public class ReflectionUtil {
@Nullable @Nullable
public static Object callDeclaredConstructor(Class<?> clazz, Object... params) { public static Object callDeclaredConstructor(Class<?> clazz, Object... params) {
try { try {
Constructor<?> con = clazz.getDeclaredConstructor(toParamTypes(params)); String cacheKey = "DeclaredConstructor:" + clazz.getName() + ":" + Arrays.hashCode(params);
con.setAccessible(true);
return con.newInstance(params); if (constructorCache.containsKey(cacheKey)) {
Constructor<?> cachedConstructor = constructorCache.get(cacheKey);
return cachedConstructor.newInstance(params);
} else {
Constructor<?> con = clazz.getDeclaredConstructor(toParamTypes(params));
con.setAccessible(true);
constructorCache.put(cacheKey, con);
return con.newInstance(params);
}
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException | } catch (NoSuchMethodException | IllegalAccessException | InstantiationException |
InvocationTargetException ex) { InvocationTargetException ex) {
ex.printStackTrace(); ex.printStackTrace();
@ -306,9 +178,17 @@ public class ReflectionUtil {
@Nullable @Nullable
public static Object callMethod(Class<?> clazz, String method, Object... params) { public static Object callMethod(Class<?> clazz, String method, Object... params) {
try { try {
Method m = clazz.getMethod(method, toParamTypes(params)); String cacheKey = "Method:" + clazz.getName() + ":" + method + ":" + Arrays.hashCode(params);
m.setAccessible(true);
return m.invoke(null, params); if (methodCache.containsKey(cacheKey)) {
Method cachedMethod = methodCache.get(cacheKey);
return cachedMethod.invoke(null, params);
} else {
Method m = clazz.getMethod(method, toParamTypes(params));
m.setAccessible(true);
methodCache.put(cacheKey, m);
return m.invoke(null, params);
}
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
ex.printStackTrace(); ex.printStackTrace();
return null; return null;
@ -318,9 +198,17 @@ public class ReflectionUtil {
@Nullable @Nullable
public static Object callMethod(Object obj, String method, Object... params) { public static Object callMethod(Object obj, String method, Object... params) {
try { try {
Method m = obj.getClass().getMethod(method, toParamTypes(params)); String cacheKey = "Method:" + obj.getClass().getName() + ":" + method + ":" + Arrays.hashCode(params);
m.setAccessible(true);
return m.invoke(obj, params); if (methodCache.containsKey(cacheKey)) {
Method cachedMethod = methodCache.get(cacheKey);
return cachedMethod.invoke(obj, params);
} else {
Method m = obj.getClass().getMethod(method, toParamTypes(params));
m.setAccessible(true);
methodCache.put(cacheKey, m);
return m.invoke(obj, params);
}
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
ex.printStackTrace(); ex.printStackTrace();
return null; return null;
@ -330,9 +218,37 @@ public class ReflectionUtil {
@Nullable @Nullable
public static Object callDeclaredMethod(Object obj, String method, Object... params) { public static Object callDeclaredMethod(Object obj, String method, Object... params) {
try { try {
Method m = obj.getClass().getDeclaredMethod(method, toParamTypes(params)); String cacheKey = "DeclaredMethod:" + obj.getClass().getName() + ":" + method + ":" + Arrays.hashCode(params);
m.setAccessible(true);
return m.invoke(obj, params); if (methodCache.containsKey(cacheKey)) {
Method cachedMethod = methodCache.get(cacheKey);
return cachedMethod.invoke(obj, params);
} else {
Method m = obj.getClass().getDeclaredMethod(method, toParamTypes(params));
m.setAccessible(true);
methodCache.put(cacheKey, m);
return m.invoke(obj, params);
}
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
ex.printStackTrace();
return null;
}
}
@Nullable
public static Object callDeclaredMethod(Class<?> clazz, Object obj, String method, Object... params) {
try {
String cacheKey = "DeclaredMethod:" + clazz.getName() + ":" + method + ":" + Arrays.hashCode(params);
if (methodCache.containsKey(cacheKey)) {
Method cachedMethod = methodCache.get(cacheKey);
return cachedMethod.invoke(obj, params);
} else {
Method m = clazz.getDeclaredMethod(method, toParamTypes(params));
m.setAccessible(true);
methodCache.put(cacheKey, m);
return m.invoke(obj, params);
}
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
ex.printStackTrace(); ex.printStackTrace();
return null; return null;
@ -341,8 +257,15 @@ public class ReflectionUtil {
public static boolean hasField(Object packet, String field) { public static boolean hasField(Object packet, String field) {
try { try {
packet.getClass().getDeclaredField(field); String cacheKey = "HasField:" + packet.getClass().getName() + ":" + field;
return true;
if (fieldCache.containsKey(cacheKey)) {
return true;
} else {
packet.getClass().getDeclaredField(field);
fieldCache.put(cacheKey, null);
return true;
}
} catch (NoSuchFieldException ex) { } catch (NoSuchFieldException ex) {
return false; return false;
} }
@ -351,9 +274,17 @@ public class ReflectionUtil {
@Nullable @Nullable
public static Object getField(Object object, String field) { public static Object getField(Object object, String field) {
try { try {
Field f = object.getClass().getField(field); String cacheKey = "Field:" + object.getClass().getName() + ":" + field;
f.setAccessible(true);
return f.get(object); if (fieldCache.containsKey(cacheKey)) {
Field cachedField = fieldCache.get(cacheKey);
return cachedField.get(object);
} else {
Field f = object.getClass().getField(field);
f.setAccessible(true);
fieldCache.put(cacheKey, f);
return f.get(object);
}
} catch (NoSuchFieldException | IllegalAccessException ex) { } catch (NoSuchFieldException | IllegalAccessException ex) {
ex.printStackTrace(); ex.printStackTrace();
return null; return null;
@ -363,9 +294,17 @@ public class ReflectionUtil {
@Nullable @Nullable
public static Object getDeclaredField(Class<?> clazz, String field) { public static Object getDeclaredField(Class<?> clazz, String field) {
try { try {
Field f = clazz.getDeclaredField(field); String cacheKey = "DeclaredField:" + clazz.getName() + ":" + field;
f.setAccessible(true);
return f.get(null); if (fieldCache.containsKey(cacheKey)) {
Field cachedField = fieldCache.get(cacheKey);
return cachedField.get(null);
} else {
Field f = clazz.getDeclaredField(field);
f.setAccessible(true);
fieldCache.put(cacheKey, f);
return f.get(null);
}
} catch (NoSuchFieldException | IllegalAccessException ex) { } catch (NoSuchFieldException | IllegalAccessException ex) {
ex.printStackTrace(); ex.printStackTrace();
return null; return null;
@ -375,9 +314,37 @@ public class ReflectionUtil {
@Nullable @Nullable
public static Object getDeclaredField(Object object, String field) { public static Object getDeclaredField(Object object, String field) {
try { try {
Field f = object.getClass().getDeclaredField(field); String cacheKey = "DeclaredField:" + object.getClass().getName() + ":" + field;
f.setAccessible(true);
return f.get(object); if (fieldCache.containsKey(cacheKey)) {
Field cachedField = fieldCache.get(cacheKey);
return cachedField.get(object);
} else {
Field f = object.getClass().getDeclaredField(field);
f.setAccessible(true);
fieldCache.put(cacheKey, f);
return f.get(object);
}
} catch (NoSuchFieldException | IllegalAccessException ex) {
ex.printStackTrace();
return null;
}
}
@Nullable
public static Object getDeclaredField(Class<?> clazz, Object object, String field) {
try {
String cacheKey = "DeclaredField:" + clazz.getName() + ":" + field;
if (fieldCache.containsKey(cacheKey)) {
Field cachedField = fieldCache.get(cacheKey);
return cachedField.get(object);
} else {
Field f = clazz.getDeclaredField(field);
f.setAccessible(true);
fieldCache.put(cacheKey, f);
return f.get(object);
}
} catch (NoSuchFieldException | IllegalAccessException ex) { } catch (NoSuchFieldException | IllegalAccessException ex) {
ex.printStackTrace(); ex.printStackTrace();
return null; return null;
@ -386,152 +353,19 @@ public class ReflectionUtil {
public static void setDeclaredField(Object object, String field, Object value) { public static void setDeclaredField(Object object, String field, Object value) {
try { try {
Field f = object.getClass().getDeclaredField(field); String cacheKey = "DeclaredField:" + object.getClass().getName() + ":" + field;
f.setAccessible(true);
f.set(object, value); if (fieldCache.containsKey(cacheKey)) {
Field cachedField = fieldCache.get(cacheKey);
cachedField.set(object, value);
} else {
Field f = object.getClass().getDeclaredField(field);
f.setAccessible(true);
fieldCache.put(cacheKey, f);
f.set(object, value);
}
} catch (NoSuchFieldException | IllegalAccessException ex) { } catch (NoSuchFieldException | IllegalAccessException ex) {
ex.printStackTrace(); ex.printStackTrace();
} }
} }
/**
* Get a NMS (net.minecraft.server) class which accepts a package for 1.17 compatibility.
*
* @param newPackage the 1.17 package name.
* @param name the name of the class.
* @return the NMS class or null if not found.
* @since 4.0.0
*/
@javax.annotation.Nullable
public static Class<?> getNMSClass(@Nonnull String newPackage, @Nonnull String name) {
if (supports(17)) name = newPackage + '.' + name;
return getNMSClass(name);
}
/**
* Get a NMS (net.minecraft.server) class.
*
* @param name the name of the class.
* @return the NMS class or null if not found.
* @since 1.0.0
*/
@javax.annotation.Nullable
public static Class<?> getNMSClass(@Nonnull String name) {
try {
return Class.forName(NMS + name);
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
return null;
}
}
/**
* Sends a packet to the player asynchronously if they're online.
* Packets are thread-safe.
*
* @param player the player to send the packet to.
* @param packets the packets to send.
* @return the async thread handling the packet.
* @see #sendPacketSync(Player, Object...)
* @since 1.0.0
*/
@Nonnull
public static CompletableFuture<Void> sendPacket(@Nonnull Player player, @Nonnull Object... packets) {
return CompletableFuture.runAsync(() -> sendPacketSync(player, packets))
.exceptionally(ex -> {
ex.printStackTrace();
return null;
});
}
/**
* Sends a packet to the player synchronously if they're online.
*
* @param player the player to send the packet to.
* @param packets the packets to send.
* @see #sendPacket(Player, Object...)
* @since 2.0.0
*/
public static void sendPacketSync(@Nonnull Player player, @Nonnull Object... packets) {
try {
Object handle = GET_HANDLE.invoke(player);
Object connection = PLAYER_CONNECTION.invoke(handle);
// Checking if the connection is not null is enough. There is no need to check if the player is online.
if (connection != null) {
for (Object packet : packets) SEND_PACKET.invoke(connection, packet);
}
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
@javax.annotation.Nullable
public static Object getHandle(@Nonnull Player player) {
Objects.requireNonNull(player, "Cannot get handle of null player");
try {
return GET_HANDLE.invoke(player);
} catch (Throwable throwable) {
throwable.printStackTrace();
return null;
}
}
@javax.annotation.Nullable
public static Object getHandle(@Nonnull World world) {
Objects.requireNonNull(world, "Cannot get handle of null world");
try {
return GET_HANDLE_WORLD.invoke(world);
} catch (Throwable throwable) {
throwable.printStackTrace();
return null;
}
}
/**
* Get a CraftBukkit (org.bukkit.craftbukkit) class.
*
* @param name the name of the class to load.
* @return the CraftBukkit class or null if not found.
* @since 1.0.0
*/
@javax.annotation.Nullable
public static Class<?> getCraftClass(@Nonnull String name) {
try {
return Class.forName(CRAFTBUKKIT + name);
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
return null;
}
}
public static final class VersionHandler<T> {
private int version;
private T handle;
private VersionHandler(int version, T handle) {
if (supports(version)) {
this.version = version;
this.handle = handle;
}
}
public VersionHandler<T> v(int version, T handle) {
if (version == this.version)
throw new IllegalArgumentException("Cannot have duplicate version handles for version: " + version);
if (version > this.version && supports(version)) {
this.version = version;
this.handle = handle;
}
return this;
}
public T orElse(T handle) {
return this.version == 0 ? handle : this.handle;
}
}
private static int toInt(String string, int def) {
return string.isBlank() ? def : Integer.parseInt(string);
}
} }

View file

@ -143,14 +143,12 @@ public class UpdateManager {
File pluginFile = getPluginFile(); // /plugins/XXX.jar File pluginFile = getPluginFile(); // /plugins/XXX.jar
if (pluginFile == null) { if (pluginFile == null) {
this.downloadResponse.accept(DownloadResponse.ERROR, null); this.downloadResponse.accept(DownloadResponse.ERROR, null);
Bukkit.getLogger().info("Pluginfile is null");
return; return;
} }
File updateFolder = Bukkit.getUpdateFolderFile(); File updateFolder = Bukkit.getUpdateFolderFile();
if (!updateFolder.exists()) { if (!updateFolder.exists()) {
if (!updateFolder.mkdirs()) { if (!updateFolder.mkdirs()) {
this.downloadResponse.accept(DownloadResponse.ERROR, null); this.downloadResponse.accept(DownloadResponse.ERROR, null);
Bukkit.getLogger().info("Updatefolder doesn't exists, and can't be made");
return; return;
} }
} }

File diff suppressed because it is too large Load diff