Compare commits

...

10 Commits

Author SHA1 Message Date
FlorianMichael 1c6be03d58
1.5.0 Release 2024-04-26 15:39:58 +02:00
FlorianMichael da747ce50e
[ci skip] Tidy up README.md 2024-04-03 13:26:05 +02:00
FlorianMichael e690fca663
Don't inform player about too old version when we can't detect his version 2024-04-03 13:20:38 +02:00
FlorianMichael 3cc472e4ae
Fix block collisions not working on Paper 2024-04-03 13:08:04 +02:00
FlorianMichael a155098cb6
Fixup field cache code 2024-04-02 21:01:49 +02:00
FlorianMichael 5f95c9edd1
Rename feature classes to their actual functionality 2024-04-02 13:18:13 +02:00
FlorianMichael 0c82936698
Rewrote reflection utilities to be failsafe, restructure project 2024-04-02 13:06:33 +02:00
FlorianMichael 74ad92d944
Merge master changes 2024-04-02 12:25:26 +02:00
FlorianMichael 99cf5cddf9
Merge remote-tracking branch 'origin/master' into dev 2024-04-02 12:22:29 +02:00
Nassim Jahnke 6d175f8bc7
Use Alpha channel in non-master branch 2024-03-06 14:20:33 +01:00
18 changed files with 499 additions and 438 deletions

View File

@ -3,6 +3,7 @@ on:
push:
branches:
- master
- dev
jobs:
publish:

View File

