Added support for modifying attributes in UPDATE_ATTRIBUTES.
This contains a fully-fledged API for reading and modifying Attribute- Snapshot and AttributeModifier. Keep in mind that these objects are immutable, so modification must be made through object builders. The packets are also shared, so packet cloning might be necessary if attributes should differ per player.
Dieser Commit ist enthalten in:
Ursprung
7170bfcadc
Commit
988026611c
@ -439,7 +439,7 @@ class CommandPacket extends CommandBase {
|
|||||||
@Override
|
@Override
|
||||||
public boolean print(StringBuilder output, Object value) {
|
public boolean print(StringBuilder output, Object value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
EquivalentConverter<Object> converter = BukkitConverters.getGenericConverters().get(value.getClass());
|
EquivalentConverter<Object> converter = BukkitConverters.getConvertersForGeneric().get(value.getClass());
|
||||||
|
|
||||||
if (converter != null) {
|
if (converter != null) {
|
||||||
output.append(converter.getSpecific(value));
|
output.append(converter.getSpecific(value));
|
||||||
|
@ -62,6 +62,7 @@ import com.comphenix.protocol.utility.MinecraftReflection;
|
|||||||
import com.comphenix.protocol.utility.StreamSerializer;
|
import com.comphenix.protocol.utility.StreamSerializer;
|
||||||
import com.comphenix.protocol.wrappers.BukkitConverters;
|
import com.comphenix.protocol.wrappers.BukkitConverters;
|
||||||
import com.comphenix.protocol.wrappers.ChunkPosition;
|
import com.comphenix.protocol.wrappers.ChunkPosition;
|
||||||
|
import com.comphenix.protocol.wrappers.WrappedAttribute;
|
||||||
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
|
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
|
||||||
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
|
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
|
||||||
import com.comphenix.protocol.wrappers.nbt.NbtBase;
|
import com.comphenix.protocol.wrappers.nbt.NbtBase;
|
||||||
@ -386,6 +387,23 @@ public class PacketContainer implements Serializable {
|
|||||||
BukkitConverters.getNbtConverter());
|
BukkitConverters.getNbtConverter());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a read/write structure for collections of attribute snapshots.
|
||||||
|
* <p>
|
||||||
|
* This modifier will automatically marshall between the visible ProtocolLib WrappedAttribute and the
|
||||||
|
* internal Minecraft AttributeSnapshot.
|
||||||
|
* @return A modifier for AttributeSnapshot collection fields.
|
||||||
|
*/
|
||||||
|
public StructureModifier<List<WrappedAttribute>> getAttributeCollectionModifier() {
|
||||||
|
// Convert to and from the ProtocolLib wrapper
|
||||||
|
return structureModifier.withType(
|
||||||
|
Collection.class,
|
||||||
|
BukkitConverters.getListConverter(
|
||||||
|
MinecraftReflection.getAttributeSnapshotClass(),
|
||||||
|
BukkitConverters.getWrappedAttributeConverter())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a read/write structure for collections of chunk positions.
|
* Retrieves a read/write structure for collections of chunk positions.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -89,6 +89,15 @@ public class StructureModifier<TField> {
|
|||||||
this(targetType, null, true);
|
this(targetType, null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a structure modifier.
|
||||||
|
* @param targetType - the structure to modify.
|
||||||
|
* @param useStructureCompiler - whether or not to use a structure compiler.
|
||||||
|
*/
|
||||||
|
public StructureModifier(Class targetType, boolean useStructureCompiler) {
|
||||||
|
this(targetType, null, true, useStructureCompiler);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a structure modifier.
|
* Creates a structure modifier.
|
||||||
* @param targetType - the structure to modify.
|
* @param targetType - the structure to modify.
|
||||||
|
@ -1,365 +1,365 @@
|
|||||||
/*
|
/*
|
||||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||||
* Copyright (C) 2012 Kristian S. Stangeland
|
* Copyright (C) 2012 Kristian S. Stangeland
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
* 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 2 of
|
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||||
* the License, or (at your option) any later version.
|
* 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;
|
* 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.
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
* See the GNU General Public License for more details.
|
* 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;
|
* You should have received a copy of the GNU General Public License along with this program;
|
||||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||||
* 02111-1307 USA
|
* 02111-1307 USA
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.comphenix.protocol.reflect.compiler;
|
package com.comphenix.protocol.reflect.compiler;
|
||||||
|
|
||||||
import java.lang.management.ManagementFactory;
|
import java.lang.management.ManagementFactory;
|
||||||
import java.lang.management.MemoryPoolMXBean;
|
import java.lang.management.MemoryPoolMXBean;
|
||||||
import java.lang.management.MemoryUsage;
|
import java.lang.management.MemoryUsage;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.RejectedExecutionException;
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
import java.util.concurrent.ThreadFactory;
|
import java.util.concurrent.ThreadFactory;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import com.comphenix.protocol.error.ErrorReporter;
|
import com.comphenix.protocol.error.ErrorReporter;
|
||||||
import com.comphenix.protocol.error.Report;
|
import com.comphenix.protocol.error.Report;
|
||||||
import com.comphenix.protocol.error.ReportType;
|
import com.comphenix.protocol.error.ReportType;
|
||||||
import com.comphenix.protocol.reflect.StructureModifier;
|
import com.comphenix.protocol.reflect.StructureModifier;
|
||||||
import com.comphenix.protocol.reflect.compiler.StructureCompiler.StructureKey;
|
import com.comphenix.protocol.reflect.compiler.StructureCompiler.StructureKey;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compiles structure modifiers on a background thread.
|
* Compiles structure modifiers on a background thread.
|
||||||
* <p>
|
* <p>
|
||||||
* This is necessary as we cannot block the main thread.
|
* This is necessary as we cannot block the main thread.
|
||||||
*
|
*
|
||||||
* @author Kristian
|
* @author Kristian
|
||||||
*/
|
*/
|
||||||
public class BackgroundCompiler {
|
public class BackgroundCompiler {
|
||||||
public static final ReportType REPORT_CANNOT_COMPILE_STRUCTURE_MODIFIER = new ReportType("Cannot compile structure. Disabing compiler.");
|
public static final ReportType REPORT_CANNOT_COMPILE_STRUCTURE_MODIFIER = new ReportType("Cannot compile structure. Disabing compiler.");
|
||||||
public static final ReportType REPORT_CANNOT_SCHEDULE_COMPILATION = new ReportType("Unable to schedule compilation task.");
|
public static final ReportType REPORT_CANNOT_SCHEDULE_COMPILATION = new ReportType("Unable to schedule compilation task.");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default format for the name of new worker threads.
|
* The default format for the name of new worker threads.
|
||||||
*/
|
*/
|
||||||
public static final String THREAD_FORMAT = "ProtocolLib-StructureCompiler %s";
|
public static final String THREAD_FORMAT = "ProtocolLib-StructureCompiler %s";
|
||||||
|
|
||||||
// How long to wait for a shutdown
|
// How long to wait for a shutdown
|
||||||
public static final int SHUTDOWN_DELAY_MS = 2000;
|
public static final int SHUTDOWN_DELAY_MS = 2000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default fraction of perm gen space after which the background compiler will be disabled.
|
* The default fraction of perm gen space after which the background compiler will be disabled.
|
||||||
*/
|
*/
|
||||||
public static final double DEFAULT_DISABLE_AT_PERM_GEN = 0.65;
|
public static final double DEFAULT_DISABLE_AT_PERM_GEN = 0.65;
|
||||||
|
|
||||||
// The single background compiler we're using
|
// The single background compiler we're using
|
||||||
private static BackgroundCompiler backgroundCompiler;
|
private static BackgroundCompiler backgroundCompiler;
|
||||||
|
|
||||||
// Classes we're currently compiling
|
// Classes we're currently compiling
|
||||||
private Map<StructureKey, List<CompileListener<?>>> listeners = Maps.newHashMap();
|
private Map<StructureKey, List<CompileListener<?>>> listeners = Maps.newHashMap();
|
||||||
private Object listenerLock = new Object();
|
private Object listenerLock = new Object();
|
||||||
|
|
||||||
private StructureCompiler compiler;
|
private StructureCompiler compiler;
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
private boolean shuttingDown;
|
private boolean shuttingDown;
|
||||||
|
|
||||||
private ExecutorService executor;
|
private ExecutorService executor;
|
||||||
private ErrorReporter reporter;
|
private ErrorReporter reporter;
|
||||||
|
|
||||||
private double disablePermGenFraction = DEFAULT_DISABLE_AT_PERM_GEN;
|
private double disablePermGenFraction = DEFAULT_DISABLE_AT_PERM_GEN;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the current background compiler.
|
* Retrieves the current background compiler.
|
||||||
* @return Current background compiler.
|
* @return Current background compiler.
|
||||||
*/
|
*/
|
||||||
public static BackgroundCompiler getInstance() {
|
public static BackgroundCompiler getInstance() {
|
||||||
return backgroundCompiler;
|
return backgroundCompiler;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the single background compiler we're using.
|
* Sets the single background compiler we're using.
|
||||||
* @param backgroundCompiler - current background compiler, or NULL if the library is not loaded.
|
* @param backgroundCompiler - current background compiler, or NULL if the library is not loaded.
|
||||||
*/
|
*/
|
||||||
public static void setInstance(BackgroundCompiler backgroundCompiler) {
|
public static void setInstance(BackgroundCompiler backgroundCompiler) {
|
||||||
BackgroundCompiler.backgroundCompiler = backgroundCompiler;
|
BackgroundCompiler.backgroundCompiler = backgroundCompiler;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize a background compiler.
|
* Initialize a background compiler.
|
||||||
* <p>
|
* <p>
|
||||||
* Uses the default {@link #THREAD_FORMAT} to name worker threads.
|
* Uses the default {@link #THREAD_FORMAT} to name worker threads.
|
||||||
* @param loader - class loader from Bukkit.
|
* @param loader - class loader from Bukkit.
|
||||||
* @param reporter - current error reporter.
|
* @param reporter - current error reporter.
|
||||||
*/
|
*/
|
||||||
public BackgroundCompiler(ClassLoader loader, ErrorReporter reporter) {
|
public BackgroundCompiler(ClassLoader loader, ErrorReporter reporter) {
|
||||||
ThreadFactory factory = new ThreadFactoryBuilder().
|
ThreadFactory factory = new ThreadFactoryBuilder().
|
||||||
setDaemon(true).
|
setDaemon(true).
|
||||||
setNameFormat(THREAD_FORMAT).
|
setNameFormat(THREAD_FORMAT).
|
||||||
build();
|
build();
|
||||||
initializeCompiler(loader, reporter, Executors.newSingleThreadExecutor(factory));
|
initializeCompiler(loader, reporter, Executors.newSingleThreadExecutor(factory));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize a background compiler utilizing the given thread pool.
|
* Initialize a background compiler utilizing the given thread pool.
|
||||||
* @param loader - class loader from Bukkit.
|
* @param loader - class loader from Bukkit.
|
||||||
* @param reporter - current error reporter.
|
* @param reporter - current error reporter.
|
||||||
* @param executor - thread pool we'll use.
|
* @param executor - thread pool we'll use.
|
||||||
*/
|
*/
|
||||||
public BackgroundCompiler(ClassLoader loader, ErrorReporter reporter, ExecutorService executor) {
|
public BackgroundCompiler(ClassLoader loader, ErrorReporter reporter, ExecutorService executor) {
|
||||||
initializeCompiler(loader, reporter, executor);
|
initializeCompiler(loader, reporter, executor);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid "Constructor call must be the first statement".
|
// Avoid "Constructor call must be the first statement".
|
||||||
private void initializeCompiler(ClassLoader loader, ErrorReporter reporter, ExecutorService executor) {
|
private void initializeCompiler(ClassLoader loader, ErrorReporter reporter, ExecutorService executor) {
|
||||||
if (loader == null)
|
if (loader == null)
|
||||||
throw new IllegalArgumentException("loader cannot be NULL");
|
throw new IllegalArgumentException("loader cannot be NULL");
|
||||||
if (executor == null)
|
if (executor == null)
|
||||||
throw new IllegalArgumentException("executor cannot be NULL");
|
throw new IllegalArgumentException("executor cannot be NULL");
|
||||||
if (reporter == null)
|
if (reporter == null)
|
||||||
throw new IllegalArgumentException("reporter cannot be NULL.");
|
throw new IllegalArgumentException("reporter cannot be NULL.");
|
||||||
|
|
||||||
this.compiler = new StructureCompiler(loader);
|
this.compiler = new StructureCompiler(loader);
|
||||||
this.reporter = reporter;
|
this.reporter = reporter;
|
||||||
this.executor = executor;
|
this.executor = executor;
|
||||||
this.enabled = true;
|
this.enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure that the indirectly given structure modifier is eventually compiled.
|
* Ensure that the indirectly given structure modifier is eventually compiled.
|
||||||
* @param cache - store of structure modifiers.
|
* @param cache - store of structure modifiers.
|
||||||
* @param key - key of the structure modifier to compile.
|
* @param key - key of the structure modifier to compile.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
public void scheduleCompilation(final Map<Class, StructureModifier> cache, final Class key) {
|
public void scheduleCompilation(final Map<Class, StructureModifier> cache, final Class key) {
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
final StructureModifier<Object> uncompiled = cache.get(key);
|
final StructureModifier<Object> uncompiled = cache.get(key);
|
||||||
|
|
||||||
if (uncompiled != null) {
|
if (uncompiled != null) {
|
||||||
scheduleCompilation(uncompiled, new CompileListener<Object>() {
|
scheduleCompilation(uncompiled, new CompileListener<Object>() {
|
||||||
@Override
|
@Override
|
||||||
public void onCompiled(StructureModifier<Object> compiledModifier) {
|
public void onCompiled(StructureModifier<Object> compiledModifier) {
|
||||||
// Update cache
|
// Update cache
|
||||||
cache.put(key, compiledModifier);
|
cache.put(key, compiledModifier);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure that the given structure modifier is eventually compiled.
|
* Ensure that the given structure modifier is eventually compiled.
|
||||||
* @param uncompiled - structure modifier to compile.
|
* @param uncompiled - structure modifier to compile.
|
||||||
* @param listener - listener responsible for responding to the compilation.
|
* @param listener - listener responsible for responding to the compilation.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||||
public <TKey> void scheduleCompilation(final StructureModifier<TKey> uncompiled, final CompileListener<TKey> listener) {
|
public <TKey> void scheduleCompilation(final StructureModifier<TKey> uncompiled, final CompileListener<TKey> listener) {
|
||||||
// Only schedule if we're enabled
|
// Only schedule if we're enabled
|
||||||
if (enabled && !shuttingDown) {
|
if (enabled && !shuttingDown) {
|
||||||
// Check perm gen
|
// Check perm gen
|
||||||
if (getPermGenUsage() > disablePermGenFraction)
|
if (getPermGenUsage() > disablePermGenFraction)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Don't try to schedule anything
|
// Don't try to schedule anything
|
||||||
if (executor == null || executor.isShutdown())
|
if (executor == null || executor.isShutdown())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Use to look up structure modifiers
|
// Use to look up structure modifiers
|
||||||
final StructureKey key = new StructureKey(uncompiled);
|
final StructureKey key = new StructureKey(uncompiled);
|
||||||
|
|
||||||
// Allow others to listen in too
|
// Allow others to listen in too
|
||||||
synchronized (listenerLock) {
|
synchronized (listenerLock) {
|
||||||
List list = listeners.get(key);
|
List list = listeners.get(key);
|
||||||
|
|
||||||
if (!listeners.containsKey(key)) {
|
if (!listeners.containsKey(key)) {
|
||||||
listeners.put(key, (List) Lists.newArrayList(listener));
|
listeners.put(key, (List) Lists.newArrayList(listener));
|
||||||
} else {
|
} else {
|
||||||
// We're currently compiling
|
// We're currently compiling
|
||||||
list.add(listener);
|
list.add(listener);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the worker that will compile our modifier
|
// Create the worker that will compile our modifier
|
||||||
Callable<?> worker = new Callable<Object>() {
|
Callable<?> worker = new Callable<Object>() {
|
||||||
@Override
|
@Override
|
||||||
public Object call() throws Exception {
|
public Object call() throws Exception {
|
||||||
StructureModifier<TKey> modifier = uncompiled;
|
StructureModifier<TKey> modifier = uncompiled;
|
||||||
List list = null;
|
List list = null;
|
||||||
|
|
||||||
// Do our compilation
|
// Do our compilation
|
||||||
try {
|
try {
|
||||||
modifier = compiler.compile(modifier);
|
modifier = compiler.compile(modifier);
|
||||||
|
|
||||||
synchronized (listenerLock) {
|
synchronized (listenerLock) {
|
||||||
list = listeners.get(key);
|
list = listeners.get(key);
|
||||||
|
|
||||||
// Prevent ConcurrentModificationExceptions
|
// Prevent ConcurrentModificationExceptions
|
||||||
if (list != null) {
|
if (list != null) {
|
||||||
list = Lists.newArrayList(list);
|
list = Lists.newArrayList(list);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only execute the listeners if there is a list
|
// Only execute the listeners if there is a list
|
||||||
if (list != null) {
|
if (list != null) {
|
||||||
for (Object compileListener : list) {
|
for (Object compileListener : list) {
|
||||||
((CompileListener<TKey>) compileListener).onCompiled(modifier);
|
((CompileListener<TKey>) compileListener).onCompiled(modifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove it when we're done
|
// Remove it when we're done
|
||||||
synchronized (listenerLock) {
|
synchronized (listenerLock) {
|
||||||
list = listeners.remove(key);
|
list = listeners.remove(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
// Disable future compilations!
|
// Disable future compilations!
|
||||||
setEnabled(false);
|
setEnabled(false);
|
||||||
|
|
||||||
// Inform about this error as best as we can
|
// Inform about this error as best as we can
|
||||||
reporter.reportDetailed(BackgroundCompiler.this,
|
reporter.reportDetailed(BackgroundCompiler.this,
|
||||||
Report.newBuilder(REPORT_CANNOT_COMPILE_STRUCTURE_MODIFIER).callerParam(uncompiled).error(e)
|
Report.newBuilder(REPORT_CANNOT_COMPILE_STRUCTURE_MODIFIER).callerParam(uncompiled).error(e)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll also return the new structure modifier
|
// We'll also return the new structure modifier
|
||||||
return modifier;
|
return modifier;
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Lookup the previous class name on the main thread.
|
// Lookup the previous class name on the main thread.
|
||||||
// This is necessary as the Bukkit class loaders are not thread safe
|
// This is necessary as the Bukkit class loaders are not thread safe
|
||||||
if (compiler.lookupClassLoader(uncompiled)) {
|
if (compiler.lookupClassLoader(uncompiled)) {
|
||||||
try {
|
try {
|
||||||
worker.call();
|
worker.call();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Impossible!
|
// Impossible!
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Perform the compilation on a seperate thread
|
// Perform the compilation on a seperate thread
|
||||||
executor.submit(worker);
|
executor.submit(worker);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (RejectedExecutionException e) {
|
} catch (RejectedExecutionException e) {
|
||||||
// Occures when the underlying queue is overflowing. Since the compilation
|
// Occures when the underlying queue is overflowing. Since the compilation
|
||||||
// is only an optmization and not really essential we'll just log this failure
|
// is only an optmization and not really essential we'll just log this failure
|
||||||
// and move on.
|
// and move on.
|
||||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_SCHEDULE_COMPILATION).error(e));
|
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_SCHEDULE_COMPILATION).error(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a compile listener if we are still waiting for the structure modifier to be compiled.
|
* Add a compile listener if we are still waiting for the structure modifier to be compiled.
|
||||||
* @param uncompiled - the structure modifier that may get compiled.
|
* @param uncompiled - the structure modifier that may get compiled.
|
||||||
* @param listener - the listener to invoke in that case.
|
* @param listener - the listener to invoke in that case.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <TKey> void addListener(final StructureModifier<TKey> uncompiled, final CompileListener<TKey> listener) {
|
public <TKey> void addListener(final StructureModifier<TKey> uncompiled, final CompileListener<TKey> listener) {
|
||||||
synchronized (listenerLock) {
|
synchronized (listenerLock) {
|
||||||
StructureKey key = new StructureKey(uncompiled);
|
StructureKey key = new StructureKey(uncompiled);
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
List list = listeners.get(key);
|
List list = listeners.get(key);
|
||||||
|
|
||||||
if (list != null) {
|
if (list != null) {
|
||||||
list.add(listener);
|
list.add(listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the current usage of the Perm Gen space in percentage.
|
* Retrieve the current usage of the Perm Gen space in percentage.
|
||||||
* @return Usage of the perm gen space.
|
* @return Usage of the perm gen space.
|
||||||
*/
|
*/
|
||||||
private double getPermGenUsage() {
|
private double getPermGenUsage() {
|
||||||
for (MemoryPoolMXBean item : ManagementFactory.getMemoryPoolMXBeans()) {
|
for (MemoryPoolMXBean item : ManagementFactory.getMemoryPoolMXBeans()) {
|
||||||
if (item.getName().contains("Perm Gen")) {
|
if (item.getName().contains("Perm Gen")) {
|
||||||
MemoryUsage usage = item.getUsage();
|
MemoryUsage usage = item.getUsage();
|
||||||
return usage.getUsed() / (double) usage.getCommitted();
|
return usage.getUsed() / (double) usage.getCommitted();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unknown
|
// Unknown
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clean up after ourselves using the default timeout.
|
* Clean up after ourselves using the default timeout.
|
||||||
*/
|
*/
|
||||||
public void shutdownAll() {
|
public void shutdownAll() {
|
||||||
shutdownAll(SHUTDOWN_DELAY_MS, TimeUnit.MILLISECONDS);
|
shutdownAll(SHUTDOWN_DELAY_MS, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clean up after ourselves.
|
* Clean up after ourselves.
|
||||||
* @param timeout - the maximum time to wait.
|
* @param timeout - the maximum time to wait.
|
||||||
* @param unit - the time unit of the timeout argument.
|
* @param unit - the time unit of the timeout argument.
|
||||||
*/
|
*/
|
||||||
public void shutdownAll(long timeout, TimeUnit unit) {
|
public void shutdownAll(long timeout, TimeUnit unit) {
|
||||||
setEnabled(false);
|
setEnabled(false);
|
||||||
shuttingDown = true;
|
shuttingDown = true;
|
||||||
executor.shutdown();
|
executor.shutdown();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
executor.awaitTermination(timeout, unit);
|
executor.awaitTermination(timeout, unit);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
// Unlikely to ever occur - it's the main thread
|
// Unlikely to ever occur - it's the main thread
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve whether or not the background compiler is enabled.
|
* Retrieve whether or not the background compiler is enabled.
|
||||||
* @return TRUE if it is enabled, FALSE otherwise.
|
* @return TRUE if it is enabled, FALSE otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
return enabled;
|
return enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets whether or not the background compiler is enabled.
|
* Sets whether or not the background compiler is enabled.
|
||||||
* @param enabled - TRUE to enable it, FALSE otherwise.
|
* @param enabled - TRUE to enable it, FALSE otherwise.
|
||||||
*/
|
*/
|
||||||
public void setEnabled(boolean enabled) {
|
public void setEnabled(boolean enabled) {
|
||||||
this.enabled = enabled;
|
this.enabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the fraction of perm gen space used after which the background compiler will be disabled.
|
* Retrieve the fraction of perm gen space used after which the background compiler will be disabled.
|
||||||
* @return The fraction after which the background compiler is disabled.
|
* @return The fraction after which the background compiler is disabled.
|
||||||
*/
|
*/
|
||||||
public double getDisablePermGenFraction() {
|
public double getDisablePermGenFraction() {
|
||||||
return disablePermGenFraction;
|
return disablePermGenFraction;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the fraction of perm gen space used after which the background compiler will be disabled.
|
* Set the fraction of perm gen space used after which the background compiler will be disabled.
|
||||||
* @param fraction - the maximum use of perm gen space.
|
* @param fraction - the maximum use of perm gen space.
|
||||||
*/
|
*/
|
||||||
public void setDisablePermGenFraction(double fraction) {
|
public void setDisablePermGenFraction(double fraction) {
|
||||||
this.disablePermGenFraction = fraction;
|
this.disablePermGenFraction = fraction;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the current structure compiler.
|
* Retrieve the current structure compiler.
|
||||||
* @return Current structure compiler.
|
* @return Current structure compiler.
|
||||||
*/
|
*/
|
||||||
public StructureCompiler getCompiler() {
|
public StructureCompiler getCompiler() {
|
||||||
return compiler;
|
return compiler;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
package com.comphenix.protocol.reflect.compiler;
|
||||||
|
|
||||||
|
import net.sf.cglib.asm.AnnotationVisitor;
|
||||||
|
import net.sf.cglib.asm.Attribute;
|
||||||
|
import net.sf.cglib.asm.ClassVisitor;
|
||||||
|
import net.sf.cglib.asm.FieldVisitor;
|
||||||
|
import net.sf.cglib.asm.MethodVisitor;
|
||||||
|
|
||||||
|
public abstract class EmptyClassVisitor implements ClassVisitor {
|
||||||
|
@Override
|
||||||
|
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
|
||||||
|
// NOP
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitAttribute(Attribute attr) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitEnd() {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
|
||||||
|
// NOP
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitInnerClass(String name, String outerName, String innerName, int access) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||||
|
// NOP
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitOuterClass(String owner, String name, String desc) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitSource(String source, String debug) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,132 @@
|
|||||||
|
package com.comphenix.protocol.reflect.compiler;
|
||||||
|
|
||||||
|
import net.sf.cglib.asm.AnnotationVisitor;
|
||||||
|
import net.sf.cglib.asm.Attribute;
|
||||||
|
import net.sf.cglib.asm.Label;
|
||||||
|
import net.sf.cglib.asm.MethodVisitor;
|
||||||
|
|
||||||
|
public class EmptyMethodVisitor implements MethodVisitor {
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitAnnotationDefault() {
|
||||||
|
// NOP
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
|
||||||
|
// NOP
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
|
||||||
|
// NOP
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitAttribute(Attribute attr) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitCode() {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitInsn(int opcode) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitIntInsn(int opcode, int operand) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitVarInsn(int opcode, int var) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitTypeInsn(int opcode, String type) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitJumpInsn(int opcode, Label label) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitLabel(Label label) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitLdcInsn(Object cst) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitIincInsn(int var, int increment) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitMultiANewArrayInsn(String desc, int dims) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitLocalVariable(String name, String desc, String signature, Label start,
|
||||||
|
Label end, int index) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitLineNumber(int line, Label start) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitMaxs(int maxStack, int maxLocals) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitEnd() {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
}
|
Datei-Diff unterdrückt, da er zu groß ist
Diff laden
@ -19,6 +19,7 @@ package com.comphenix.protocol.utility;
|
|||||||
|
|
||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
import java.io.DataOutput;
|
import java.io.DataOutput;
|
||||||
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Array;
|
import java.lang.reflect.Array;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
@ -33,12 +34,19 @@ import java.util.regex.Pattern;
|
|||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import net.sf.cglib.asm.ClassReader;
|
||||||
|
import net.sf.cglib.asm.MethodVisitor;
|
||||||
|
import net.sf.cglib.asm.Opcodes;
|
||||||
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Server;
|
import org.bukkit.Server;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
|
||||||
import com.comphenix.protocol.injector.BukkitUnwrapper;
|
import com.comphenix.protocol.injector.BukkitUnwrapper;
|
||||||
|
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
|
import com.comphenix.protocol.reflect.compiler.EmptyClassVisitor;
|
||||||
|
import com.comphenix.protocol.reflect.compiler.EmptyMethodVisitor;
|
||||||
import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher;
|
import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher;
|
||||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract;
|
import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract;
|
||||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
|
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
|
||||||
@ -952,6 +960,79 @@ public class MinecraftReflection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the attribute snapshot class.
|
||||||
|
* <p>
|
||||||
|
* This stores the final value of an attribute, along with all the associated computational steps.
|
||||||
|
* @return The attribute snapshot class.
|
||||||
|
*/
|
||||||
|
public static Class<?> getAttributeSnapshotClass() {
|
||||||
|
try {
|
||||||
|
return getMinecraftClass("AttributeSnapshot");
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
final Class<?> packetUpdateAttributes = PacketRegistry.getPacketClassFromID(44, true);
|
||||||
|
final String packetSignature = packetUpdateAttributes.getCanonicalName().replace('.', '/');
|
||||||
|
|
||||||
|
// HACK - class is found by inspecting code
|
||||||
|
try {
|
||||||
|
ClassReader reader = new ClassReader(packetUpdateAttributes.getCanonicalName());
|
||||||
|
|
||||||
|
reader.accept(new EmptyClassVisitor() {
|
||||||
|
@Override
|
||||||
|
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||||
|
// The read method
|
||||||
|
if (desc.startsWith("(Ljava/io/DataInput")) {
|
||||||
|
return new EmptyMethodVisitor() {
|
||||||
|
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
|
||||||
|
if (opcode == Opcodes.INVOKESPECIAL && isConstructor(name)) {
|
||||||
|
String className = owner.replace('/', '.');
|
||||||
|
|
||||||
|
// Use signature to distinguish between constructors
|
||||||
|
if (desc.startsWith("(L" + packetSignature)) {
|
||||||
|
setMinecraftClass("AttributeSnapshot", MinecraftReflection.getClass(className));
|
||||||
|
} else if (desc.startsWith("(Ljava/util/UUID;Ljava/lang/String")) {
|
||||||
|
setMinecraftClass("AttributeModifier", MinecraftReflection.getClass(className));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
} catch (IOException e1) {
|
||||||
|
throw new RuntimeException("Unable to read the content of Packet44UpdateAttributes.", e1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If our dirty ASM trick failed, this will throw an exception
|
||||||
|
return getMinecraftClass("AttributeSnapshot");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the attribute modifier class.
|
||||||
|
* @return Attribute modifier class.
|
||||||
|
*/
|
||||||
|
public static Class<?> getAttributeModifierClass() {
|
||||||
|
try {
|
||||||
|
return getMinecraftClass("AttributeModifier");
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
// Initialize first
|
||||||
|
getAttributeSnapshotClass();
|
||||||
|
return getMinecraftClass("AttributeModifier");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if a given method retrieved by ASM is a constructor.
|
||||||
|
* @param name - the name of the method.
|
||||||
|
* @return TRUE if it is, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
private static boolean isConstructor(String name) {
|
||||||
|
return "<init>".equals(name);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the ItemStack[] class.
|
* Retrieve the ItemStack[] class.
|
||||||
* @return The ItemStack[] class.
|
* @return The ItemStack[] class.
|
||||||
@ -1106,6 +1187,20 @@ public class MinecraftReflection {
|
|||||||
return unwrapper.unwrapItem(stack);
|
return unwrapper.unwrapItem(stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the given class by name.
|
||||||
|
* @param className - name of the class.
|
||||||
|
* @return The class.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
private static Class getClass(String className) {
|
||||||
|
try {
|
||||||
|
return MinecraftReflection.class.getClassLoader().loadClass(className);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new RuntimeException("Cannot find class " + className, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the class object of a specific CraftBukkit class.
|
* Retrieve the class object of a specific CraftBukkit class.
|
||||||
* @param className - the specific CraftBukkit class.
|
* @param className - the specific CraftBukkit class.
|
||||||
|
@ -49,6 +49,7 @@ import com.google.common.collect.ImmutableMap;
|
|||||||
public class BukkitConverters {
|
public class BukkitConverters {
|
||||||
// Check whether or not certain classes exists
|
// Check whether or not certain classes exists
|
||||||
private static boolean hasWorldType = false;
|
private static boolean hasWorldType = false;
|
||||||
|
private static boolean hasAttributeSnapshot = false;
|
||||||
|
|
||||||
// The static maps
|
// The static maps
|
||||||
private static Map<Class<?>, EquivalentConverter<Object>> specificConverters;
|
private static Map<Class<?>, EquivalentConverter<Object>> specificConverters;
|
||||||
@ -60,9 +61,15 @@ public class BukkitConverters {
|
|||||||
|
|
||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
Class.forName(MinecraftReflection.getMinecraftPackage() + ".WorldType");
|
MinecraftReflection.getWorldTypeClass();
|
||||||
hasWorldType = true;
|
hasWorldType = true;
|
||||||
} catch (ClassNotFoundException e) {
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
MinecraftReflection.getAttributeSnapshotClass();
|
||||||
|
hasAttributeSnapshot = true;
|
||||||
|
} catch (Exception e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,6 +165,12 @@ public class BukkitConverters {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve an equivalent converter for a list of generic items.
|
||||||
|
* @param genericItemType - the generic item type.
|
||||||
|
* @param itemConverter - an equivalent converter for the generic type.
|
||||||
|
* @return An equivalent converter.
|
||||||
|
*/
|
||||||
public static <T> EquivalentConverter<List<T>> getListConverter(final Class<?> genericItemType, final EquivalentConverter<T> itemConverter) {
|
public static <T> EquivalentConverter<List<T>> getListConverter(final Class<?> genericItemType, final EquivalentConverter<T> itemConverter) {
|
||||||
// Convert to and from the wrapper
|
// Convert to and from the wrapper
|
||||||
return new IgnoreNullConverter<List<T>>() {
|
return new IgnoreNullConverter<List<T>>() {
|
||||||
@ -208,6 +221,29 @@ public class BukkitConverters {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a converter for wrapped attribute snapshots.
|
||||||
|
* @return Wrapped attribute snapshot converter.
|
||||||
|
*/
|
||||||
|
public static EquivalentConverter<WrappedAttribute> getWrappedAttributeConverter() {
|
||||||
|
return new IgnoreNullConverter<WrappedAttribute>() {
|
||||||
|
@Override
|
||||||
|
protected Object getGenericValue(Class<?> genericType, WrappedAttribute specific) {
|
||||||
|
return specific.getHandle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected WrappedAttribute getSpecificValue(Object generic) {
|
||||||
|
return WrappedAttribute.fromHandle(generic);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<WrappedAttribute> getSpecificType() {
|
||||||
|
return WrappedAttribute.class;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve a converter for watchable objects and the respective wrapper.
|
* Retrieve a converter for watchable objects and the respective wrapper.
|
||||||
* @return A watchable object converter.
|
* @return A watchable object converter.
|
||||||
@ -429,7 +465,7 @@ public class BukkitConverters {
|
|||||||
* @return Every converter with a unique specific class.
|
* @return Every converter with a unique specific class.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||||
public static Map<Class<?>, EquivalentConverter<Object>> getSpecificConverters() {
|
public static Map<Class<?>, EquivalentConverter<Object>> getConvertersForSpecific() {
|
||||||
if (specificConverters == null) {
|
if (specificConverters == null) {
|
||||||
// Generics doesn't work, as usual
|
// Generics doesn't work, as usual
|
||||||
ImmutableMap.Builder<Class<?>, EquivalentConverter<Object>> builder =
|
ImmutableMap.Builder<Class<?>, EquivalentConverter<Object>> builder =
|
||||||
@ -440,9 +476,10 @@ public class BukkitConverters {
|
|||||||
put(NbtCompound.class, (EquivalentConverter) getNbtConverter()).
|
put(NbtCompound.class, (EquivalentConverter) getNbtConverter()).
|
||||||
put(WrappedWatchableObject.class, (EquivalentConverter) getWatchableObjectConverter());
|
put(WrappedWatchableObject.class, (EquivalentConverter) getWatchableObjectConverter());
|
||||||
|
|
||||||
if (hasWorldType) {
|
if (hasWorldType)
|
||||||
builder.put(WorldType.class, (EquivalentConverter) getWorldTypeConverter());
|
builder.put(WorldType.class, (EquivalentConverter) getWorldTypeConverter());
|
||||||
}
|
if (hasAttributeSnapshot)
|
||||||
|
builder.put(WrappedAttribute.class, (EquivalentConverter) getWrappedAttributeConverter());
|
||||||
specificConverters = builder.build();
|
specificConverters = builder.build();
|
||||||
}
|
}
|
||||||
return specificConverters;
|
return specificConverters;
|
||||||
@ -453,7 +490,7 @@ public class BukkitConverters {
|
|||||||
* @return Every converter with a unique generic class.
|
* @return Every converter with a unique generic class.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||||
public static Map<Class<?>, EquivalentConverter<Object>> getGenericConverters() {
|
public static Map<Class<?>, EquivalentConverter<Object>> getConvertersForGeneric() {
|
||||||
if (genericConverters == null) {
|
if (genericConverters == null) {
|
||||||
// Generics doesn't work, as usual
|
// Generics doesn't work, as usual
|
||||||
ImmutableMap.Builder<Class<?>, EquivalentConverter<Object>> builder =
|
ImmutableMap.Builder<Class<?>, EquivalentConverter<Object>> builder =
|
||||||
@ -464,9 +501,10 @@ public class BukkitConverters {
|
|||||||
put(MinecraftReflection.getNBTCompoundClass(), (EquivalentConverter) getNbtConverter()).
|
put(MinecraftReflection.getNBTCompoundClass(), (EquivalentConverter) getNbtConverter()).
|
||||||
put(MinecraftReflection.getWatchableObjectClass(), (EquivalentConverter) getWatchableObjectConverter());
|
put(MinecraftReflection.getWatchableObjectClass(), (EquivalentConverter) getWatchableObjectConverter());
|
||||||
|
|
||||||
if (hasWorldType) {
|
if (hasWorldType)
|
||||||
builder.put(MinecraftReflection.getWorldTypeClass(), (EquivalentConverter) getWorldTypeConverter());
|
builder.put(MinecraftReflection.getWorldTypeClass(), (EquivalentConverter) getWorldTypeConverter());
|
||||||
}
|
if (hasAttributeSnapshot)
|
||||||
|
builder.put(MinecraftReflection.getAttributeSnapshotClass(), (EquivalentConverter) getWrappedAttributeConverter());
|
||||||
genericConverters = builder.build();
|
genericConverters = builder.build();
|
||||||
}
|
}
|
||||||
return genericConverters;
|
return genericConverters;
|
||||||
|
@ -0,0 +1,419 @@
|
|||||||
|
package com.comphenix.protocol.wrappers;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.Packets;
|
||||||
|
import com.comphenix.protocol.events.PacketContainer;
|
||||||
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
|
import com.comphenix.protocol.reflect.StructureModifier;
|
||||||
|
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
||||||
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||||
|
import com.comphenix.protocol.wrappers.collection.CachedSet;
|
||||||
|
import com.comphenix.protocol.wrappers.collection.ConvertedSet;
|
||||||
|
import com.google.common.base.Objects;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a single attribute sent in packet 44.
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public class WrappedAttribute {
|
||||||
|
// Shared structure modifier
|
||||||
|
private static StructureModifier<Object> ATTRIBUTE_MODIFIER;
|
||||||
|
|
||||||
|
// The one constructor
|
||||||
|
private static Constructor<?> ATTRIBUTE_CONSTRUCTOR;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the underlying attribute snapshot.
|
||||||
|
*/
|
||||||
|
protected Object handle;
|
||||||
|
protected StructureModifier<Object> modifier;
|
||||||
|
|
||||||
|
// Cached computed value
|
||||||
|
private double computedValue = Double.NaN;
|
||||||
|
|
||||||
|
// Cached modifiers list
|
||||||
|
private Set<WrappedAttributeModifier> attributeModifiers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new wrapped attribute around a specific NMS instance.
|
||||||
|
* @param handle - handle to a NMS AttributeSnapshot.
|
||||||
|
* @return The attribute wrapper.
|
||||||
|
* @throws IllegalArgumentException If the handle is not a AttributeSnapshot.
|
||||||
|
*/
|
||||||
|
public static WrappedAttribute fromHandle(@Nonnull Object handle) {
|
||||||
|
return new WrappedAttribute(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new wrapped attribute builder.
|
||||||
|
* @return The new builder.
|
||||||
|
*/
|
||||||
|
public static Builder newBuilder() {
|
||||||
|
return new Builder(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new wrapped attribute builder initialized to the values from a template.
|
||||||
|
* @param template - the attribute template.
|
||||||
|
* @return The new builder.
|
||||||
|
*/
|
||||||
|
public static Builder newBuilder(@Nonnull WrappedAttribute template) {
|
||||||
|
return new Builder(Preconditions.checkNotNull(template, "template cannot be NULL."));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a wrapper around a specific NMS instance.
|
||||||
|
* @param handle - the NMS instance.
|
||||||
|
*/
|
||||||
|
private WrappedAttribute(@Nonnull Object handle) {
|
||||||
|
this.handle = Preconditions.checkNotNull(handle, "handle cannot be NULL.");
|
||||||
|
|
||||||
|
// Check handle type
|
||||||
|
if (!MinecraftReflection.getAttributeSnapshotClass().isAssignableFrom(handle.getClass())) {
|
||||||
|
throw new IllegalArgumentException("handle (" + handle + ") must be a AttributeSnapshot.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize modifier
|
||||||
|
if (ATTRIBUTE_MODIFIER == null) {
|
||||||
|
ATTRIBUTE_MODIFIER = new StructureModifier<Object>(MinecraftReflection.getAttributeSnapshotClass());
|
||||||
|
}
|
||||||
|
this.modifier = ATTRIBUTE_MODIFIER.withTarget(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the underlying NMS attribute snapshot.
|
||||||
|
* @return The underlying attribute snapshot.
|
||||||
|
*/
|
||||||
|
public Object getHandle() {
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the unique attribute key that identifies its function.
|
||||||
|
* <p>
|
||||||
|
* Example: "generic.maxHealth"
|
||||||
|
* @return The attribute key.
|
||||||
|
*/
|
||||||
|
public String getAttributeKey() {
|
||||||
|
return (String) modifier.withType(String.class).read(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the base value of this attribute, before any of the modifiers have been taken into account.
|
||||||
|
* @return The base value.
|
||||||
|
*/
|
||||||
|
public double getBaseValue() {
|
||||||
|
return (Double) modifier.withType(double.class).read(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the final computed value.
|
||||||
|
* @return The final value.
|
||||||
|
*/
|
||||||
|
public double getFinalValue() {
|
||||||
|
if (Double.isNaN(computedValue)) {
|
||||||
|
computedValue = computeValue();
|
||||||
|
}
|
||||||
|
return computedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the parent update attributes packet.
|
||||||
|
* @return The parent packet.
|
||||||
|
*/
|
||||||
|
public PacketContainer getParentPacket() {
|
||||||
|
return new PacketContainer(
|
||||||
|
Packets.Server.UPDATE_ATTRIBUTES,
|
||||||
|
modifier.withType(MinecraftReflection.getPacketClass()).read(0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the attribute has a given attribute modifier, identified by UUID.
|
||||||
|
* @return TRUE if it does, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public boolean hasModifier(UUID id) {
|
||||||
|
return getModifiers().contains(WrappedAttributeModifier.newBuilder(id).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve an attribute modifier by UUID.
|
||||||
|
* @param id - the id to look for.
|
||||||
|
* @return The single attribute modifier with the given ID.
|
||||||
|
*/
|
||||||
|
public WrappedAttributeModifier getModifierByUUID(UUID id) {
|
||||||
|
if (hasModifier(id)) {
|
||||||
|
for (WrappedAttributeModifier modifier : getModifiers()) {
|
||||||
|
if (Objects.equal(modifier.getUUID(), id)) {
|
||||||
|
return modifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve an immutable set of all the attribute modifiers that will compute the final value of this attribute.
|
||||||
|
* @return Every attribute modifier.
|
||||||
|
*/
|
||||||
|
public Set<WrappedAttributeModifier> getModifiers() {
|
||||||
|
if (attributeModifiers == null) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Collection<Object> collection = (Collection<Object>) modifier.withType(Collection.class).read(0);
|
||||||
|
|
||||||
|
// Convert to an equivalent wrapper
|
||||||
|
ConvertedSet<Object, WrappedAttributeModifier> converted =
|
||||||
|
new ConvertedSet<Object, WrappedAttributeModifier>(getSetSafely(collection)) {
|
||||||
|
@Override
|
||||||
|
protected Object toInner(WrappedAttributeModifier outer) {
|
||||||
|
return outer.getHandle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected WrappedAttributeModifier toOuter(Object inner) {
|
||||||
|
return WrappedAttributeModifier.fromHandle(inner);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
attributeModifiers = new CachedSet<WrappedAttributeModifier>(converted);
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableSet(attributeModifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an attribute with the same key and name, but a different list of modifiers.
|
||||||
|
* @param modifiers - attribute modifiers.
|
||||||
|
* @return The new attribute.
|
||||||
|
*/
|
||||||
|
public WrappedAttribute withModifiers(Collection<WrappedAttributeModifier> modifiers) {
|
||||||
|
return newBuilder(this).modifiers(modifiers).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj)
|
||||||
|
return true;
|
||||||
|
if (obj instanceof WrappedAttribute) {
|
||||||
|
WrappedAttribute other = (WrappedAttribute) obj;
|
||||||
|
|
||||||
|
return getBaseValue() == other.getBaseValue() &&
|
||||||
|
Objects.equal(getAttributeKey(), other.getAttributeKey()) &&
|
||||||
|
Sets.symmetricDifference(
|
||||||
|
getModifiers(),
|
||||||
|
other.getModifiers()
|
||||||
|
).isEmpty();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
if (attributeModifiers == null)
|
||||||
|
getModifiers();
|
||||||
|
return Objects.hashCode(getAttributeKey(), getBaseValue(), attributeModifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the final value from the current attribute modifers.
|
||||||
|
* @return The final value.
|
||||||
|
*/
|
||||||
|
private double computeValue() {
|
||||||
|
Collection<WrappedAttributeModifier> modifiers = getModifiers();
|
||||||
|
double x = getBaseValue();
|
||||||
|
double y = 0;
|
||||||
|
|
||||||
|
// Compute each phase
|
||||||
|
for (int phase = 0; phase < 3; phase++) {
|
||||||
|
for (WrappedAttributeModifier modifier : modifiers) {
|
||||||
|
if (modifier.getOperation().getId() == phase) {
|
||||||
|
switch (phase) {
|
||||||
|
case 0: // Adding phase
|
||||||
|
x += modifier.getAmount();
|
||||||
|
break;
|
||||||
|
case 1: // Multiply percentage
|
||||||
|
y += x * modifier.getAmount();
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
y *= 1 + modifier.getAmount();
|
||||||
|
break;
|
||||||
|
default :
|
||||||
|
throw new IllegalStateException("Unknown phase: " + phase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The additive phase is finished
|
||||||
|
if (phase == 0) {
|
||||||
|
y = x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return Objects.toStringHelper("WrappedAttribute").
|
||||||
|
add("key", getAttributeKey()).
|
||||||
|
add("baseValue", getBaseValue()).
|
||||||
|
add("finalValue", getFinalValue()).
|
||||||
|
add("modifiers", getModifiers()).
|
||||||
|
toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the collection is a set, retrieve it - otherwise, create a new set with the same elements.
|
||||||
|
* @param collection - the collection.
|
||||||
|
* @return A set with the same elements.
|
||||||
|
*/
|
||||||
|
private static <U> Set<U> getSetSafely(Collection<U> collection) {
|
||||||
|
return collection instanceof Set ? (Set<U>) collection : Sets.newHashSet(collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that the given double is not infinite nor NaN.
|
||||||
|
* @param value - the value to check.
|
||||||
|
*/
|
||||||
|
static double checkDouble(double value) {
|
||||||
|
if (Double.isInfinite(value))
|
||||||
|
throw new IllegalArgumentException("value cannot be infinite.");
|
||||||
|
if (Double.isNaN(value))
|
||||||
|
throw new IllegalArgumentException("value cannot be NaN.");
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a builder for wrapped attributes.
|
||||||
|
* <p>
|
||||||
|
* Use {@link WrappedAttribute#newBuilder()} to construct it.
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public static class Builder {
|
||||||
|
private double baseValue = Double.NaN;
|
||||||
|
private String attributeKey;
|
||||||
|
private PacketContainer packet;
|
||||||
|
private Collection<WrappedAttributeModifier> modifiers = Collections.emptyList();
|
||||||
|
|
||||||
|
private Builder(WrappedAttribute template) {
|
||||||
|
if (template != null) {
|
||||||
|
baseValue = template.getBaseValue();
|
||||||
|
attributeKey = template.getAttributeKey();
|
||||||
|
packet = template.getParentPacket();
|
||||||
|
modifiers = template.getModifiers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the base value of the attribute.
|
||||||
|
* <p>
|
||||||
|
* The modifiers will automatically supply a value if this is unset.
|
||||||
|
* @param value - the final value.
|
||||||
|
* @return This builder, for chaining.
|
||||||
|
*/
|
||||||
|
public Builder baseValue(double baseValue) {
|
||||||
|
this.baseValue = checkDouble(baseValue);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the unique attribute key that identifies its function.
|
||||||
|
* <p>
|
||||||
|
* This is required.
|
||||||
|
* @param attributeKey - the unique attribute key.
|
||||||
|
* @return This builder, for chaining.
|
||||||
|
*/
|
||||||
|
public Builder attributeKey(String attributeKey) {
|
||||||
|
this.attributeKey = Preconditions.checkNotNull(attributeKey, "attributeKey cannot be NULL.");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the modifers that will be supplied to the client, and used to compute the final value.
|
||||||
|
* <p>
|
||||||
|
* Call {@link #recomputeValue()} to force the builder to recompute the final value.
|
||||||
|
* @param modifiers - the attribute modifiers.
|
||||||
|
* @return This builder, for chaining.
|
||||||
|
*/
|
||||||
|
public Builder modifiers(Collection<WrappedAttributeModifier> modifiers) {
|
||||||
|
this.modifiers = Preconditions.checkNotNull(modifiers, "modifiers cannot be NULL - use an empty list instead.");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the parent update attributes packet (44).
|
||||||
|
* @param packet - the parent packet.
|
||||||
|
* @return This builder, for chaining.
|
||||||
|
*/
|
||||||
|
public Builder packet(PacketContainer packet) {
|
||||||
|
if (Preconditions.checkNotNull(packet, "packet cannot be NULL").getID() != Packets.Server.UPDATE_ATTRIBUTES) {
|
||||||
|
throw new IllegalArgumentException("Packet must be UPDATE_ATTRIBUTES (44)");
|
||||||
|
}
|
||||||
|
this.packet = packet;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the unwrapped modifiers.
|
||||||
|
* @return Unwrapped modifiers.
|
||||||
|
*/
|
||||||
|
private Set<Object> getUnwrappedModifiers() {
|
||||||
|
Set<Object> output = Sets.newHashSet();
|
||||||
|
|
||||||
|
for (WrappedAttributeModifier modifier : modifiers) {
|
||||||
|
output.add(modifier.getHandle());
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a new wrapped attribute with the values of this builder.
|
||||||
|
* @return The wrapped attribute.
|
||||||
|
* @throws RuntimeException If anything went wrong with the reflection.
|
||||||
|
*/
|
||||||
|
public WrappedAttribute build() {
|
||||||
|
Preconditions.checkNotNull(packet, "packet cannot be NULL.");
|
||||||
|
Preconditions.checkNotNull(attributeKey, "attributeKey cannot be NULL.");
|
||||||
|
|
||||||
|
// Remember to set the base value
|
||||||
|
if (Double.isNaN(baseValue)) {
|
||||||
|
throw new IllegalStateException("Base value has not been set.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the correct constructor
|
||||||
|
if (ATTRIBUTE_CONSTRUCTOR == null) {
|
||||||
|
ATTRIBUTE_CONSTRUCTOR = FuzzyReflection.fromClass(MinecraftReflection.getAttributeSnapshotClass(), true).getConstructor(
|
||||||
|
FuzzyMethodContract.newBuilder().parameterCount(4).
|
||||||
|
parameterDerivedOf(MinecraftReflection.getPacketClass(), 0).
|
||||||
|
parameterExactType(String.class, 1).
|
||||||
|
parameterExactType(double.class, 2).
|
||||||
|
parameterDerivedOf(Collection.class, 3).
|
||||||
|
build()
|
||||||
|
);
|
||||||
|
// Just in case
|
||||||
|
ATTRIBUTE_CONSTRUCTOR.setAccessible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Object handle = ATTRIBUTE_CONSTRUCTOR.newInstance(
|
||||||
|
packet.getHandle(),
|
||||||
|
attributeKey,
|
||||||
|
baseValue,
|
||||||
|
getUnwrappedModifiers());
|
||||||
|
|
||||||
|
// Create it
|
||||||
|
return new WrappedAttribute(handle);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Cannot construct AttributeSnapshot.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,412 @@
|
|||||||
|
package com.comphenix.protocol.wrappers;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
|
import com.comphenix.protocol.reflect.StructureModifier;
|
||||||
|
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
||||||
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||||
|
import com.google.common.base.Objects;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a wrapper around a AttributeModifier.
|
||||||
|
* <p>
|
||||||
|
* This is used to compute the final attribute value.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public class WrappedAttributeModifier {
|
||||||
|
/**
|
||||||
|
* Represents the different modifier operations.
|
||||||
|
* <p>
|
||||||
|
* The final value is computed as follows:
|
||||||
|
* <ol>
|
||||||
|
* <li>Set X = base value.</li>
|
||||||
|
* <li>Execute all modifiers with {@link Operation#ADD_NUMBER}.
|
||||||
|
* <li>Set Y = X.</li>
|
||||||
|
* <li>Execute all modifiers with {@link Operation#MULTIPLY_PERCENTAGE}.</li>
|
||||||
|
* <li>Execute all modifiers with {@link Operation#ADD_PERCENTAGE}.</li>
|
||||||
|
* <li>Y is the final value.</li>
|
||||||
|
* </ol>
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public enum Operation {
|
||||||
|
/**
|
||||||
|
* Increment X by amount.
|
||||||
|
*/
|
||||||
|
ADD_NUMBER(0),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increment Y by X * amount.
|
||||||
|
*/
|
||||||
|
MULTIPLY_PERCENTAGE(1),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multiply Y by (1 + amount)
|
||||||
|
*/
|
||||||
|
ADD_PERCENTAGE(2);
|
||||||
|
|
||||||
|
private int id;
|
||||||
|
|
||||||
|
private Operation(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the unique operation ID.
|
||||||
|
* @return Operation ID.
|
||||||
|
*/
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the associated operation from an ID.
|
||||||
|
* @param id - the ID.
|
||||||
|
* @return The operation.
|
||||||
|
*/
|
||||||
|
public static Operation fromId(int id) {
|
||||||
|
// Linear scan is very fast for small N
|
||||||
|
for (Operation op : values()) {
|
||||||
|
if (op.getId() == id) {
|
||||||
|
return op;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Corrupt operation ID " + id + " detected.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shared structure modifier
|
||||||
|
private static StructureModifier<Object> BASE_MODIFIER;
|
||||||
|
|
||||||
|
// The constructor we are interested in
|
||||||
|
private static Constructor<?> ATTRIBUTE_MODIFIER_CONSTRUCTOR;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle to the underlying AttributeModifier.
|
||||||
|
*/
|
||||||
|
protected Object handle;
|
||||||
|
protected StructureModifier<Object> modifier;
|
||||||
|
|
||||||
|
// Cached values
|
||||||
|
private final UUID uuid;
|
||||||
|
private final String name;
|
||||||
|
private final Operation operation;
|
||||||
|
private final double amount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new attribute modifier builder.
|
||||||
|
* <p>
|
||||||
|
* It will automatically be supplied with a random UUID.
|
||||||
|
* @return The new builder.
|
||||||
|
*/
|
||||||
|
public static Builder newBuilder() {
|
||||||
|
return new Builder(null).uuid(UUID.randomUUID());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new attribute modifier builder with the given UUID.
|
||||||
|
* @param id - the new UUID.
|
||||||
|
* @return Thew new builder.
|
||||||
|
*/
|
||||||
|
public static Builder newBuilder(UUID id) {
|
||||||
|
return new Builder(null).uuid(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new wrapped attribute modifier builder initialized to the values from a template.
|
||||||
|
* @param template - the attribute modifier template.
|
||||||
|
* @return The new builder.
|
||||||
|
*/
|
||||||
|
public static Builder newBuilder(@Nonnull WrappedAttributeModifier template) {
|
||||||
|
return new Builder(Preconditions.checkNotNull(template, "template cannot be NULL."));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an attribute modifier wrapper around a given NMS instance.
|
||||||
|
* @param handle - the NMS instance.
|
||||||
|
* @return The created attribute modifier.
|
||||||
|
* @throws IllegalArgumentException If the handle is not an AttributeModifier.
|
||||||
|
*/
|
||||||
|
public static WrappedAttributeModifier fromHandle(@Nonnull Object handle) {
|
||||||
|
return new WrappedAttributeModifier(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new wrapped attribute modifier with no associated handle.
|
||||||
|
* @param uuid - the UUID.
|
||||||
|
* @param name - the human readable name.
|
||||||
|
* @param amount - the amount.
|
||||||
|
* @param operation - the operation.
|
||||||
|
*/
|
||||||
|
protected WrappedAttributeModifier(UUID uuid, String name, double amount, Operation operation) {
|
||||||
|
// Use the supplied values instead of reading from the NMS instance
|
||||||
|
this.uuid = uuid;
|
||||||
|
this.name = name;
|
||||||
|
this.amount = amount;
|
||||||
|
this.operation = operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an attribute modifier wrapper around a given NMS instance.
|
||||||
|
* @param handle - the NMS instance.
|
||||||
|
*/
|
||||||
|
protected WrappedAttributeModifier(@Nonnull Object handle) {
|
||||||
|
// Update handle and modifier
|
||||||
|
setHandle(handle);
|
||||||
|
initializeModifier(handle);
|
||||||
|
|
||||||
|
// Load final values, caching them
|
||||||
|
this.uuid = (UUID) modifier.withType(UUID.class).read(0);
|
||||||
|
this.name = (String) modifier.withType(String.class).read(0);
|
||||||
|
this.amount = (Double) modifier.withType(double.class).read(0);
|
||||||
|
this.operation = Operation.fromId((Integer) modifier.withType(int.class).read(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an attribute modifier wrapper around a NMS instance.
|
||||||
|
* @param handle - the NMS instance.
|
||||||
|
* @param uuid - the UUID.
|
||||||
|
* @param name - the human readable name.
|
||||||
|
* @param amount - the amount.
|
||||||
|
* @param operation - the operation.
|
||||||
|
*/
|
||||||
|
protected WrappedAttributeModifier(@Nonnull Object handle, UUID uuid, String name, double amount, Operation operation) {
|
||||||
|
this(uuid, name, amount, operation);
|
||||||
|
|
||||||
|
// Initialize handle and modifier
|
||||||
|
setHandle(handle);
|
||||||
|
initializeModifier(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize modifier from a given handle.
|
||||||
|
* @param handle - the handle.
|
||||||
|
* @return The given handle.
|
||||||
|
*/
|
||||||
|
private void initializeModifier(@Nonnull Object handle) {
|
||||||
|
// Initialize modifier
|
||||||
|
if (BASE_MODIFIER == null) {
|
||||||
|
BASE_MODIFIER = new StructureModifier<Object>(MinecraftReflection.getAttributeModifierClass());
|
||||||
|
}
|
||||||
|
this.modifier = BASE_MODIFIER.withTarget(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the handle of a modifier.
|
||||||
|
* @param handle - the underlying handle.
|
||||||
|
*/
|
||||||
|
private void setHandle(Object handle) {
|
||||||
|
// Check handle type
|
||||||
|
if (!MinecraftReflection.getAttributeModifierClass().isAssignableFrom(handle.getClass()))
|
||||||
|
throw new IllegalArgumentException("handle (" + handle + ") must be a AttributeModifier.");
|
||||||
|
this.handle = handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the unique UUID that identifies the origin of this modifier.
|
||||||
|
* @return The unique UUID.
|
||||||
|
*/
|
||||||
|
public UUID getUUID() {
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a human readable name of this modifier.
|
||||||
|
* <p>
|
||||||
|
* Note that this will be "Unknown synced attribute modifier" on the client side.
|
||||||
|
* @return The attribute key.
|
||||||
|
*/
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the operation that is used to compute the final attribute value.
|
||||||
|
* @return The operation.
|
||||||
|
*/
|
||||||
|
public Operation getOperation() {
|
||||||
|
return operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the amount to modify in the operation.
|
||||||
|
* @return The amount.
|
||||||
|
*/
|
||||||
|
public double getAmount() {
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when we need to construct a handle object.
|
||||||
|
*/
|
||||||
|
protected void checkHandle() {
|
||||||
|
if (handle == null) {
|
||||||
|
handle = newBuilder(this).build().getHandle();
|
||||||
|
initializeModifier(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the underlying attribute modifier.
|
||||||
|
* @return The underlying modifier.
|
||||||
|
*/
|
||||||
|
public Object getHandle() {
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether or not the modifier is pending synchronization with the client.
|
||||||
|
* <p>
|
||||||
|
* This value will be disregarded for {@link #equals(Object)}.
|
||||||
|
* @param pending - TRUE if is is, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public void setPendingSynchronization(boolean pending) {
|
||||||
|
modifier.withType(boolean.class).write(0, pending);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the modifier is pending synchronization with the client.
|
||||||
|
* @return TRUE if it is, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isPendingSynchronization() {
|
||||||
|
return (Boolean) modifier.withType(boolean.class).read(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if a given modifier is equal to the current modifier.
|
||||||
|
* <p>
|
||||||
|
* Two modifiers are considered equal if they use the same UUID.
|
||||||
|
* @param obj - the object to check against.
|
||||||
|
* @return TRUE if the given object is the same, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == this)
|
||||||
|
return true;
|
||||||
|
if (obj instanceof WrappedAttributeModifier) {
|
||||||
|
WrappedAttributeModifier other = (WrappedAttributeModifier) obj;
|
||||||
|
|
||||||
|
// Ensure they are equal
|
||||||
|
return Objects.equal(uuid, other.getUUID());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return uuid != null ? uuid.hashCode() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "[amount=" + amount + ", operation=" + operation + ", name='" + name + "', id=" + uuid + ", serialize=" + isPendingSynchronization() + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a builder of attribute modifiers.
|
||||||
|
* <p>
|
||||||
|
* Use {@link WrappedAttributeModifier#newBuilder()} to construct an instance of the builder.
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public static class Builder {
|
||||||
|
private Operation operation = Operation.ADD_NUMBER;
|
||||||
|
private String name = "Unknown";
|
||||||
|
private double amount;
|
||||||
|
private UUID uuid;
|
||||||
|
|
||||||
|
private Builder(WrappedAttributeModifier template) {
|
||||||
|
if (template != null) {
|
||||||
|
operation = template.getOperation();
|
||||||
|
name = template.getName();
|
||||||
|
amount = template.getAmount();
|
||||||
|
uuid = template.getUUID();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the unique UUID that identifies the origin of this modifier.
|
||||||
|
* <p>
|
||||||
|
* This parameter is automatically supplied with a random UUID, or the
|
||||||
|
* UUID from an attribute modifier to clone.
|
||||||
|
*
|
||||||
|
* @param uuid - the uuid to supply to the new object.
|
||||||
|
* @return This builder, for chaining.
|
||||||
|
*/
|
||||||
|
public Builder uuid(@Nonnull UUID uuid) {
|
||||||
|
this.uuid = Preconditions.checkNotNull(uuid, "uuid cannot be NULL.");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the operation that is used to compute the final attribute value.
|
||||||
|
*
|
||||||
|
* @param operation - the operation to supply to the new object.
|
||||||
|
* @return This builder, for chaining.
|
||||||
|
*/
|
||||||
|
public Builder operation(@Nonnull Operation operation) {
|
||||||
|
this.operation = Preconditions.checkNotNull(operation, "operation cannot be NULL.");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a human readable name of this modifier.
|
||||||
|
* @param attributeKey - the attribute key to supply to the new object.
|
||||||
|
* @return This builder, for chaining.
|
||||||
|
*/
|
||||||
|
public Builder name(@Nonnull String name) {
|
||||||
|
this.name = Preconditions.checkNotNull(name, "name cannot be NULL.");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the amount to modify in the operation.
|
||||||
|
*
|
||||||
|
* @param amount - the amount to supply to the new object.
|
||||||
|
* @return This builder, for chaining.
|
||||||
|
*/
|
||||||
|
public Builder amount(double amount) {
|
||||||
|
this.amount = WrappedAttribute.checkDouble(amount);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new attribute modifier and its wrapper using the supplied values in this builder.
|
||||||
|
* @return The new attribute modifier.
|
||||||
|
* @throws NullPointerException If UUID has not been set.
|
||||||
|
* @throws RuntimeException If we are unable to construct the underlying attribute modifier.
|
||||||
|
*/
|
||||||
|
public WrappedAttributeModifier build() {
|
||||||
|
Preconditions.checkNotNull(uuid, "uuid cannot be NULL.");
|
||||||
|
|
||||||
|
// Retrieve the correct constructor
|
||||||
|
if (ATTRIBUTE_MODIFIER_CONSTRUCTOR == null) {
|
||||||
|
ATTRIBUTE_MODIFIER_CONSTRUCTOR = FuzzyReflection.fromClass(
|
||||||
|
MinecraftReflection.getAttributeModifierClass(), true).getConstructor(
|
||||||
|
FuzzyMethodContract.newBuilder().parameterCount(4).
|
||||||
|
parameterDerivedOf(UUID.class, 0).
|
||||||
|
parameterExactType(String.class, 1).
|
||||||
|
parameterExactType(double.class, 2).
|
||||||
|
parameterExactType(int.class, 3).build());
|
||||||
|
|
||||||
|
// Just in case
|
||||||
|
ATTRIBUTE_MODIFIER_CONSTRUCTOR.setAccessible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct it
|
||||||
|
try {
|
||||||
|
// No need to read these values with a modifier
|
||||||
|
return new WrappedAttributeModifier(
|
||||||
|
ATTRIBUTE_MODIFIER_CONSTRUCTOR.newInstance(
|
||||||
|
uuid, name, amount, operation.getId()),
|
||||||
|
uuid, name, amount, operation
|
||||||
|
);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Cannot construct AttributeModifier.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,203 @@
|
|||||||
|
package com.comphenix.protocol.wrappers.collection;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.Iterators;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a set that will (best effort) cache elements before using
|
||||||
|
* an underlying set to retrieve the actual element.
|
||||||
|
* <p>
|
||||||
|
* The cache will be invalidated when data is removed.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
* @param <T> - type of each element in the collection.
|
||||||
|
*/
|
||||||
|
public class CachedCollection<T> implements Collection<T> {
|
||||||
|
protected Set<T> delegate;
|
||||||
|
protected Object[] cache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a cached collection with the given delegate.
|
||||||
|
* <p>
|
||||||
|
* Objects are cached before they can be extracted from this collection.
|
||||||
|
* @param delegate - the delegate.
|
||||||
|
*/
|
||||||
|
public CachedCollection(Set<T> delegate) {
|
||||||
|
this.delegate = Preconditions.checkNotNull(delegate, "delegate cannot be NULL.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct the cache if needed.
|
||||||
|
*/
|
||||||
|
private void initializeCache() {
|
||||||
|
if (cache == null) {
|
||||||
|
cache = new Object[delegate.size()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that the cache is big enough.
|
||||||
|
*/
|
||||||
|
private void growCache() {
|
||||||
|
// We'll delay making the cache
|
||||||
|
if (cache == null)
|
||||||
|
return;
|
||||||
|
int newLength = cache.length;
|
||||||
|
|
||||||
|
// Ensure that the cache is big enoigh
|
||||||
|
while (newLength < delegate.size()) {
|
||||||
|
newLength *= 2;
|
||||||
|
}
|
||||||
|
if (newLength != cache.length) {
|
||||||
|
cache = Arrays.copyOf(cache, newLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return delegate.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return delegate.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(Object o) {
|
||||||
|
return delegate.contains(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<T> iterator() {
|
||||||
|
final Iterator<T> source = delegate.iterator();
|
||||||
|
initializeCache();
|
||||||
|
|
||||||
|
return new Iterator<T>() {
|
||||||
|
int currentIndex = -1;
|
||||||
|
int iteratorIndex = -1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return currentIndex < delegate.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public T next() {
|
||||||
|
currentIndex++;
|
||||||
|
|
||||||
|
if (cache[currentIndex] == null) {
|
||||||
|
cache[currentIndex] = getSourceValue();
|
||||||
|
}
|
||||||
|
return (T) cache[currentIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
// Increment iterator
|
||||||
|
getSourceValue();
|
||||||
|
source.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the corresponding value from the source iterator.
|
||||||
|
*/
|
||||||
|
private T getSourceValue() {
|
||||||
|
T last = null;
|
||||||
|
|
||||||
|
while (iteratorIndex < currentIndex) {
|
||||||
|
iteratorIndex++;
|
||||||
|
last = source.next();
|
||||||
|
}
|
||||||
|
return last;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object[] toArray() {
|
||||||
|
Iterators.size(iterator());
|
||||||
|
return cache.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"unchecked", "hiding", "rawtypes"})
|
||||||
|
@Override
|
||||||
|
public <T> T[] toArray(T[] a) {
|
||||||
|
Iterators.size(iterator());
|
||||||
|
return (T[]) Arrays.copyOf(cache, size(), (Class) a.getClass().getComponentType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean add(T e) {
|
||||||
|
boolean result = delegate.add(e);
|
||||||
|
|
||||||
|
growCache();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean addAll(Collection<? extends T> c) {
|
||||||
|
boolean result = delegate.addAll(c);
|
||||||
|
|
||||||
|
growCache();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsAll(Collection<?> c) {
|
||||||
|
return delegate.containsAll(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean remove(Object o) {
|
||||||
|
cache = null;
|
||||||
|
return delegate.remove(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeAll(Collection<?> c) {
|
||||||
|
cache = null;
|
||||||
|
return delegate.removeAll(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean retainAll(Collection<?> c) {
|
||||||
|
cache = null;
|
||||||
|
return delegate.retainAll(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
cache = null;
|
||||||
|
delegate.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = 1;
|
||||||
|
|
||||||
|
// Combine all the hashCodes()
|
||||||
|
for (Object element : this)
|
||||||
|
result = 31 * result + (element == null ? 0 : element.hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
Iterators.size(iterator());
|
||||||
|
StringBuilder result = new StringBuilder("[");
|
||||||
|
|
||||||
|
for (T element : this) {
|
||||||
|
if (result.length() > 1)
|
||||||
|
result.append(", ");
|
||||||
|
result.append(element);
|
||||||
|
}
|
||||||
|
return result.append("]").toString();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package com.comphenix.protocol.wrappers.collection;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a cached set. Enumeration of the set will use a cached inner list.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
* @param <T> - the element type.
|
||||||
|
*/
|
||||||
|
public class CachedSet<T> extends CachedCollection<T> implements Set<T> {
|
||||||
|
/**
|
||||||
|
* Construct a cached set from the given delegate.
|
||||||
|
* @param delegate - the set delegate.
|
||||||
|
*/
|
||||||
|
public CachedSet(Set<T> delegate) {
|
||||||
|
super(delegate);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
package com.comphenix.protocol.wrappers;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import net.minecraft.server.v1_6_R2.AttributeModifier;
|
||||||
|
import net.minecraft.server.v1_6_R2.AttributeSnapshot;
|
||||||
|
import net.minecraft.server.v1_6_R2.Packet44UpdateAttributes;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.BukkitInitialization;
|
||||||
|
import com.comphenix.protocol.Packets;
|
||||||
|
import com.comphenix.protocol.events.PacketContainer;
|
||||||
|
import com.comphenix.protocol.wrappers.WrappedAttributeModifier.Operation;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
public class WrappedAttributeTest {
|
||||||
|
private WrappedAttributeModifier doubleModifier;
|
||||||
|
private WrappedAttributeModifier constantModifier;
|
||||||
|
private WrappedAttribute attribute;
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void initializeBukkit() throws IllegalAccessException {
|
||||||
|
BukkitInitialization.initializePackage();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
// Create a couple of modifiers
|
||||||
|
doubleModifier =
|
||||||
|
WrappedAttributeModifier.newBuilder().
|
||||||
|
name("Double Damage").
|
||||||
|
amount(1).
|
||||||
|
operation(Operation.ADD_PERCENTAGE).
|
||||||
|
build();
|
||||||
|
constantModifier =
|
||||||
|
WrappedAttributeModifier.newBuilder().
|
||||||
|
name("Damage Bonus").
|
||||||
|
amount(5).
|
||||||
|
operation(Operation.ADD_NUMBER).
|
||||||
|
build();
|
||||||
|
|
||||||
|
// Create attribute
|
||||||
|
attribute = WrappedAttribute.newBuilder().
|
||||||
|
attributeKey("generic.attackDamage").
|
||||||
|
baseValue(2).
|
||||||
|
packet(new PacketContainer(Packets.Server.UPDATE_ATTRIBUTES)).
|
||||||
|
modifiers(Lists.newArrayList(constantModifier, doubleModifier)).
|
||||||
|
build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEquality() {
|
||||||
|
// Check wrapped equality
|
||||||
|
assertEquals(doubleModifier, doubleModifier);
|
||||||
|
assertNotSame(constantModifier, doubleModifier);
|
||||||
|
|
||||||
|
assertEquals(doubleModifier.getHandle(), getModifierCopy(doubleModifier));
|
||||||
|
assertEquals(constantModifier.getHandle(), getModifierCopy(constantModifier));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAttribute() {
|
||||||
|
assertEquals(attribute, WrappedAttribute.fromHandle(getAttributeCopy(attribute)));
|
||||||
|
|
||||||
|
assertTrue(attribute.hasModifier(doubleModifier.getUUID()));
|
||||||
|
assertTrue(attribute.hasModifier(constantModifier.getUUID()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the equivalent NMS attribute.
|
||||||
|
* @param attribute - the wrapped attribute.
|
||||||
|
* @return The equivalent NMS attribute.
|
||||||
|
*/
|
||||||
|
private AttributeSnapshot getAttributeCopy(WrappedAttribute attribute) {
|
||||||
|
List<AttributeModifier> modifiers = Lists.newArrayList();
|
||||||
|
|
||||||
|
for (WrappedAttributeModifier wrapper : attribute.getModifiers()) {
|
||||||
|
modifiers.add((AttributeModifier) wrapper.getHandle());
|
||||||
|
}
|
||||||
|
return new AttributeSnapshot(
|
||||||
|
(Packet44UpdateAttributes) attribute.getParentPacket().getHandle(),
|
||||||
|
attribute.getAttributeKey(), attribute.getBaseValue(), modifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AttributeModifier getModifierCopy(WrappedAttributeModifier modifier) {
|
||||||
|
return new AttributeModifier(modifier.getUUID(), modifier.getName(), modifier.getAmount(), modifier.getOperation().getId());
|
||||||
|
}
|
||||||
|
}
|
In neuem Issue referenzieren
Einen Benutzer sperren