mirror of
https://github.com/amalthea-mc/ShopChest.git
synced 2024-11-22 10:22:29 +00:00
Add reflection implementation structure
This commit is contained in:
parent
278b4462f3
commit
fe27bd5e60
@ -23,6 +23,11 @@
|
|||||||
<artifactId>shopchest-nms-interface</artifactId>
|
<artifactId>shopchest-nms-interface</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.inventivetalent</groupId>
|
||||||
|
<artifactId>reflectionhelper</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
@ -0,0 +1,22 @@
|
|||||||
|
package de.epiceric.shopchest.nms.reflection;
|
||||||
|
|
||||||
|
import de.epiceric.shopchest.nms.FakeArmorStand;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
public class FakeArmorStandImpl extends FakeEntityImpl implements FakeArmorStand {
|
||||||
|
|
||||||
|
public FakeArmorStandImpl(ShopChestDebug debug) {
|
||||||
|
super(debug);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendData(String name, Iterable<Player> receivers) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLocation(Location location, Iterable<Player> receivers) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package de.epiceric.shopchest.nms.reflection;
|
||||||
|
|
||||||
|
import de.epiceric.shopchest.nms.FakeEntity;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class FakeEntityImpl implements FakeEntity {
|
||||||
|
|
||||||
|
protected final int entityId;
|
||||||
|
protected final ShopChestDebug debug;
|
||||||
|
|
||||||
|
public FakeEntityImpl(ShopChestDebug debug) {
|
||||||
|
this.entityId = ReflectionUtils.getFreeEntityId();
|
||||||
|
this.debug = debug;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getEntityId() {
|
||||||
|
return entityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void spawn(UUID uuid, Location location, Iterable<Player> receivers) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(Iterable<Player> receivers) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package de.epiceric.shopchest.nms.reflection;
|
||||||
|
|
||||||
|
import de.epiceric.shopchest.nms.FakeItem;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
|
||||||
|
public class FakeItemImpl extends FakeEntityImpl implements FakeItem {
|
||||||
|
|
||||||
|
public FakeItemImpl(ShopChestDebug debug) {
|
||||||
|
super(debug);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendData(ItemStack item, Iterable<Player> receivers) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resetVelocity(Iterable<Player> receivers) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,243 @@
|
|||||||
|
package de.epiceric.shopchest.nms.reflection;
|
||||||
|
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.inventivetalent.reflection.resolver.FieldResolver;
|
||||||
|
import org.inventivetalent.reflection.resolver.minecraft.NMSClassResolver;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class JsonBuilder {
|
||||||
|
|
||||||
|
public static class Part {
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
public Part() {
|
||||||
|
this("", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Part(Object value) {
|
||||||
|
this(value, value instanceof CharSequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Part(Object value, boolean appendQuotes) {
|
||||||
|
if (appendQuotes) {
|
||||||
|
this.value = "\"" + value + "\"";
|
||||||
|
} else {
|
||||||
|
this.value = String.valueOf(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PartArray toArray() {
|
||||||
|
return new PartArray(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PartMap toMap() {
|
||||||
|
PartMap map = new PartMap();
|
||||||
|
map.setValue("text", new Part());
|
||||||
|
map.setValue("extra", toArray());
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PartMap extends Part {
|
||||||
|
private Map<String, Part> values = new HashMap<>();
|
||||||
|
|
||||||
|
public PartMap() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public PartMap(Map<String, Part> values) {
|
||||||
|
this.values.putAll(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(String key, Part value) {
|
||||||
|
values.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeValue(String key) {
|
||||||
|
values.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringJoiner joiner = new StringJoiner(",", "{", "}");
|
||||||
|
values.forEach((key, value) -> joiner.add("\"" + key + "\":" + value.toString()));
|
||||||
|
return joiner.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PartMap toMap() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PartArray extends Part {
|
||||||
|
private List<Part> parts = new ArrayList<>();
|
||||||
|
|
||||||
|
public PartArray(Part... parts) {
|
||||||
|
this.parts.addAll(Arrays.asList(parts));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addPart(Part part) {
|
||||||
|
parts.add(part);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringJoiner joiner = new StringJoiner(",", "[", "]");
|
||||||
|
parts.forEach(part -> joiner.add(part.toString()));
|
||||||
|
return joiner.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PartArray toArray() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Pattern PART_PATTERN = Pattern.compile("((§(?:[a-fA-Fk-oK-OrR0-9]|#[a-fA-F0-9]{6}))+)([^§]*)");
|
||||||
|
private static final Pattern HEX_PATTERN = Pattern.compile("(§[a-fA-F0-9]){6}");
|
||||||
|
|
||||||
|
private Part rootPart;
|
||||||
|
private ShopChestDebug debug;
|
||||||
|
|
||||||
|
private final NMSClassResolver nmsClassResolver = new NMSClassResolver();
|
||||||
|
private Class<?> iChatBaseComponentClass = nmsClassResolver.resolveSilent("network.chat.IChatBaseComponent");
|
||||||
|
private Class<?> packetPlayOutChatClass = nmsClassResolver.resolveSilent("network.protocol.game.PacketPlayOutChat");
|
||||||
|
private Class<?> chatSerializerClass = nmsClassResolver.resolveSilent("ChatSerializer", "network.chat.IChatBaseComponent$ChatSerializer");
|
||||||
|
private Class<?> chatMessageTypeClass;
|
||||||
|
|
||||||
|
public JsonBuilder(ShopChestDebug debug) {
|
||||||
|
this.debug = debug;
|
||||||
|
|
||||||
|
if (ReflectionUtils.getMajorVersion() >= 16) {
|
||||||
|
chatMessageTypeClass = nmsClassResolver.resolveSilent("network.chat.ChatMessageType");
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?>[] requiredClasses = new Class<?>[] {
|
||||||
|
iChatBaseComponentClass, packetPlayOutChatClass, chatSerializerClass
|
||||||
|
};
|
||||||
|
|
||||||
|
for (Class<?> c : requiredClasses) {
|
||||||
|
if (c == null) {
|
||||||
|
debug.debug("Failed to instantiate JsonBuilder: Could not find all required classes");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Part parse(String text) {
|
||||||
|
Matcher hexMatcher = HEX_PATTERN.matcher(text);
|
||||||
|
while (hexMatcher.find()) {
|
||||||
|
String hexCode = hexMatcher.group(0).replace("§", "");
|
||||||
|
text = text.replace(hexMatcher.group(0), "§#" + hexCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
Matcher matcher = PART_PATTERN.matcher(text);
|
||||||
|
|
||||||
|
if (!matcher.find()) {
|
||||||
|
return new Part(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
matcher.reset();
|
||||||
|
|
||||||
|
PartArray array = new PartArray(new Part());
|
||||||
|
int lastEndIndex = 0;
|
||||||
|
|
||||||
|
while (matcher.find()) {
|
||||||
|
int startIndex = matcher.start();
|
||||||
|
int endIndex = matcher.end();
|
||||||
|
|
||||||
|
if (lastEndIndex != startIndex) {
|
||||||
|
String betweenMatches = text.substring(lastEndIndex, startIndex);
|
||||||
|
array.addPart(new Part(betweenMatches));
|
||||||
|
}
|
||||||
|
|
||||||
|
String format = matcher.group(1);
|
||||||
|
String value = matcher.group(3);
|
||||||
|
|
||||||
|
PartMap part = new PartMap();
|
||||||
|
part.setValue("text", new Part(value));
|
||||||
|
|
||||||
|
String[] formats = format.split("§");
|
||||||
|
for (String f : formats) {
|
||||||
|
switch (f.toLowerCase()) {
|
||||||
|
case "":
|
||||||
|
break;
|
||||||
|
case "k":
|
||||||
|
part.setValue("obuscated", new Part(true));
|
||||||
|
break;
|
||||||
|
case "l":
|
||||||
|
part.setValue("bold", new Part(true));
|
||||||
|
break;
|
||||||
|
case "m":
|
||||||
|
part.setValue("strikethrough", new Part(true));
|
||||||
|
break;
|
||||||
|
case "n":
|
||||||
|
part.setValue("underlined", new Part(true));
|
||||||
|
break;
|
||||||
|
case "o":
|
||||||
|
part.setValue("italic", new Part(true));
|
||||||
|
break;
|
||||||
|
case "r":
|
||||||
|
part.removeValue("obfuscated");
|
||||||
|
part.removeValue("bold");
|
||||||
|
part.removeValue("strikethrough");
|
||||||
|
part.removeValue("underlined");
|
||||||
|
part.removeValue("italic");
|
||||||
|
part.removeValue("color");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (f.startsWith("#")) {
|
||||||
|
part.setValue("color", new Part(f));
|
||||||
|
} else {
|
||||||
|
part.setValue("color", new Part(ChatColor.getByChar(f).name().toLowerCase()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
array.addPart(part);
|
||||||
|
lastEndIndex = endIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return rootPart.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Part getRootPart() {
|
||||||
|
return rootPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRootPart(Part rootPart) {
|
||||||
|
this.rootPart = rootPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendJson(Player p) {
|
||||||
|
try {
|
||||||
|
Object iChatBaseComponent = chatSerializerClass.getMethod("a", String.class).invoke(null, toString());
|
||||||
|
Object packetPlayOutChat = ReflectionUtils.getMajorVersion() < 16
|
||||||
|
? packetPlayOutChatClass.getConstructor(iChatBaseComponentClass).newInstance(iChatBaseComponent)
|
||||||
|
: packetPlayOutChatClass.getConstructor(iChatBaseComponentClass, chatMessageTypeClass, UUID.class)
|
||||||
|
.newInstance(iChatBaseComponent, (new FieldResolver(chatMessageTypeClass)).resolve("CHAT", "a").get(null), UUID.randomUUID());
|
||||||
|
|
||||||
|
ReflectionUtils.sendPacket(debug, packetPlayOutChat, p);
|
||||||
|
debug.debug("Sent JSON: " + toString());
|
||||||
|
} catch (ReflectiveOperationException e) {
|
||||||
|
debug.getLogger().severe("Failed to send JSON with reflection");
|
||||||
|
debug.debug("Failed to send JSON with reflection: " + toString());
|
||||||
|
debug.debug(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package de.epiceric.shopchest.nms.reflection;
|
||||||
|
|
||||||
|
import de.epiceric.shopchest.nms.FakeArmorStand;
|
||||||
|
import de.epiceric.shopchest.nms.FakeItem;
|
||||||
|
import de.epiceric.shopchest.nms.Platform;
|
||||||
|
|
||||||
|
public class PlatformImpl implements Platform {
|
||||||
|
|
||||||
|
private final ShopChestDebug debug;
|
||||||
|
|
||||||
|
public PlatformImpl(ShopChestDebug debug) {
|
||||||
|
this.debug = debug;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FakeArmorStand createFakeArmorStand() {
|
||||||
|
return new FakeArmorStandImpl(debug);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FakeItem createFakeItem() {
|
||||||
|
return new FakeItemImpl(debug);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,285 @@
|
|||||||
|
package de.epiceric.shopchest.nms.reflection;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.entity.EntityType;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.inventivetalent.reflection.resolver.FieldResolver;
|
||||||
|
import org.inventivetalent.reflection.resolver.minecraft.NMSClassResolver;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
public class ReflectionUtils {
|
||||||
|
static NMSClassResolver nmsClassResolver = new NMSClassResolver();
|
||||||
|
static Class<?> entityClass = nmsClassResolver.resolveSilent("world.entity.Entity");
|
||||||
|
static Class<?> entityArmorStandClass = nmsClassResolver.resolveSilent("world.entity.decoration.EntityArmorStand");
|
||||||
|
static Class<?> entityItemClass = nmsClassResolver.resolveSilent("world.entity.item.EntityItem");
|
||||||
|
static Class<?> dataWatcherClass = nmsClassResolver.resolveSilent("network.syncher.DataWatcher");
|
||||||
|
static Class<?> dataWatcherObjectClass = nmsClassResolver.resolveSilent("network.syncher.DataWatcherObject");
|
||||||
|
static Class<?> chatSerializerClass = nmsClassResolver.resolveSilent("ChatSerializer", "network.chat.IChatBaseComponent$ChatSerializer");
|
||||||
|
|
||||||
|
private ReflectionUtils() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a NMS data watcher object to send via a {@code PacketPlayOutEntityMetadata} packet.
|
||||||
|
* Gravity will be disabled and the custom name will be displayed if available.
|
||||||
|
* @param customName Custom Name of the entity or {@code null}
|
||||||
|
* @param nmsItemStack NMS ItemStack or {@code null} if armor stand
|
||||||
|
*/
|
||||||
|
public static Object createDataWatcher(ShopChestDebug debug, String customName, Object nmsItemStack) {
|
||||||
|
String version = getServerVersion();
|
||||||
|
int majorVersion = getMajorVersion();
|
||||||
|
|
||||||
|
try {
|
||||||
|
byte entityFlags = nmsItemStack == null ? (byte) 0b100000 : 0; // invisible if armor stand
|
||||||
|
byte armorStandFlags = nmsItemStack == null ? (byte) 0b10000 : 0; // marker (since 1.8_R2)
|
||||||
|
|
||||||
|
Object dataWatcher = dataWatcherClass.getConstructor(entityClass).newInstance((Object) null);
|
||||||
|
if (majorVersion < 9) {
|
||||||
|
if (getRevision() == 1) armorStandFlags = 0; // Marker not supported on 1.8_R1
|
||||||
|
|
||||||
|
Method a = dataWatcherClass.getMethod("a", int.class, Object.class);
|
||||||
|
a.invoke(dataWatcher, 0, entityFlags); // flags
|
||||||
|
a.invoke(dataWatcher, 1, (short) 300); // air ticks (?)
|
||||||
|
a.invoke(dataWatcher, 3, (byte) (customName != null ? 1 : 0)); // custom name visible
|
||||||
|
a.invoke(dataWatcher, 2, customName != null ? customName : ""); // custom name
|
||||||
|
a.invoke(dataWatcher, 4, (byte) 1); // silent
|
||||||
|
a.invoke(dataWatcher, 10, nmsItemStack == null ? armorStandFlags : nmsItemStack); // item / armor stand flags
|
||||||
|
} else {
|
||||||
|
Method register = dataWatcherClass.getMethod("register", dataWatcherObjectClass, Object.class);
|
||||||
|
String[] dataWatcherObjectFieldNames;
|
||||||
|
|
||||||
|
if ("v1_9_R1".equals(version)) {
|
||||||
|
dataWatcherObjectFieldNames = new String[] {"ax", "ay", "aA", "az", "aB", null, "c", "a"};
|
||||||
|
} else if ("v1_9_R2".equals(version)){
|
||||||
|
dataWatcherObjectFieldNames = new String[] {"ay", "az", "aB", "aA", "aC", null, "c", "a"};
|
||||||
|
} else if ("v1_10_R1".equals(version)) {
|
||||||
|
dataWatcherObjectFieldNames = new String[] {"aa", "az", "aB", "aA", "aC", "aD", "c", "a"};
|
||||||
|
} else if ("v1_11_R1".equals(version)) {
|
||||||
|
dataWatcherObjectFieldNames = new String[] {"Z", "az", "aB", "aA", "aC", "aD", "c", "a"};
|
||||||
|
} else if ("v1_12_R1".equals(version) || "v1_12_R2".equals(version)) {
|
||||||
|
dataWatcherObjectFieldNames = new String[] {"Z", "aA", "aC", "aB", "aD", "aE", "c", "a"};
|
||||||
|
} else if ("v1_13_R1".equals(version) || "v1_13_R2".equals(version)) {
|
||||||
|
dataWatcherObjectFieldNames = new String[] {"ac", "aD", "aF", "aE", "aG", "aH", "b", "a"};
|
||||||
|
} else if ("v1_14_R1".equals(version)) {
|
||||||
|
dataWatcherObjectFieldNames = new String[] {"W", "AIR_TICKS", "aA", "az", "aB", "aC", "ITEM", "b"};
|
||||||
|
} else if ("v1_15_R1".equals(version)) {
|
||||||
|
dataWatcherObjectFieldNames = new String[] {"T", "AIR_TICKS", "aA", "az", "aB", "aC", "ITEM", "b"};
|
||||||
|
} else if ("v1_16_R1".equals(version)) {
|
||||||
|
dataWatcherObjectFieldNames = new String[] {"T", "AIR_TICKS", "ay", "ax", "az", "aA", "ITEM", "b"};
|
||||||
|
} else if ("v1_16_R2".equals(version) || "v1_16_R3".equals(version)) {
|
||||||
|
dataWatcherObjectFieldNames = new String[] {"S", "AIR_TICKS", "ar", "aq", "as", "at", "ITEM", "b"};
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Field fEntityFlags = entityClass.getDeclaredField(dataWatcherObjectFieldNames[0]);
|
||||||
|
Field fAirTicks = entityClass.getDeclaredField(dataWatcherObjectFieldNames[1]);
|
||||||
|
Field fNameVisible = entityClass.getDeclaredField(dataWatcherObjectFieldNames[2]);
|
||||||
|
Field fCustomName = entityClass.getDeclaredField(dataWatcherObjectFieldNames[3]);
|
||||||
|
Field fSilent = entityClass.getDeclaredField(dataWatcherObjectFieldNames[4]);
|
||||||
|
Field fNoGravity = majorVersion >= 10 ? entityClass.getDeclaredField(dataWatcherObjectFieldNames[5]) : null;
|
||||||
|
Field fItem = entityItemClass.getDeclaredField(dataWatcherObjectFieldNames[6]);
|
||||||
|
Field fArmorStandFlags = entityArmorStandClass.getDeclaredField(dataWatcherObjectFieldNames[7]);
|
||||||
|
|
||||||
|
fEntityFlags.setAccessible(true);
|
||||||
|
fAirTicks.setAccessible(true);
|
||||||
|
fNameVisible.setAccessible(true);
|
||||||
|
fCustomName.setAccessible(true);
|
||||||
|
fSilent.setAccessible(true);
|
||||||
|
if (majorVersion >= 10) fNoGravity.setAccessible(true);
|
||||||
|
fItem.setAccessible(true);
|
||||||
|
fArmorStandFlags.setAccessible(true);
|
||||||
|
|
||||||
|
register.invoke(dataWatcher, fEntityFlags.get(null), entityFlags);
|
||||||
|
register.invoke(dataWatcher, fAirTicks.get(null), 300);
|
||||||
|
register.invoke(dataWatcher, fNameVisible.get(null), customName != null);
|
||||||
|
register.invoke(dataWatcher, fSilent.get(null), true);
|
||||||
|
if (majorVersion < 13) register.invoke(dataWatcher, fCustomName.get(null), customName != null ? customName : "");
|
||||||
|
|
||||||
|
if (nmsItemStack != null) {
|
||||||
|
register.invoke(dataWatcher, fItem.get(null), majorVersion < 11 ? com.google.common.base.Optional.of(nmsItemStack) : nmsItemStack);
|
||||||
|
} else {
|
||||||
|
register.invoke(dataWatcher, fArmorStandFlags.get(null), armorStandFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (majorVersion >= 10) {
|
||||||
|
register.invoke(dataWatcher, fNoGravity.get(null), true);
|
||||||
|
if (majorVersion >= 13) {
|
||||||
|
if (customName != null) {
|
||||||
|
Object iChatBaseComponent = chatSerializerClass.getMethod("a", String.class).invoke(null, JsonBuilder.parse(customName).toString());
|
||||||
|
register.invoke(dataWatcher, fCustomName.get(null), Optional.of(iChatBaseComponent));
|
||||||
|
} else {
|
||||||
|
register.invoke(dataWatcher, fCustomName.get(null), Optional.empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dataWatcher;
|
||||||
|
} catch (InstantiationException | InvocationTargetException | NoSuchFieldException | IllegalAccessException | NoSuchMethodException e) {
|
||||||
|
debug.getLogger().severe("Failed to create data watcher!");
|
||||||
|
debug.debug("Failed to create data watcher");
|
||||||
|
debug.debug(e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a free entity ID for use in {@link #createPacketSpawnEntity(ShopChestDebug, int, UUID, Location, EntityType)}
|
||||||
|
*
|
||||||
|
* @return The id or {@code -1} if a free entity ID could not be retrieved.
|
||||||
|
*/
|
||||||
|
public static int getFreeEntityId() {
|
||||||
|
try {
|
||||||
|
Field entityCountField = new FieldResolver(entityClass).resolve("entityCount", "b");
|
||||||
|
entityCountField.setAccessible(true);
|
||||||
|
if (entityCountField.getType() == int.class) {
|
||||||
|
int id = entityCountField.getInt(null);
|
||||||
|
entityCountField.setInt(null, id+1);
|
||||||
|
return id;
|
||||||
|
} else if (entityCountField.getType() == AtomicInteger.class) {
|
||||||
|
return ((AtomicInteger) entityCountField.get(null)).incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@code PacketPlayOutSpawnEntity} object.
|
||||||
|
* Only {@link EntityType#ARMOR_STAND} and {@link EntityType#DROPPED_ITEM} are supported!
|
||||||
|
*/
|
||||||
|
public static Object createPacketSpawnEntity(ShopChestDebug debug, int id, UUID uuid, Location loc, EntityType type) {
|
||||||
|
try {
|
||||||
|
Class<?> packetPlayOutSpawnEntityClass = nmsClassResolver.resolveSilent("network.protocol.game.PacketPlayOutSpawnEntity");
|
||||||
|
Class<?> entityTypesClass = nmsClassResolver.resolveSilent("world.entity.EntityTypes");
|
||||||
|
|
||||||
|
boolean isPre9 = getMajorVersion() < 9;
|
||||||
|
boolean isPre14 = getMajorVersion() < 14;
|
||||||
|
|
||||||
|
double y = loc.getY();
|
||||||
|
if (type == EntityType.ARMOR_STAND && !getServerVersion().equals("v1_8_R1")) {
|
||||||
|
// Marker armor stand => lift by normal armor stand height
|
||||||
|
y += 1.975;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object packet = packetPlayOutSpawnEntityClass.getConstructor().newInstance();
|
||||||
|
|
||||||
|
Field[] fields = new Field[12];
|
||||||
|
fields[0] = packetPlayOutSpawnEntityClass.getDeclaredField("a"); // ID
|
||||||
|
fields[1] = packetPlayOutSpawnEntityClass.getDeclaredField("b"); // UUID (Only 1.9+)
|
||||||
|
fields[2] = packetPlayOutSpawnEntityClass.getDeclaredField(isPre9 ? "b" : "c"); // Loc X
|
||||||
|
fields[3] = packetPlayOutSpawnEntityClass.getDeclaredField(isPre9 ? "c" : "d"); // Loc Y
|
||||||
|
fields[4] = packetPlayOutSpawnEntityClass.getDeclaredField(isPre9 ? "d" : "e"); // Loc Z
|
||||||
|
fields[5] = packetPlayOutSpawnEntityClass.getDeclaredField(isPre9 ? "e" : "f"); // Mot X
|
||||||
|
fields[6] = packetPlayOutSpawnEntityClass.getDeclaredField(isPre9 ? "f" : "g"); // Mot Y
|
||||||
|
fields[7] = packetPlayOutSpawnEntityClass.getDeclaredField(isPre9 ? "g" : "h"); // Mot Z
|
||||||
|
fields[8] = packetPlayOutSpawnEntityClass.getDeclaredField(isPre9 ? "h" : "i"); // Pitch
|
||||||
|
fields[9] = packetPlayOutSpawnEntityClass.getDeclaredField(isPre9 ? "i" : "j"); // Yaw
|
||||||
|
fields[10] = packetPlayOutSpawnEntityClass.getDeclaredField(isPre9 ? "j" : "k"); // Type
|
||||||
|
fields[11] = packetPlayOutSpawnEntityClass.getDeclaredField(isPre9 ? "k" : "l"); // Data
|
||||||
|
|
||||||
|
for (Field field : fields) {
|
||||||
|
field.setAccessible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object entityType = null;
|
||||||
|
if (!isPre14) {
|
||||||
|
entityType = entityTypesClass.getField(type == EntityType.ARMOR_STAND ? "ARMOR_STAND" : "ITEM").get(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
fields[0].set(packet, id);
|
||||||
|
if (!isPre9) fields[1].set(packet, uuid);
|
||||||
|
if (isPre9) {
|
||||||
|
fields[2].set(packet, (int)(loc.getX() * 32));
|
||||||
|
fields[3].set(packet, (int)(y * 32));
|
||||||
|
fields[4].set(packet, (int)(loc.getZ() * 32));
|
||||||
|
} else {
|
||||||
|
fields[2].set(packet, loc.getX());
|
||||||
|
fields[3].set(packet, y);
|
||||||
|
fields[4].set(packet, loc.getZ());
|
||||||
|
}
|
||||||
|
fields[5].set(packet, 0);
|
||||||
|
fields[6].set(packet, 0);
|
||||||
|
fields[7].set(packet, 0);
|
||||||
|
fields[8].set(packet, 0);
|
||||||
|
fields[9].set(packet, 0);
|
||||||
|
if (isPre14) fields[10].set(packet, type == EntityType.ARMOR_STAND ? 78 : 2);
|
||||||
|
else fields[10].set(packet, entityType);
|
||||||
|
fields[11].set(packet, 0);
|
||||||
|
|
||||||
|
return packet;
|
||||||
|
} catch (NoSuchMethodException | NoSuchFieldException | IllegalAccessException | InvocationTargetException | InstantiationException e) {
|
||||||
|
debug.getLogger().severe("Failed to create packet to spawn entity!");
|
||||||
|
debug.debug("Failed to create packet to spawn entity!");
|
||||||
|
debug.debug(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a packet to a player
|
||||||
|
* @param debug An instance of the {@link ShopChestDebug} debug instance
|
||||||
|
* @param packet Packet to send
|
||||||
|
* @param player Player to which the packet should be sent
|
||||||
|
* @return {@code true} if the packet was sent, or {@code false} if an exception was thrown
|
||||||
|
*/
|
||||||
|
public static boolean sendPacket(ShopChestDebug debug, Object packet, Player player) {
|
||||||
|
try {
|
||||||
|
if (packet == null) {
|
||||||
|
debug.debug("Failed to send packet: Packet is null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> packetClass = nmsClassResolver.resolveSilent("network.protocol.Packet");
|
||||||
|
if (packetClass == null) {
|
||||||
|
debug.debug("Failed to send packet: Could not find Packet class");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object nmsPlayer = player.getClass().getMethod("getHandle").invoke(player);
|
||||||
|
Field fConnection = (new FieldResolver(nmsPlayer.getClass())).resolve("playerConnection", "b");
|
||||||
|
Object playerConnection = fConnection.get(nmsPlayer);
|
||||||
|
|
||||||
|
playerConnection.getClass().getMethod("sendPacket", packetClass).invoke(playerConnection, packet);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (NoSuchMethodException | NoSuchFieldException | IllegalAccessException | InvocationTargetException e) {
|
||||||
|
debug.getLogger().severe("Failed to send packet " + packet.getClass().getName());
|
||||||
|
debug.debug("Failed to send packet " + packet.getClass().getName());
|
||||||
|
debug.debug(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The current server version with revision number (e.g. v1_9_R2, v1_10_R1)
|
||||||
|
*/
|
||||||
|
public static String getServerVersion() {
|
||||||
|
String packageName = Bukkit.getServer().getClass().getPackage().getName();
|
||||||
|
|
||||||
|
return packageName.substring(packageName.lastIndexOf('.') + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The revision of the current server version (e.g. <i>2</i> for v1_9_R2, <i>1</i> for v1_10_R1)
|
||||||
|
*/
|
||||||
|
public static int getRevision() {
|
||||||
|
return Integer.parseInt(getServerVersion().substring(getServerVersion().length() - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The major version of the server (e.g. <i>9</i> for 1.9.2, <i>10</i> for 1.10)
|
||||||
|
*/
|
||||||
|
public static int getMajorVersion() {
|
||||||
|
return Integer.parseInt(getServerVersion().split("_")[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package de.epiceric.shopchest.nms.reflection;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
public class ShopChestDebug {
|
||||||
|
|
||||||
|
private final Logger logger;
|
||||||
|
private final Consumer<String> debugConsumer;
|
||||||
|
private final Consumer<Throwable> throwableConsumer;
|
||||||
|
|
||||||
|
public ShopChestDebug(Logger logger, Consumer<String> debugConsumer, Consumer<Throwable> throwableConsumer) {
|
||||||
|
this.logger = logger;
|
||||||
|
this.debugConsumer = debugConsumer;
|
||||||
|
this.throwableConsumer = throwableConsumer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Logger getLogger() {
|
||||||
|
return logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void debug(String message){
|
||||||
|
debugConsumer.accept(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void debug(Throwable e){
|
||||||
|
throwableConsumer.accept(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user