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;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.PacketConstructor;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
/**
|
||||
@ -133,6 +136,13 @@ public interface ProtocolManager {
|
||||
*/
|
||||
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.
|
||||
* @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.MethodProxy;
|
||||
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
@ -336,7 +337,7 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
|
||||
return PacketConstructor.DEFAULT.withPacket(id, types);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Set<Integer> getSendingFilters() {
|
||||
return ImmutableSet.copyOf(sendingFilters);
|
||||
@ -347,6 +348,11 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
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.
|
||||
* @param players - list of players to inject.
|
||||
|
@ -199,6 +199,37 @@ public class FuzzyReflection {
|
||||
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).
|
||||
* @return Every field.
|
||||
|
In neuem Issue referenzieren
Einen Benutzer sperren