blob: 205f98c57c1367d7c645d7869b140cbb513937ed [file] [log] [blame] [raw]
package net.glowstone.entity.meta;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import net.glowstone.util.DynamicallyTypedMapWithFloats;
import net.glowstone.util.TextMessage;
import org.bukkit.entity.Entity;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.BlockVector;
/**
* A map for entity metadata.
*/
@ToString(of = {"entityClass", "map"})
public class MetadataMap implements DynamicallyTypedMapWithFloats<MetadataIndex> {
private final Map<MetadataIndex, Object> map = new EnumMap<>(MetadataIndex.class);
private final List<Entry> changes = new ArrayList<>(4);
private final Class<? extends Entity> entityClass;
public MetadataMap(Class<? extends Entity> entityClass) {
this.entityClass = entityClass;
set(MetadataIndex.STATUS, 0); // all entities have to have at least this
}
public boolean containsKey(MetadataIndex index) {
return map.containsKey(index);
}
/**
* Sets the value of a metadata field.
*
* @param index the field to set
* @param value the new value
*/
public void set(MetadataIndex index, Object value) {
set(index, value, false);
}
/**
* Sets the value of a metadata field.
*
* @param index the field to set
* @param value the new value
* @param force if the value should be forced as a change regardless of equality
*/
public void set(MetadataIndex index, Object value, boolean force) {
// take numbers down to the correct precision
if (value != null) {
if (value instanceof Number) {
Number n = (Number) value;
switch (index.getType()) {
case BYTE:
value = n.byteValue();
break;
case INT:
value = n.intValue();
break;
case FLOAT:
value = n.floatValue();
break;
default:
// do nothing
}
}
if (!index.getType().getDataType().isAssignableFrom(value.getClass())) {
throw new IllegalArgumentException(
"Cannot assign " + value + " to " + index + ", expects " + index.getType());
}
if (!index.appliesTo(entityClass)) {
throw new IllegalArgumentException(
"Index " + index + " does not apply to " + entityClass.getSimpleName()
+ ", only " + index.getAppliesTo().getSimpleName());
}
}
Object prev = map.put(index, value);
if (force || !Objects.equals(prev, value)) {
changes.add(new Entry(index, value));
}
}
public Object get(MetadataIndex index) {
return map.get(index);
}
@SuppressWarnings("unchecked")
private <T> T get(MetadataIndex index, MetadataType expected, T def) {
if (index.getType() != expected) {
throw new IllegalArgumentException(
"Cannot get " + index + ": is " + index.getType() + ", not " + expected);
}
T t = (T) map.get(index);
if (t == null) {
return def;
}
return t;
}
public boolean getBit(MetadataIndex index, int bit) {
return (getNumber(index).intValue() & bit) != 0;
}
/**
* Sets or clears bits in an integer field.
*
* @param index the field to update
* @param bit a mask of the bits to set or clear
* @param status true to set; false to clear
*/
public void setBit(MetadataIndex index, int bit, boolean status) {
if (status) {
set(index, getNumber(index).intValue() | bit);
} else {
set(index, getNumber(index).intValue() & ~bit);
}
}
/**
* Returns the numeric value of a metadata field.
*
* @param index the field to look up
* @return the numeric value
* @throws IllegalArgumentException if the value doesn't exist or isn't numeric
*/
public Number getNumber(MetadataIndex index) {
if (!containsKey(index)) {
return 0;
}
Object o = get(index);
if (!(o instanceof Number)) {
throw new IllegalArgumentException(
"Index " + index + " is of non-number type " + index.getType());
}
return (Number) o;
}
@Override
public boolean getBoolean(MetadataIndex index) {
return get(index, MetadataType.BOOLEAN, false);
}
public byte getByte(MetadataIndex index) {
return get(index, MetadataType.BYTE, (byte) 0);
}
@Override
public int getInt(MetadataIndex index) {
return get(index, MetadataType.INT, 0);
}
@Override
public float getFloat(MetadataIndex index) {
return get(index, MetadataType.FLOAT, 0f);
}
@Override
public String getString(MetadataIndex index) {
return get(index, MetadataType.STRING, null);
}
public ItemStack getItem(MetadataIndex index) {
return get(index, MetadataType.ITEM, null);
}
/**
* Gets the optional position value for the given MetadataIndex.
*
* @param index the MetadataIndex of the optional position
* @return the position value as a BlockVector, null if the value is not present
*/
public BlockVector getOptPosition(MetadataIndex index) {
return get(index, MetadataType.OPTPOSITION, null);
}
public TextMessage getChat(MetadataIndex index) {
return get(index, MetadataType.CHAT, null);
}
/**
* Gets the optional chat value for the given MetadataIndex.
*
* @param index the MetadataIndex of the optional chat
* @return the chat value as a TextMessage, null if the value is not present
*/
public TextMessage getOptChat(MetadataIndex index) {
return get(index, MetadataType.OPTCHAT, null);
}
/**
* Returns a list containing copies of all the entries.
*
* @return a list containing copies of all the entries
*/
public List<Entry> getEntryList() {
List<Entry> result = new ArrayList<>(map.size());
result.addAll(
map.entrySet().stream().map(entry -> new Entry(entry.getKey(), entry.getValue()))
.collect(Collectors.toList()));
Collections.sort(result);
return result;
}
public List<Entry> getChanges() {
Collections.sort(changes);
return ImmutableList.copyOf(changes);
}
public void resetChanges() {
changes.clear();
}
@RequiredArgsConstructor
@EqualsAndHashCode
public static class Entry implements Comparable<Entry> {
public final MetadataIndex index;
public final Object value;
@Override
public int compareTo(Entry o) {
return o.index.getIndex() - index.getIndex();
}
@Override
public String toString() {
return index + "=" + value;
}
}
}