v1.6.4: Added support for 1.20.5, 1.20.6 and 1.21 #30

Merged
stijnb1234 merged 9 commits from development into master 2024-06-30 22:46:01 +02:00
23 changed files with 2685 additions and 738 deletions

View file

@ -10,10 +10,10 @@ jobs:
steps:
- uses: actions/checkout@v1
- name: Set up JDK 11
- name: Set up JDK 21
uses: actions/setup-java@v1
with:
java-version: 11
java-version: 21
- name: Build with Maven
run: mvn -B package --file pom.xml

3
.idea/misc.xml generated
View file

@ -29,9 +29,8 @@
<option value="$PROJECT_DIR$/NMS-v1_19_R1/pom.xml" />
</set>
</option>
<option name="workspaceImportForciblyTurnedOn" value="true" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="temurin-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" />
</component>
</project>

2
.idea/vcs.xml generated
View file

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

37
pom.xml
View file

@ -24,7 +24,7 @@
<groupId>tech.sbdevelopment</groupId>
<artifactId>MapReflectionAPI</artifactId>
<version>1.6.3</version>
<version>1.6.4</version>
<packaging>jar</packaging>
<name>MapReflectionAPI</name>
@ -48,14 +48,14 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<version>3.13.0</version>
<configuration>
<release>11</release>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<version>1.18.34</version>
</path>
</annotationProcessorPaths>
</configuration>
@ -63,7 +63,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.3.0</version>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
@ -81,6 +81,10 @@
<pattern>org.bstats</pattern>
<shadedPattern>tech.sbdevelopment.mapreflectionapi.libs.bstats</shadedPattern>
</relocation>
<relocation>
<pattern>com.cryptomorin.xseries</pattern>
<shadedPattern>tech.sbdevelopment.mapreflectionapi.libs.xseries</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
@ -103,12 +107,19 @@
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.4.0</version>
<version>3.7.0</version>
<configuration>
<release>11</release>
<sourcepath>${maven.lombok.delombok-target}</sourcepath>
@ -161,34 +172,38 @@
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.20.4-R0.1-SNAPSHOT</version>
<version>1.21-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<version>1.18.34</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.bstats</groupId>
<artifactId>bstats-bukkit</artifactId>
<version>3.0.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.github.cryptomorin</groupId>
<artifactId>XSeries</artifactId>
<version>11.2.0</version>
</dependency>
<!-- Libraries below are provided by Spigot -->
<!-- Libraries below are provided by CraftBukkit -->
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations-java5</artifactId>
<version>23.0.0</version>
<version>24.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport</artifactId>
<version>4.1.77.Final</version>
<version>4.1.97.Final</version>
<scope>provided</scope>
</dependency>
</dependencies>

View file