@ -8,19 +8,18 @@
This is an addon to [ViaRewind](https://github.com/ViaVersion/ViaRewind).
The features of this plugin will never be added to ViaRewind, this is an addon on purpose.
ViaRewind is a multiplatform (Paper, BungeeCord, Velocity and Sponge) plugin and this plugin is for Paper only! Adding features to ViaRewind, which do not work on all the supported platforms would be too confusing.
Support Status
-
While ViaRewind-Legacy-Support will keep getting updates to function with changes to ViaVersion or ViaBackwards, it will likely not receive many bug fixes or additional features anymore.
ViaRewind is a multiplatform (Paper, BungeeCord, Velocity and Sponge) plugin and this plugin is for Paper only!
Releases / Dev Builds
-
You can find releases in the following places:
- **Hangar (for our plugins)**: https://hangar.papermc.io/ViaVersion/ViaRewindLegacySupport
Dev builds for **all** of our projects are on our Jenkins server:
- **Jenkins**: https://ci.viaversion.com/view/ViaRewind/job/ViaRewind%20Legacy%20Support/
- **Jenkins**: https://ci.viaversion.com/view/ViaRewind/
## Installation

View File

@ -23,7 +23,7 @@ version = project.maven_version
group = project.maven_group
dependencies {
compileOnly "com.viaversion:viaversion-api:4.9.3"
compileOnly "com.viaversion:viaversion-api:4.10.0"
compileOnly "org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT"
}
@ -78,16 +78,27 @@ def latestCommitMessage() {
return byteOut.toString('UTF-8').trim()
}
def branchName() {
def byteOut = new ByteArrayOutputStream()
exec {
commandLine 'git', 'rev-parse', '--abbrev-ref', 'HEAD'
standardOutput = byteOut
}
return byteOut.toString('UTF-8').trim()
}
def baseVersion = project.maven_version
def isRelease = !baseVersion.contains('-')
def suffixedVersion = isRelease ? baseVersion : baseVersion + "+" + System.getenv("GITHUB_RUN_NUMBER")
def commitHash = latestCommitHash()
def changelogContent = "[${commitHash}](https://github.com/ViaVersion/iaRewind-Legacy-Support/commit/${commitHash}) ${latestCommitMessage()}"
def branch = branchName()
def isMainBranch = branch == "master"
hangarPublish {
publications.register("plugin") {
version.set(suffixedVersion)
id.set("ViaRewindLegacySupport")
channel.set(isRelease ? "Release" : "Snapshot")
channel.set(isRelease ? "Release" : isMainBranch ? "Snapshot" : "Alpha")
changelog.set(changelogContent)
apiKey.set(System.getenv("HANGAR_TOKEN"))
platforms {

View File

@ -4,7 +4,7 @@ org.gradle.parallel=true
# project
maven_version=1.5.0-SNAPSHOT
maven_version=1.5.0
maven_group=com.viaversion
mcVersionRange=1.8-1.20.4
mcVersionRange=1.8-1.20.5

View File

@ -18,15 +18,15 @@
package com.viaversion.viarewind.legacysupport;
import com.viaversion.viarewind.legacysupport.injector.BoundingBoxFixer;
import com.viaversion.viarewind.legacysupport.feature.BlockCollisionChanges;
import com.viaversion.viarewind.legacysupport.versioninfo.VersionInformer;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viarewind.legacysupport.listener.AreaEffectCloudListener;
import com.viaversion.viarewind.legacysupport.listener.BounceListener;
import com.viaversion.viarewind.legacysupport.listener.BrewingListener;
import com.viaversion.viarewind.legacysupport.listener.ElytraListener;
import com.viaversion.viarewind.legacysupport.listener.EnchantingListener;
import com.viaversion.viarewind.legacysupport.listener.SoundListener;
import com.viaversion.viarewind.legacysupport.feature.AreaEffectCloudEmulator;
import com.viaversion.viarewind.legacysupport.feature.SlimeBounceEmulator;
import com.viaversion.viarewind.legacysupport.feature.BrewingInteractionEmulator;
import com.viaversion.viarewind.legacysupport.feature.ElytraVelocityEmulator;
import com.viaversion.viarewind.legacysupport.feature.EnchantingGuiEmulator;
import com.viaversion.viarewind.legacysupport.feature.BlockPlaceSoundEmulator;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration;
@ -35,6 +35,8 @@ import org.bukkit.scheduler.BukkitRunnable;
public class BukkitPlugin extends JavaPlugin {
private ProtocolVersion serverProtocol;
@Override
public void onEnable() {
// Make the config file
@ -49,44 +51,54 @@ public class BukkitPlugin extends JavaPlugin {
@Override
public void run() {
final int serverProtocol = Via.getAPI().getServerVersion().lowestSupportedVersion();
if (serverProtocol == -1) return;
serverProtocol = Via.getAPI().getServerVersion().lowestSupportedProtocolVersion();
if (!serverProtocol.isKnown()) return;
cancel();
if (serverProtocol >= ProtocolVersion.v1_8.getVersion()) {
if (config.getBoolean("enchanting-gui-fix"))
Bukkit.getPluginManager().registerEvents(new EnchantingListener(), BukkitPlugin.this);
if (config.getBoolean("slime-fix"))
Bukkit.getPluginManager().registerEvents(new BounceListener(), BukkitPlugin.this);
if (serverProtocol.newerThanOrEqualTo(ProtocolVersion.v1_8)) {
if (config.getBoolean("enchanting-gui-fix")) {
Bukkit.getPluginManager().registerEvents(new EnchantingGuiEmulator(), BukkitPlugin.this);
}
if (config.getBoolean("slime-fix")) {
Bukkit.getPluginManager().registerEvents(new SlimeBounceEmulator(), BukkitPlugin.this);
}
}
if (serverProtocol >= ProtocolVersion.v1_9.getVersion()) {
if (config.getBoolean("sound-fix"))
Bukkit.getPluginManager().registerEvents(new SoundListener(BukkitPlugin.this), BukkitPlugin.this);
if (config.getBoolean("ladder-fix")) // 15w31a
BoundingBoxFixer.fixLadder(getLogger(), serverProtocol);
if (config.getBoolean("area-effect-cloud-particles")) // 15w32c
Bukkit.getPluginManager().registerEvents(new AreaEffectCloudListener(BukkitPlugin.this), BukkitPlugin.this);
if (config.getBoolean("elytra-fix")) // 15w40b
Bukkit.getPluginManager().registerEvents(new ElytraListener(), BukkitPlugin.this);
if (config.getBoolean("brewing-stand-gui-fix")) // 15w41b
Bukkit.getPluginManager().registerEvents(new BrewingListener(), BukkitPlugin.this);
if (config.getBoolean("lily-pad-fix")) // 15w44b
BoundingBoxFixer.fixLilyPad(getLogger(), serverProtocol);
if (serverProtocol.newerThanOrEqualTo(ProtocolVersion.v1_9)) {
if (config.getBoolean("sound-fix")) {
Bukkit.getPluginManager().registerEvents(new BlockPlaceSoundEmulator(BukkitPlugin.this), BukkitPlugin.this);
}
if (config.getBoolean("ladder-fix")) { // 15w31a
BlockCollisionChanges.fixLadder(getLogger(), serverProtocol);
}
if (config.getBoolean("area-effect-cloud-particles")) { // 15w32c
Bukkit.getPluginManager().registerEvents(new AreaEffectCloudEmulator(BukkitPlugin.this), BukkitPlugin.this);
}
if (config.getBoolean("elytra-fix")) { // 15w40b
Bukkit.getPluginManager().registerEvents(new ElytraVelocityEmulator(), BukkitPlugin.this);
}
if (config.getBoolean("brewing-stand-gui-fix")) { // 15w41b
Bukkit.getPluginManager().registerEvents(new BrewingInteractionEmulator(), BukkitPlugin.this);
}
if (config.getBoolean("lily-pad-fix")) { // 15w44b
BlockCollisionChanges.fixLilyPad(getLogger(), serverProtocol);
}
}
if (serverProtocol >= ProtocolVersion.v1_14_4.getVersion() && config.getBoolean("carpet-fix")) {
BoundingBoxFixer.fixCarpet(getLogger(), serverProtocol);
if (serverProtocol.newerThanOrEqualTo(ProtocolVersion.v1_14_4) && config.getBoolean("carpet-fix")) {
BlockCollisionChanges.fixCarpet(getLogger(), serverProtocol);
}
if (config.getBoolean("versioninfo.active")) {
new VersionInformer(BukkitPlugin.this, config);
}
}
}.runTaskTimer(this, 1L, 1L);
}
public ProtocolVersion getServerProtocol() {
return serverProtocol;
}
public static BukkitPlugin getInstance() {
return getPlugin(BukkitPlugin.class);
}
}

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.viaversion.viarewind.legacysupport.listener;
package com.viaversion.viarewind.legacysupport.feature;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viarewind.legacysupport.BukkitPlugin;
@ -24,7 +24,6 @@ import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.World;
import org.bukkit.entity.AreaEffectCloud;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@ -36,12 +35,12 @@ import java.util.Set;
import java.util.stream.Collectors;
@SuppressWarnings("unchecked")
public class AreaEffectCloudListener implements Listener {
public class AreaEffectCloudEmulator implements Listener {
private final ArrayList<AreaEffectCloud> effectClouds = new ArrayList<>();
public AreaEffectCloudListener(final BukkitPlugin plugin) {
public AreaEffectCloudEmulator(final BukkitPlugin plugin) {
Bukkit.getScheduler().runTaskTimer(plugin, () -> {
final Set<Player> affectedPlayers = Bukkit.getOnlinePlayers().stream().filter(p -> Via.getAPI().getPlayerVersion(p) >= ProtocolVersion.v1_8.getVersion()).collect(Collectors.toSet());
final Set<Player> affectedPlayers = Bukkit.getOnlinePlayers().stream().filter(p -> Via.getAPI().getPlayerProtocolVersion(p).newerThanOrEqualTo(ProtocolVersion.v1_8)).collect(Collectors.toSet());
effectClouds.removeIf(e -> !e.isValid());
effectClouds.forEach(cloud -> {
final Location location = cloud.getLocation();

View File

@ -16,9 +16,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.viaversion.viarewind.legacysupport.injector;
package com.viaversion.viarewind.legacysupport.feature;
import com.viaversion.viarewind.legacysupport.reflection.ReflectionAPI;
import com.viaversion.viarewind.legacysupport.BukkitPlugin;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
import java.lang.reflect.Field;
@ -28,12 +28,14 @@ import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
public class BoundingBoxFixer {
import static com.viaversion.viarewind.legacysupport.util.ReflectionUtil.*;
import static com.viaversion.viarewind.legacysupport.util.NMSUtil.*;
public static void fixLilyPad(final Logger logger, final int serverVersion) {
public class BlockCollisionChanges {
public static void fixLilyPad(final Logger logger, final ProtocolVersion serverVersion) {
try {
final Field boundingBoxField = ReflectionAPI.getFieldAccessible(NMSReflection.getNMSBlock("BlockWaterLily"),
serverVersion <= ProtocolVersion.v1_20_2.getVersion() ? "a" : "b");
final Field boundingBoxField = getFieldAccessible(getNMSBlockClass("BlockWaterLily"), serverVersion.olderThanOrEqualTo(ProtocolVersion.v1_20_2) ? "a" : "b");
setBoundingBox(boundingBoxField.get(null), 0.0625, 0.0, 0.0625, 0.9375, 0.015625, 0.9375);
} catch (Exception ex) {
@ -41,30 +43,30 @@ public class BoundingBoxFixer {
}
}
public static void fixCarpet(final Logger logger, final int serverVersion) {
public static void fixCarpet(final Logger logger, final ProtocolVersion serverVersion) {
try {
final Class<?> blockCarpetClass = serverVersion <= ProtocolVersion.v1_16_4.getVersion() ? NMSReflection.getNMSBlock("BlockCarpet") : NMSReflection.getNMSBlock("CarpetBlock");
final Class<?> blockCarpetClass = serverVersion.olderThanOrEqualTo(ProtocolVersion.v1_16_4) ? getNMSBlockClass("BlockCarpet") : getNMSBlockClass("CarpetBlock");
final Field boundingBoxField = ReflectionAPI.getFieldAccessible(blockCarpetClass, serverVersion <= ProtocolVersion.v1_20_2.getVersion() ? "a" : "b");
final Field boundingBoxField = getFieldAccessible(blockCarpetClass, serverVersion.olderThanOrEqualTo(ProtocolVersion.v1_20_2) ? "a" : "b");
setBoundingBox(boundingBoxField.get(0), 0.0D, -0.0000001D, 0.0D, 1.0D, 0.0000001D, 1.0D);
} catch (Exception ex) {
logger.log(Level.SEVERE, "Could not fix carpet bounding box.", ex);
}
}
public static void fixLadder(final Logger logger, final int serverVersion) {
public static void fixLadder(final Logger logger, final ProtocolVersion serverVersion) {
try {
final boolean pre1_12_2 = serverVersion <= ProtocolVersion.v1_12_2.getVersion();
final boolean pre1_13_2 = serverVersion <= ProtocolVersion.v1_13_2.getVersion();
final boolean pre1_16_4 = serverVersion <= ProtocolVersion.v1_16_4.getVersion();
final boolean pre1_20_2 = serverVersion <= ProtocolVersion.v1_20_2.getVersion();
final boolean pre1_12_2 = serverVersion.olderThanOrEqualTo(ProtocolVersion.v1_12_2);
final boolean pre1_13_2 = serverVersion.olderThanOrEqualTo(ProtocolVersion.v1_13_2);
final boolean pre1_16_4 = serverVersion.olderThanOrEqualTo(ProtocolVersion.v1_16_4);
final boolean pre1_20_2 = serverVersion.olderThanOrEqualTo(ProtocolVersion.v1_20_2);
final Class<?> blockLadderClass = NMSReflection.getNMSBlock("BlockLadder");
final Class<?> blockLadderClass = getNMSBlockClass("BlockLadder");
final Field boundingBoxEastField = ReflectionAPI.getFieldAccessible(blockLadderClass, pre1_12_2 ? "b" : pre1_13_2 ? "c" : pre1_16_4 ? "c" : pre1_20_2 ? "d" : "e");
final Field boundingBoxWestField = ReflectionAPI.getFieldAccessible(blockLadderClass, pre1_12_2 ? "c" : pre1_13_2 ? "o" : pre1_16_4 ? "d" : pre1_20_2 ? "e" : "f");
final Field boundingBoxSouthField = ReflectionAPI.getFieldAccessible(blockLadderClass, pre1_12_2 ? "d" : pre1_13_2 ? "p" : pre1_16_4 ? "e" : pre1_20_2 ? "f" : "g");
final Field boundingBoxNorthField = ReflectionAPI.getFieldAccessible(blockLadderClass, pre1_12_2 ? "e" : pre1_13_2 ? "q" : pre1_16_4 ? "f" : pre1_20_2 ? "g" : "h");
final Field boundingBoxEastField = getFieldAccessible(blockLadderClass, pre1_12_2 ? "b" : pre1_13_2 ? "c" : pre1_16_4 ? "c" : pre1_20_2 ? "d" : "e");
final Field boundingBoxWestField = getFieldAccessible(blockLadderClass, pre1_12_2 ? "c" : pre1_13_2 ? "o" : pre1_16_4 ? "d" : pre1_20_2 ? "e" : "f");
final Field boundingBoxSouthField = getFieldAccessible(blockLadderClass, pre1_12_2 ? "d" : pre1_13_2 ? "p" : pre1_16_4 ? "e" : pre1_20_2 ? "f" : "g");
final Field boundingBoxNorthField = getFieldAccessible(blockLadderClass, pre1_12_2 ? "e" : pre1_13_2 ? "q" : pre1_16_4 ? "f" : pre1_20_2 ? "g" : "h");
setBoundingBox(boundingBoxEastField.get(null), 0.0D, 0.0D, 0.0D, 0.125D, 1.0D, 1.0D);
setBoundingBox(boundingBoxWestField.get(null), 0.875D, 0.0D, 0.0D, 1.0D, 1.0D, 1.0D);
@ -76,23 +78,19 @@ public class BoundingBoxFixer {
}
private static void setBoundingBox(Object boundingBox, double... values) throws ReflectiveOperationException {
if (boundingBox.getClass().getSimpleName().equals("VoxelShapeArray")) {
setVoxelShapeArray(boundingBox, values);
return;
switch (boundingBox.getClass().getSimpleName()) {
case "AxisAlignedBB": // Legacy NMS
setAxisAlignedBB(boundingBox, values);
break;
case "VoxelShapeArray": // Paper
setVoxelShapeArray(boundingBox, values);
break;
case "AABBVoxelShape": // Tuinity
setAABBVoxelShape(boundingBox, values);
break;
default:
throw new IllegalStateException("Unknown bounding box type: " + boundingBox.getClass().getName());
}
if (boundingBox.getClass().getSimpleName().equals("AxisAlignedBB")) {
setAxisAlignedBB(boundingBox, values);
return;
}
// Tuinity support
if (boundingBox.getClass().getSimpleName().equals("AABBVoxelShape")) {
setAABBVoxelShape(boundingBox, values);
return;
}
throw new IllegalStateException("Unknown bounding box type: " + boundingBox.getClass().getName());
}
private static void setAABBVoxelShape(Object boundingBox, double[] values) throws ReflectiveOperationException {
@ -142,5 +140,21 @@ public class BoundingBoxFixer {
field.setAccessible(true);
field.set(voxelShapeArray, wrapMethod.invoke(null, (Object) array));
}
// Handle Paper voxel shape caching by clearing the cache
final Class<?> voxelShape = voxelShapeArray.getClass().getSuperclass();
final Field shape = getFieldAccessible(voxelShape, "a");
final Field cachedShapeData = getFieldAccessible(shape.getType(), "cachedShapeData");
if (cachedShapeData == null) { // No Paper or too old version
return;
}
cachedShapeData.set(shape.get(voxelShapeArray), null);
final Field isEmpty = getFieldAccessible(voxelShape, "isEmpty");
isEmpty.setBoolean(voxelShapeArray, true);
final Method initCache = voxelShape.getDeclaredMethod("initCache");
initCache.invoke(voxelShapeArray);
}
}

View File

@ -16,13 +16,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.viaversion.viarewind.legacysupport.listener;
package com.viaversion.viarewind.legacysupport.feature;
import com.viaversion.viarewind.legacysupport.reflection.MethodSignature;
import com.viaversion.viarewind.legacysupport.reflection.ReflectionAPI;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viarewind.legacysupport.BukkitPlugin;
import com.viaversion.viarewind.legacysupport.injector.NMSReflection;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
import org.bukkit.Bukkit;
import org.bukkit.Location;
@ -42,8 +39,11 @@ import org.bukkit.event.player.PlayerPickupItemEvent;
import java.lang.reflect.Method;
import java.util.logging.Level;
import static com.viaversion.viarewind.legacysupport.util.ReflectionUtil.*;
import static com.viaversion.viarewind.legacysupport.util.NMSUtil.*;
@SuppressWarnings("unchecked")
public class SoundListener implements Listener {
public class BlockPlaceSoundEmulator implements Listener {
private static boolean isSoundCategory = false;
@ -55,7 +55,7 @@ public class SoundListener implements Listener {
}
}
public SoundListener(final BukkitPlugin plugin) {
public BlockPlaceSoundEmulator(final BukkitPlugin plugin) {
try {
Class.forName("org.bukkit.event.entity.EntityPickupItemEvent");
@ -65,7 +65,7 @@ public class SoundListener implements Listener {
public void onItemPickUp(EntityPickupItemEvent e) {
if (!(e.getEntity() instanceof Player)) return;
SoundListener.this.onItemPickUp((Player) e.getEntity());
BlockPlaceSoundEmulator.this.onItemPickUp((Player) e.getEntity());
}
}, plugin);
@ -74,7 +74,7 @@ public class SoundListener implements Listener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onItemPickUp(PlayerPickupItemEvent e) {
SoundListener.this.onItemPickUp(e.getPlayer());
BlockPlaceSoundEmulator.this.onItemPickUp(e.getPlayer());
}
}, plugin);
@ -84,9 +84,9 @@ public class SoundListener implements Listener {
@EventHandler
public void onBlockPlace(BlockPlaceEvent e) {
final Player player = e.getPlayer();
if (Via.getAPI().getPlayerVersion(player) >= ProtocolVersion.v1_9.getVersion()) return;
if (Via.getAPI().getPlayerProtocolVersion(player).newerThanOrEqualTo(ProtocolVersion.v1_9)) return;
if (Via.getAPI().getServerVersion().lowestSupportedVersion() >= ProtocolVersion.v1_17.getVersion()) {
if (Via.getAPI().getServerVersion().lowestSupportedProtocolVersion().newerThanOrEqualTo(ProtocolVersion.v1_17)) {
player.playSound(e.getBlockPlaced().getLocation(), e.getBlock().getBlockData().getSoundGroup().getPlaceSound(), 1.0f, 0.8f);
} else {
try {
@ -131,7 +131,7 @@ public class SoundListener implements Listener {
private static void playBlockPlaceSoundNMS(Player player, Block block) throws Exception {
World world = block.getWorld();
Object nmsWorld = world.getClass().getMethod("getHandle").invoke(world);
Class<?> blockPositionClass = NMSReflection.getBlockPositionClass();
Class<?> blockPositionClass = getBlockPositionClass();
Object blockPosition = null;
if (blockPositionClass != null)
@ -148,11 +148,11 @@ public class SoundListener implements Listener {
Method getStepSound;
final int serverProtocol = Via.getAPI().getServerVersion().lowestSupportedVersion();
if (serverProtocol > ProtocolVersion.v1_8.getVersion() && serverProtocol < ProtocolVersion.v1_12.getVersion()) {
getStepSound = ReflectionAPI.findRecursiveMethodOrNull(nmsBlock.getClass(), "w");
getStepSound = getMethod(nmsBlock.getClass(), "w");
} else if (serverProtocol > ProtocolVersion.v1_10.getVersion() && serverProtocol < ProtocolVersion.v1_13.getVersion()) {
getStepSound = ReflectionAPI.findRecursiveMethodOrNull(nmsBlock.getClass(), "getStepSound");
getStepSound = getMethod(nmsBlock.getClass(), "getStepSound");
} else { // 1.14 - 1.16.5
getStepSound = ReflectionAPI.findRecursiveMethodOrNull(nmsBlock.getClass(), "getStepSound", blockData.getClass());
getStepSound = getMethod(nmsBlock.getClass(), "getStepSound", blockData.getClass());
}
if (getStepSound == null) {
Via.getPlatform().getLogger().severe("Could not find getStepSound method in " + nmsBlock.getClass().getName());
@ -186,7 +186,7 @@ public class SoundListener implements Listener {
Object soundEffect = soundEffectMethod.invoke(soundType);
float volume = (float) volumeMethod.invoke(soundType);
float pitch = (float) pitchMethod.invoke(soundType);
Object soundCategory = Enum.valueOf(NMSReflection.getSoundCategoryClass(), "BLOCKS");
Object soundCategory = Enum.valueOf(getSoundCategoryClass(), "BLOCKS");
volume = (volume + 1.0f) / 2.0f;
pitch *= 0.8;
@ -197,7 +197,7 @@ public class SoundListener implements Listener {
// 1.8.8 -> 1.16.5
private static void playSound(Player player, Object soundEffect, Object soundCategory, double x, double y, double z, float volume, float pitch) {
try {
Object packet = NMSReflection.getGamePacketClass("PacketPlayOutNamedSoundEffect").getConstructor(
Object packet = getGamePacketClass("PacketPlayOutNamedSoundEffect").getConstructor(
soundEffect.getClass(), soundCategory.getClass(),
double.class, double.class, double.class,
float.class, float.class
@ -209,7 +209,7 @@ public class SoundListener implements Listener {
// Volume = 1
// Pitch = .8
NMSReflection.sendPacket(player, packet);
sendPacket(player, packet);
} catch (Exception ex) {
ex.printStackTrace();
}

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.viaversion.viarewind.legacysupport.listener;
package com.viaversion.viarewind.legacysupport.feature;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
@ -32,14 +32,14 @@ import org.bukkit.inventory.BrewerInventory;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
public class BrewingListener implements Listener {
public class BrewingInteractionEmulator implements Listener {
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPlayerInteract(PlayerInteractEvent e) {
if (!e.hasBlock() || e.getClickedBlock().getType() != Material.BREWING_STAND) return;
Player player = e.getPlayer();
int version = Via.getAPI().getPlayerVersion(player);
if (version > ProtocolVersion.v1_9.getVersion()) return;
ProtocolVersion version = Via.getAPI().getPlayerProtocolVersion(player);
if (version.newerThan(ProtocolVersion.v1_9)) return;
ItemStack blazePowder = new ItemStack(Material.BLAZE_POWDER);
ItemStack playerItem = e.getItem();
if (playerItem == null) playerItem = new ItemStack(Material.AIR);

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.viaversion.viarewind.legacysupport.listener;
package com.viaversion.viarewind.legacysupport.feature;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
@ -28,13 +28,13 @@ import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.util.Vector;
@SuppressWarnings("unchecked")
public class ElytraListener implements Listener {
public class ElytraVelocityEmulator implements Listener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerMove(PlayerMoveEvent e) {
final Player player = e.getPlayer();
if (Via.getAPI().getPlayerVersion(player) >= ProtocolVersion.v1_9.getVersion()) return; // Only apply for 1.8 and below players
if (Via.getAPI().getPlayerProtocolVersion(player).newerThanOrEqualTo(ProtocolVersion.v1_9)) return; // Only apply for 1.8 and below players
if (!player.isGliding()) return; // Only apply if the player is gliding
final Vector direction = player.getLocation().getDirection();

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.viaversion.viarewind.legacysupport.listener;
package com.viaversion.viarewind.legacysupport.feature;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
@ -34,12 +34,12 @@ import org.bukkit.inventory.PlayerInventory;
import java.util.Map;
@SuppressWarnings("unchecked")
public class EnchantingListener implements Listener {
public class EnchantingGuiEmulator implements Listener {
private final boolean newMaterialNames;
private final Material lapisMaterial;
public EnchantingListener() {
public EnchantingGuiEmulator() {
newMaterialNames = Material.getMaterial("LAPIS_LAZULI") != null;
if (newMaterialNames) {
@ -54,7 +54,7 @@ public class EnchantingListener implements Listener {
if (!(e.getInventory() instanceof EnchantingInventory)) return;
final Player player = (Player) e.getPlayer();
if (Via.getAPI().getPlayerVersion(player) >= ProtocolVersion.v1_8.getVersion()) return;
if (Via.getAPI().getPlayerProtocolVersion(player).newerThanOrEqualTo(ProtocolVersion.v1_8)) return;
final PlayerInventory inv = player.getInventory();
final ItemStack lapis = newMaterialNames ? new ItemStack(lapisMaterial) : new ItemStack(lapisMaterial, 1, (short) 4);
@ -89,7 +89,7 @@ public class EnchantingListener implements Listener {
if (!(e.getInventory() instanceof EnchantingInventory)) return;
final Player player = (Player) e.getPlayer();
if (Via.getAPI().getPlayerVersion(player) >= ProtocolVersion.v1_8.getVersion()) return;
if (Via.getAPI().getPlayerProtocolVersion(player).newerThanOrEqualTo(ProtocolVersion.v1_8)) return;
final PlayerInventory inv = player.getInventory();
final EnchantingInventory replacement = (EnchantingInventory) e.getInventory();

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.viaversion.viarewind.legacysupport.listener;
package com.viaversion.viarewind.legacysupport.feature;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
@ -29,14 +29,14 @@ import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.util.Vector;
@SuppressWarnings({"unchecked", "DataFlowIssue"})
public class BounceListener implements Listener {
public class SlimeBounceEmulator implements Listener {
@EventHandler
public void onPlayerMove(PlayerMoveEvent e) {
if (e.getTo().getY() >= e.getFrom().getY()) return; // Only check upwards motion
final Player player = e.getPlayer();
if (Via.getAPI().getPlayerVersion(player) >= ProtocolVersion.v1_8.getVersion()) return; // Only apply for 1.7 and below players
if (Via.getAPI().getPlayerProtocolVersion(player).newerThanOrEqualTo(ProtocolVersion.v1_8)) return; // Only apply for 1.7 and below players
if (Math.floor(e.getTo().getY()) + 0.01 < e.getTo().getY()) return;
if (player.isSneaking()) return;

View File

@ -1,141 +0,0 @@
/*
* This file is part of ViaRewind-Legacy-Support - https://github.com/ViaVersion/ViaRewind-Legacy-Support
* Copyright (C) 2018-2024 ViaVersion and contributors
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.viaversion.viarewind.legacysupport.injector;
import com.viaversion.viaversion.api.Via;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
public class NMSReflection {
private static final int PROTOCOL_1_17 = 755;
private static int protocolVersion = -1;
private static String version;
private static Field playerConnectionField;
public static String getVersion() {
return version == null ? version = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3] : version;
}
public static int getProtocolVersion() {
return protocolVersion == -1 ? protocolVersion = Via.getAPI().getServerVersion().lowestSupportedVersion() : protocolVersion;
}
public static Class<?> getBlockPositionClass() {
try {
if (getProtocolVersion() >= PROTOCOL_1_17) {
return Class.forName("net.minecraft.core.BlockPosition");
}
return getLegacyNMSClass("BlockPosition");
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
return null;
}
public static Class<?> getNMSBlock(String name) {
try {
if (getProtocolVersion() >= PROTOCOL_1_17) {
return Class.forName("net.minecraft.world.level.block." + name);
}
return getLegacyNMSClass(name);
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
return null;
}
@SuppressWarnings("rawtypes")
public static Class getSoundCategoryClass() {
try {
if (getProtocolVersion() >= PROTOCOL_1_17) {
return Class.forName("net.minecraft.sounds.SoundCategory");
}
return getLegacyNMSClass("SoundCategory");
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
return null;
}
public static Class<?> getPacketClass() {
try {
if (getProtocolVersion() >= PROTOCOL_1_17) {
return Class.forName("net.minecraft.network.protocol.Packet");
}
return getLegacyNMSClass("Packet");
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
return null;
}
public static Class<?> getGamePacketClass(String packetType) {
try {
if (getProtocolVersion() >= PROTOCOL_1_17) {
return Class.forName("net.minecraft.network.protocol.game." + packetType);
}
return getLegacyNMSClass(packetType);
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
return null;
}
public static Class<?> getPlayerConnectionClass() {
try {
if (getProtocolVersion() >= PROTOCOL_1_17) {
return Class.forName("net.minecraft.server.network.PlayerConnection");
}
return getLegacyNMSClass("PlayerConnection");
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
return null;
}
public static Class<?> getLegacyNMSClass(String name) throws ClassNotFoundException {
return Class.forName("net.minecraft.server." + getVersion() + "." + name);
}
public static void sendPacket(Player player, Object packet) throws ReflectiveOperationException {
Object nmsPlayer = player.getClass().getMethod("getHandle").invoke(player);
if (playerConnectionField == null) {
playerConnectionField = Arrays.stream(nmsPlayer.getClass().getFields())
.filter(field -> field.getType() == getPlayerConnectionClass()).findFirst()
.orElseThrow(() -> new ReflectiveOperationException("Failed to find PlayerConnection field in EntityPlayer"));
}
Object playerConnection = playerConnectionField.get(nmsPlayer);
Method sendPacket;
try { // TODO find better way
sendPacket = playerConnection.getClass().getDeclaredMethod("sendPacket", getPacketClass());
} catch (Exception e) {
try {
sendPacket = playerConnection.getClass().getDeclaredMethod("a", getPacketClass());
} catch (Exception e2) {
throw new ReflectiveOperationException("Failed to find sendPacket method in PlayerConnection");
}
}
sendPacket.invoke(playerConnection, packet);
}
}

View File

@ -1,61 +0,0 @@
/*
* This file is part of ViaRewind-Legacy-Support - https://github.com/ViaVersion/ViaRewind-Legacy-Support
* Copyright (C) 2018-2024 ViaVersion and contributors
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.viaversion.viarewind.legacysupport.reflection;
import java.util.Arrays;
import java.util.Objects;
import java.util.StringJoiner;
public class MethodSignature {
private final String name;
private final Class<?>[] parameterTypes;
private Class<?> returnType;
public MethodSignature(String name, Class<?>... parameterTypes) {
this.name = name;
this.parameterTypes = parameterTypes;
}
public String name() {
return name;
}
public Class<?>[] parameterTypes() {
return parameterTypes;
}
public Class<?> returnType() {
return returnType;
}
public MethodSignature withReturnType(Class<?> returnType) {
Objects.requireNonNull(returnType);
this.returnType = returnType;
return this;
}
@Override
public String toString() {
return new StringJoiner(", ", MethodSignature.class.getSimpleName() + "[", "]")
.add("name='" + name + "'")
.add("parameterTypes=" + Arrays.toString(parameterTypes))
.toString();
}
}

View File

@ -1,113 +0,0 @@
/*
* This file is part of ViaRewind-Legacy-Support - https://github.com/ViaVersion/ViaRewind-Legacy-Support
* Copyright (C) 2018-2024 ViaVersion and contributors
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.viaversion.viarewind.legacysupport.reflection;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class ReflectionAPI {
private static final Map<String, Field> fields = new HashMap<>();
private static boolean staticFinalModificationBlocked;
static {
try {
Field.class.getDeclaredField("modifiers");
} catch (NoSuchFieldException ex) {
staticFinalModificationBlocked = true;
}
}
public static Field getField(Class clazz, String fieldname) {
String key = clazz.getName() + ":" + fieldname;
Field field = null;
if (fields.containsKey(key)) {
field = fields.get(key);
} else {
try {
field = clazz.getDeclaredField(fieldname);
} catch (NoSuchFieldException ignored) {
}
fields.put(key, field);
}
return field;
}
public static Field getFieldAccessible(Class clazz, String fieldname) {
Field field = getField(clazz, fieldname);
if (field != null) field.setAccessible(true);
return field;
}
public static void setFieldNotFinal(Field field) {
int modifiers = field.getModifiers();
if (!Modifier.isFinal(modifiers)) return;
if (staticFinalModificationBlocked) {
try {
Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
getDeclaredFields0.setAccessible(true);
Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false);
for (Field classField : fields) {
if ("modifiers".equals(classField.getName())) {
classField.setAccessible(true);
classField.set(field, modifiers & ~Modifier.FINAL);
break;
}
}
} catch (ReflectiveOperationException ex) {
ex.printStackTrace();
}
} else {
setValuePrintException(Field.class, field, "modifiers", modifiers & ~Modifier.FINAL);
}
}
public static void setValue(Class clazz, Object object, String fieldname, Object value, boolean isFinal) throws IllegalAccessException {
Field field = getFieldAccessible(clazz, fieldname);
if (isFinal) setFieldNotFinal(field);
field.set(object, value);
}
public static void setValue(Class clazz, Object object, String fieldname, Object value) throws IllegalAccessException {
setValue(clazz, object, fieldname, value, false);
}
public static void setValuePrintException(Class clazz, Object object, String fieldname, Object value) {
try {
setValue(clazz, object, fieldname, value);
} catch (Exception ex) {
ex.printStackTrace();
}
}
public static Method findRecursiveMethodOrNull(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
try {
return clazz.getDeclaredMethod(methodName, parameterTypes);
} catch (NoSuchMethodException ex) {
Class<?> superClass = clazz.getSuperclass();
if (superClass == null) return null;
return findRecursiveMethodOrNull(superClass, methodName, parameterTypes);
}
}
}

View File

@ -0,0 +1,139 @@
/*
* This file is part of ViaRewind-Legacy-Support - https://github.com/ViaVersion/ViaRewind-Legacy-Support
* Copyright (C) 2018-2024 ViaVersion and contributors
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.viaversion.viarewind.legacysupport.util;
import com.viaversion.viarewind.legacysupport.BukkitPlugin;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.logging.Level;
import static com.viaversion.viarewind.legacysupport.util.ReflectionUtil.failSafeGetClass;
public class NMSUtil {
public static String nmsVersionPackage;
private static Field playerConnectionField;
static {
nmsVersionPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
}
public static Class<?> getBlockPositionClass() {
if (BukkitPlugin.getInstance().getServerProtocol().newerThanOrEqualTo(ProtocolVersion.v1_17)) {
return failSafeGetClass("net.minecraft.core.BlockPosition");
} else {
return getLegacyNMSClass("BlockPosition");
}
}
public static Class<?> getNMSBlockClass(final String name) {
if (BukkitPlugin.getInstance().getServerProtocol().newerThanOrEqualTo(ProtocolVersion.v1_17)) {
return failSafeGetClass("net.minecraft.world.level.block." + name);
} else {
return getLegacyNMSClass(name);
}
}
public static Class getSoundCategoryClass() { // Bypass generics
if (BukkitPlugin.getInstance().getServerProtocol().newerThanOrEqualTo(ProtocolVersion.v1_17)) {
return failSafeGetClass("net.minecraft.sounds.SoundCategory");
} else {
return getLegacyNMSClass("SoundCategory");
}
}
public static Class<?> getPacketClass() {
if (BukkitPlugin.getInstance().getServerProtocol().newerThanOrEqualTo(ProtocolVersion.v1_17)) {
return failSafeGetClass("net.minecraft.network.protocol.Packet");
} else {
return getLegacyNMSClass("Packet");
}
}
public static Class<?> getGamePacketClass(final String packet) {
if (BukkitPlugin.getInstance().getServerProtocol().newerThanOrEqualTo(ProtocolVersion.v1_17)) {
return failSafeGetClass("net.minecraft.network.protocol.game." + packet);
} else {
return getLegacyNMSClass(packet);
}
}
public static Class<?> getPlayerConnectionClass() {
if (BukkitPlugin.getInstance().getServerProtocol().newerThanOrEqualTo(ProtocolVersion.v1_17)) {
return failSafeGetClass("net.minecraft.server.network.PlayerConnection");
} else {
return getLegacyNMSClass("PlayerConnection");
}
}
public static void sendPacket(final Player player, final Object packet) {
Object nmsPlayer = null;
try {
player.getClass().getMethod("getHandle").invoke(player);
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
BukkitPlugin.getInstance().getLogger().log(Level.SEVERE, "Failed to get EntityPlayer from player", e);
return;
}
// Cache result as it never changes
if (playerConnectionField == null) {
final Class<?> playerConnection = getPlayerConnectionClass();
for (Field field : nmsPlayer.getClass().getFields()) {
if (field.getType() == playerConnection) {
playerConnectionField = field;
break;
}
}
// If reflection failed, log and return
if (playerConnectionField == null) {
BukkitPlugin.getInstance().getLogger().log(Level.SEVERE, "Failed to find PlayerConnection field in EntityPlayer");
return;
}
}
Object playerConnection;
try {
playerConnection = playerConnectionField.get(nmsPlayer);
} catch (IllegalAccessException e) {
BukkitPlugin.getInstance().getLogger().log(Level.SEVERE, "Failed to get PlayerConnection from EntityPlayer", e);
return;
}
try {
final Method sendPacket = ReflectionUtil.findMethod(player.getClass(), new String[] {"sendPacket", "a"}, getPacketClass());
sendPacket.invoke(playerConnection, packet);
} catch (IllegalAccessException | InvocationTargetException | NullPointerException e) {
BukkitPlugin.getInstance().getLogger().log(Level.SEVERE, "Failed to send packet to player", e);
}
}
public static Class<?> getLegacyNMSClass(final String name) {
try {
return Class.forName("net.minecraft.server." + nmsVersionPackage + "." + name);
} catch (ClassNotFoundException e) {
BukkitPlugin.getInstance().getLogger().log(Level.SEVERE, "Could not find NMS class " + name + "! NMS version package: " + nmsVersionPackage, e);
return null;
}
}
}

View File

@ -0,0 +1,200 @@
/*
* This file is part of ViaRewind-Legacy-Support - https://github.com/ViaVersion/ViaRewind-Legacy-Support
* Copyright (C) 2018-2024 ViaVersion and contributors
*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.viaversion.viarewind.legacysupport.util;
import com.viaversion.viarewind.legacysupport.BukkitPlugin;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
/**
* Common reflection utilities for Java 8 and newer.
*/
public class ReflectionUtil {
private static final Map<String, Field> fieldCache = new HashMap<>();
private static boolean staticFinalModificationBlocked;
static {
try {
Field.class.getDeclaredField("modifiers");
} catch (NoSuchFieldException ex) {
staticFinalModificationBlocked = true;
}
}
public static Method findMethod(final Class<?> clazz, final String[] methodNames, final Class<?>... parameterTypes) {
for (String methodName : methodNames) {
final Method method = getMethod(clazz, methodName, parameterTypes);
if (method != null) {
return method;
}
}
return null;
}
/**
* Recursively search for a method in the class and its superclasses. Returns null if the method is not found.
*
* @param clazz The class to search in
* @param methodName The name of the method
* @param parameterTypes The parameter types of the method
* @return The method if found, otherwise null
*/
public static Method getMethod(final Class<?> clazz, final String methodName, final Class<?>... parameterTypes) {
try {
return clazz.getDeclaredMethod(methodName, parameterTypes);
} catch (NoSuchMethodException ex) {
final Class<?> superClass = clazz.getSuperclass();
if (superClass == null) return null;
return getMethod(superClass, methodName, parameterTypes);
}
}
public static Field getFieldAndCache(final Class<?> clazz, final String fieldName) {
final String key = clazz.getName() + ":" + fieldName;
if (fieldCache.containsKey(key)) {
return fieldCache.get(key);
} else {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException ignored) {} // Cache non-existing field too
fieldCache.put(key, field);
return field;
}
}
/**
* Gets and field from the {@link #fieldCache} or directly and makes it accessible.
*
* @param clazz The class
* @param fieldName The field name
* @return The field
*/
public static Field getFieldAccessible(final Class<?> clazz, final String fieldName) {
final Field field = getFieldAndCache(clazz, fieldName);
if (field != null) {
field.setAccessible(true);
}
return field;
}
/**
* Get a field from a class, and print an error message if it fails.
*
* @param clazz The class
* @param fieldName The field name
* @return The field, or null if it fails
*/
public static Field failSafeGetField(final Class<?> clazz, final String fieldName) {
try {
return getFieldAccessible(clazz, fieldName);
} catch (Exception e) {
BukkitPlugin.getInstance().getLogger().log(Level.SEVERE, "Failed to get field " + fieldName + " in class " + clazz.getName(), e);
return null;
}
}
public static void setValue(final Class<?> clazz, final Object object, final String name, final Object value, final boolean isFinal) throws IllegalAccessException {
final Field field = getFieldAccessible(clazz, name);
if (isFinal) {
removeFinal(field);
}
field.set(object, value);
}
/**
* Remove the final modifier from a field.
*
* @param field The field
*/
public static void removeFinal(final Field field) {
final int modifiers = field.getModifiers();
if (!Modifier.isFinal(modifiers)) {
// Non-finals don't need to be modified
return;
}
if (!staticFinalModificationBlocked) {
// Older Java versions allow us to modify the modifiers directly and remove
// the final modifier
failSafeSetValue(Field.class, field, "modifiers", modifiers & ~Modifier.FINAL);
return;
}
// Modern Java bypass
try {
final Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
getDeclaredFields0.setAccessible(true);
final Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false);
for (Field classField : fields) {
if ("modifiers".equals(classField.getName())) {
classField.setAccessible(true);
classField.set(field, modifiers & ~Modifier.FINAL);
break;
}
}
} catch (ReflectiveOperationException e) {
BukkitPlugin.getInstance().getLogger().log(Level.SEVERE, "Failed to remove final modifier from field " + field.getName(), e);
}
}
/**
* Unsafe wrapper for value setting which bypasses final checks.
*
* @param clazz The class
* @param object The object
* @param name The field name
* @param value The value
* @throws IllegalAccessException If the field cannot be accessed
*/
public static void setValue(final Class<?> clazz, final Object object, final String name, final Object value) throws IllegalAccessException {
setValue(clazz, object, name, value, false);
}
/**
* Set a field value, and print an error message if it fails.
*
* @param clazz The class
* @param object The object
* @param name The field name
* @param value The value
*/
public static void failSafeSetValue(final Class<?> clazz, final Object object, final String name, final Object value) {
try {
setValue(clazz, object, name, value);
} catch (Exception e) {
BukkitPlugin.getInstance().getLogger().log(Level.SEVERE, "Failed to set value for field " + name + " in class " + clazz.getName(), e);
}
}
public static Class<?> failSafeGetClass(final String name) {
try {
return Class.forName(name);
} catch (ClassNotFoundException e) {
BukkitPlugin.getInstance().getLogger().log(Level.SEVERE, "Failed to get class " + name, e);
return null;
}
}
}

View File

@ -57,9 +57,10 @@ public class VersionInformer implements Listener {
}
protected void inform(final Player player) {
int version = Via.getAPI().getPlayerVersion(player);
if (version > maxVersion) return;
final int version = Via.getAPI().getPlayerVersion(player);
if (version == -1 || version > maxVersion) {
return;
}
player.sendMessage(this.versionMessage);
}