Added the ability to refresh the apperance of entities and players.
Dieser Commit ist enthalten in:
Ursprung
3783328cb1
Commit
8f9939f65c
@ -18,14 +18,17 @@
|
|||||||
package com.comphenix.protocol;
|
package com.comphenix.protocol;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
import com.comphenix.protocol.events.PacketContainer;
|
import com.comphenix.protocol.events.PacketContainer;
|
||||||
import com.comphenix.protocol.events.PacketListener;
|
import com.comphenix.protocol.events.PacketListener;
|
||||||
import com.comphenix.protocol.injector.PacketConstructor;
|
import com.comphenix.protocol.injector.PacketConstructor;
|
||||||
|
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -133,6 +136,13 @@ public interface ProtocolManager {
|
|||||||
*/
|
*/
|
||||||
public PacketConstructor createPacketConstructor(int id, Object... arguments);
|
public PacketConstructor createPacketConstructor(int id, Object... arguments);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Completely refresh all clients about an entity.
|
||||||
|
* @param entity - entity to refresh.
|
||||||
|
* @param observers - the clients to update.
|
||||||
|
*/
|
||||||
|
public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a immutable set containing the ID of the sent server packets that will be observed by listeners.
|
* Retrieves a immutable set containing the ID of the sent server packets that will be observed by listeners.
|
||||||
* @return Every filtered server packet.
|
* @return Every filtered server packet.
|
||||||
|
159
ProtocolLib/src/com/comphenix/protocol/injector/EntityUtilities.java
Normale Datei
159
ProtocolLib/src/com/comphenix/protocol/injector/EntityUtilities.java
Normale Datei
@ -0,0 +1,159 @@
|
|||||||
|
package com.comphenix.protocol.injector;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.craftbukkit.CraftWorld;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.injector.PacketConstructor.BukkitUnwrapper;
|
||||||
|
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||||
|
import com.comphenix.protocol.reflect.FieldUtils;
|
||||||
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to perform certain operations on entities.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
class EntityUtilities {
|
||||||
|
|
||||||
|
private static Field entityTrackerField;
|
||||||
|
private static Field trackedEntitiesField;
|
||||||
|
private static Field trackedPlayersField;
|
||||||
|
|
||||||
|
private static Method hashGetMethod;
|
||||||
|
private static Method scanPlayersMethod;
|
||||||
|
|
||||||
|
public static void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException {
|
||||||
|
|
||||||
|
World world = entity.getWorld();
|
||||||
|
Object worldServer = ((CraftWorld) world).getHandle();
|
||||||
|
|
||||||
|
// We have to rely on the class naming here.
|
||||||
|
if (entityTrackerField == null)
|
||||||
|
entityTrackerField = FuzzyReflection.fromObject(worldServer).getFieldByType(".*Tracker");
|
||||||
|
|
||||||
|
// Get the tracker
|
||||||
|
Object tracker = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
tracker = FieldUtils.readField(entityTrackerField, worldServer, false);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new FieldAccessException("Cannot access 'tracker' field due to security limitations.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trackedEntitiesField == null) {
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Set<Class> ignoredTypes = new HashSet<Class>();
|
||||||
|
|
||||||
|
// Well, this is more difficult. But we're looking for a Minecraft object that is not
|
||||||
|
// created by the constructor(s).
|
||||||
|
for (Constructor<?> constructor : tracker.getClass().getConstructors()) {
|
||||||
|
for (Class<?> type : constructor.getParameterTypes()) {
|
||||||
|
ignoredTypes.add(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Minecraft field that's NOT filled in by the constructor
|
||||||
|
trackedEntitiesField = FuzzyReflection.fromObject(tracker, true).
|
||||||
|
getFieldByType(FuzzyReflection.MINECRAFT_OBJECT, ignoredTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the entity hashmap
|
||||||
|
Object trackedEntities = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
trackedEntities = FieldUtils.readField(trackedEntitiesField, tracker, true);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new FieldAccessException("Cannot access 'trackedEntities' field due to security limitations.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getting the "get" method is pretty hard, but first - try to just get it by name
|
||||||
|
if (hashGetMethod == null) {
|
||||||
|
|
||||||
|
Class<?> type = trackedEntities.getClass();
|
||||||
|
|
||||||
|
try {
|
||||||
|
hashGetMethod = type.getMethod("get", int.class);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
|
||||||
|
Class<?>[] params = { int.class };
|
||||||
|
|
||||||
|
// Then it's probably the lowest named method that takes an int-parameter
|
||||||
|
for (Method method : type.getMethods()) {
|
||||||
|
if (Arrays.equals(params, method.getParameterTypes())) {
|
||||||
|
if (hashGetMethod == null ||
|
||||||
|
method.getName().compareTo(hashGetMethod.getName()) < 0) {
|
||||||
|
hashGetMethod = method;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
//EntityTrackerEntry trackEntity = (EntityTrackerEntry) tracker.trackedEntities.get(entity.getEntityId());
|
||||||
|
Object trackerEntry = hashGetMethod.invoke(trackedEntities, entity.getEntityId());
|
||||||
|
|
||||||
|
if (trackedPlayersField == null) {
|
||||||
|
// This one is fairly easy
|
||||||
|
trackedPlayersField = FuzzyReflection.fromObject(trackerEntry).getFieldByType("java\\.util\\..*");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phew, finally there.
|
||||||
|
Collection<?> trackedPlayers = (Collection<?>) FieldUtils.readField(trackedPlayersField, trackerEntry, false);
|
||||||
|
List<Object> nmsPlayers = unwrapBukkit(observers);
|
||||||
|
|
||||||
|
// trackEntity.trackedPlayers.clear();
|
||||||
|
trackedPlayers.removeAll(nmsPlayers);
|
||||||
|
|
||||||
|
// We have to rely on a NAME once again. Damn it.
|
||||||
|
if (scanPlayersMethod == null) {
|
||||||
|
scanPlayersMethod = trackerEntry.getClass().getMethod("scanPlayers", List.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
//trackEntity.scanPlayers(server.players);
|
||||||
|
scanPlayersMethod.invoke(trackerEntry, nmsPlayers);
|
||||||
|
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new FieldAccessException("Security limitation prevents access to 'get' method in IntHashMap", e);
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
throw new RuntimeException("Exception occurred in Minecraft.", e);
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
throw new FieldAccessException("Security limitation prevents access to 'scanPlayers' method in trackerEntry.", e);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
throw new FieldAccessException("Canot find 'scanPlayers' method. Is ProtocolLib up to date?", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Object> unwrapBukkit(List<Player> players) {
|
||||||
|
|
||||||
|
List<Object> output = Lists.newArrayList();
|
||||||
|
BukkitUnwrapper unwrapper = new BukkitUnwrapper();
|
||||||
|
|
||||||
|
// Get the NMS equivalent
|
||||||
|
for (Player player : players) {
|
||||||
|
Object result = unwrapper.unwrapItem(player);
|
||||||
|
|
||||||
|
if (result != null)
|
||||||
|
output.add(result);
|
||||||
|
else
|
||||||
|
throw new IllegalArgumentException("Cannot unwrap item " + player);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
@ -34,6 +34,7 @@ import net.sf.cglib.proxy.Enhancer;
|
|||||||
import net.sf.cglib.proxy.MethodInterceptor;
|
import net.sf.cglib.proxy.MethodInterceptor;
|
||||||
import net.sf.cglib.proxy.MethodProxy;
|
import net.sf.cglib.proxy.MethodProxy;
|
||||||
|
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.EventPriority;
|
import org.bukkit.event.EventPriority;
|
||||||
@ -347,6 +348,11 @@ public final class PacketFilterManager implements ProtocolManager {
|
|||||||
return ImmutableSet.copyOf(packetInjector.getPacketHandlers());
|
return ImmutableSet.copyOf(packetInjector.getPacketHandlers());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException {
|
||||||
|
EntityUtilities.updateEntity(entity, observers);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the packet injection for every player.
|
* Initialize the packet injection for every player.
|
||||||
* @param players - list of players to inject.
|
* @param players - list of players to inject.
|
||||||
|
@ -199,6 +199,37 @@ public class FuzzyReflection {
|
|||||||
typeRegex + " in " + source.getName());
|
typeRegex + " in " + source.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a field by type.
|
||||||
|
* <p>
|
||||||
|
* Note that the type is matched using the full canonical representation, i.e.:
|
||||||
|
* <ul>
|
||||||
|
* <li>java.util.List</li>
|
||||||
|
* <li>net.comphenix.xp.ExperienceMod</li>
|
||||||
|
* </ul>
|
||||||
|
* @param typeRegex - regular expression that will match the field type.
|
||||||
|
* @param ignoredTypes - types to ignore.
|
||||||
|
* @return The first field with a type that matches the given regular expression.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public Field getFieldByType(String typeRegex, Set<Class> ignored) {
|
||||||
|
|
||||||
|
Pattern match = Pattern.compile(typeRegex);
|
||||||
|
|
||||||
|
// Like above, only here we test the field type
|
||||||
|
for (Field field : getFields()) {
|
||||||
|
Class type = field.getType();
|
||||||
|
|
||||||
|
if (!ignored.contains(type) && match.matcher(type.getName()).matches()) {
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks like we're outdated. Too bad.
|
||||||
|
throw new RuntimeException("Unable to find a field with the type " +
|
||||||
|
typeRegex + " in " + source.getName());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves all private and public fields in declared order (after JDK 1.5).
|
* Retrieves all private and public fields in declared order (after JDK 1.5).
|
||||||
* @return Every field.
|
* @return Every field.
|
||||||
|
In neuem Issue referenzieren
Einen Benutzer sperren