@ -27,7 +27,7 @@ import java.awt.*;
import java.io.InputStream;
import java.util.Arrays;
import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.supports;
import static com.cryptomorin.xseries.reflection.XReflection.supports;
/**
* Additional functionality on top of Bukkit's MapPalette

View file

@ -30,12 +30,11 @@ import tech.sbdevelopment.mapreflectionapi.listeners.MapListener;
import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener;
import tech.sbdevelopment.mapreflectionapi.managers.Configuration;
import tech.sbdevelopment.mapreflectionapi.utils.MainUtil;
import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil;
import tech.sbdevelopment.mapreflectionapi.utils.UpdateManager;
import java.util.logging.Level;
import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.supports;
import static com.cryptomorin.xseries.reflection.XReflection.supports;
public class MapReflectionAPI extends JavaPlugin {
private static MapReflectionAPI instance;
@ -70,7 +69,7 @@ public class MapReflectionAPI extends JavaPlugin {
getLogger().info("Made by © Copyright SBDevelopment 2023");
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);
return;
}

View file

@ -19,7 +19,6 @@
package tech.sbdevelopment.mapreflectionapi.api;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.entity.ItemFrame;
import org.bukkit.entity.Player;
@ -117,13 +116,4 @@ public interface MapController extends IMapController {
* @param frame {@link ItemFrame} to clear
*/
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;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.entity.ItemFrame;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable;
import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException;
import tech.sbdevelopment.mapreflectionapi.managers.Configuration;
import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil;
import java.awt.image.BufferedImage;
import java.util.HashSet;
@ -30,6 +33,8 @@ import java.util.List;
import java.util.Set;
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.
*/
@ -172,6 +177,30 @@ public class MapManager {
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
*

View file

@ -27,7 +27,8 @@ import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil;
import java.util.ArrayList;
import java.util.List;
import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.*;
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.
@ -86,6 +87,7 @@ public class MapSender {
private static final Class<?> packetPlayOutMapClass = getNMSClass("network.protocol.game", "PacketPlayOutMap");
private static final Class<?> worldMapData = supports(17) ? getNMSClass("world.level.saveddata.maps", "WorldMap$b") : null;
private static final Class<?> mapId = supports(21) ? getNMSClass("world.level.saveddata.maps", "MapId") : null;
/**
* Send a map to a player
@ -110,9 +112,28 @@ public class MapSender {
return;
}
final int id = -id0;
int id = -id0;
Object packet;
if (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(getNMSClass("world.level.saveddata.maps", "MapId"), 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,
content.minX, //X pos
content.minY, //Y pos

View file

@ -19,7 +19,9 @@
package tech.sbdevelopment.mapreflectionapi.api;
import lombok.Getter;
import org.bukkit.*;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.ItemFrame;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
@ -31,6 +33,7 @@ import tech.sbdevelopment.mapreflectionapi.api.events.MapContentUpdateEvent;
import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException;
import tech.sbdevelopment.mapreflectionapi.managers.Configuration;
import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil;
import tech.sbdevelopment.mapreflectionapi.utils.XMaterial;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@ -38,22 +41,18 @@ import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.*;
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.
*/
@Getter
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;
private static final Material MAP_MATERIAL;
static {
MAP_MATERIAL = supports(13) ? Material.FILLED_MAP : Material.MAP;
}
/**
* Construct a new {@link MapWrapper}
*
@ -70,6 +69,8 @@ public class MapWrapper extends AbstractMapWrapper {
private static final Class<?> entityMetadataPacketClass = getNMSClass("network.protocol.game", "PacketPlayOutEntityMetadata");
private static final Class<?> entityItemFrameClass = getNMSClass("world.entity.decoration", "EntityItemFrame");
private static final Class<?> dataWatcherItemClass = getNMSClass("network.syncher", "DataWatcher$Item");
private static final Class<?> minecraftKeyClass = getNMSClass("resources", "MinecraftKey");
private static final Class<?> builtInRegistriesClass = getNMSClass("core.registries", "BuiltInRegistries");
protected MapController controller = new MapController() {
private final Map<UUID, Integer> viewers = new HashMap<>();
@ -167,9 +168,12 @@ public class MapWrapper extends AbstractMapWrapper {
}
String inventoryMenuName;
if (supports(20)) {
//>= 1.20.2 = bR, 1.20(.1) = bQ
inventoryMenuName = supports(20, 2) ? "bR" : "bQ";
if (supports(21)) {
//1.21 = cc
inventoryMenuName = "cc";
} else if (supports(20)) {
//1.20.5 = cb, 1.20.2 - 1.20.4 = bR, 1.20(.1) = bQ
inventoryMenuName = supports(20, 4) ? "cb" : supports(20, 2) ? "bR" : "bQ";
} else if (supports(19)) {
//1.19.4 = bO, >= 1.19.3 = bT
inventoryMenuName = supports(19, 3) ? "bO" : "bT";
@ -205,7 +209,7 @@ public class MapWrapper extends AbstractMapWrapper {
);
}
sendPacketSync(player, packet);
sendPacket(player, packet);
}
@Override
@ -215,7 +219,7 @@ public class MapWrapper extends AbstractMapWrapper {
@Override
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;
showInInventory(player, player.getInventory().getHeldItemSlot(), force);
@ -233,7 +237,7 @@ public class MapWrapper extends AbstractMapWrapper {
@Override
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;
showInFrame(player, frame.getEntityId());
@ -248,7 +252,7 @@ public class MapWrapper extends AbstractMapWrapper {
public void showInFrame(Player player, int entityId, String debugInfo) {
if (!isViewing(player)) return;
ItemStack stack = new ItemStack(MAP_MATERIAL, 1);
ItemStack stack = new ItemStack(XMaterial.FILLED_MAP.parseMaterial(), 1);
if (debugInfo != null) {
ItemMeta itemMeta = stack.getItemMeta();
itemMeta.setDisplayName(debugInfo);
@ -256,7 +260,7 @@ public class MapWrapper extends AbstractMapWrapper {
}
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());
frame.setMetadata(REFERENCE_METADATA, new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper.this));
@ -270,7 +274,7 @@ public class MapWrapper extends AbstractMapWrapper {
public void clearFrame(Player player, int entityId) {
sendItemFramePacket(player, entityId, null, -1);
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());
});
}
@ -280,29 +284,8 @@ public class MapWrapper extends AbstractMapWrapper {
clearFrame(player, frame.getEntityId());
}
@Override
public ItemFrame getItemFrameById(World world, int entityId) {
Object worldHandle = 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;
}
private Object asCraftItemStack(Player player) {
return createCraftItemStack(supports(13)
? new ItemStack(MAP_MATERIAL, 1)
: new ItemStack(MAP_MATERIAL, 1, (short) getMapId(player)
), (short) getMapId(player));
return createCraftItemStack(new ItemStack(XMaterial.FILLED_MAP.parseMaterial(), 1, (short) getMapId(player)), (short) getMapId(player));
}
private Object createCraftItemStack(@NotNull ItemStack stack, int mapId) {
@ -310,7 +293,22 @@ public class MapWrapper extends AbstractMapWrapper {
Object nmsStack = ReflectionUtil.callMethod(craftStackClass, "asNMSCopy", stack);
if (supports(13)) {
//1.20.5 uses new NBT compound system
if (supports(20, 4)) {
Object mapIdComponent = ReflectionUtil.getDeclaredField(getNMSClass("core.component", "DataComponents"), "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;
if (supports(20)) { //1.20
nbtObjectName = "w";
@ -331,7 +329,9 @@ public class MapWrapper extends AbstractMapWrapper {
Object nmsStack = createCraftItemStack(stack, mapId);
String dataWatcherObjectName;
if (supports(19, 3)) { //1.19.3 and 1.20(.1)
if (supports(21)) { //1.21
dataWatcherObjectName = "f";
} else if (supports(19, 3)) { //1.19.3 and 1.20(.1)
dataWatcherObjectName = "g";
} else if (supports(19)) { //1.19-1.19.2
dataWatcherObjectName = "ao";
@ -352,7 +352,7 @@ public class MapWrapper extends AbstractMapWrapper {
Object packet;
if (supports(19, 3)) { //1.19.3
Class<?> dataWatcherRecordClass = 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
Object dataWatcherItem;
try {
@ -383,7 +383,7 @@ public class MapWrapper extends AbstractMapWrapper {
ReflectionUtil.setDeclaredField(packet, "b", list);
}
sendPacketSync(player, packet);
sendPacket(player, packet);
}
};
}

View file

@ -144,8 +144,8 @@ public interface MultiMapController extends IMapController {
* Called to get debug information for a frame
*
* @param controller the {@link MapController}
* @param row Row of the current frame
* @param column Column of the current frame
* @param row Row 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>
* @see MapController#showInFrame(Player, int, String)
*/

View file

@ -1,6 +1,6 @@
/*
* 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
* 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.RequiredArgsConstructor;
import lombok.Setter;
import org.bukkit.Material;
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.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI;
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
*/
@RequiredArgsConstructor
@Getter
public class CreativeInventoryMapUpdateEvent extends Event implements Cancellable {
private static final HandlerList handlerList = new HandlerList();
@Setter
private boolean cancelled;
public class CreativeInventoryMapUpdateEvent extends CancellableEvent {
private final Player player;
private final int slot;
private final ItemStack item;
@ -62,11 +54,6 @@ public class CreativeInventoryMapUpdateEvent extends Event implements Cancellabl
this.item = item;
}
@Override
public @NotNull HandlerList getHandlers() {
return handlerList;
}
/**
* Get the {@link MapWrapper} of the map of this event
*
@ -76,7 +63,7 @@ public class CreativeInventoryMapUpdateEvent extends Event implements Cancellabl
public MapWrapper getMapWrapper() {
if (mapWrapper == 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());
}

View file

@ -1,6 +1,6 @@
/*
* 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
* 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.RequiredArgsConstructor;
import lombok.Setter;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
import tech.sbdevelopment.mapreflectionapi.api.events.types.CancellableEvent;
/**
* This event gets fired when a map creation is cancelled
*/
@RequiredArgsConstructor
@Getter
public class MapCancelEvent extends Event implements Cancellable {
private static final HandlerList handlerList = new HandlerList();
@Setter
private boolean cancelled;
public class MapCancelEvent extends CancellableEvent {
private final Player player;
private final int id;
@ -52,9 +44,4 @@ public class MapCancelEvent extends Event implements Cancellable {
this.player = player;
this.id = id;
}
@Override
public @NotNull HandlerList getHandlers() {
return handlerList;
}
}

View file

@ -1,6 +1,6 @@
/*
* 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
* 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.RequiredArgsConstructor;
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.MapWrapper;
import tech.sbdevelopment.mapreflectionapi.api.events.types.Event;
/**
* This event gets fired when the content of a {@link MapWrapper} is updated
@ -33,8 +31,6 @@ import tech.sbdevelopment.mapreflectionapi.api.MapWrapper;
@RequiredArgsConstructor
@Getter
public class MapContentUpdateEvent extends Event {
private static final HandlerList handlerList = new HandlerList();
private final MapWrapper wrapper;
private final ArrayImage content;
@Setter
@ -52,9 +48,4 @@ public class MapContentUpdateEvent extends Event {
this.wrapper = wrapper;
this.content = content;
}
@Override
public @NotNull HandlerList getHandlers() {
return handlerList;
}
}

View file

@ -1,6 +1,6 @@
/*
* 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
* 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.RequiredArgsConstructor;
import lombok.Setter;
import org.bukkit.entity.ItemFrame;
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.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI;
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
*/
@RequiredArgsConstructor
@Getter
public class MapInteractEvent extends Event implements Cancellable {
private static final HandlerList handlerList = new HandlerList();
@Setter
private boolean cancelled;
public class MapInteractEvent extends CancellableEvent {
private final Player player;
private final int entityID;
private final int action;
@ -69,11 +61,6 @@ public class MapInteractEvent extends Event implements Cancellable {
this.hand = hand;
}
@Override
public @NotNull HandlerList getHandlers() {
return handlerList;
}
/**
* Get the {@link ItemFrame} the map is in
*
@ -81,10 +68,8 @@ public class MapInteractEvent extends Event implements Cancellable {
*/
@Nullable
public ItemFrame getFrame() {
if (getMapWrapper() == null) return null;
if (frame == null) {
frame = getMapWrapper().getController().getItemFrameById(player.getWorld(), entityID);
frame = MapReflectionAPI.getMapManager().getItemFrameById(player.getWorld(), entityID);
}
return frame;
}
@ -96,10 +81,11 @@ public class MapInteractEvent extends Event implements Cancellable {
*/
@Nullable
public MapWrapper getMapWrapper() {
if (getFrame() == null) return 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;
}
}

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

@ -30,16 +30,21 @@ import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import sun.misc.Unsafe;
import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI;
import tech.sbdevelopment.mapreflectionapi.api.events.CreativeInventoryMapUpdateEvent;
import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent;
import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent;
import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.concurrent.TimeUnit;
import static com.cryptomorin.xseries.reflection.minecraft.MinecraftConnection.getConnection;
import static com.cryptomorin.xseries.reflection.minecraft.MinecraftConnection.getHandle;
import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*;
import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.*;
import static com.cryptomorin.xseries.reflection.XReflection.*;
public class PacketListener implements Listener {
private static final Class<?> packetPlayOutMapClass = getNMSClass("network.protocol.game", "PacketPlayOutMap");
@ -50,7 +55,7 @@ public class PacketListener implements Listener {
private static final Class<?> playerCommonConnection;
static {
if (supports(20) && supportsPatch(2)) {
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 {
@ -72,42 +77,79 @@ public class PacketListener implements Listener {
ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() {
@Override
public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception {
boolean cancel = false;
if (packet.getClass().isAssignableFrom(packetPlayOutMapClass)) {
Object packetPlayOutMap = packetPlayOutMapClass.cast(packet);
int id = (int) getDeclaredField(packetPlayOutMap, "a");
if (id < 0) {
int newId = -id;
setDeclaredField(packetPlayOutMap, "a", newId);
int id;
boolean inv = false;
if (supports(20, 4)) { //1.20.4 uses MapId class and record classes (final fields...)
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 {
id = (int) getDeclaredField(packetPlayOutMap, "a");
if (id < 0) {
setDeclaredField(packetPlayOutMap, "a", -id);
inv = true;
}
}
if (!inv) {
boolean async = !MapReflectionAPI.getInstance().getServer().isPrimaryThread();
MapCancelEvent event = new MapCancelEvent(player, id, async);
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
public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception {
boolean cancel = false;
if (packet.getClass().isAssignableFrom(packetPlayInUseEntityClass)) {
Object packetPlayInEntity = packetPlayInUseEntityClass.cast(packet);
int entityId = (int) getDeclaredField(packetPlayInEntity, "a");
int entityId = (int) getDeclaredField(packetPlayInEntity, supports(20, 4) ? "b" : "a");
Enum<?> actionEnum;
Enum<?> hand;
Object pos;
if (supports(17)) {
Object action = getDeclaredField(packetPlayInEntity, "b");
actionEnum = (Enum<?>) callDeclaredMethod(action, "a"); //action type
hand = hasField(action, "a") ? (Enum<?>) getDeclaredField(action, "a") : null;
pos = hasField(action, "b") ? getDeclaredField(action, "b") : null;
Object action = getDeclaredField(packetPlayInEntity, supports(20, 4) ? "c" : "b");
actionEnum = (Enum<?>) callDeclaredMethod(action, "a");
Class<?> d = getNMSClass("network.protocol.game", "PacketPlayInUseEntity$d");
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 {
actionEnum = (Enum<?>) callDeclaredMethod(packetPlayInEntity, supports(13) ? "b" : "a"); //1.13 = b, 1.12 = a
hand = (Enum<?>) callDeclaredMethod(packetPlayInEntity, supports(13) ? "c" : "b"); //1.13 = c, 1.12 = b
@ -122,23 +164,28 @@ public class PacketListener implements Listener {
return event.isCancelled();
}
return false;
}).get(1, TimeUnit.SECONDS)) return;
}).get(1, TimeUnit.SECONDS)) cancel = true;
} else if (packet.getClass().isAssignableFrom(packetPlayInSetCreativeSlotClass)) {
Object packetPlayInSetCreativeSlot = packetPlayInSetCreativeSlotClass.cast(packet);
int slot = (int) ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, supports(19, 4) ? "a" : supports(13) ? "b" : "a"); //1.19.4 = a, 1.19.3 - 1.13 = b, 1.12 = a
Object nmsStack = ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, supports(20, 2) ? "d" : supports(18) ? "c" : "getItemStack"); //1.20.2 = d, >= 1.18 = c, 1.17 = getItemStack
int slot;
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);
boolean async = !MapReflectionAPI.getInstance().getServer().isPrimaryThread();
CreativeInventoryMapUpdateEvent event = new CreativeInventoryMapUpdateEvent(player, slot, craftStack, async);
if (event.getMapWrapper() != null) {
Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled()) return;
if (event.isCancelled()) cancel = true;
}
}
super.channelRead(ctx, packet);
if (!cancel) super.channelRead(ctx, packet);
}
};
@ -152,9 +199,7 @@ public class PacketListener implements Listener {
}
private Channel getChannel(Player player) {
Object playerHandle = getHandle(player);
Object playerConnection = getDeclaredField(playerHandle, supports(20) ? "c" : supports(17) ? "b" : "playerConnection"); //1.20 = c, 1.17-1.19 = b, 1.16 = playerConnection
Object networkManager = getDeclaredField(playerCommonConnection, playerConnection, 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 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
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
}

View file

@ -18,6 +18,9 @@
package tech.sbdevelopment.mapreflectionapi.utils;
import java.util.Map;
import java.util.function.Supplier;
public class MainUtil {
private MainUtil() {
}

View file

@ -18,6 +18,7 @@
package tech.sbdevelopment.mapreflectionapi.utils;
import org.bukkit.World;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -27,24 +28,30 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import static com.cryptomorin.xseries.reflection.XReflection.getCraftClass;
import static com.cryptomorin.xseries.reflection.XReflection.getNMSClass;
public class ReflectionUtil {
private static final Map<String, Constructor<?>> constructorCache = new HashMap<>();
private static final Map<String, Method> methodCache = new HashMap<>();
private static final Map<String, Field> fieldCache = new HashMap<>();
private static final Class<?> craftWorld = getCraftClass("CraftWorld");
/**
* Helper class converted to {@link List}
*
* @param <E> The storage type
*/
public static class ListParam<E> extends ArrayList<E> {}
public static class ListParam<E> extends ArrayList<E> {
}
/**
* Helper class converted to {@link Collection}
*
* @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) {
if (clazz == Boolean.class) return boolean.class;
@ -69,6 +76,11 @@ public class ReflectionUtil {
.toArray(Class<?>[]::new);
}
@Nullable
public static Object getHandle(@NotNull World world) {;
return callDeclaredMethod(craftWorld, world, "getHandle");
}
@Nullable
public static Class<?> getClass(@NotNull String name) {
try {
@ -223,6 +235,26 @@ public class ReflectionUtil {
}
}
@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) {
ex.printStackTrace();
return null;
}
}
public static boolean hasField(Object packet, String field) {
try {
String cacheKey = "HasField:" + packet.getClass().getName() + ":" + field;

View file

@ -1,566 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2023 Crypto Morin
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
* FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package tech.sbdevelopment.mapreflectionapi.utils;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.entity.Player;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* <b>ReflectionUtils</b> - Reflection handler for NMS and CraftBukkit.<br>
* 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.
* <p>
* A useful resource used to compare mappings is <a href="https://minidigger.github.io/MiniMappingViewer/#/spigot">Mini's Mapping Viewer</a>
*
* @author Crypto Morin
* @version 7.1.0.0.1
*/
public final class ReflectionUtils {
/**
* We use reflection mainly to avoid writing a new class for version barrier.
* The version barrier is for NMS that uses the Minecraft version as the main package name.
* <p>
* 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.
* <p>
* <a href="https://www.spigotmc.org/wiki/spigot-nms-and-minecraft-versions-legacy/">Versions Legacy</a>
*/
public static final String NMS_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'");
NMS_VERSION = found;
}
/**
* The raw minor version number.
* E.g. {@code v1_17_R1} to {@code 17}
*
* @see #supports(int)
* @since 4.0.0
*/
public static final int MINOR_NUMBER;
/**
* The raw patch version number. Refers to the <a href="https://en.wikipedia.org/wiki/Software_versioning">major.minor.patch version scheme</a>.
* E.g.
* <ul>
* <li>{@code v1.20.4} to {@code 4}</li>
* <li>{@code v1.18.2} to {@code 2}</li>
* <li>{@code v1.19.1} to {@code 1}</li>
* </ul>
* <p>
* I'd not recommend developers to support individual patches at all. You should always support the latest patch.
* For example, between v1.14.0, v1.14.1, v1.14.2, v1.14.3 and v1.14.4 you should only support v1.14.4
* <p>
* This can be used to warn server owners when your plugin will break on older patches.
*
* @see #supportsPatch(int)
* @since 7.0.0
*/
public static final int PATCH_NUMBER;
static {
String[] split = NMS_VERSION.substring(1).split("_");
if (split.length < 1) {
throw new IllegalStateException("Version number division error: " + Arrays.toString(split) + ' ' + getVersionInformation());
}
String minorVer = split[1];
try {
MINOR_NUMBER = Integer.parseInt(minorVer);
if (MINOR_NUMBER < 0)
throw new IllegalStateException("Negative minor number? " + minorVer + ' ' + getVersionInformation());
} catch (Throwable ex) {
throw new RuntimeException("Failed to parse minor number: " + minorVer + ' ' + getVersionInformation(), ex);
}
// Bukkit.getBukkitVersion() = "1.12.2-R0.1-SNAPSHOT"
Matcher bukkitVer = Pattern.compile("^\\d+\\.\\d+\\.(\\d+)").matcher(Bukkit.getBukkitVersion());
if (bukkitVer.find()) { // matches() won't work, we just want to match the start using "^"
try {
// group(0) gives the whole matched string, we just want the captured group.
PATCH_NUMBER = Integer.parseInt(bukkitVer.group(1));
} catch (Throwable ex) {
throw new RuntimeException("Failed to parse minor number: " + bukkitVer + ' ' + getVersionInformation(), ex);
}
} else {
// 1.8-R0.1-SNAPSHOT
PATCH_NUMBER = 0;
}
}
/**
* Gets the full version information of the server. Useful for including in errors.
*
* @since 7.0.0
*/
public static String getVersionInformation() {
return "(NMS: " + NMS_VERSION + " | " +
"Minecraft: " + Bukkit.getVersion() + " | " +
"Bukkit: " + Bukkit.getBukkitVersion() + ')';
}
/**
* Gets the latest known patch number of the given minor version.
* For example: 1.14 -> 4, 1.17 -> 10
* The latest version is expected to get newer patches, so make sure to account for unexpected results.
*
* @param minorVersion the minor version to get the patch number of.
* @return the patch number of the given minor version if recognized, otherwise null.
* @since 7.0.0
*/
public static Integer getLatestPatchNumberOf(int minorVersion) {
if (minorVersion <= 0) throw new IllegalArgumentException("Minor version must be positive: " + minorVersion);
// https://minecraft.wiki/w/Java_Edition_version_history
// There are many ways to do this, but this is more visually appealing.
int[] patches = {
/* 1 */ 1,
/* 2 */ 5,
/* 3 */ 2,
/* 4 */ 7,
/* 5 */ 2,
/* 6 */ 4,
/* 7 */ 10,
/* 8 */ 8, // I don't think they released a server version for 1.8.9
/* 9 */ 4,
/* 10 */ 2,// ,_ _ _,
/* 11 */ 2,// \o-o/
/* 12 */ 2,// ,(.-.),
/* 13 */ 2,// _/ |) (| \_
/* 14 */ 4,// /\=-=/\
/* 15 */ 2,// ,| \=/ |,
/* 16 */ 5,// _/ \ | / \_
/* 17 */ 1,// \_!_/
/* 18 */ 2,
/* 19 */ 4,
/* 20 */ 4,
};
if (minorVersion > patches.length) return null;
return patches[minorVersion - 1];
}
/**
* Mojang remapped their NMS in 1.17: <a href="https://www.spigotmc.org/threads/spigot-bungeecord-1-17.510208/#post-4184317">Spigot Thread</a>
*/
public static final String
CRAFTBUKKIT_PACKAGE = "org.bukkit.craftbukkit." + NMS_VERSION + '.',
NMS_PACKAGE = v(17, "net.minecraft.").orElse("net.minecraft.server." + NMS_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;
/**
* Responsible for getting the NMS handler {@code WorldServer} object for the world.
* {@code CraftWorld} is simply a wrapper for {@code WorldServer}.
*/
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");
Class<?> playerCommonConnection;
if (supports(20) && supportsPatch(2)) {
// The packet send method has been abstracted from ServerGamePacketListenerImpl to ServerCommonPacketListenerImpl in 1.20.2
playerCommonConnection = getNMSClass("server.network", "ServerCommonPacketListenerImpl");
} else {
playerCommonConnection = playerConnection;
}
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle sendPacket = null, getHandle = null, getHandleWorld = null, connection = null;
try {
connection = lookup.findGetter(entityPlayer,
v(20, "c").v(17, "b").orElse("playerConnection"), playerConnection);
getHandle = lookup.findVirtual(craftPlayer, "getHandle", MethodType.methodType(entityPlayer));
getHandleWorld = lookup.findVirtual(craftWorld, "getHandle", MethodType.methodType(worldServer));
sendPacket = lookup.findVirtual(playerCommonConnection,
v(20, 2, "b").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 ReflectionUtils() {
}
/**
* Gives the {@code handle} object if the server version is equal or greater than the given version.
* This method is purely for readability and should be always used with {@link VersionHandler#orElse(Object)}.
*
* @see #v(int, int, Object)
* @see VersionHandler#orElse(Object)
* @since 5.0.0
*/
public static <T> VersionHandler<T> v(int version, T handle) {
return new VersionHandler<>(version, handle);
}
/**
* Overload for {@link #v(int, T)} that supports patch versions
*
* @since 9.5.0
*/
public static <T> VersionHandler<T> v(int version, int patch, T handle) {
return new VersionHandler<>(version, patch, handle);
}
public static <T> CallableVersionHandler<T> v(int version, Callable<T> handle) {
return new CallableVersionHandler<>(version, handle);
}
/**
* Checks whether the server version is equal or greater than the given version.
*
* @param minorNumber the version to compare the server version with.
* @return true if the version is equal or newer, otherwise false.
* @see #MINOR_NUMBER
* @since 4.0.0
*/
public static boolean supports(int minorNumber) {
return MINOR_NUMBER >= minorNumber;
}
/**
* Checks whether the server version is equal or greater than the given version.
*
* @param minorNumber the minor version to compare the server version with.
* @param patchNumber the patch number to compare the server version with.
* @return true if the version is equal or newer, otherwise false.
* @see #MINOR_NUMBER
* @see #PATCH_NUMBER
* @since 7.1.0
*/
public static boolean supports(int minorNumber, int patchNumber) {
return (MINOR_NUMBER == minorNumber && supportsPatch(patchNumber)) || MINOR_NUMBER > minorNumber;
}
/**
* Checks whether the server version is equal or greater than the given version.
*
* @param patchNumber the version to compare the server version with.
* @return true if the version is equal or newer, otherwise false.
* @see #PATCH_NUMBER
* @since 7.0.0
*/
public static boolean supportsPatch(int patchNumber) {
return PATCH_NUMBER >= patchNumber;
}
/**
* Get a NMS (net.minecraft.server) class which accepts a package for 1.17 compatibility.
*
* @param packageName the 1.17+ package name of this class.
* @param name the name of the class.
* @return the NMS class or null if not found.
* @since 4.0.0
*/
@Nullable
public static Class<?> getNMSClass(@Nullable String packageName, @Nonnull String name) {
if (packageName != null && supports(17)) name = packageName + '.' + name;
try {
return Class.forName(NMS_PACKAGE + name);
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
return null;
}
}
/**
* Get a NMS {@link #NMS_PACKAGE} class.
*
* @param name the name of the class.
* @return the NMS class or null if not found.
* @since 1.0.0
*/
@Nullable
public static Class<?> getNMSClass(@Nonnull String name) {
return getNMSClass(null, name);
}
/**
* 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();
}
}
@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;
}
}
@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;
}
}
@Nullable
public static Object getConnection(@Nonnull Player player) {
Objects.requireNonNull(player, "Cannot get connection of null player");
try {
Object handle = GET_HANDLE.invoke(player);
return PLAYER_CONNECTION.invoke(handle);
} 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
*/
@Nullable
public static Class<?> getCraftClass(@Nonnull String name) {
try {
return Class.forName(CRAFTBUKKIT_PACKAGE + name);
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
return null;
}
}
/**
* @deprecated Use {@link #toArrayClass(Class)} instead.
*/
@Deprecated
public static Class<?> getArrayClass(String clazz, boolean nms) {
clazz = "[L" + (nms ? NMS_PACKAGE : CRAFTBUKKIT_PACKAGE) + clazz + ';';
try {
return Class.forName(clazz);
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
return null;
}
}
/**
* Gives an array version of a class. For example if you wanted {@code EntityPlayer[]} you'd use:
* <pre>{@code
* Class EntityPlayer = ReflectionUtils.getNMSClass("...", "EntityPlayer");
* Class EntityPlayerArray = ReflectionUtils.toArrayClass(EntityPlayer);
* }</pre>
*
* @param clazz the class to get the array version of. You could use for multi-dimensions arrays too.
*/
public static Class<?> toArrayClass(Class<?> clazz) {
try {
return Class.forName("[L" + clazz.getName() + ';');
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
return null;
}
}
public static final class VersionHandler<T> {
private int version, patch;
private T handle;
private VersionHandler(int version, T handle) {
this(version, 0, handle);
}
private VersionHandler(int version, int patch, T handle) {
if (supports(version) && supportsPatch(patch)) {
this.version = version;
this.patch = patch;
this.handle = handle;
}
}
public VersionHandler<T> v(int version, T handle) {
return v(version, 0, handle);
}
public VersionHandler<T> v(int version, int patch, T handle) {
if (version == this.version && patch == this.patch)
throw new IllegalArgumentException("Cannot have duplicate version handles for version: " + version + '.' + patch);
if (version > this.version && supports(version) && patch >= this.patch && supportsPatch(patch)) {
this.version = version;
this.patch = patch;
this.handle = handle;
}
return this;
}
/**
* If none of the previous version checks matched, it'll return this object.
*/
public T orElse(T handle) {
return this.version == 0 ? handle : this.handle;
}
}
public static final class CallableVersionHandler<T> {
private int version;
private Callable<T> handle;
private CallableVersionHandler(int version, Callable<T> handle) {
if (supports(version)) {
this.version = version;
this.handle = handle;
}
}
public CallableVersionHandler<T> v(int version, Callable<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(Callable<T> handle) {
try {
return (this.version == 0 ? handle : this.handle).call();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
}

View file

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

File diff suppressed because it is too large Load diff