Implemented more ways to get Ping on bukkit

Affects issues:
- Close #1962
This commit is contained in:
Risto Lahtela 2021-07-03 10:05:34 +03:00
parent 170add8989
commit db0d3ff6ac
11 changed files with 442 additions and 66 deletions

View File

@ -31,7 +31,6 @@ import com.djrapitops.plan.settings.config.paths.DataGatheringSettings;
import com.djrapitops.plan.settings.config.paths.TimeSettings;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.transactions.events.PingStoreTransaction;
import com.djrapitops.plan.utilities.java.Reflection;
import net.playeranalytics.plugin.scheduling.RunnableFactory;
import net.playeranalytics.plugin.scheduling.TimeAmount;
import net.playeranalytics.plugin.server.Listeners;
@ -44,10 +43,6 @@ import org.bukkit.event.player.PlayerQuitEvent;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
@ -69,9 +64,6 @@ public class BukkitPingCounter extends TaskSystem.Task implements Listener {
private static boolean pingMethodAvailable;
private static MethodHandle pingField;
private static MethodHandle getHandleMethod;
private final Map<UUID, List<DateObj<Integer>>> playerHistory;
private final Listeners listeners;
@ -80,6 +72,8 @@ public class BukkitPingCounter extends TaskSystem.Task implements Listener {
private final ServerInfo serverInfo;
private final RunnableFactory runnableFactory;
private PingMethod pingMethod;
@Inject
public BukkitPingCounter(
Listeners listeners,
@ -89,57 +83,46 @@ public class BukkitPingCounter extends TaskSystem.Task implements Listener {
RunnableFactory runnableFactory
) {
this.listeners = listeners;
BukkitPingCounter.loadPingMethodDetails();
this.config = config;
this.dbSystem = dbSystem;
this.serverInfo = serverInfo;
this.runnableFactory = runnableFactory;
playerHistory = new HashMap<>();
Optional<PingMethod> pingMethod = loadPingMethod();
if (pingMethod.isPresent()) {
this.pingMethod = pingMethod.get();
pingMethodAvailable = true;
} else {
pingMethodAvailable = false;
}
}
private Optional<PingMethod> loadPingMethod() {
PingMethod[] methods = new PingMethod[]{
new PaperPingMethod(),
new ReflectiveLatencyFieldMethod(),
new ReflectivePingFieldMethod(),
new ReflectiveLevelEntityPlayerLatencyFieldMethod(),
new ReflectiveUnmappedLatencyFieldMethod()
};
StringBuilder reasonsForUnavailability = new StringBuilder();
private static void loadPingMethodDetails() {
pingMethodAvailable = isPingMethodAvailable();
MethodHandle localHandle = null;
MethodHandle localPing = null;
if (!pingMethodAvailable) {
try {
Class<?> craftPlayerClass = Reflection.getCraftBukkitClass("entity.CraftPlayer");
Class<?> entityPlayer = Reflection.getMinecraftClass("EntityPlayer");
Lookup lookup = MethodHandles.publicLookup();
Method getHandleMethod = craftPlayerClass.getDeclaredMethod("getHandle");
localHandle = lookup.unreflect(getHandleMethod);
localPing = lookup.findGetter(entityPlayer, "ping", Integer.TYPE);
} catch (NoSuchMethodException | IllegalAccessException | NoSuchFieldException reflectiveEx) {
Logger.getGlobal().log(
Level.WARNING,
"Plan: Could not register Ping counter due to " + reflectiveEx
);
} catch (IllegalArgumentException e) {
Logger.getGlobal().log(
Level.WARNING,
"Plan: No Ping method handle found - Ping will not be recorded."
);
for (PingMethod potentialMethod : methods) {
if (potentialMethod.isAvailable()) {
return Optional.of(potentialMethod);
} else {
reasonsForUnavailability.append("\n ").append(potentialMethod.getReasonForUnavailability());
}
}
Logger.getGlobal().log(
Level.WARNING,
"Plan: No Ping method found - Ping will not be recorded:" + reasonsForUnavailability.toString()
);
getHandleMethod = localHandle;
pingField = localPing;
return Optional.empty();
}
private static boolean isPingMethodAvailable() {
try {
//Only available in Paper
Class.forName("org.bukkit.entity.Player$Spigot").getDeclaredMethod("getPing");
return true;
} catch (ClassNotFoundException | NoSuchMethodException noSuchMethodEx) {
return false;
}
}
@Override
public void register(RunnableFactory runnableFactory) {
@ -164,7 +147,7 @@ public class BukkitPingCounter extends TaskSystem.Task implements Listener {
Player player = Bukkit.getPlayer(uuid);
if (player != null) {
int ping = getPing(player);
if (ping < -1 || ping > TimeUnit.SECONDS.toMillis(8L)) {
if (ping <= -1 || ping > TimeUnit.SECONDS.toMillis(8L)) {
// Don't accept bad values
continue;
}
@ -191,22 +174,9 @@ public class BukkitPingCounter extends TaskSystem.Task implements Listener {
private int getPing(Player player) {
if (pingMethodAvailable) {
// This method is from Paper
return player.spigot().getPing();
}
return getReflectionPing(player);
}
private int getReflectionPing(Player player) {
try {
Object entityPlayer = getHandleMethod.invoke(player);
return (int) pingField.invoke(entityPlayer);
} catch (Exception ex) {
return -1;
} catch (Throwable throwable) {
throw (Error) throwable;
return pingMethod.getPing(player);
}
return -1;
}
@EventHandler

View File

@ -0,0 +1,53 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2016-2018
*
* 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 com.djrapitops.plan.gathering.timed;
import org.bukkit.entity.Player;
public class PaperPingMethod implements PingMethod {
private String reasonForUnavailability;
@Override
public boolean isAvailable() {
try {
//Only available in Paper
Class.forName("org.bukkit.entity.Player$Spigot").getDeclaredMethod("getPing");
return true;
} catch (ClassNotFoundException | NoSuchMethodException noSuchMethodEx) {
reasonForUnavailability = noSuchMethodEx.toString();
return false;
}
}
@Override
public int getPing(Player player) {
return player.spigot().getPing();
}
@Override
public String getReasonForUnavailability() {
return reasonForUnavailability;
}
}

View File

@ -0,0 +1,36 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2016-2018
*
* 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 com.djrapitops.plan.gathering.timed;
import org.bukkit.entity.Player;
public interface PingMethod {
boolean isAvailable();
int getPing(Player player);
String getReasonForUnavailability();
}

View File

@ -0,0 +1,79 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2016-2018
*
* 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 com.djrapitops.plan.gathering.timed;
import com.djrapitops.plan.utilities.java.Reflection;
import org.bukkit.entity.Player;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
public class ReflectiveLatencyFieldMethod implements PingMethod {
private static MethodHandle pingField;
private static MethodHandle getHandleMethod;
private String reasonForUnavailability;
@Override
public boolean isAvailable() {
MethodHandle localHandle = null;
MethodHandle localPing = null;
try {
Class<?> craftPlayerClass = Reflection.getCraftBukkitClass("entity.CraftPlayer");
Class<?> entityPlayer = Reflection.getMinecraftClass("EntityPlayer");
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
Method getHandleMethod = craftPlayerClass.getDeclaredMethod("getHandle");
localHandle = lookup.unreflect(getHandleMethod);
localPing = lookup.findGetter(entityPlayer, "ping", Integer.TYPE);
} catch (NoSuchMethodException | IllegalAccessException | NoSuchFieldException | IllegalArgumentException reflectiveEx) {
reasonForUnavailability = reflectiveEx.toString();
return false;
}
getHandleMethod = localHandle;
pingField = localPing;
return pingField != null;
}
@Override
public int getPing(Player player) {
try {
Object entityPlayer = getHandleMethod.invoke(player);
return (int) pingField.invoke(entityPlayer);
} catch (Exception ex) {
return -1;
} catch (Throwable throwable) {
throw (Error) throwable;
}
}
@Override
public String getReasonForUnavailability() {
return reasonForUnavailability;
}
}

View File

@ -0,0 +1,79 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2016-2018
*
* 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 com.djrapitops.plan.gathering.timed;
import com.djrapitops.plan.utilities.java.Reflection;
import org.bukkit.entity.Player;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
public class ReflectiveLevelEntityPlayerLatencyFieldMethod implements PingMethod {
private static MethodHandle pingField;
private static MethodHandle getHandleMethod;
private String reasonForUnavailability;
@Override
public boolean isAvailable() {
MethodHandle localHandle = null;
MethodHandle localPing = null;
try {
Class<?> craftPlayerClass = Reflection.getCraftBukkitClass("entity.CraftPlayer");
Class<?> entityPlayer = Class.forName("net.minecraft.server.level.EntityPlayer");
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
Method getHandleMethod = craftPlayerClass.getDeclaredMethod("getHandle");
localHandle = lookup.unreflect(getHandleMethod);
localPing = lookup.findGetter(entityPlayer, "latency", Integer.TYPE);
} catch (NoSuchMethodException | IllegalAccessException | NoSuchFieldException | ClassNotFoundException | IllegalArgumentException reflectiveEx) {
reasonForUnavailability = reflectiveEx.toString();
return false;
}
getHandleMethod = localHandle;
pingField = localPing;
return pingField != null;
}
@Override
public int getPing(Player player) {
try {
Object entityPlayer = getHandleMethod.invoke(player);
return (int) pingField.invoke(entityPlayer);
} catch (Exception ex) {
return -1;
} catch (Throwable throwable) {
throw (Error) throwable;
}
}
@Override
public String getReasonForUnavailability() {
return reasonForUnavailability;
}
}

View File

@ -0,0 +1,79 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2016-2018
*
* 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 com.djrapitops.plan.gathering.timed;
import com.djrapitops.plan.utilities.java.Reflection;
import org.bukkit.entity.Player;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
public class ReflectivePingFieldMethod implements PingMethod {
private static MethodHandle pingField;
private static MethodHandle getHandleMethod;
private String reasonForUnavailability;
@Override
public boolean isAvailable() {
MethodHandle localHandle = null;
MethodHandle localPing = null;
try {
Class<?> craftPlayerClass = Reflection.getCraftBukkitClass("entity.CraftPlayer");
Class<?> entityPlayer = Reflection.getMinecraftClass("EntityPlayer");
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
Method getHandleMethod = craftPlayerClass.getDeclaredMethod("getHandle");
localHandle = lookup.unreflect(getHandleMethod);
localPing = lookup.findGetter(entityPlayer, "ping", Integer.TYPE);
} catch (NoSuchMethodException | IllegalAccessException | NoSuchFieldException | IllegalArgumentException reflectiveEx) {
reasonForUnavailability = reflectiveEx.toString();
return false;
}
getHandleMethod = localHandle;
pingField = localPing;
return pingField != null;
}
@Override
public int getPing(Player player) {
try {
Object entityPlayer = getHandleMethod.invoke(player);
return (int) pingField.invoke(entityPlayer);
} catch (Exception ex) {
return -1;
} catch (Throwable throwable) {
throw (Error) throwable;
}
}
@Override
public String getReasonForUnavailability() {
return reasonForUnavailability;
}
}

View File

@ -0,0 +1,80 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2016-2018
*
* 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 com.djrapitops.plan.gathering.timed;
import com.djrapitops.plan.utilities.java.Reflection;
import org.bukkit.entity.Player;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
public class ReflectiveUnmappedLatencyFieldMethod implements PingMethod {
private static MethodHandle pingField;
private static MethodHandle getHandleMethod;
private String reasonForUnavailability;
@Override
public boolean isAvailable() {
MethodHandle localHandle = null;
MethodHandle localPing = null;
try {
Class<?> craftPlayerClass = Reflection.getCraftBukkitClass("entity.CraftPlayer");
Class<?> entityPlayer = Class.forName("net.minecraft.server.level.EntityPlayer");
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
Method getHandleMethod = craftPlayerClass.getDeclaredMethod("getHandle");
localHandle = lookup.unreflect(getHandleMethod);
// e is latency in unmapped jar
localPing = lookup.findGetter(entityPlayer, "e", Integer.TYPE);
} catch (NoSuchMethodException | IllegalAccessException | NoSuchFieldException | ClassNotFoundException | IllegalArgumentException reflectiveEx) {
reasonForUnavailability = reflectiveEx.toString();
return false;
}
getHandleMethod = localHandle;
pingField = localPing;
return pingField != null;
}
@Override
public int getPing(Player player) {
try {
Object entityPlayer = getHandleMethod.invoke(player);
return (int) pingField.invoke(entityPlayer);
} catch (Exception ex) {
return -1;
} catch (Throwable throwable) {
throw (Error) throwable;
}
}
@Override
public String getReasonForUnavailability() {
return reasonForUnavailability;
}
}

View File

@ -90,7 +90,7 @@ public class BungeePingCounter extends TaskSystem.Task implements Listener {
ProxiedPlayer player = ProxyServer.getInstance().getPlayer(uuid);
if (player != null) {
int ping = getPing(player);
if (ping < -1 || ping > TimeUnit.SECONDS.toMillis(8L)) {
if (ping <= -1 || ping > TimeUnit.SECONDS.toMillis(8L)) {
// Don't accept bad values
continue;
}

View File

@ -93,7 +93,7 @@ public class NukkitPingCounter extends TaskSystem.Task implements Listener {
Optional<Player> player = Server.getInstance().getPlayer(uuid);
if (player.isPresent()) {
int ping = player.get().getPing();
if (ping < -1 || ping > TimeUnit.SECONDS.toMillis(8L)) {
if (ping <= -1 || ping > TimeUnit.SECONDS.toMillis(8L)) {
// Don't accept bad values
continue;
}

View File

@ -86,7 +86,7 @@ public class SpongePingCounter extends TaskSystem.Task {
Optional<Player> player = Sponge.getServer().getPlayer(uuid);
if (player.isPresent()) {
int ping = getPing(player.get());
if (ping < -1 || ping > TimeUnit.SECONDS.toMillis(8L)) {
if (ping <= -1 || ping > TimeUnit.SECONDS.toMillis(8L)) {
// Don't accept bad values
continue;
}

View File

@ -94,7 +94,7 @@ public class VelocityPingCounter extends TaskSystem.Task {
Player player = plugin.getProxy().getPlayer(uuid).orElse(null);
if (player != null) {
int ping = getPing(player);
if (ping < -1 || ping > TimeUnit.SECONDS.toMillis(8L)) {
if (ping <= -1 || ping > TimeUnit.SECONDS.toMillis(8L)) {
// Don't accept bad values
continue;
}