diff --git a/src/main/java/malilib/mixin/access/TileEntityMixin.java b/src/main/java/malilib/mixin/access/TileEntityMixin.java new file mode 100644 index 0000000000..2d351c4083 --- /dev/null +++ b/src/main/java/malilib/mixin/access/TileEntityMixin.java @@ -0,0 +1,14 @@ +package malilib.mixin.access; + +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.registry.RegistryNamespaced; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(TileEntity.class) +public interface TileEntityMixin +{ + @Accessor("REGISTRY") + RegistryNamespaced> malilib_getTileEntityRegistry(); +} diff --git a/src/main/java/malilib/util/data/DyeColorCode.java b/src/main/java/malilib/util/data/DyeColorCode.java index fd883e534a..0d680b16d0 100644 --- a/src/main/java/malilib/util/data/DyeColorCode.java +++ b/src/main/java/malilib/util/data/DyeColorCode.java @@ -1,5 +1,7 @@ package malilib.util.data; +import net.minecraft.item.EnumDyeColor; + public enum DyeColorCode { WHITE ( 0, "§f", "white"), @@ -56,4 +58,22 @@ public static DyeColorCode getByMeta(int meta) return COLOR_CODES_BY_META[meta]; } + + public static DyeColorCode fromStringOrDefault(String name, DyeColorCode defaultValue) + { + for (DyeColorCode color : COLOR_CODES_BY_META) + { + if (color.getName().equalsIgnoreCase(name)) + { + return color; + } + } + + return defaultValue; + } + + public EnumDyeColor toVanilla() + { + return EnumDyeColor.byMetadata(this.meta); + } } diff --git a/src/main/java/malilib/util/data/Identifier.java b/src/main/java/malilib/util/data/Identifier.java index 64f3fdeade..ceb83779fb 100644 --- a/src/main/java/malilib/util/data/Identifier.java +++ b/src/main/java/malilib/util/data/Identifier.java @@ -1,5 +1,7 @@ package malilib.util.data; +import java.util.Optional; + import net.minecraft.util.ResourceLocation; /** @@ -23,4 +25,14 @@ public Identifier(String namespaceIn, String pathIn) { super(namespaceIn, pathIn); } + + public static Identifier of(String resourceName) + { + return new Identifier(resourceName); + } + + public static Identifier of(String namespaceIn, String pathIn) + { + return new Identifier(namespaceIn, pathIn); + } } diff --git a/src/main/java/malilib/util/data/tag/ArrayData.java b/src/main/java/malilib/util/data/tag/ArrayData.java new file mode 100644 index 0000000000..07d3a5061c --- /dev/null +++ b/src/main/java/malilib/util/data/tag/ArrayData.java @@ -0,0 +1,64 @@ +package malilib.util.data.tag; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import javax.annotation.Nonnull; + +/** + * Informs {@link malilib.util.data.tag.util.DataOps} that the inherited class + * is an Array Value, and can perform the basic {@link java.util.ArrayList} operations + * @implNote They will also need to be Mutable objects. + */ +public interface ArrayData extends Iterable +{ + void clear(); + + boolean set(int index, BaseData entry); + + boolean add(int index, BaseData entry); + + BaseData remove(int index); + + BaseData get(int index); + + int size(); + + default boolean isEmpty() + { + return size() == 0; + } + + default @Nonnull Iterator iterator() + { + return new Iterator() + { + private int index; + + @Override + public boolean hasNext() + { + return this.index < ArrayData.this.size(); + } + + @Override + public BaseData next() + { + if (this.hasNext()) + { + return ArrayData.this.get(this.index++); + } + else + { + throw new NoSuchElementException(); + } + } + }; + } + + default Stream stream() + { + return StreamSupport.stream(this.spliterator(), false); + } +} diff --git a/src/main/java/malilib/util/data/tag/BaseData.java b/src/main/java/malilib/util/data/tag/BaseData.java index 8c526d6ab6..b4cd5e9ee6 100644 --- a/src/main/java/malilib/util/data/tag/BaseData.java +++ b/src/main/java/malilib/util/data/tag/BaseData.java @@ -3,6 +3,7 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; +import java.util.Optional; import malilib.util.data.Constants; import malilib.util.data.tag.util.SizeTracker; @@ -30,6 +31,15 @@ public String getDisplayName() public abstract BaseData copy(); + public abstract String toString(); + + public abstract boolean isEmpty(); + + public Optional asNumber() + { + return Optional.empty(); + } + public abstract void write(DataOutput output) throws IOException; public static BaseData createTag(int tagType, DataInput input, int depth, SizeTracker sizeTracker) throws IOException @@ -48,6 +58,7 @@ public static BaseData createTag(int tagType, DataInput input, int depth, SizeTr case Constants.NBT.TAG_LONG_ARRAY: return LongArrayData.read(input, depth, sizeTracker); case Constants.NBT.TAG_COMPOUND: return CompoundData.read(input, depth, sizeTracker); case Constants.NBT.TAG_LIST: return ListData.read(input, depth, sizeTracker); + case Constants.NBT.TAG_END: return EmptyData.read(input, depth, sizeTracker); default: throw new IOException("Unknown tag type " + tagType); } diff --git a/src/main/java/malilib/util/data/tag/ByteArrayData.java b/src/main/java/malilib/util/data/tag/ByteArrayData.java index 1c05ece0aa..b19ed1afbd 100644 --- a/src/main/java/malilib/util/data/tag/ByteArrayData.java +++ b/src/main/java/malilib/util/data/tag/ByteArrayData.java @@ -8,7 +8,8 @@ import malilib.util.data.Constants; import malilib.util.data.tag.util.SizeTracker; -public class ByteArrayData extends BaseData +public class ByteArrayData extends BaseData // implements ArrayData + // todo ArrayData { public static final String TAG_NAME = "TAG_ByteArray"; @@ -52,6 +53,83 @@ public String toString() return sb.append(']').toString(); } + @Override + public boolean isEmpty() + { + return this.value.length == 0; + } + + // todo ArrayData +// @Override +// public void clear() +// { +// this.value = new byte[0]; +// } +// +// @Override +// public boolean set(int index, BaseData entry) +// { +// Optional opt = entry.asNumber(); +// +// if (index < this.size() && +// index >= 0 && +// opt.isPresent()) +// { +// this.value[index] = opt.get().byteValue(); +// return true; +// } +// +// return false; +// } +// +// @Override +// public boolean add(int index, BaseData entry) +// { +// Optional opt = entry.asNumber(); +// +// if (index < this.size() && +// index >= 0 && +// opt.isPresent()) +// { +// this.value = ArrayUtils.add(this.value, index, opt.get().byteValue()); +// return true; +// } +// +// return false; +// } +// +// @Override +// @Nullable +// public ByteData remove(int index) +// { +// if (index < this.size() && index >= 0) +// { +// byte entry = this.value[index]; +// this.value = ArrayUtils.remove(this.value, index); +// return new ByteData(entry); +// } +// +// return null; +// } +// +// @Override +// @Nullable +// public ByteData get(int index) +// { +// if (index < this.size() && index >= 0) +// { +// return new ByteData(this.value[index]); +// } +// +// return null; +// } +// +// @Override +// public int size() +// { +// return this.value.length; +// } + @Override public void write(DataOutput output) throws IOException { diff --git a/src/main/java/malilib/util/data/tag/ByteData.java b/src/main/java/malilib/util/data/tag/ByteData.java index bef1cf7cc8..2f2131e041 100644 --- a/src/main/java/malilib/util/data/tag/ByteData.java +++ b/src/main/java/malilib/util/data/tag/ByteData.java @@ -3,11 +3,12 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; +import java.util.Optional; import malilib.util.data.Constants; import malilib.util.data.tag.util.SizeTracker; -public class ByteData extends BaseData +public class ByteData extends BaseData implements NumberData { public static final String TAG_NAME = "TAG_Byte"; @@ -37,6 +38,18 @@ public String toString() return this.value + "b"; } + @Override + public boolean isEmpty() + { + return false; + } + + @Override + public Optional asNumber() + { + return Optional.of(this.value); + } + @Override public void write(DataOutput output) throws IOException { diff --git a/src/main/java/malilib/util/data/tag/CompoundData.java b/src/main/java/malilib/util/data/tag/CompoundData.java index cbb57f8457..e5480f1aa6 100644 --- a/src/main/java/malilib/util/data/tag/CompoundData.java +++ b/src/main/java/malilib/util/data/tag/CompoundData.java @@ -52,6 +52,11 @@ public Set getKeys() return this.values.keySet(); } + public Set> entrySet() + { + return this.values.entrySet(); + } + @Override public boolean contains(String key, int requestedType) { @@ -263,7 +268,6 @@ public ListData getList(String key, int containedType) return data != null && data.getType() == Constants.NBT.TAG_LIST ? (ListData) data : new ListData(containedType); } - public CompoundData putBoolean(String key, boolean value) { this.values.put(key, new ByteData(value ? (byte) 1 : 0)); @@ -358,6 +362,33 @@ public CompoundData copy() return copy; } + // Primarily needed for DataOps, but can be utilized for other things + public CompoundData combine(CompoundData other) + { + if (other == null || other.isEmpty()) + { + return this.copy(); + } + + for (String key : other.values.keySet()) + { + BaseData data = other.values.get(key); + + if (data.getType() == Constants.NBT.TAG_COMPOUND && + this.values.containsKey(key) && + this.values.get(key).getType() == Constants.NBT.TAG_COMPOUND) + { + CompoundData out = ((CompoundData) this.values.get(key)).combine((CompoundData) data); + this.values.put(key, out); + continue; + } + + this.values.put(key, data); + } + + return this.copy(); + } + @Override public String toString() { @@ -459,6 +490,28 @@ public boolean equals(Object o) CompoundData other = (CompoundData) o; return Objects.equals(this.values, other.values); + + // todo +// // Match any member of the compound using equals(), +// // in any insertion order. +// if (o instanceof CompoundData data) +// { +// boolean result = false; +// +// for (String key : this.getKeys()) +// { +// result = this.values.get(key).equals(data.values.get(key)); +// +// if (!result) +// { +// break; +// } +// } +// +// return result; +// } +// +// return false; } @Override diff --git a/src/main/java/malilib/util/data/tag/DataView.java b/src/main/java/malilib/util/data/tag/DataView.java index c58271b91b..29fd625d90 100644 --- a/src/main/java/malilib/util/data/tag/DataView.java +++ b/src/main/java/malilib/util/data/tag/DataView.java @@ -45,7 +45,6 @@ public interface DataView ListData getList(String key, int containedType); - default boolean getBooleanOrDefault(String key, boolean defaultValue) { if (this.contains(key, Constants.NBT.TAG_BYTE) == false) diff --git a/src/main/java/malilib/util/data/tag/DoubleData.java b/src/main/java/malilib/util/data/tag/DoubleData.java index 92c2950d1b..56e7594cae 100644 --- a/src/main/java/malilib/util/data/tag/DoubleData.java +++ b/src/main/java/malilib/util/data/tag/DoubleData.java @@ -3,11 +3,12 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; +import java.util.Optional; import malilib.util.data.Constants; import malilib.util.data.tag.util.SizeTracker; -public class DoubleData extends BaseData +public class DoubleData extends BaseData implements NumberData { public static final String TAG_NAME = "TAG_Double"; @@ -37,6 +38,18 @@ public String toString() return this.value + "d"; } + @Override + public boolean isEmpty() + { + return false; + } + + @Override + public Optional asNumber() + { + return Optional.of(this.value); + } + @Override public void write(DataOutput output) throws IOException { diff --git a/src/main/java/malilib/util/data/tag/EmptyData.java b/src/main/java/malilib/util/data/tag/EmptyData.java new file mode 100644 index 0000000000..62715f1562 --- /dev/null +++ b/src/main/java/malilib/util/data/tag/EmptyData.java @@ -0,0 +1,62 @@ +package malilib.util.data.tag; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import malilib.util.data.Constants; +import malilib.util.data.tag.util.SizeTracker; + +public class EmptyData extends BaseData +{ + public static final String TAG_NAME = "TAG_End"; + public static final EmptyData INSTANCE = new EmptyData(); + + protected EmptyData() + { + super(Constants.NBT.TAG_END, TAG_NAME); + } + + @Override + public EmptyData copy() + { + return this; + } + + @Override + public String toString() + { + return ""; + } + + @Override + public boolean isEmpty() + { + return true; + } + + @Override + public void write(DataOutput output) throws IOException + { + } + + public static EmptyData read(DataInput input, int depth, SizeTracker sizeTracker) throws IOException + { + return new EmptyData(); + } + + @Override + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + return o != null && this.getClass() == o.getClass(); + } + + @Override + public int hashCode() + { + return 0; + } +} diff --git a/src/main/java/malilib/util/data/tag/FloatData.java b/src/main/java/malilib/util/data/tag/FloatData.java index 445983d886..a1ee3e9e98 100644 --- a/src/main/java/malilib/util/data/tag/FloatData.java +++ b/src/main/java/malilib/util/data/tag/FloatData.java @@ -3,11 +3,12 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; +import java.util.Optional; import malilib.util.data.Constants; import malilib.util.data.tag.util.SizeTracker; -public class FloatData extends BaseData +public class FloatData extends BaseData implements NumberData { public static final String TAG_NAME = "TAG_Float"; @@ -37,6 +38,18 @@ public String toString() return this.value + "f"; } + @Override + public boolean isEmpty() + { + return false; + } + + @Override + public Optional asNumber() + { + return Optional.of(this.value); + } + @Override public void write(DataOutput output) throws IOException { diff --git a/src/main/java/malilib/util/data/tag/IntArrayData.java b/src/main/java/malilib/util/data/tag/IntArrayData.java index 95435c29a3..d92ec86293 100644 --- a/src/main/java/malilib/util/data/tag/IntArrayData.java +++ b/src/main/java/malilib/util/data/tag/IntArrayData.java @@ -8,7 +8,8 @@ import malilib.util.data.Constants; import malilib.util.data.tag.util.SizeTracker; -public class IntArrayData extends BaseData +public class IntArrayData extends BaseData // implements ArrayData + // todo ArrayData { public static final String TAG_NAME = "TAG_IntArray"; @@ -52,6 +53,81 @@ public String toString() return sb.append(']').toString(); } + @Override + public boolean isEmpty() + { + return this.value.length == 0; + } + + // todo ArrayData +// @Override +// public void clear() +// { +// this.value = new int[0]; +// } +// +// @Override +// public boolean set(int index, BaseData entry) +// { +// Optional opt = entry.asNumber(); +// +// if (index < this.size() && +// index >= 0 && +// opt.isPresent()) +// { +// this.value[index] = opt.get().intValue(); +// return true; +// } +// +// return false; +// } +// +// @Override +// public boolean add(int index, BaseData entry) +// { +// Optional opt = entry.asNumber(); +// +// if (index < this.size() && +// index >= 0 && +// opt.isPresent()) +// { +// this.value = ArrayUtils.add(this.value, index, opt.get().intValue()); +// return true; +// } +// +// return false; +// } +// +// @Override +// public IntData remove(int index) +// { +// if (index < this.size() && index >= 0) +// { +// int entry = this.value[index]; +// this.value = ArrayUtils.remove(this.value, index); +// return new IntData(entry); +// } +// +// return null; +// } +// +// @Override +// public IntData get(int index) +// { +// if (index < this.size() && index >= 0) +// { +// return new IntData(this.value[index]); +// } +// +// return null; +// } +// +// @Override +// public int size() +// { +// return this.value.length; +// } + @Override public void write(DataOutput output) throws IOException { diff --git a/src/main/java/malilib/util/data/tag/IntData.java b/src/main/java/malilib/util/data/tag/IntData.java index 4bdcd9182b..e3d6caacca 100644 --- a/src/main/java/malilib/util/data/tag/IntData.java +++ b/src/main/java/malilib/util/data/tag/IntData.java @@ -3,11 +3,12 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; +import java.util.Optional; import malilib.util.data.Constants; import malilib.util.data.tag.util.SizeTracker; -public class IntData extends BaseData +public class IntData extends BaseData implements NumberData { public static final String TAG_NAME = "TAG_Int"; @@ -37,6 +38,18 @@ public String toString() return this.value + ""; } + @Override + public boolean isEmpty() + { + return false; + } + + @Override + public Optional asNumber() + { + return Optional.of(this.value); + } + @Override public void write(DataOutput output) throws IOException { diff --git a/src/main/java/malilib/util/data/tag/ListData.java b/src/main/java/malilib/util/data/tag/ListData.java index 3604994fec..b4e84541b2 100644 --- a/src/main/java/malilib/util/data/tag/ListData.java +++ b/src/main/java/malilib/util/data/tag/ListData.java @@ -11,7 +11,8 @@ import malilib.util.data.Constants; import malilib.util.data.tag.util.SizeTracker; -public class ListData extends BaseData +public class ListData extends BaseData // implements ArrayData + // todo ArrayData - 1.21.5+ or whenever Mojang adds the DFU packages { public static final String TAG_NAME = "TAG_List"; @@ -86,6 +87,62 @@ public BaseData get(int index) return this.list.get(index); } + // todo ArrayData - 1.21.5+ or whenever Mojang adds the DFU packages +// @Override +// public boolean set(int index, BaseData entry) +// { +// int type = this.getContainedType(); +// +// if (type == Constants.NBT.TAG_END || +// entry.getType() != type || +// index < 0 || +// index >= this.list.size()) +// { +// return false; +// } +// +// if (index < this.size()) +// { +// this.list.set(index, entry); +// return true; +// } +// +// return false; +// } +// +// @Override +// public boolean add(int index, BaseData entry) +// { +// int type = this.getContainedType(); +// +// if (type == Constants.NBT.TAG_END || +// entry.getType() != type || +// index < 0 || +// index >= this.list.size()) +// { +// return false; +// } +// +// if (index < this.size()) +// { +// this.list.add(index, entry); +// return true; +// } +// +// return false; +// } +// +// @Override +// public BaseData remove(int index) +// { +// if (index < this.list.size()) +// { +// return this.list.remove(index); +// } +// +// return EmptyData.INSTANCE; +// } + public byte getByteAt(int index) { if (this.containedType == Constants.NBT.TAG_BYTE) @@ -146,6 +203,16 @@ public double getDoubleAt(int index) return 0.0; } + public String getStringAt(int index) + { + if (this.containedType == Constants.NBT.TAG_STRING) + { + return ((StringData) this.list.get(index)).value; + } + + return ""; + } + public CompoundData getCompoundAt(int index) { if (this.containedType == Constants.NBT.TAG_COMPOUND) @@ -187,6 +254,12 @@ public String toString() return sb.append(']').toString(); } + @Override + public boolean isEmpty() + { + return this.list.isEmpty(); + } + @Override public void write(DataOutput output) throws IOException { diff --git a/src/main/java/malilib/util/data/tag/LongArrayData.java b/src/main/java/malilib/util/data/tag/LongArrayData.java index 6bc1fb64f3..e2538c573f 100644 --- a/src/main/java/malilib/util/data/tag/LongArrayData.java +++ b/src/main/java/malilib/util/data/tag/LongArrayData.java @@ -8,7 +8,8 @@ import malilib.util.data.Constants; import malilib.util.data.tag.util.SizeTracker; -public class LongArrayData extends BaseData +public class LongArrayData extends BaseData // implements ArrayData + // todo ArrayData { public static final String TAG_NAME = "TAG_LongArray"; @@ -52,6 +53,81 @@ public String toString() return sb.append(']').toString(); } + @Override + public boolean isEmpty() + { + return this.value.length == 0; + } + + // todo ArrayData +// @Override +// public void clear() +// { +// this.value = new long[0]; +// } +// +// @Override +// public boolean set(int index, BaseData entry) +// { +// Optional opt = entry.asNumber(); +// +// if (index < this.size() && +// index >= 0 && +// opt.isPresent()) +// { +// this.value[index] = opt.get().longValue(); +// return true; +// } +// +// return false; +// } +// +// @Override +// public boolean add(int index, BaseData entry) +// { +// Optional opt = entry.asNumber(); +// +// if (index < this.size() && +// index >= 0 && +// opt.isPresent()) +// { +// this.value = ArrayUtils.add(this.value, index, opt.get().longValue()); +// return true; +// } +// +// return false; +// } +// +// @Override +// public LongData remove(int index) +// { +// if (index < this.size() && index >= 0) +// { +// long entry = this.value[index]; +// this.value = ArrayUtils.remove(this.value, index); +// return new LongData(entry); +// } +// +// return null; +// } +// +// @Override +// public LongData get(int index) +// { +// if (index < this.size() && index >= 0) +// { +// return new LongData(this.value[index]); +// } +// +// return null; +// } +// +// @Override +// public int size() +// { +// return this.value.length; +// } + @Override public void write(DataOutput output) throws IOException { diff --git a/src/main/java/malilib/util/data/tag/LongData.java b/src/main/java/malilib/util/data/tag/LongData.java index 94441aad40..29757b68b3 100644 --- a/src/main/java/malilib/util/data/tag/LongData.java +++ b/src/main/java/malilib/util/data/tag/LongData.java @@ -3,11 +3,12 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; +import java.util.Optional; import malilib.util.data.Constants; import malilib.util.data.tag.util.SizeTracker; -public class LongData extends BaseData +public class LongData extends BaseData implements NumberData { public static final String TAG_NAME = "TAG_Long"; @@ -37,6 +38,18 @@ public String toString() return this.value + "L"; } + @Override + public boolean isEmpty() + { + return false; + } + + @Override + public Optional asNumber() + { + return Optional.of(this.value); + } + @Override public void write(DataOutput output) throws IOException { diff --git a/src/main/java/malilib/util/data/tag/NumberData.java b/src/main/java/malilib/util/data/tag/NumberData.java new file mode 100644 index 0000000000..2799dfed61 --- /dev/null +++ b/src/main/java/malilib/util/data/tag/NumberData.java @@ -0,0 +1,8 @@ +package malilib.util.data.tag; + +/** + * Informs {@link malilib.util.data.tag.util.DataOps} that the inherited class is a Number value + */ +public interface NumberData +{ +} diff --git a/src/main/java/malilib/util/data/tag/ShortData.java b/src/main/java/malilib/util/data/tag/ShortData.java index 30e2edb39c..39141d7ecf 100644 --- a/src/main/java/malilib/util/data/tag/ShortData.java +++ b/src/main/java/malilib/util/data/tag/ShortData.java @@ -3,11 +3,12 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; +import java.util.Optional; import malilib.util.data.Constants; import malilib.util.data.tag.util.SizeTracker; -public class ShortData extends BaseData +public class ShortData extends BaseData implements NumberData { public static final String TAG_NAME = "TAG_Short"; @@ -37,6 +38,18 @@ public String toString() return this.value + "s"; } + @Override + public boolean isEmpty() + { + return false; + } + + @Override + public Optional asNumber() + { + return Optional.of(this.value); + } + @Override public void write(DataOutput output) throws IOException { diff --git a/src/main/java/malilib/util/data/tag/StringData.java b/src/main/java/malilib/util/data/tag/StringData.java index 10e0e989e6..2a3959e401 100644 --- a/src/main/java/malilib/util/data/tag/StringData.java +++ b/src/main/java/malilib/util/data/tag/StringData.java @@ -38,6 +38,12 @@ public String toString() return quoteAndEscape(this.value); } + @Override + public boolean isEmpty() + { + return this.value.isEmpty(); + } + @Override public void write(DataOutput output) throws IOException { diff --git a/src/main/java/malilib/util/data/tag/converter/DataConverterNbt.java b/src/main/java/malilib/util/data/tag/converter/DataConverterNbt.java index 4870edcee9..ee6895949e 100644 --- a/src/main/java/malilib/util/data/tag/converter/DataConverterNbt.java +++ b/src/main/java/malilib/util/data/tag/converter/DataConverterNbt.java @@ -38,6 +38,8 @@ public class DataConverterNbt @Nullable public static BaseData fromVanillaNbt(NBTBase vanillaTag) { + if (vanillaTag == null) return null; // EmptyData.INSTANCE; + switch (vanillaTag.getId()) { case Constants.NBT.TAG_BYTE: return new ByteData(((NBTTagByte) vanillaTag).getByte()); @@ -66,6 +68,7 @@ public static BaseData fromVanillaNbt(NBTBase vanillaTag) } default: MaLiLib.LOGGER.warn("DataConverterNbt.fromVanillaCompound: Unknown NBT tag id {}", vanillaTag.getId()); +// return EmptyData.INSTANCE; // Return as EmptyData } return null; @@ -74,6 +77,7 @@ public static BaseData fromVanillaNbt(NBTBase vanillaTag) @Nullable public static ListData fromVanillaList(NBTTagList vanillaList) { + if (vanillaList == null || vanillaList.isEmpty()) { return null; } // new ListData(Constants.NBT.TAG_COMPOUND); ListData list = new ListData(vanillaList.getTagType()); for (int index = 0; index < vanillaList.tagCount(); index++) @@ -84,6 +88,7 @@ public static ListData fromVanillaList(NBTTagList vanillaList) { MaLiLib.LOGGER.warn("DataConverterNbt.fromVanillaList: Got TAG_End in a list at index {}", index); return null; +// return list; } BaseData convertedTag = fromVanillaNbt(entry); @@ -92,6 +97,7 @@ public static ListData fromVanillaList(NBTTagList vanillaList) { MaLiLib.LOGGER.warn("DataConverterNbt.fromVanillaList: Got a null tag in a list at index {}", index); return null; +// continue; } list.add(convertedTag); @@ -103,6 +109,7 @@ public static ListData fromVanillaList(NBTTagList vanillaList) public static CompoundData fromVanillaCompound(NBTTagCompound vanillaCompound) { CompoundData data = new CompoundData(); + if (vanillaCompound == null || vanillaCompound.isEmpty()) { return data; } for (String key : vanillaCompound.getKeySet()) { @@ -123,6 +130,8 @@ public static CompoundData fromVanillaCompound(NBTTagCompound vanillaCompound) @Nullable public static NBTBase toVanillaNbt(BaseData data) { + if (data == null) { return null; } // return new NBTTagList().get(-1); + switch (data.getType()) { case Constants.NBT.TAG_BYTE: return new NBTTagByte(((ByteData) data).value); @@ -139,6 +148,7 @@ public static NBTBase toVanillaNbt(BaseData data) case Constants.NBT.TAG_LIST: return toVanillaList((ListData) data); default: MaLiLib.LOGGER.warn("DataConverterNbt.toVanillaNbt: Unknown NBT tag id {}", data.getType()); +// return new NBTTagList().get(-1); // Returns new NBTTagEnd() } return null; @@ -148,6 +158,7 @@ public static NBTBase toVanillaNbt(BaseData data) public static NBTTagList toVanillaList(ListData listData) { NBTTagList list = new NBTTagList(); + if (listData == null || listData.isEmpty()) { return list; } for (int index = 0; index < listData.size(); index++) { @@ -157,6 +168,7 @@ public static NBTTagList toVanillaList(ListData listData) { MaLiLib.LOGGER.warn("DataConverterNbt.toVanillaList: Got TAG_End in a list at index {}", index); return null; +// return list; } NBTBase convertedTag = toVanillaNbt(entry); @@ -165,6 +177,7 @@ public static NBTTagList toVanillaList(ListData listData) { MaLiLib.LOGGER.warn("DataConverterNbt.toVanillaList: Got a null tag in a list at index {}", index); return null; +// continue; } list.appendTag(convertedTag); @@ -176,6 +189,7 @@ public static NBTTagList toVanillaList(ListData listData) public static NBTTagCompound toVanillaCompound(CompoundData compoundData) { NBTTagCompound tag = new NBTTagCompound(); + if (compoundData == null || compoundData.isEmpty()) { return tag; } for (String key : compoundData.getKeys()) { diff --git a/src/main/java/malilib/util/data/tag/util/DataByteBufUtils.java b/src/main/java/malilib/util/data/tag/util/DataByteBufUtils.java new file mode 100644 index 0000000000..b5e0569b46 --- /dev/null +++ b/src/main/java/malilib/util/data/tag/util/DataByteBufUtils.java @@ -0,0 +1,73 @@ +package malilib.util.data.tag.util; + +import java.util.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufInputStream; +import io.netty.buffer.ByteBufOutputStream; +import io.netty.buffer.UnpooledByteBufAllocator; +import malilib.MaLiLib; +import malilib.util.data.tag.BaseData; +import malilib.util.data.tag.EmptyData; + +public class DataByteBufUtils +{ + /** + * Write to Data Tags from a {@link ByteBuf} + * + * @param byteBuf The {@link ByteBuf} to read Data from + * @return The {@link Optional} of the resulting data or empty + */ + public static Optional fromByteBuf(ByteBuf byteBuf) + { + try (ByteBufInputStream is = new ByteBufInputStream(byteBuf)) + { + return Optional.ofNullable(DataFileUtils.readFromNbtStream(is)); + } + catch (Exception e) + { + MaLiLib.LOGGER.error("Exception while reading data from ByteBuf; {}", e.getLocalizedMessage()); + } + + return Optional.empty(); + } + + /** + * Write Data Tags to a new Unpooled {@link ByteBuf} + * + * @param data The Data Tags to write + * @param rootTagName The Root Tag Name + * @return The {@link ByteBuf} or an Unpooled Buffer + */ + public static ByteBuf toByteBuf(@Nullable BaseData data, String rootTagName) + { + ByteBuf byteBuf = UnpooledByteBufAllocator.DEFAULT.buffer(); + return toByteBuf(byteBuf, data, rootTagName); + } + + /** + * Write Data Tags to an existing {@link ByteBuf} + * + * @param byteBuf The input {@link ByteBuf} to add data to. Buffer must be allocated. + * @param data The Data Tags to write + * @param rootTagName The Root Tag Name + * @return The {@link ByteBuf} or an Unpooled Buffer + */ + public static ByteBuf toByteBuf(@Nonnull ByteBuf byteBuf, @Nullable BaseData data, String rootTagName) + { + if (data == null || data.isEmpty()) { data = EmptyData.INSTANCE; } + + try (ByteBufOutputStream os = new ByteBufOutputStream(byteBuf)) + { + DataFileUtils.writeToNbtStream(os, data, rootTagName); + } + catch (Exception e) + { + MaLiLib.LOGGER.error("Exception while writing data to a ByteBuf; {}", e.getLocalizedMessage()); + } + + // Return remaining Buffer + return byteBuf; + } +} diff --git a/src/main/java/malilib/util/data/tag/util/DataFileUtils.java b/src/main/java/malilib/util/data/tag/util/DataFileUtils.java index 063102c0f5..463ee59f40 100644 --- a/src/main/java/malilib/util/data/tag/util/DataFileUtils.java +++ b/src/main/java/malilib/util/data/tag/util/DataFileUtils.java @@ -87,7 +87,7 @@ public static BaseData readFromNbtStream(DataInput input) if (tagType == Constants.NBT.TAG_END) { - return null; + return null; // EmptyData.INSTANCE } // Discard the name of the root tag diff --git a/src/main/java/malilib/util/data/tag/util/DataOps.java b/src/main/java/malilib/util/data/tag/util/DataOps.java new file mode 100644 index 0000000000..6ae36cddd8 --- /dev/null +++ b/src/main/java/malilib/util/data/tag/util/DataOps.java @@ -0,0 +1,697 @@ +package malilib.util.data.tag.util; + +/** + * DFU DynamicOps<> Adapter for use with {@link malilib.util.data.tag.BaseData}; which can then + * provide direct Vanilla-compatible Serialization via the Codec<> Interface. + */ +public class DataOps // implements DynamicOps + // todo 1.16.5+ (Or whenever the com.mojang.datafixerupper.serialization package fully exists) +{ + public static final DataOps INSTANCE = new DataOps(); + + private DataOps() {} + +// @Override +// public BaseData empty() +// { +// return EmptyData.INSTANCE; +// } +// +// @Override +// public BaseData emptyList() +// { +// return new ListData(); +// } +// +// @Override +// public BaseData emptyMap() +// { +// return new CompoundData(); +// } +// +// @Override +// public String toString() +// { +// return "DATA"; +// } +// +// @Override +// public U convertTo(DynamicOps ops, BaseData data) +// { +// return switch (data.getType()) +// { +// case Constants.NBT.TAG_BYTE -> ops.createByte(((ByteData) data).value); +// case Constants.NBT.TAG_SHORT -> ops.createShort(((ShortData) data).value); +// case Constants.NBT.TAG_INT -> ops.createInt(((IntData) data).value); +// case Constants.NBT.TAG_LONG -> ops.createLong(((LongData) data).value); +// case Constants.NBT.TAG_FLOAT -> ops.createFloat(((FloatData) data).value); +// case Constants.NBT.TAG_DOUBLE -> ops.createDouble(((DoubleData) data).value); +// case Constants.NBT.TAG_STRING -> ops.createString(((StringData) data).value); +// case Constants.NBT.TAG_BYTE_ARRAY -> ops.createByteList(ByteBuffer.wrap(((ByteArrayData) data).value)); +// case Constants.NBT.TAG_INT_ARRAY -> ops.createIntList(Arrays.stream(((IntArrayData) data).value)); +// case Constants.NBT.TAG_LONG_ARRAY -> ops.createLongList(Arrays.stream(((LongArrayData) data).value)); +// case Constants.NBT.TAG_COMPOUND -> this.convertMap(ops, data); +// case Constants.NBT.TAG_LIST -> this.convertList(ops, data); +// case Constants.NBT.TAG_END -> ops.empty(); +// default -> throw new RuntimeException("(DataOps) Invalid data type: " + data.getType()); +// }; +// } +// +// @Override +// public BaseData createNumeric(Number number) +// { +// return new DoubleData(number.doubleValue()); +// } +// +// @Override +// public DataResult getNumberValue(BaseData data) +// { +// return data.asNumber().map(DataResult::success).orElseGet(() -> DataResult.error(() -> "(DataOps) Not a number: "+data.toString())); +// } +// +// @Override +// public Number getNumberValue(BaseData data, Number defaultValue) +// { +// DataResult result = this.getNumberValue(data); +// +// if (result.hasResultOrPartial()) +// { +// return result.getPartialOrThrow(); +// } +// +// return defaultValue; +// } +// +// @Override +// public BaseData createByte(byte value) +// { +// return new ByteData(value); +// } +// +// @Override +// public BaseData createShort(short value) +// { +// return new ShortData(value); +// } +// +// @Override +// public BaseData createInt(int value) +// { +// return new IntData(value); +// } +// +// @Override +// public BaseData createLong(long value) +// { +// return new LongData(value); +// } +// +// @Override +// public BaseData createFloat(float value) +// { +// return new FloatData(value); +// } +// +// @Override +// public BaseData createDouble(double value) +// { +// return new DoubleData(value); +// } +// +// @Override +// public BaseData createBoolean(boolean value) +// { +// return new ByteData((byte) (value ? 1 : 0)); +// } +// +// @Override +// public DataResult getBooleanValue(BaseData data) +// { +// if (data.getType() == Constants.NBT.TAG_BYTE) +// { +// byte value = ((ByteData) data).value; +// +// if (value == 0 || value == 1) +// { +// return DataResult.success(value == 1); +// } +// } +// +// return DataResult.error(() -> "(DataOps) Not a boolean: "+data.toString()); +// } +// +// @Override +// public BaseData createString(String s) +// { +// return new StringData(s); +// } +// +// @Override +// public DataResult getStringValue(BaseData data) +// { +// if (data.getType() == Constants.NBT.TAG_STRING) +// { +// return DataResult.success(((StringData) data).value); +// } +// +// return DataResult.error(() -> "(DataOps) Not a string: "+data.toString()); +// } +// +// @Override +// public DataResult mergeToList(BaseData data, BaseData entry) +// { +// return createArrayFactory(data) +// .map(factory -> DataResult.success(factory.accept(entry).result())) +// .orElseGet(() -> DataResult.error(() -> "(DataOps) Not a list: "+data.toString())); +// } +// +// @Override +// public DataResult mergeToList(BaseData data, List entry) +// { +// return createArrayFactory(data) +// .map(factory -> DataResult.success(factory.acceptAll(entry).result())) +// .orElseGet(() -> DataResult.error(() -> "(DataOps) Not a list: "+data.toString())); +// } +// +// @Override +// public DataResult mergeToMap(BaseData data, BaseData key, BaseData entry) +// { +// if (data.getType() != Constants.NBT.TAG_COMPOUND && data.getType() != Constants.NBT.TAG_END) +// { +// return DataResult.error(() -> "(DataOps) Not a Map: "+data.toString()); +// } +// else if (key.getType() == Constants.NBT.TAG_STRING) +// { +// String keyValue = ((StringData) key).value; +// CompoundData compoundData = data instanceof CompoundData comp ? comp.copy() : new CompoundData(); +// +// compoundData.put(keyValue, entry); +// return DataResult.success(compoundData); +// } +// else +// { +// return DataResult.error(() -> "(DataOps) Not a String key: "+key.toString()); +// } +// } +// +// @Override +// public DataResult mergeToMap(BaseData data, MapLike mapLike) +// { +// if (data.getType() != Constants.NBT.TAG_COMPOUND && data.getType() != Constants.NBT.TAG_END) +// { +// return DataResult.error(() -> "(DataOps) Not a Map: "+data.toString()); +// } +// else +// { +// Iterator> iter = mapLike.entries().iterator(); +// +// if (!iter.hasNext()) +// { +// return data == this.empty() ? DataResult.success(this.emptyMap()) : DataResult.success(data); +// } +// else +// { +// CompoundData compoundData = data instanceof CompoundData comp ? comp.copy() : new CompoundData(); +// List list = new ArrayList<>(); +// +// iter.forEachRemaining(pair -> +// { +// BaseData entry = pair.getFirst(); +// +// if (entry.getType() == Constants.NBT.TAG_STRING) +// { +// compoundData.put(((StringData) pair.getFirst()).value, pair.getSecond()); +// } +// else +// { +// list.add(entry); +// } +// }); +// +// return !list.isEmpty() ? DataResult.error(() -> "(DataOps) Some keys are not strings: "+list.toString(), compoundData) : DataResult.success(compoundData); +// } +// } +// } +// +// @Override +// public DataResult mergeToMap(BaseData data, Map map) +// { +// if (data.getType() != Constants.NBT.TAG_COMPOUND && data.getType() != Constants.NBT.TAG_END) +// { +// return DataResult.error(() -> "(DataOps) Not a Map: "+data.toString()); +// } +// else if (map.isEmpty()) +// { +// return data == this.empty() ? DataResult.success(this.emptyMap()) : DataResult.success(data); +// } +// else +// { +// CompoundData compoundData = data instanceof CompoundData comp ? comp.copy() : new CompoundData(); +// List list = new ArrayList<>(); +// +// for (Map.Entry entry : map.entrySet()) +// { +// BaseData key = entry.getKey(); +// +// if (key.getType() == Constants.NBT.TAG_STRING) +// { +// compoundData.put(((StringData) key).value, entry.getValue()); +// } +// else +// { +// list.add(key); +// } +// } +// +// return !list.isEmpty() ? DataResult.error(() -> "(DataOps) Some keys are not strings: "+list.toString(), compoundData) : DataResult.success(compoundData); +// } +// } +// +// @Override +// public DataResult>> getMapValues(BaseData data) +// { +// if (data.getType() == Constants.NBT.TAG_COMPOUND) +// { +// return DataResult.success( +// ((CompoundData) data).entrySet().stream() +// .map(ent -> +// Pair.of(this.createString(ent.getKey()), ent.getValue())) +// ); +// } +// +// return DataResult.error(() -> "(DataOps) Not a Map: "+ data.toString()); +// } +// +// @Override +// public DataResult>> getMapEntries(BaseData data) +// { +// if (data.getType() == Constants.NBT.TAG_COMPOUND) +// { +// return DataResult.success( +// biCons -> +// { +// for (Map.Entry entry : ((CompoundData) data).entrySet()) +// { +// biCons.accept(this.createString(entry.getKey()), entry.getValue()); +// } +// } +// ); +// } +// +// return DataResult.error(() -> "(DataOps) Not a Map: "+data.toString()); +// } +// +// @Override +// public DataResult> getMap(BaseData data) +// { +// if (data.getType() == Constants.NBT.TAG_COMPOUND) +// { +// CompoundData comp = (CompoundData) data; +// +// return DataResult.success(new MapLike<>() +// { +// @Override +// public @Nullable BaseData get(BaseData key) +// { +// if (key.getType() == Constants.NBT.TAG_STRING) +// { +// return comp.getData(((StringData) key).value).orElse(null); +// } +// else +// { +// throw new RuntimeException("(DataOps) Key is not a string: " + key.toString()); +// } +// } +// +// @Override +// public @Nullable BaseData get(String key) +// { +// return comp.getData(key).orElse(null); +// } +// +// @Override +// public Stream> entries() +// { +// return comp.entrySet().stream().map( +// entry -> Pair.of(DataOps.this.createString(entry.getKey()), entry.getValue())); +// } +// }); +// } +// else +// { +// return DataResult.error(() -> "(DataOps) Not a Map: "+data.toString()); +// } +// } +// +// @Override +// public BaseData createMap(Stream> stream) +// { +// CompoundData data = new CompoundData(); +// +// stream.forEach(pair -> +// { +// BaseData key = pair.getFirst(); +// BaseData value = pair.getSecond(); +// +// if (key.getType() == Constants.NBT.TAG_STRING) +// { +// data.put(((StringData) key).value, value); +// } +// else +// { +// throw new RuntimeException("(DataOps) Key is not a string: "+key.toString()); +// } +// }); +// +// return data; +// } +// +// @Override +// public DataResult> getStream(BaseData data) +// { +// if (data instanceof ArrayData array) +// { +// return DataResult.success(array.stream()); +// } +// +// return DataResult.error(() -> "(DataOps) Not an Array: "+data.toString()); +// } +// +// @Override +// public DataResult>> getList(BaseData data) +// { +// if (data instanceof ArrayData array) +// { +// return DataResult.success(array::forEach); +// } +// +// return DataResult.error(() -> "(DataOps) Not a List: "+data.toString()); +// } +// +// @Override +// public BaseData createList(Stream stream) +// { +// return new ListData((ArrayList) stream.collect(Util.toMutableList())); +// } +// +// @Override +// public DataResult getByteBuffer(BaseData data) +// { +// if (data.getType() == Constants.NBT.TAG_BYTE_ARRAY) +// { +// return DataResult.success(ByteBuffer.wrap(((ByteArrayData) data).value)); +// } +// +// return DynamicOps.super.getByteBuffer(data); +// } +// +// @Override +// public BaseData createByteList(ByteBuffer buf) +// { +// ByteBuffer buffer = buf.duplicate().clear(); +// byte[] bs = new byte[buf.capacity()]; +// +// buffer.get(0, bs, 0, bs.length); +// +// return new ByteArrayData(bs); +// } +// +// @Override +// public DataResult getIntStream(BaseData data) +// { +// if (data.getType() == Constants.NBT.TAG_INT_ARRAY) +// { +// return DataResult.success(Arrays.stream(((IntArrayData) data).value)); +// } +// +// return DynamicOps.super.getIntStream(data); +// } +// +// @Override +// public BaseData createIntList(IntStream stream) +// { +// return new IntArrayData(stream.toArray()); +// } +// +// @Override +// public DataResult getLongStream(BaseData data) +// { +// if (data.getType() == Constants.NBT.TAG_LONG_ARRAY) +// { +// return DataResult.success(Arrays.stream(((LongArrayData) data).value)); +// } +// +// return DynamicOps.super.getLongStream(data); +// } +// +// @Override +// public BaseData createLongList(LongStream stream) +// { +// return new LongArrayData(stream.toArray()); +// } +// +// @Override +// public RecordBuilder mapBuilder() +// { +// return new Builder(); +// } +// +// @Override +// @Nullable +// public BaseData remove(BaseData data, String key) +// { +// if (data.getType() == Constants.NBT.TAG_COMPOUND) +// { +// return ((CompoundData) data).getData(key).orElse(null); +// } +// +// return null; +// } +// +// private static Optional createArrayFactory(BaseData data) +// { +// if (data.getType() == Constants.NBT.TAG_END) +// { +// return Optional.of(new GenericArrayFactory()); +// } +// else if (data.getType() == Constants.NBT.TAG_LIST) +// { +// return Optional.of(new GenericArrayFactory(((ListData) data))); +// } +// else if (data.getType() == Constants.NBT.TAG_BYTE_ARRAY) +// { +// return Optional.of(new ByteArrayFactory(((ByteArrayData) data).value)); +// } +// else if (data.getType() == Constants.NBT.TAG_INT_ARRAY) +// { +// return Optional.of(new IntArrayFactory(((IntArrayData) data).value)); +// } +// else if (data.getType() == Constants.NBT.TAG_LONG_ARRAY) +// { +// return Optional.of(new LongArrayFactory(((LongArrayData) data).value)); +// } +// else +// { +// return Optional.empty(); +// } +// } +// +// private interface ArrayFactory +// { +// ArrayFactory accept(BaseData data); +// +// default ArrayFactory acceptAll(Iterable iterable) +// { +// ArrayFactory listFactory = this; +// +// for (BaseData data : iterable) +// { +// listFactory = listFactory.accept(data); +// } +// +// return listFactory; +// } +// +// default ArrayFactory acceptAll(Stream stream) +// { +// return this.acceptAll(stream::iterator); +// } +// +// BaseData result(); +// } +// +// private static class GenericArrayFactory implements ArrayFactory +// { +// private final ListData listData = new ListData(); +// +// public GenericArrayFactory() {} +// +// public GenericArrayFactory(ListData listData) +// { +// this.listData.add(listData); +// } +// +// public GenericArrayFactory(IntArrayList list) +// { +// list.forEach(i -> this.listData.add(new IntData(i))); +// } +// +// public GenericArrayFactory(ByteArrayList list) +// { +// list.forEach(b -> this.listData.add(new ByteData(b))); +// } +// +// public GenericArrayFactory(LongArrayList list) +// { +// list.forEach(l -> this.listData.add(new LongData(l))); +// } +// +// @Override +// public ArrayFactory accept(BaseData data) +// { +// this.listData.add(data); +// return this; +// } +// +// @Override +// public BaseData result() +// { +// return this.listData; +// } +// } +// +// private static class ByteArrayFactory implements ArrayFactory +// { +// private final ByteArrayList values = new ByteArrayList(); +// +// public ByteArrayFactory(byte[] bs) +// { +// this.values.addElements(0, bs); +// } +// +// @Override +// public ArrayFactory accept(BaseData data) +// { +// if (data.getType() == Constants.NBT.TAG_BYTE) +// { +// this.values.add(((ByteData) data).value); +// return this; +// } +// else +// { +// return new GenericArrayFactory(this.values).accept(data); +// } +// } +// +// @Override +// public BaseData result() +// { +// return new ByteArrayData(this.values.toByteArray()); +// } +// } +// +// private static class IntArrayFactory implements ArrayFactory +// { +// private final IntArrayList values = new IntArrayList(); +// +// public IntArrayFactory(int[] is) +// { +// this.values.addElements(0, is); +// } +// +// @Override +// public ArrayFactory accept(BaseData data) +// { +// if (data.getType() == Constants.NBT.TAG_INT) +// { +// this.values.add(((IntData) data).value); +// return this; +// } +// else +// { +// return new GenericArrayFactory(this.values).accept(data); +// } +// } +// +// @Override +// public BaseData result() +// { +// return new IntArrayData(this.values.toIntArray()); +// } +// } +// +// private static class LongArrayFactory implements ArrayFactory +// { +// private final LongArrayList values = new LongArrayList(); +// +// public LongArrayFactory(long[] ls) +// { +// this.values.addElements(0, ls); +// } +// +// @Override +// public ArrayFactory accept(BaseData data) +// { +// if (data.getType() == Constants.NBT.TAG_LONG) +// { +// this.values.add(((LongData) data).value); +// return this; +// } +// else +// { +// return new GenericArrayFactory(this.values).accept(data); +// } +// } +// +// @Override +// public BaseData result() +// { +// return new LongArrayData(this.values.toLongArray()); +// } +// } +// +// private class Builder extends RecordBuilder.AbstractStringBuilder +// { +// protected Builder() +// { +// super(DataOps.this); +// } +// +// @Override +// protected CompoundData initBuilder() +// { +// return new CompoundData(); +// } +// +// @Override +// protected CompoundData append(String key, BaseData value, CompoundData data) +// { +// data.put(key, value); +// return data; +// } +// +// @Override +// protected DataResult build(CompoundData data, BaseData prefix) +// { +// if (prefix == null || prefix.getType() == Constants.NBT.TAG_END) +// { +// return DataResult.success(data); +// } +// else if (prefix.getType() != Constants.NBT.TAG_COMPOUND) +// { +// return DataResult.error(() -> "(DataOps) Not a map: "+prefix.toString()); +// } +// else +// { +// CompoundData result = ((CompoundData) prefix).copy(); +// +// for (Map.Entry entry : data.entrySet()) +// { +// result.put(entry.getKey(), entry.getValue().copy()); +// } +// +// return DataResult.success(result); +// } +// } +// } +} diff --git a/src/main/java/malilib/util/data/tag/util/DataTypeUtils.java b/src/main/java/malilib/util/data/tag/util/DataTypeUtils.java index 2941577686..da030db0e0 100644 --- a/src/main/java/malilib/util/data/tag/util/DataTypeUtils.java +++ b/src/main/java/malilib/util/data/tag/util/DataTypeUtils.java @@ -1,10 +1,18 @@ package malilib.util.data.tag.util; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.Collection; +import java.util.Optional; import java.util.UUID; import java.util.function.Function; +import javax.annotation.Nonnull; import javax.annotation.Nullable; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.text.ITextComponent; + import malilib.util.data.Constants; import malilib.util.data.tag.BaseData; import malilib.util.data.tag.CompoundData; @@ -12,6 +20,8 @@ import malilib.util.data.tag.DoubleData; import malilib.util.data.tag.IntData; import malilib.util.data.tag.ListData; +import malilib.util.data.tag.converter.DataConverterNbt; +import malilib.util.nbt.NbtKeys; import malilib.util.position.BlockPos; import malilib.util.position.Vec3d; import malilib.util.position.Vec3i; @@ -46,6 +56,48 @@ public static void writeUuidToLongs(CompoundData tag, UUID uuid, String keyM, St tag.putLong(keyL, uuid.getLeastSignificantBits()); } + @Nullable + public static UUID readUuidFromIntArray(DataView tag) + { + return readUuidFromIntArray(tag, NbtKeys.UUID); + } + + @Nullable + public static UUID readUuidFromIntArray(DataView tag, String key) + { + if (tag.contains(key, Constants.NBT.TAG_INT_ARRAY)) + { + final int[] bits = tag.getIntArray(key); + return new UUID((long) bits[0] << 32 | bits[1] & 4294967295L, (long) bits[2] << 32 | bits[3] & 4294967295L); + } + + return null; + } + + public static void writeUuidToIntArray(CompoundData tag, UUID uuid) + { + writeUuidToIntArray(tag, uuid, NbtKeys.UUID); + } + + public static void writeUuidToIntArray(CompoundData tag, UUID uuid, String key) + { + final long most = uuid.getMostSignificantBits(); + final long least = uuid.getLeastSignificantBits(); + tag.putIntArray(key, new int[]{(int) (most >> 32), (int) most, (int) (least >> 32), (int) least}); + } + + public static void writeUuidToByteArray(CompoundData tag, UUID uuid) + { + writeUuidToByteArray(tag, uuid, NbtKeys.UUID); + } + + public static void writeUuidToByteArray(CompoundData tag, UUID uuid, String key) + { + byte[] bits = new byte[16]; + ByteBuffer.wrap(bits).order(ByteOrder.BIG_ENDIAN).putLong(uuid.getMostSignificantBits()).putLong(uuid.getLeastSignificantBits()); + tag.putByteArray(key, bits); + } + public static CompoundData getOrCreateCompound(CompoundData tagIn, String tagName) { CompoundData tag; @@ -270,4 +322,175 @@ public static Vec3d readVec3dFromListTag(DataView data, String tagName) return null; } + + /** + * Read the "Block Attached" BlockPos from Data Tags. + * + * @param tag - + * @return - + */ + @Nullable + public static BlockPos readAttachedPosFromTag(@Nonnull DataView tag) + { + return readPrefixedPosFromTag(tag, "Tile"); + } + + /** + * Write the "Block Attached" BlockPos to Data Tags. + * + * @param pos - + * @param tag - + * @return - + */ + public static @Nonnull CompoundData writeAttachedPosToTag(@Nonnull BlockPos pos, @Nonnull CompoundData tag) + { + return writePrefixedPosToTag(pos, tag, "Tile"); + } + + /** + * Read a prefixed BlockPos from Data Tags. + * + * @param tag - + * @param pre - + * @return - + */ + @Nullable + public static BlockPos readPrefixedPosFromTag(@Nonnull DataView tag, String pre) + { + if (tag.contains(pre+"X", Constants.NBT.TAG_INT) && + tag.contains(pre+"Y", Constants.NBT.TAG_INT) && + tag.contains(pre+"Z", Constants.NBT.TAG_INT)) + { + return new BlockPos(tag.getInt(pre+"X"), tag.getInt(pre+"Y"), tag.getInt(pre+"Z")); + } + + return null; + } + + /** + * Write a prefixed BlockPos to Data Tags. + * + * @param pos - + * @param tag - + * @param pre - + * @return - + */ + public static @Nonnull CompoundData writePrefixedPosToTag(@Nonnull BlockPos pos, @Nonnull CompoundData tag, String pre) + { + tag.putInt(pre+"X", pos.getX()); + tag.putInt(pre+"Y", pos.getY()); + tag.putInt(pre+"Z", pos.getZ()); + + return tag; + } + + /** + * Deserialize an ItemStack from a Data tag + * @param data - + * @return - + * @implNote In the future, after ~1.21; the Registry / Data Ops is required here + */ + public static Optional toItemStack(@Nonnull CompoundData data) + // @Nonnull RegistryAccess registry + { + if (data.contains(NbtKeys.ID, Constants.NBT.TAG_STRING)) + { + return Optional.of(new ItemStack(DataConverterNbt.toVanillaCompound(data))); + } + + return Optional.empty(); + } + + /** + * Serialize an ItemStack to a Data tag + * @param stack - + * @return - + * @implNote In the future, after ~1.21; the Registry / Data Ops is required here + */ + public static CompoundData fromItemStack(@Nonnull ItemStack stack) + // @Nonnull RegistryAccess registry + { + CompoundData data = new CompoundData(); + + if (!stack.isEmpty()) + { + data.combine(DataConverterNbt.fromVanillaCompound(stack.writeToNBT(new NBTTagCompound()))); + + if (data.contains(NbtKeys.ID, Constants.NBT.TAG_STRING)) + { + return data; + } + } + + return new CompoundData(); + } + + /** + * Deserialize a Data Tag that contains a {@link ITextComponent} as a JSON String, utilizing a key. + * @param data - + * @param key - + * @return - + * @implNote In the future, after ~1.21; the Registry / Data Ops is required here + */ + public static Optional toTextComponent(@Nonnull CompoundData data, String key) + // @Nonnull RegistryAccess registry + { + final String json = data.getStringOrDefault(key, ""); + if (json.isEmpty()) { return Optional.empty(); } + + try + { + return Optional.ofNullable(ITextComponent.Serializer.jsonToComponent(json)); + } + catch (Exception ignored) {} + + return Optional.empty(); + } + + /** + * Deserialize a Data Tag that contains a {@link ITextComponent} as a JSON String. + * @param jsonStr - + * @return - + * @implNote In the future, after ~1.21; the Registry / Data Ops is required here + */ + public static Optional toTextComponent(@Nonnull final String jsonStr) + // @Nonnull RegistryAccess registry + { + if (jsonStr.isEmpty()) { return Optional.empty(); } + + try + { + return Optional.ofNullable(ITextComponent.Serializer.jsonToComponent(jsonStr)); + } + catch (Exception ignored) {} + + return Optional.empty(); + } + + /** + * Serialize a {@link ITextComponent} into a Data Tag with a key param, and optionally, a source Data Tag + * @param text - + * @param key - + * @param dataIn - + * @return - + * @implNote In the future, after ~1.21; the Registry / Data Ops is required here + */ + public static CompoundData fromTextComponent(@Nonnull ITextComponent text, String key, @Nullable CompoundData dataIn) + // @Nonnull RegistryAccess registry + { + CompoundData data = dataIn != null ? dataIn.copy() : new CompoundData(); + + try + { + final String json = ITextComponent.Serializer.componentToJson(text); + + if (!json.isEmpty()) + { + data.putString(key, json); + } + } + catch (Exception ignored) {} + + return data; + } } diff --git a/src/main/java/malilib/util/game/DataEntityUtils.java b/src/main/java/malilib/util/game/DataEntityUtils.java new file mode 100644 index 0000000000..ec38135e2e --- /dev/null +++ b/src/main/java/malilib/util/game/DataEntityUtils.java @@ -0,0 +1,2009 @@ +package malilib.util.game; + +import java.util.Optional; +import java.util.UUID; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import malilib.util.MathUtils; +import malilib.util.StringUtils; +import malilib.util.data.Constants; +import malilib.util.data.DyeColorCode; +import malilib.util.data.Identifier; +import malilib.util.data.tag.CompoundData; +import malilib.util.data.tag.ListData; +import malilib.util.data.tag.converter.DataConverterNbt; +import malilib.util.data.tag.util.DataTypeUtils; +import malilib.util.game.wrap.DefaultedList; +import malilib.util.nbt.NbtKeys; +import malilib.util.position.BlockPos; +import malilib.util.position.Direction; +import org.apache.commons.lang3.tuple.Pair; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLeashKnot; +import net.minecraft.entity.EntityList; +import net.minecraft.entity.SharedMonsterAttributes; +import net.minecraft.entity.ai.attributes.AttributeMap; +import net.minecraft.entity.ai.attributes.AttributeModifier; +import net.minecraft.entity.ai.attributes.IAttribute; +import net.minecraft.entity.ai.attributes.IAttributeInstance; +import net.minecraft.entity.passive.EntityRabbit; +import net.minecraft.inventory.EntityEquipmentSlot; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.potion.Potion; +import net.minecraft.potion.PotionEffect; +import net.minecraft.util.FoodStats; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.util.text.event.HoverEvent; +import net.minecraft.village.MerchantRecipe; +import net.minecraft.village.MerchantRecipeList; + +/** + * The purpose of this Library is to fully utilize {@link malilib.util.data.tag.DataView} Tags to parse NBT tags for use in downstream mods; + * such as {@link Entity} Variants for MiniHUD's Info Lines; or other specific {@link malilib.util.data.tag.DataView} needs. + * It should not fail to mimic the various 'readNbt()' / 'writeNbt()' type functions under Vanilla's {@link Entity} system, + * without needing to instance a new {@link Entity} object; which can potentially break other people's mods if used too often. + * These are more important in later versions of Minecraft; especially when it can be hard to remember every NBT tag name; + * or CODEC method; to say; export a Mob's {@link AttributeMap} from Raw Tags, and then find a specific value; for example. + * Utilizing {@link NbtKeys} is also very helpful to track changes across versions of Minecraft. + */ +public class DataEntityUtils +{ + /** + * Get the Entity Type from the Data Tag + * @param data - + * @return - + */ + public static Optional getEntityType(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + if (data.contains(NbtKeys.ID, Constants.NBT.TAG_STRING)) + { + Identifier id = Identifier.of(data.getString(NbtKeys.ID)); + + if (id != null && EntityList.REGISTRY.containsKey(id)) + { + return Optional.of(id); + } + } + + return Optional.empty(); + } + + /** + * Get the Entity Name from the Data Tag, in it's translated form. + * @param data - + * @return - + */ + public static Optional getEntityName(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + Identifier id = getEntityType(data).orElse(null); + + if (id != null) + { + String translationKey = EntityList.getTranslationName(id); + + if (translationKey != null) + { + return Optional.ofNullable(StringUtils.translate(translationKey)); + } + else + { + return Optional.ofNullable(StringUtils.translate("entity.generic.name")); + } + } + + return Optional.empty(); + } + + /** + * Write an Entity Type to Data Tag + * + * @param entity - + * @param dataIn - + * @return - + */ + public CompoundData setEntityType(Class entity, @Nullable CompoundData dataIn) // @Nonnull RegistryAccess registry + { + CompoundData data = new CompoundData(); + ResourceLocation rl = EntityList.getKey(entity); + + if (rl != null) + { + if (dataIn != null) + { + dataIn.putString(NbtKeys.ID, rl.toString()); + return dataIn; + } + else + { + data.putString(NbtKeys.ID, rl.toString()); + } + } + + return data; + } + + /** + * Get an Entity UUID from Data Tags + * @param data - + * @return - + */ + public static Optional getUUID(@Nonnull CompoundData data) + { + if (data.contains(NbtKeys.UUID_MOST, Constants.NBT.TAG_ANY_NUMERIC) && + data.contains(NbtKeys.UUID_LEAST, Constants.NBT.TAG_ANY_NUMERIC)) + { + return Optional.ofNullable(DataTypeUtils.readUuidFromLongs(data, NbtKeys.UUID_MOST, NbtKeys.UUID_LEAST)); + } + else if (data.contains(NbtKeys.UUID, Constants.NBT.TAG_STRING)) + { + return Optional.of(UUID.fromString(data.getString(NbtKeys.UUID))); + } + else if (data.contains(NbtKeys.UUID, Constants.NBT.TAG_INT_ARRAY)) + { + return Optional.ofNullable(DataTypeUtils.readUuidFromIntArray(data, NbtKeys.UUID)); + } + + return Optional.empty(); + } + + /** + * Read the CustomName from Data Tag, as a String + * @implNote The Text Serialization changes in future versions, such as 1.20.2 and ~1.21.5 + * @param data - + * @return - + */ + public static Optional getCustomNameStr(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + if (data.contains(NbtKeys.CUSTOM_NAME, Constants.NBT.TAG_COMPOUND)) + { + return Optional.ofNullable(data.getString(NbtKeys.CUSTOM_NAME)); + } + + return Optional.empty(); + } + + /** + * Get either the Custom Name or the Entity Name from Data Tag + * @implNote The Text Serialization changes in future versions, such as 1.20.2 and ~1.21.5 + * @param data - + * @return - + */ + public static Optional getCustomOrEntityName(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + final String customName = getCustomNameStr(data).orElse(null); + final Optional name = getEntityName(data); + + if (customName != null && !customName.isEmpty()) + { + return Optional.of(customName); + } + + return name; + } + + /** + * Read the CustomName from Data Tag, as a Text ITextComponent. + * This function does not read the Team Color, if present. + * @implNote The Text Serialization changes in future versions, such as 1.20.2 and ~1.21.5 + * @param data - + * @return - + */ + public static Optional getCustomNameText(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + String name = getCustomNameStr(data).orElse(null); + if (name == null) { return Optional.empty(); } + + UUID uuid = getUUID(data).orElse(null); + ITextComponent text = new TextComponentString(name); // Omits the Scoreboard / Team Color + + getNameHoverEvent(data, uuid).ifPresent( + hover -> text.getStyle().setHoverEvent(hover)); + + if (uuid != null) + { + text.getStyle().setInsertion(uuid.toString()); + } + + return Optional.of(text); + } + + /** + * Get an Entity Hover Event from Data Tag + * @param data - + * @return - + */ + public static Optional getNameHoverEvent(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + return getNameHoverEvent(data, null); + } + + /** + * Get an Entity Hover Event from Data Tag + * @implNote The Text Serialization changes in future versions, such as 1.20.2 and ~1.21.5 + * @param data - + * @param uuid - + * @return - + */ + public static Optional getNameHoverEvent(@Nonnull CompoundData data, @Nullable UUID uuid) // @Nonnull RegistryAccess registry + { + Identifier id = getEntityType(data).orElse(null); + + if (id != null) + { + if (uuid == null) + { + uuid = getUUID(data).orElse(null); + } + + if (uuid != null) + { + NBTTagCompound nbt = new NBTTagCompound(); + final String uuidStr = uuid.toString(); + final String name = getCustomOrEntityName(data).orElse("unknown"); + + nbt.setString("id", uuidStr); + nbt.setString("type", id.toString()); + nbt.setString("name", name); + + return Optional.of(new HoverEvent(HoverEvent.Action.SHOW_ENTITY, new TextComponentString(nbt.toString()))); + } + } + + return Optional.empty(); + } + + /** + * Get the AttributeMap with modifiers from Data Tag + * + * @param data - + * @return - + */ + public static Optional getAttributeMap(@Nonnull CompoundData data) + { + Identifier id = getEntityType(data).orElse(null); + + if (id != null && data.containsList(NbtKeys.ATTRIBUTES, Constants.NBT.TAG_COMPOUND)) + { + AttributeMap container = new AttributeMap(); + ListData list = data.getList(NbtKeys.ATTRIBUTES, Constants.NBT.TAG_COMPOUND); + + for (int i = 0; i < list.size(); i++) + { + CompoundData entry = list.getCompoundAt(i); + + if (entry != null && entry.contains(NbtKeys.ATTR_MOD_NAME, Constants.NBT.TAG_STRING)) + { + final String name = entry.getStringOrDefault(NbtKeys.ATTR_MOD_NAME, ""); + IAttributeInstance inst = container.getAttributeInstanceByName(name); + if (inst == null) { continue; } + + inst.setBaseValue(entry.getDoubleOrDefault(NbtKeys.ATTRIB_BASE, 0.0D)); + getAttributeModifiersForInstance(inst, entry); + } + } + + return Optional.of(container); + } + + return Optional.empty(); + } + + /** + * Read the Attribute Modifiers into the Attribute Instance from Data Tag + * @param inst - + * @param data - + */ + public static void getAttributeModifiersForInstance(@Nonnull IAttributeInstance inst, @Nonnull CompoundData data) + { + if (data.containsList(NbtKeys.ATTRIB_MODIFIERS, Constants.NBT.TAG_COMPOUND)) + { + ListData modifiers = data.getList(NbtKeys.ATTRIB_MODIFIERS, Constants.NBT.TAG_COMPOUND); + + for (int j = 0; j < modifiers.size(); j++) + { + CompoundData mod = modifiers.getCompoundAt(j); + if (mod == null || mod.isEmpty()) { continue; } + + UUID uuid = DataTypeUtils.readUuidFromLongs(mod, NbtKeys.UUID_MOST, NbtKeys.UUID_LEAST); + AttributeModifier modifier; + + try + { + modifier = new AttributeModifier(uuid, + mod.getStringOrDefault(NbtKeys.ATTR_MOD_NAME, ""), + mod.getDoubleOrDefault(NbtKeys.ATTR_MOD_AMOUNT, 0.0D), + mod.getIntOrDefault(NbtKeys.ATTR_MOD_OPERATION, 0)); + } + catch (Exception e) { continue; } + + if (inst.getModifier(modifier.getID()) != null) + { + inst.removeModifier(modifier.getID()); + } + + inst.applyModifier(modifier); + } + } + } + + /** + * Get a specific Attribute Instance from Data Tag + * @param data - + * @param attribute - + * @return - + */ + public static Optional getAttributeInstance(@Nonnull CompoundData data, IAttribute attribute) + { + AttributeMap map = getAttributeMap(data).orElse(null); + if (map == null) { return Optional.empty(); } + + return Optional.ofNullable(map.getAttributeInstanceByName(attribute.getName())); + } + + /** Get a specified Attribute Base Value from Data Tag + * + * @param data - + * @param attribute - + * @return - + */ + public static Optional getAttributeBaseValue(@Nonnull CompoundData data, IAttribute attribute) + { + IAttributeInstance inst = getAttributeInstance(data, attribute).orElse(null); + + if (inst != null) + { + return Optional.of(inst.getBaseValue()); + } + + return Optional.empty(); + } + + /** Get a specified Attribute Value from Data Tag + * + * @param data - + * @param attribute - + * @return - + */ + public static Optional getAttributeValue(@Nonnull CompoundData data, IAttribute attribute) + { + IAttributeInstance inst = getAttributeInstance(data, attribute).orElse(null); + + if (inst != null) + { + return Optional.of(inst.getAttributeValue()); + } + + return Optional.empty(); + } + + /** + * Get an entities' Health / Max Health from Data Tag. + * + * @param data - + * @return - + */ + public static Optional> getHealth(@Nonnull CompoundData data) + { + double health = 0.0F; + double maxHealth; + + if (data.contains(NbtKeys.HEALTH, Constants.NBT.TAG_FLOAT)) + { + health = data.getFloatOrDefault(NbtKeys.HEALTH, 0.0F); + } + + maxHealth = SharedMonsterAttributes.MAX_HEALTH.clampValue(getAttributeValue(data, SharedMonsterAttributes.MAX_HEALTH).orElse(20.0D)); + health = MathUtils.clamp(health, 0.0F, maxHealth); + + return Optional.of(Pair.of(health, maxHealth)); + } + + /** + * Get an entities Movement Speed, and Jump Strength attributes from Data Tag. + * + * @param data - + * @return - + */ + public static Optional> getSpeedAndJumpStrength(@Nonnull CompoundData data) + { + AttributeMap map = getAttributeMap(data).orElse(null); + double moveSpeed = 0.0D; + double jumpStrength = 0.0D; + + if (map != null) + { + IAttributeInstance inst = map.getAttributeInstance(SharedMonsterAttributes.MOVEMENT_SPEED); + + if (inst != null) + { + moveSpeed = inst.getAttributeValue(); + } + + // There is probably a better way to get the attribute type, such as using an access widener + inst = map.getAttributeInstanceByName("horse.jumpStrength"); + + if (inst != null) + { + jumpStrength = inst.getAttributeValue(); + } + } + + if (moveSpeed == 0.0D && jumpStrength == 0.0D) { return Optional.empty(); } + + return Optional.of(Pair.of(moveSpeed, jumpStrength)); + } + + /** + * Get Active Effects from Data Tag + * @param data - + * @return - + * @implNote `PotionEffect` get replaced/renamed to 'MobEffect' + */ + public static ImmutableMap getActiveEffects(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + ImmutableMap.Builder builder = ImmutableMap.builder(); + + if (data.containsList(NbtKeys.ACTIVE_EFFECTS, Constants.NBT.TAG_COMPOUND)) + { + ListData list = data.getList(NbtKeys.ACTIVE_EFFECTS, Constants.NBT.TAG_LIST); + + for (int i = 0; i < list.size(); i++) + { + CompoundData entry = list.getCompoundAt(i); + if (entry == null) { continue; } + + getPotionEffect(entry).ifPresent( + effect -> builder.put(effect.getPotion(), effect)); + } + } + + return builder.build(); + } + + /** + * Get a Potion Effect from Data Tag + * @param data - + * @return - + * @implNote `PotionEffect` get replaced/renamed to 'MobEffect' + */ + public static Optional getPotionEffect(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + if (data.contains(NbtKeys.EFFECT_ID, Constants.NBT.TAG_BYTE)) + { + try + { + final byte id = data.getByteOrDefault(NbtKeys.EFFECT_ID, (byte) -1); + Potion pot = Potion.REGISTRY.getObjectById(id); + + if (pot != null) + { + final byte amp = data.getByteOrDefault(NbtKeys.EFFECT_AMPLIFIER, (byte) 0); + final int dur = data.getIntOrDefault(NbtKeys.EFFECT_DURATION, 0); + final boolean amb = data.getBooleanOrDefault(NbtKeys.EFFECT_AMBIENT, false); + boolean particles = data.getBooleanOrDefault(NbtKeys.EFFECT_SHOW_PART, true); + + return Optional.of(new PotionEffect(pot, dur, (amp < 0 ? 0 : amp), amb, particles)); + } + } + catch (Exception ignored) {} + } + + return Optional.empty(); + } + + /** + * Get a ItemStack List of all Equipment Slots + * 0/1 [{MainHand}, {OffHand}] + * 2/3/4/5 [{Head}, {Chest}, {Legs}, {Feet}] + * 6/7 [{BodyArmor}, {Saddle}] + * @implNote Note that slots 6/7 are added under 1.21.5+; so we are emulating the same behavior. + * @param data - + * @return - + */ + public static DefaultedList getEquipmentSlots(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + DefaultedList stacks = DefaultedList.ofSize(8, ItemStack.EMPTY); + + // 1.21.5+ + if (data.contains(NbtKeys.NEW_EQUIPMENT, Constants.NBT.TAG_COMPOUND)) + { + getNewEquipmentSlots(data.getCompound(NbtKeys.NEW_EQUIPMENT), stacks); + } + else + { + getLegacyEquipmentSlots(data, stacks); + } + + return stacks; + } + + /** + * Get Entity Equipment from 1.21.5+ type Equipment slots from New Equipment Data Tag + * @param data - + * @param stacks - + */ + public static void getNewEquipmentSlots(@Nonnull CompoundData data, @Nonnull DefaultedList stacks) + // @Nonnull RegistryAccess registry + { + for (EntityEquipmentSlot slot : EntityEquipmentSlot.values()) + { + if (data.contains(slot.getName(), Constants.NBT.TAG_COMPOUND)) + { + ItemStack stack = DataTypeUtils.toItemStack(data.getCompoundOrDefault(slot.getName(), new CompoundData())).orElse(ItemStack.EMPTY); + + if (!stack.isEmpty()) + { + stacks.set(slot.getSlotIndex(), stack.copy()); + } + } + } + + if (data.contains(NbtKeys.NEW_ANIMAL_ARMOR, Constants.NBT.TAG_COMPOUND)) + { + ItemStack bodyArmor = DataTypeUtils.toItemStack(data.getCompoundOrDefault(NbtKeys.NEW_ANIMAL_ARMOR, new CompoundData())).orElse(ItemStack.EMPTY); + + if (!bodyArmor.isEmpty()) + { + stacks.set(6, bodyArmor.copy()); + } + } + + if (data.contains(NbtKeys.NEW_SADDLE, Constants.NBT.TAG_COMPOUND)) + { + ItemStack saddle = DataTypeUtils.toItemStack(data.getCompoundOrDefault(NbtKeys.NEW_SADDLE, new CompoundData())).orElse(ItemStack.EMPTY); + + if (!saddle.isEmpty()) + { + stacks.set(7, saddle.copy()); + } + } + } + + /** + * Get Entity Equipment from pre-1.21.5 type Equipment slots. + * @param data - + * @param stacks - + */ + public static void getLegacyEquipmentSlots(@Nonnull CompoundData data, @Nonnull DefaultedList stacks) + // @Nonnull RegistryAccess registry + { + stacks.addAll(getLegacyHandSlots(data)); + stacks.addAll(getLegacyArmorSlots(data)); + + ItemStack bodyArmor = getLegacyHorseArmor(data).orElse(ItemStack.EMPTY); + ItemStack saddle = getLegacySaddle(data).orElse(ItemStack.EMPTY); + + if (!bodyArmor.isEmpty()) + { + stacks.set(6, bodyArmor.copy()); + } + if (!saddle.isEmpty()) + { + stacks.set(7, saddle.copy()); + } + } + + /** + * Get Hand Item Slots from Data Tag + * @implNote This is removed under 1.21.5 + * @param data - + * @return - + */ + public static DefaultedList getLegacyHandSlots(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + DefaultedList stacks = DefaultedList.ofSize(8, ItemStack.EMPTY); + + if (data.containsList(NbtKeys.HAND_ITEMS, Constants.NBT.TAG_COMPOUND)) + { + ListData list = data.getList(NbtKeys.HAND_ITEMS, Constants.NBT.TAG_LIST); + + for (int i = 0; i < list.size(); i++) + { + CompoundData entry = list.getCompoundAt(i); + if (entry == null) { continue; } + if (i > 8) break; + + processEachSlot(stacks, EntityEquipmentSlot.Type.HAND, entry, i); + } + } + + return stacks; + } + + /** + * Get Armor Item Slots from Data Tag + * @implNote This is removed under 1.21.5 + * @param data - + * @return - + */ + public static DefaultedList getLegacyArmorSlots(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + DefaultedList stacks = DefaultedList.ofSize(8, ItemStack.EMPTY); + + if (data.containsList(NbtKeys.ARMOR_ITEMS, Constants.NBT.TAG_COMPOUND)) + { + ListData list = data.getList(NbtKeys.ARMOR_ITEMS, Constants.NBT.TAG_LIST); + + for (int i = 0; i < list.size(); i++) + { + CompoundData entry = list.getCompoundAt(i); + if (entry == null) { continue; } + if (i > 8) break; + + processEachSlot(stacks, EntityEquipmentSlot.Type.ARMOR, entry, i); + } + } + + return stacks; + } + + /** + * Get Body Armor slot from Data Tag + * @implNote This is an actual Equipment Slot under 1.21.5+ + * @param data - + * @return - + */ + public static Optional getLegacyHorseArmor(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + if (data.contains(NbtKeys.HORSE_ARMOR, Constants.NBT.TAG_COMPOUND)) + { + return DataTypeUtils.toItemStack(data.getCompoundOrDefault(NbtKeys.HORSE_ARMOR, new CompoundData())); + } + + return Optional.empty(); + } + + /** + * Get A mount's Saddle from Data Tag + * @implNote The Saddle is an actual Equipment Slot under 1.21.5+ + * @param data - + * @return - + */ + public static Optional getLegacySaddle(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + if (data.contains(NbtKeys.SADDLE, Constants.NBT.TAG_COMPOUND)) + { + return DataTypeUtils.toItemStack(data.getCompound(NbtKeys.HORSE_ARMOR)); + } + + return Optional.empty(); + } + + /** + * Get a ItemStack List of all Equipped Horse/Wolf/Llama/Camel/Etc Slots + * 0/1 [{BodyArmor}, {Saddle}] + * + * @param data () + * @return () + */ + public static DefaultedList getHorseEquipment(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + DefaultedList list = DefaultedList.ofSize(2, ItemStack.EMPTY); + + ItemStack bodyArmor = getLegacyHorseArmor(data).orElse(ItemStack.EMPTY); + ItemStack saddle = getLegacySaddle(data).orElse(ItemStack.EMPTY); + + if (!bodyArmor.isEmpty()) + { + list.set(0, bodyArmor.copy()); + } + + if (!saddle.isEmpty()) + { + list.set(1, saddle.copy()); + } + + return list; + } + + /** + * Internal Process for mapping all Stacks to the correct Equipment Slot. + * @param stacks - + * @param type - + * @param entry - + * @param index - + */ + private static void processEachSlot(@Nonnull DefaultedList stacks, + EntityEquipmentSlot.Type type, + @Nonnull CompoundData entry, int index) + // @Nonnull RegistryAccess registry + { + ItemStack stack = DataTypeUtils.toItemStack(entry).orElse(ItemStack.EMPTY);; + + if (type == EntityEquipmentSlot.Type.HAND) + { + if (EntityEquipmentSlot.MAINHAND.getSlotIndex() == index) + { + stacks.set(index, stack); + } + else if (EntityEquipmentSlot.OFFHAND.getSlotIndex() == index) + { + stacks.set(index, stack); + } + } + else if (type == EntityEquipmentSlot.Type.ARMOR) + { + if (EntityEquipmentSlot.FEET.getSlotIndex() == index) + { + stacks.set(index, stack); + } + else if (EntityEquipmentSlot.LEGS.getSlotIndex() == index) + { + stacks.set(index, stack); + } + else if (EntityEquipmentSlot.CHEST.getSlotIndex() == index) + { + stacks.set(index, stack); + } + else if (EntityEquipmentSlot.HEAD.getSlotIndex() == index) + { + stacks.set(index, stack); + } + } + else if (index == 6) + { + // Horse Armor + stacks.set(index, stack); + } + else if (index == 7) + { + // Saddle + stacks.set(index, stack); + } + } + + /** + * Get A Tamable owner UUID from Data Tag, and if the mob is sitting + * @param data - + * @return - + */ + public static Optional> getTamableOwner(@Nonnull CompoundData data) + { + UUID uuid = null; // new UUID(0L, 0L) ? + boolean sitting = false; + + if (data.contains(NbtKeys.TAMABLE_OWNER, Constants.NBT.TAG_STRING)) + { + uuid = UUID.fromString(data.getStringOrDefault(NbtKeys.TAMABLE_OWNER, "")); + } + else if (data.contains(NbtKeys.OWNER, Constants.NBT.TAG_INT_ARRAY)) + { + uuid = DataTypeUtils.readUuidFromIntArray(data, NbtKeys.OWNER); + } + + if (data.contains(NbtKeys.SITTING, Constants.NBT.TAG_BYTE)) + { + sitting = data.getBoolean(NbtKeys.SITTING); + } + + if (uuid == null) { return Optional.empty(); } + + return Optional.of(Pair.of(uuid, sitting)); + } + + /** + * Get the Common Age / ForcedAge data from Data Tag + * + * @param data - + * @return - + */ + public static Optional> getAge(@Nonnull CompoundData data) + { + int breedingAge = 0; + int forcedAge = 0; + + if (data.contains(NbtKeys.GROWING_AGE, Constants.NBT.TAG_INT)) + { + breedingAge = data.getInt(NbtKeys.GROWING_AGE); + } + + if (data.contains(NbtKeys.FORCED_AGE, Constants.NBT.TAG_INT)) + { + forcedAge = data.getInt(NbtKeys.FORCED_AGE); + } + + if (breedingAge == 0 && forcedAge == 0) { return Optional.empty(); } + + return Optional.of(Pair.of(breedingAge, forcedAge)); + } + + /** + * Get the Merchant Trade Offer's Object from Data Tag + * + * @param data - + * @return - + */ + public static Optional getTradeOffers(@Nonnull CompoundData data) + { + if (data.contains(NbtKeys.TRADE_OFFERS, Constants.NBT.TAG_COMPOUND)) + { + return Optional.of(new MerchantRecipeList(DataConverterNbt.toVanillaCompound(data.getCompoundOrDefault(NbtKeys.TRADE_OFFERS, new CompoundData())))); + } + + return Optional.empty(); + } + + /** + * Get the Merchant Trade Offer's as a List from Data Tag + * + * @param data - + * @return - + */ + public static ImmutableList getTradeOfferRecipesAsVanilla(@Nonnull CompoundData data) + { + ImmutableList.Builder builder = ImmutableList.builder(); + + if (data.contains(NbtKeys.TRADE_OFFERS, Constants.NBT.TAG_COMPOUND)) + { + CompoundData tradeOffers = data.getCompoundOrDefault(NbtKeys.TRADE_OFFERS, new CompoundData()); + + if (tradeOffers.containsList(NbtKeys.TRADE_RECIPES, Constants.NBT.TAG_COMPOUND)) + { + ListData list = tradeOffers.getList(NbtKeys.TRADE_RECIPES, Constants.NBT.TAG_COMPOUND); + + for (int i = 0; i < list.size(); i++) + { + CompoundData entry = list.getCompoundAt(i); + if (entry == null || entry.isEmpty()) { continue; } + + builder.add(new MerchantRecipe(DataConverterNbt.toVanillaCompound(entry))); + } + } + } + + return builder.build(); + } + + /** + * Get the Merchant Trade Offer's as a List from Data Tag, + * in the form of a {@link TradeData} List in
+ * 'buy, buyB, sell', etc format. + * + * @param data - + * @return - + */ + public static ImmutableList getTradeOfferRecipesAsData(@Nonnull CompoundData data) + { + ImmutableList.Builder builder = ImmutableList.builder(); + + if (data.contains(NbtKeys.TRADE_OFFERS, Constants.NBT.TAG_COMPOUND)) + { + CompoundData tradeOffers = data.getCompoundOrDefault(NbtKeys.TRADE_OFFERS, new CompoundData()); + + if (tradeOffers.containsList(NbtKeys.TRADE_RECIPES, Constants.NBT.TAG_COMPOUND)) + { + ListData list = tradeOffers.getList(NbtKeys.TRADE_RECIPES, Constants.NBT.TAG_COMPOUND); + + for (int i = 0; i < list.size(); i++) + { + CompoundData entry = list.getCompoundAt(i); + if (entry == null || entry.isEmpty()) { continue; } + + getTradeData(entry).ifPresent(builder::add); + } + } + } + + return builder.build(); + } + + /** + * Return a {@link TradeData} of a single Merchant's 'buy, buyB, sell', etc trade. + * @param data - + * @return - + */ + public static Optional getTradeData(@Nonnull CompoundData data) + { + ItemStack buy = ItemStack.EMPTY; + ItemStack buyB = null; // Null if not found, using EMPTY here breaks future versions + ItemStack sell = ItemStack.EMPTY; + int uses = 0; + int maxUses = 4; + boolean rewardsExp = true; + int specialPrice = 0; + int demand = 0; + float multiplier = 0.0F; + int xp = 1; + + if (data.contains(NbtKeys.TRADE_BUY, Constants.NBT.TAG_COMPOUND)) + { + buy = DataTypeUtils.toItemStack(data.getCompoundOrDefault(NbtKeys.TRADE_BUY, new CompoundData())).orElse(ItemStack.EMPTY);; + } + if (data.contains(NbtKeys.TRADE_BUY_B, Constants.NBT.TAG_COMPOUND)) + { + buyB = DataTypeUtils.toItemStack(data.getCompoundOrDefault(NbtKeys.TRADE_BUY_B, new CompoundData())).orElse(ItemStack.EMPTY);; + } + if (data.contains(NbtKeys.TRADE_SELL, Constants.NBT.TAG_COMPOUND)) + { + sell = DataTypeUtils.toItemStack(data.getCompoundOrDefault(NbtKeys.TRADE_SELL, new CompoundData())).orElse(ItemStack.EMPTY);; + } + if (data.contains(NbtKeys.TRADE_USES, Constants.NBT.TAG_INT)) + { + uses = data.getInt(NbtKeys.TRADE_USES); + } + if (data.contains(NbtKeys.TRADE_MAX_USES, Constants.NBT.TAG_INT)) + { + maxUses = data.getInt(NbtKeys.TRADE_MAX_USES); + } + if (data.contains(NbtKeys.TRADE_REWARD_EXP, Constants.NBT.TAG_BYTE)) + { + rewardsExp = data.getBoolean(NbtKeys.TRADE_REWARD_EXP); + } + if (data.contains(NbtKeys.ORG_TRADE_SP_PRICE, Constants.NBT.TAG_INT)) + { + specialPrice = data.getInt(NbtKeys.ORG_TRADE_SP_PRICE); + } + if (data.contains(NbtKeys.ORG_TRADE_DEMAND, Constants.NBT.TAG_INT)) + { + demand = data.getInt(NbtKeys.ORG_TRADE_DEMAND); + } + if (data.contains(NbtKeys.ORG_TRADE_PRICE_MUL, Constants.NBT.TAG_FLOAT)) + { + multiplier = data.getFloat(NbtKeys.ORG_TRADE_PRICE_MUL); + } + if (data.contains(NbtKeys.ORG_TRADE_XP, Constants.NBT.TAG_INT)) + { + xp = data.getInt(NbtKeys.ORG_TRADE_XP); + } + + if (buy.isEmpty() && sell.isEmpty()) { return Optional.empty(); } + + return Optional.of(new TradeData(buy, buyB, sell, uses, maxUses, rewardsExp, specialPrice, demand, multiplier, xp)); + } + + public static class TradeData + { + private final ItemStack buyItem; + private final @Nullable ItemStack buyBItem; + private final ItemStack sellItem; + private final int uses; + private final int maxUses; + private final boolean rewardXp; + private final int specialPrice; + private final int demand; + private final float multiplier; + private final int xp; + + TradeData(ItemStack buyItem, @Nullable ItemStack buyBItem, ItemStack sellItem, int uses, int maxUses, boolean rewardXp) + { + this(buyItem, buyBItem, sellItem, uses, maxUses, rewardXp, 0, 0, 1.0F, 0); + } + + // 1.14.x+ + TradeData(ItemStack buyItem, @Nullable ItemStack buyBItem, ItemStack sellItem, + int uses, int maxUses, boolean rewardXp, + int specialPrice, int demand, float multiplier, int xp) + { + this.buyItem = buyItem; + this.buyBItem = buyBItem; + this.sellItem = sellItem; + this.uses = uses; + this.maxUses = maxUses; + this.rewardXp = rewardXp; + this.specialPrice = specialPrice; + this.demand = demand; + this.multiplier = multiplier; + this.xp = xp; + } + + public ItemStack buyItem() { return this.buyItem; } + + // Using 'Optional/ItemStack.EMPTY' here can break certain future 1.21.X builds, but it was eventually fixed + public @Nullable ItemStack buyBItem() { return this.buyBItem; } + + public ItemStack sellItem() { return this.sellItem; } + + public int uses() { return this.uses; } + + public int maxUses() { return this.maxUses; } + + public boolean rewardXp() { return this.rewardXp; } + + public int specialPrice() { return this.specialPrice; } + + public int demand() { return this.demand; } + + public float multiplier() { return this.multiplier; } + + public int xp() { return this.xp; } + } + + /** + * Get the VillagerData from Data Tag. + * @implNote This gets upgraded to 'VillagerData' under 1.14.x + * @param data - + * @return - + */ + public static Optional getVillagerJobData(@Nonnull CompoundData data) + { + int profession = -1; + int career = -1; + int level = -1; + String newType = ""; + String newProf = ""; + + if (data.contains(NbtKeys.PROFESSION, Constants.NBT.TAG_INT)) + { + profession = data.getIntOrDefault(NbtKeys.PROFESSION, 0); + } + if (data.contains(NbtKeys.CAREER, Constants.NBT.TAG_INT)) + { + career = data.getIntOrDefault(NbtKeys.CAREER, 0); + } + if (data.contains(NbtKeys.CAREER_LEVEL, Constants.NBT.TAG_INT)) + { + level = data.getIntOrDefault(NbtKeys.CAREER_LEVEL, 0); + } + + if (data.contains(NbtKeys.ORG_TYPE, Constants.NBT.TAG_STRING)) + { + newType = data.getString(NbtKeys.ORG_TYPE); + } + + if (data.contains(NbtKeys.ORG_VILLAGER_PROFESSION, Constants.NBT.TAG_STRING)) + { + newProf = data.getString(NbtKeys.ORG_VILLAGER_PROFESSION); + } + + if (profession < 0 && career < 0 && level < 0 && newType.isEmpty() && newProf.isEmpty()) { return Optional.empty(); } + + return Optional.of(new VillagerJobData(profession, career, level, newType, newProf)); + } + + public static class VillagerJobData + { + private final int profession; + private final int career; + private final int level; + private final String newType; + private final String newProfession; + + VillagerJobData(int profession, int career, int level, String newType, String newProfession) + { + this.profession = profession; + this.career = career; + this.level = level; + this.newType = newType; + this.newProfession = newProfession; + } + + public int legacyProfession() { return this.profession; } + + public int legacyCareer() { return this.career; } + + public int level() { return this.level; } + + public String newType() { return this.newType; } + + public String newProfession() { return this.newProfession; } + } + + /** + * Get the Zombie Villager cure timer. + * + * @param data - + * @return - + */ + public static Optional> getZombieConversionTimer(@Nonnull CompoundData data) + { + int timer = -1; + UUID player = null; + + if (data.contains(NbtKeys.ZOMBIE_CONVERSION, Constants.NBT.TAG_INT)) + { + timer = data.getInt(NbtKeys.ZOMBIE_CONVERSION); + } + if (data.contains(NbtKeys.CONVERSION_PLAYER+"Most", Constants.NBT.TAG_ANY_NUMERIC)) + { + player = DataTypeUtils.readUuidFromLongs(data, NbtKeys.CONVERSION_PLAYER+"Most", NbtKeys.CONVERSION_PLAYER+"Least"); + } + else if (data.contains(NbtKeys.CONVERSION_PLAYER, Constants.NBT.TAG_INT_ARRAY)) + { + player = DataTypeUtils.readUuidFromIntArray(data, NbtKeys.CONVERSION_PLAYER); + } + + if (player == null && timer < 0) { return Optional.empty(); } + + return Optional.of(Pair.of(timer, player)); + } + + /** + * Get Drowned conversion timer from a Zombie being in Water + * @implNote This gets added in 1.13.x + * + * @param data - + * @return - + */ + public static Optional> getDrownedConversionTimer(@Nonnull CompoundData data) + { + int drowning = -1; + int inWater = -1; + + if (data.contains(NbtKeys.ORG_DROWNED_CONVERSION, Constants.NBT.TAG_INT)) + { + drowning = data.getInt(NbtKeys.ORG_DROWNED_CONVERSION); + } + if (data.contains(NbtKeys.ORG_IN_WATER, Constants.NBT.TAG_INT)) + { + inWater = data.getInt(NbtKeys.ORG_IN_WATER); + } + + if (drowning < 0 || inWater < 0) { return Optional.empty(); } + + return Optional.of(Pair.of(drowning, inWater)); + } + + /** + * Get Stray Conversion Timer from being in Powered Snow + * @implNote This gets added in 1.17.x + * @param data - + * @return - + */ + public static Optional getStrayConversionTime(@Nonnull CompoundData data) + { + if (data.contains(NbtKeys.ORG_STRAY_CONVERSION, Constants.NBT.TAG_INT)) + { + return Optional.of(data.getInt(NbtKeys.ORG_STRAY_CONVERSION)); + } + + return Optional.empty(); + } + + /** + * Get the LeashData from Data Tag. + * @implNote This gets changed to using a 'LeashData' object in later versions, + * and after that it changes more around 1.21.8+ + * @param data - + * @return - + */ + public static Optional getLeashableData(@Nonnull CompoundData data) + { + UUID uuid = null; + BlockPos pos = null; + + if (data.contains(NbtKeys.NEW_LEASH, Constants.NBT.TAG_COMPOUND)) + { + CompoundData leash = data.getCompound(NbtKeys.NEW_LEASH); + + if (leash.contains(NbtKeys.UUID, Constants.NBT.TAG_INT_ARRAY)) + { + uuid = DataTypeUtils.readUuidFromIntArray(leash, NbtKeys.UUID); + + if (uuid != null) + { + return Optional.of(new LeashableData(null, uuid, null)); + } + } + + return Optional.empty(); + } + else if (data.contains(NbtKeys.NEW_LEASH, Constants.NBT.TAG_INT_ARRAY)) + { + pos = DataTypeUtils.readBlockPosFromArrayTag(data, NbtKeys.NEW_LEASH); + return Optional.of(new LeashableData(null, null, pos)); + } + + if (data.contains(NbtKeys.LEASH, Constants.NBT.TAG_COMPOUND)) + { + CompoundData leash = data.getCompoundOrDefault(NbtKeys.LEASH, new CompoundData()); + + if (leash.contains(NbtKeys.UUID_MOST, Constants.NBT.TAG_LONG)) + { + uuid = DataTypeUtils.readUuidFromLongs(leash, NbtKeys.UUID_MOST, NbtKeys.UUID_LEAST); + } + else if (leash.contains(NbtKeys.SH_BUL_OWNER_POS_X, Constants.NBT.TAG_INT) && + leash.contains(NbtKeys.SH_BUL_OWNER_POS_Y, Constants.NBT.TAG_INT) && + leash.contains(NbtKeys.SH_BUL_OWNER_POS_Z, Constants.NBT.TAG_INT)) + { + pos = new BlockPos(leash.getInt(NbtKeys.SH_BUL_OWNER_POS_X), leash.getInt(NbtKeys.SH_BUL_OWNER_POS_Y), leash.getInt(NbtKeys.SH_BUL_OWNER_POS_Z)); + } + + if (uuid == null && pos == null) { return Optional.empty(); } + + return Optional.of(new LeashableData(null, uuid, pos)); + } + + return Optional.empty(); + } + + public static class LeashableData + { + private final @Nullable Entity holder; + private @Nullable UUID holderUuid; + private @Nullable BlockPos pos; + + LeashableData(@Nullable Entity holder, @Nullable UUID holderUuid, @Nullable BlockPos pos) + { + this.holder = holder; + this.holderUuid = holderUuid; + this.pos = pos; + this.resolveKnotOrEntity(holder); + } + + public boolean resolveKnotOrEntity(@Nullable Entity holder) + { + if (holder == null && this.holder != null) + { + holder = this.holder; + } + + if (holder != null) + { + if (this.holderUuid == null) + { + this.holderUuid = holder.getUniqueID(); + } + + if (holder instanceof EntityLeashKnot) + { + EntityLeashKnot knot = (EntityLeashKnot) holder; + this.pos = BlockPos.of(knot.getPosition()); + } + + return true; + } + + return false; + } + + public Optional holder() { return Optional.ofNullable(this.holder); } + + public Optional holderUuid() { return Optional.ofNullable(this.holderUuid); } + + public Optional pos() { return Optional.ofNullable(this.pos); } + } + + /** + * Get the Full Block Attachment data for an item frame, including its Item from a Data Tag + * @param data - + * @return - + */ + public static Optional getItemFrameData(@Nonnull CompoundData data) + { + BlockPos pos = null; + Direction facing = null; + Direction itemRot = null; + ItemStack item = null; + + if (data.contains(NbtKeys.TILE_X, Constants.NBT.TAG_INT) && + data.contains(NbtKeys.TILE_Y, Constants.NBT.TAG_INT) && + data.contains(NbtKeys.TILE_Z, Constants.NBT.TAG_INT)) + { + pos = DataTypeUtils.readAttachedPosFromTag(data); + } + else if (data.contains(NbtKeys.NEW_ATTACHED_BLOCK_POS, Constants.NBT.TAG_INT_ARRAY)) + { + pos = DataTypeUtils.readBlockPosFromArrayTag(data, NbtKeys.NEW_ATTACHED_BLOCK_POS); + } + if (data.contains(NbtKeys.FACING_2, Constants.NBT.TAG_INT)) + { + facing = Direction.byHorizontalIndex(data.getInt(NbtKeys.FACING_2)); + } + if (data.contains(NbtKeys.ITEM_ROTATION, Constants.NBT.TAG_BYTE)) + { + itemRot = Direction.byIndex(data.getInt(NbtKeys.ITEM_ROTATION)); + } + if (data.contains(NbtKeys.ITEM, Constants.NBT.TAG_COMPOUND)) + { + item = DataTypeUtils.toItemStack(data.getCompoundOrDefault(NbtKeys.ITEM, new CompoundData())).orElse(ItemStack.EMPTY);; + } + + if (pos == null && facing == null && itemRot == null && item == null) { return Optional.empty(); } + + return Optional.of(new BlockAttachedData(pos, facing, itemRot, item)); + } + + /** + * Get a Painting's Direction and Variant from Data Tag. + * @implNote Future versions use the 'PaintingVariant' object instead of a String + * @param data - + * @return - + */ + public static Optional> getPaintingData(@Nonnull CompoundData data) + { + BlockPos pos = null; + Direction facing = null; + String variant = ""; + + if (data.contains(NbtKeys.TILE_X, Constants.NBT.TAG_INT) && + data.contains(NbtKeys.TILE_Y, Constants.NBT.TAG_INT) && + data.contains(NbtKeys.TILE_Z, Constants.NBT.TAG_INT)) + { + pos = DataTypeUtils.readAttachedPosFromTag(data); + } + else if (data.contains(NbtKeys.NEW_ATTACHED_BLOCK_POS, Constants.NBT.TAG_INT_ARRAY)) + { + pos = DataTypeUtils.readBlockPosFromArrayTag(data, NbtKeys.NEW_ATTACHED_BLOCK_POS); + } + if (data.contains(NbtKeys.FACING_2, Constants.NBT.TAG_INT)) + { + facing = Direction.byHorizontalIndex(data.getInt(NbtKeys.FACING_2)); + } + else if (data.contains(NbtKeys.NEW_FACING, Constants.NBT.TAG_BYTE)) + { + facing = Direction.byHorizontalIndex(data.getByte(NbtKeys.NEW_FACING)); + } + if (data.contains(NbtKeys.PAINTING_TYPE, Constants.NBT.TAG_STRING)) + { + variant = data.getString(NbtKeys.PAINTING_TYPE); + } + + if (variant.isEmpty()) { return Optional.empty(); } + + return Optional.of(Pair.of(new BlockAttachedData(pos, facing), variant)); + } + + public static class BlockAttachedData + { + private final BlockPos pos; + private final Direction facing; + private final @Nullable Direction itemRot; + private final @Nullable ItemStack item; + + // Basic / Painting + BlockAttachedData(BlockPos pos, Direction facing) + { + this(pos, facing, null, null); + } + + // Item Frame + BlockAttachedData(BlockPos pos, Direction facing, @Nullable Direction itemRot, @Nullable ItemStack item) + { + this.pos = pos; + this.facing = facing; + this.itemRot = itemRot; + this.item = item; + } + + public BlockPos pos() { return this.pos; } + + public Direction facing() { return this.facing; } + + public Optional itemRot() { return Optional.ofNullable(this.itemRot); } + + public Optional item() { return Optional.ofNullable(this.item); } + } + + /** + * Get the Horse Variant information via {@link HorseVariantData} with a hacky workaround. + * @implNote In future versions, the 'HorseColor' and 'Markings' objects are added + * @param data - + * @return - + */ + public static Optional getHorseVariantData(@Nonnull CompoundData data) + { + if (data.contains(NbtKeys.VARIANT, Constants.NBT.TAG_INT)) + { + return Optional.of(new HorseVariantData(data.getInt(NbtKeys.VARIANT))); + } + + return Optional.empty(); + } + + /** + * @implNote The color (Variant) / Markings turns into Enums under 1.16.x+. + */ + public static class HorseVariantData + { + public static final String[] HORSE_COLORS = new String[]{"white", "creamy", "chestnut", "brown", "black", "gray", "darkbrown"}; + public static final String[] HORSE_MARKINGS = new String[]{"", "white", "whitefield", "whitedots", "blackdots"}; + private int variant; + private int colorId = -1; + private int markingId = -1; + private String color = ""; + private String marking = ""; + + public HorseVariantData(int variant) + { + this.variant = variant; + this.calculateVariant(variant); + } + + public void calculateVariant(int variant) + { + if (variant > 0 && this.variant != variant) + { + this.variant = variant; + } + + this.colorId = (this.variant & 0xFF) % 7; + this.markingId = ((this.variant & 0xFF00) >> 8) % 5; + this.color = HORSE_COLORS[this.colorId]; + this.marking = HORSE_MARKINGS[this.markingId]; + } + + public int variant() { return this.variant; } + + public int colorId() { return this.colorId; } + + public int markingId() { return this.markingId; } + + public String color() { return this.color; } + + public String marking() { return this.marking; } + } + + /** + * Get a Llama's Variant type from Data Tag. + * @implNote In future versions, the Llama has more Variants; and utilizes the 'Llama.Variant' Object + * @param data - + * @return - + */ + public static Optional getLlamaTypeData(@Nonnull CompoundData data) + { + int variant = -1; + int strength = -1; + ItemStack stack = ItemStack.EMPTY; + + if (data.contains(NbtKeys.VARIANT, Constants.NBT.TAG_INT)) + { + variant = data.getInt(NbtKeys.VARIANT); + } + + if (data.contains(NbtKeys.STRENGTH, Constants.NBT.TAG_INT)) + { + strength = data.getInt(NbtKeys.STRENGTH); + } + + if (data.contains(NbtKeys.LLAMA_DECOR, Constants.NBT.TAG_COMPOUND)) + { + stack = DataTypeUtils.toItemStack(data.getCompoundOrDefault(NbtKeys.LLAMA_DECOR, new CompoundData())).orElse(ItemStack.EMPTY);; + } + + if (stack.isEmpty() && variant == -1 && strength == -1) { return Optional.empty(); } + + return Optional.of(new LlamaVariantData(variant, strength, stack)); + } + + public static class LlamaVariantData + { + private final int variant; + private final int strength; + private final @Nullable ItemStack carpet; + + public LlamaVariantData(int variant, int strength, @Nullable ItemStack carpet) + { + this.variant = variant; + this.strength = strength; + this.carpet = carpet; + } + + public int variant() { return this.variant; } + + public int strength() { return this.strength; } + + public Optional carpet() { return Optional.ofNullable(this.carpet); } + } + + /** + * Get a Sheep's Color from Data Tag. + * + * @param data - + * @return - + */ + public static Optional getSheepColor(@Nonnull CompoundData data) + { + if (data.contains(NbtKeys.COLOR, Constants.NBT.TAG_BYTE)) + { + return Optional.ofNullable(DyeColorCode.getByMeta(data.getByte(NbtKeys.COLOR))); + } + else if (data.contains(NbtKeys.COLOR, Constants.NBT.TAG_INT)) + { + return Optional.ofNullable(DyeColorCode.getByMeta(data.getInt(NbtKeys.COLOR))); + } + + return Optional.empty(); + } + + /** + * Get a Rabbit's Variant type from Data Tag. + * + * @param data - + * @return - + */ + public static Optional getRabbitType(@Nonnull CompoundData data) + { + if (data.contains(NbtKeys.RABBIT_TYPE, Constants.NBT.TAG_INT)) + { + return Optional.of(new EntityRabbit.RabbitTypeData(data.getInt(NbtKeys.RABBIT_TYPE))); + } + + return Optional.empty(); + } + + /** + * Get a Parrot's Variant from Data Tag. + * @implNote In future versions, the Parrot uses the 'Parrot.Variant' object + * @param data - + * @return - + */ + public static Optional getParrotVariant(@Nonnull CompoundData data) + { + if (data.contains(NbtKeys.VARIANT, Constants.NBT.TAG_INT)) + { + return Optional.of(data.getInt(NbtKeys.VARIANT)); + } + + return Optional.empty(); + } + + /** + * Get a Cat's Variant, and Collar Color from Data Tag. + * @implNote In later versions, Cats are added alone (Not Ocelots) under 1.14.x; + * and then use the 'CatVariant' Object is used under 1.21.4+ + * @param data - + * @return - + */ + public static Optional> getCatVariant(@Nonnull CompoundData data) + { + String variant = null; + DyeColorCode collar = null; + + if (data.contains(NbtKeys.NEW_VARIANT, Constants.NBT.TAG_STRING)) + { + variant = data.getString(NbtKeys.NEW_VARIANT); + } + if (data.contains(NbtKeys.COLLAR_COLOR, Constants.NBT.TAG_ANY_NUMERIC)) + { + collar = DyeColorCode.getByMeta(data.getInt(NbtKeys.COLLAR_COLOR)); + } + + if (variant == null && collar == null) { return Optional.empty(); } + + return Optional.of(Pair.of(variant, collar)); + } + + /** + * Get a Tropical Fish Variant from Data Tag. + * @implNote In future versions, the Tropical Fish are added to the game, + * and then uses the 'TropicalFish.Variant' and 'TropicalFish.Pattern' objects + * @param data - + * @return - + */ + public static Optional getTropicalFishVariant(@Nonnull CompoundData data) + { + if (data.contains(NbtKeys.VARIANT, Constants.NBT.TAG_INT)) + { + return Optional.of(data.getInt(NbtKeys.VARIANT) & '\uffff'); + } + else if (data.contains(NbtKeys.ORG_BUCKET_VARIANT, Constants.NBT.TAG_INT)) + { + return Optional.of(data.getInt(NbtKeys.ORG_BUCKET_VARIANT) & '\uffff'); + } + + return Optional.empty(); + } + + /** + * Get a Dolphin's TreasurePos and other data from Data Tag. + * @implNote In the future for 1.21.8+; they removed the "Treasure" functionality. + * @param data - + * @return - + */ + public static Optional getDolphinData(@Nonnull CompoundData data) + { + boolean hasFish = false; + boolean canFind = false; + int moist = -1; + BlockPos treasure = null; + + if (data.contains(NbtKeys.ORG_MOISTNESS, Constants.NBT.TAG_INT)) + { + moist = data.getInt(NbtKeys.ORG_MOISTNESS); + } + + if (data.contains(NbtKeys.ORG_GOT_FISH, Constants.NBT.TAG_BYTE)) + { + hasFish = data.getBoolean(NbtKeys.ORG_GOT_FISH); + } + + if (data.contains(NbtKeys.ORG_CAN_FIND_TREASURE, Constants.NBT.TAG_BYTE)) + { + canFind = data.getBoolean(NbtKeys.ORG_CAN_FIND_TREASURE); + } + + if (data.contains(NbtKeys.ORG_TREASURE_X, Constants.NBT.TAG_INT) && + data.contains(NbtKeys.ORG_TREASURE_Y, Constants.NBT.TAG_INT) && + data.contains(NbtKeys.ORG_TREASURE_Z, Constants.NBT.TAG_INT)) + { + treasure = new BlockPos(data.getInt(NbtKeys.ORG_TREASURE_X), data.getInt(NbtKeys.ORG_TREASURE_Y), data.getInt(NbtKeys.ORG_TREASURE_Z)); + } + + if (!hasFish && !canFind && moist == -1 && treasure == null) { return Optional.empty(); } + + return Optional.of(new DolphinData(moist, hasFish, canFind, treasure)); + } + + /** + * @implNote In the future for 1.21.8+; they removed the "Treasure" functionality. + */ + public static class DolphinData + { + private final int moistness; + private final boolean hasFish; + private final boolean canFindTreasure; + private final @Nullable BlockPos treasure; + + DolphinData(int moistness, boolean hasFish) + { + this(moistness, hasFish, false, null); + } + + DolphinData(int moistness, boolean hasFish, boolean canFindTreasure, @Nullable BlockPos treasure) + { + this.moistness = moistness; + this.hasFish = hasFish; + this.canFindTreasure = canFindTreasure; + this.treasure = treasure; + } + + public int moistness() { return this.moistness; } + + public boolean hasFish() { return this.hasFish; } + + public boolean canFindTreasure() { return this.canFindTreasure; } + + public Optional treasure() { return Optional.ofNullable(this.treasure); } + } + + /** + * Get a Fox Variant from Data Tag. + * @implNote Future Versions after the Fox is added under 1.14.x; uses the 'Fox.Type' object, and then later on, using the 'Fox.Variant' object + * @param data - + * @return - + */ + public static Optional getFoxVariant(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + if (data.contains(NbtKeys.TYPE, Constants.NBT.TAG_STRING)) + { + return Optional.ofNullable(data.getString(NbtKeys.TYPE)); + } + + return Optional.empty(); + } + + /** + * Get a Mooshroom's Variant from Data Tag. + * @implNote This was added around 1.14.x using the 'MushroomCow.MushroomType' object, and then later on, using the 'MushroomCow.Variant' object + * @param data - + * @return - + */ + public static Optional getMooshroomVariant(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + if (data.contains(NbtKeys.TYPE, Constants.NBT.TAG_STRING)) + { + return Optional.ofNullable(data.getString(NbtKeys.TYPE)); + } + + return Optional.empty(); + } + + /** + * Get the Panda Gene's from Data Tag + * @implNote Future Versions after the Panda is added under 1.14.x; uses the 'Panda.Gene' object + * @param data - + * @return - + */ + public static Optional> getPandaGenes(@Nonnull CompoundData data) + { + String mainGene = ""; + String hiddenGene = ""; + + if (data.contains(NbtKeys.ORG_MAIN_GENE, Constants.NBT.TAG_STRING)) + { + mainGene = data.getString(NbtKeys.ORG_MAIN_GENE); + } + if (data.contains(NbtKeys.ORG_HIDDEN_GENE, Constants.NBT.TAG_STRING)) + { + hiddenGene = data.getString(NbtKeys.ORG_HIDDEN_GENE); + } + + if (mainGene.isEmpty() && hiddenGene.isEmpty()) { return Optional.empty(); } + + return Optional.of(Pair.of(mainGene, hiddenGene)); + } + + /** + * Get an Axolotl's Variant from Data Tag. + * @implNote When the Axolotl gets added to the game under 1.17.x, it uses the 'Axolotl.Variant' object + * @param data - + * @return - + */ + public static Optional getAxolotlVariant(@Nonnull CompoundData data) + { + if (data.contains(NbtKeys.VARIANT, Constants.NBT.TAG_INT)) + { + return Optional.of(data.getIntOrDefault(NbtKeys.VARIANT, -1)); + } + + return Optional.empty(); + } + + /** + * Get a Frog's Variant from Data Tag. + * @implNote When Frogs are added under 1.19.x, they utilize the 'FrogVariant' object. + * @param data - + * @return - + */ + public static Optional getFrogVariant(@Nonnull CompoundData data) + { + if (data.contains(NbtKeys.NEW_VARIANT, Constants.NBT.TAG_STRING)) + { + return Optional.ofNullable(data.getString(NbtKeys.NEW_VARIANT)); + } + + return Optional.empty(); + } + + /** + * Get a Wolves' Variant and Collar Color from Data Tag. + * @implNote This information isn't added until 1.20.6, which then uses the 'WolfVariant' object + * @param data - + * @return - + */ + public static Optional> getWolfVariant(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + DyeColorCode collar = null; + String variant = ""; + + if (data.contains(NbtKeys.NEW_VARIANT, Constants.NBT.TAG_STRING)) + { + variant = data.getString(NbtKeys.NEW_VARIANT); + } + + if (data.contains(NbtKeys.COLLAR_COLOR, Constants.NBT.TAG_STRING)) + { + collar = DyeColorCode.valueOf(data.getString(NbtKeys.COLLAR_COLOR)); + } + else if (data.contains(NbtKeys.COLLAR_COLOR, Constants.NBT.TAG_INT)) + { + collar = DyeColorCode.getByMeta(data.getInt(NbtKeys.COLLAR_COLOR)); + } + + if (variant.isEmpty() && collar == null) { return Optional.empty(); } + + return Optional.of(Pair.of(variant, collar)); + } + + /** + * Get a Salmon Variant from Data Tag. + * @implNote This was added around 1.21.2+ using the 'Salmon.Variant' object + * @param data - + * @return - + */ + public static Optional getSalmonVariant(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + if (data.contains(NbtKeys.ORG_TYPE, Constants.NBT.TAG_STRING)) + { + return Optional.ofNullable(data.getString(NbtKeys.ORG_TYPE)); + } + + return Optional.empty(); + } + + /** + * Get a Wolves' Sound Type Variant from Data Tag. + * @implNote This was added around 1.21.5+ using the 'WolfSoundVariant' object + * @param data - + * @return - + */ + public static Optional getWolfSoundType(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + if (data.contains(NbtKeys.NEW_SOUND_VARIANT, Constants.NBT.TAG_STRING)) + { + return Optional.ofNullable(data.getString(NbtKeys.NEW_SOUND_VARIANT)); + } + + return Optional.empty(); + } + + /** + * Get a Chicken's Variant from Data Tag. + * @implNote This was added around 1.21.5+ using the 'ChickenVariant' object + * @param data - + * @return - + */ + public static Optional getChickenVariant(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + if (data.contains(NbtKeys.NEW_VARIANT, Constants.NBT.TAG_STRING)) + { + return Optional.ofNullable(data.getString(NbtKeys.NEW_VARIANT)); + } + + return Optional.empty(); + } + + /** + * Get a Pig's Variant from Data Tag. + * @implNote This was added around 1.21.5+ using the 'PigVariant' object + * @param data - + * @return - + */ + public static Optional getPigVariant(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + if (data.contains(NbtKeys.NEW_VARIANT, Constants.NBT.TAG_STRING)) + { + return Optional.ofNullable(data.getString(NbtKeys.NEW_VARIANT)); + } + + return Optional.empty(); + } + + /** + * Get a Cow's Variant from Data Tag. + * @implNote This was added around 1.21.5+ using the 'CowVariant' object + * @param data - + * @return - + */ + public static Optional getCowVariant(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + if (data.contains(NbtKeys.NEW_VARIANT, Constants.NBT.TAG_STRING)) + { + return Optional.ofNullable(data.getString(NbtKeys.NEW_VARIANT)); + } + + return Optional.empty(); + } + + /** + * Get a Mob's Home Pos and Radius from Data Tag + * @implNote This first appears under 1.21.8+, and it is utilized by a number of various entities; including Leash Knots. + * @param data - + * @return - + */ + public static Optional> getHomePos(@Nonnull CompoundData data) + { + BlockPos pos = null; + int radius = -1; + + if (data.contains(NbtKeys.NEW_HOME_POS, Constants.NBT.TAG_INT_ARRAY)) + { + pos = DataTypeUtils.readBlockPosFromArrayTag(data, NbtKeys.NEW_HOME_POS); + } + + if (data.contains(NbtKeys.NEW_HOME_RADIUS, Constants.NBT.TAG_INT)) + { + radius = data.getInt(NbtKeys.NEW_HOME_RADIUS); + } + + if (pos == null && radius < 0) { return Optional.empty(); } + + return Optional.of(Pair.of(pos, radius)); + } + + /** + * Get a Copper Golem's Weathering Data from Data Tag + * @implNote The Copper Golem first appears under 1.21.10, and utilizes the 'WeatheringCopper.WeatherState' object + * @param data - + * @return - + */ + public static Optional getWeatheringState(@Nonnull CompoundData data) + { + String state = ""; + long age = -1L; + + if (data.contains(NbtKeys.NEW_WEATHER_STATE, Constants.NBT.TAG_STRING)) + { + state = data.getString(NbtKeys.NEW_WEATHER_STATE); + } + + if (data.contains(NbtKeys.NEW_NEXT_WEATHER_AGE, Constants.NBT.TAG_LONG)) + { + age = data.getLong(NbtKeys.NEW_NEXT_WEATHER_AGE); + } + + if (state.isEmpty() && age < 0) { return Optional.empty(); } + + return Optional.of(new OxidizationState(state, age)); + } + + /** + * This Object describes the "Weathering" status of Copper + */ + public static class OxidizationState + { + private final String state; + private final long age; + + public OxidizationState(String state, long age) + { + this.state = state; + this.age = age; + } + + public String state() { return this.state; } + + public long age() { return this.age; } + } + + /** + * Get a Zombie Nautilus's Variant from Data Tag. + * @implNote This was added under 1.21.11 using the 'ZombieNautilusVariant' object + * @param data - + * @return - + */ + public static Optional getZombieNautilusVariant(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + if (data.contains(NbtKeys.NEW_VARIANT, Constants.NBT.TAG_STRING)) + { + return Optional.ofNullable(data.getString(NbtKeys.NEW_VARIANT)); + } + + return Optional.empty(); + } + + /** + * Get a player's Experience values from Data Tag. + * + * @param data - + * @return - + */ + public static Optional getPlayerExp(@Nonnull CompoundData data) + { + int level = -1; + int total = -1; + float progress = 0.0F; + + if (data.contains(NbtKeys.EXP_LEVEL, Constants.NBT.TAG_INT)) + { + level = data.getInt(NbtKeys.EXP_LEVEL); + } + if (data.contains(NbtKeys.EXP_TOTAL, Constants.NBT.TAG_INT)) + { + total = data.getInt(NbtKeys.EXP_TOTAL); + } + if (data.contains(NbtKeys.EXP_PROGRESS, Constants.NBT.TAG_FLOAT)) + { + progress = data.getFloat(NbtKeys.EXP_PROGRESS); + } + + if (level < 0 && total < 0 && progress == 0.0F) { return Optional.empty(); } + + return Optional.of(new PlayerExpData(level, total, progress)); + } + + public static class PlayerExpData + { + private final int level; + private final int total; + private final float progress; + + PlayerExpData(int level, int total, float progress) + { + this.level = level; + this.total = total; + this.progress = progress; + } + + public int level() { return this.level; } + + public int total() { return this.total; } + + public float progress() { return this.progress; } + } + + /** + * Get a Player's Hunger Manager from Data Tag. + * + * @param data - + * @return - + */ + public static Optional getPlayerHunger(@Nonnull CompoundData data) + { + FoodStats hunger = null; + + if (data.contains(NbtKeys.FOOD_LEVEL, Constants.NBT.TAG_ANY_NUMERIC)) + { + hunger = new FoodStats(); + hunger.readNBT(DataConverterNbt.toVanillaCompound(data)); + } + + return Optional.ofNullable(hunger); + } +} diff --git a/src/main/java/malilib/util/game/DataTileEntityUtils.java b/src/main/java/malilib/util/game/DataTileEntityUtils.java new file mode 100644 index 0000000000..6f19cd5ee8 --- /dev/null +++ b/src/main/java/malilib/util/game/DataTileEntityUtils.java @@ -0,0 +1,893 @@ +package malilib.util.game; + +import java.util.*; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import com.google.common.collect.ImmutableSet; +import malilib.util.data.Constants; +import malilib.util.data.DyeColorCode; +import malilib.util.data.Identifier; +import malilib.util.data.tag.CompoundData; +import malilib.util.data.tag.ListData; +import malilib.util.data.tag.converter.DataConverterNbt; +import malilib.util.data.tag.util.DataTypeUtils; +import malilib.util.game.wrap.RegistryUtils; +import malilib.util.nbt.NbtKeys; +import malilib.util.position.BlockPos; +import org.apache.commons.lang3.tuple.Pair; + +import com.mojang.authlib.GameProfile; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTUtil; +import net.minecraft.potion.Potion; +import net.minecraft.tileentity.BannerPattern; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.tileentity.TileEntityBeacon; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TextComponentString; + +/** + * The purpose of this Library is to fully utilize {@link malilib.util.data.tag.DataView} Tags to parse NBT tags for use in downstream mods; + * such as {@link TileEntity} data for MiniHUD's Info Lines; or other specific {@link malilib.util.data.tag.DataView} needs. + * It should not fail to mimic the various 'readNbt()' / 'writeNbt()' type functions under Vanilla's {@link TileEntity} system, + * without needing to instance a new {@link TileEntity} object; which can potentially break other people's mods if used too often. + * These are more important in later versions of Minecraft; especially when it can be hard to remember every NBT tag name; + * or CODEC method; to say; export a Skulls's {@link GameProfile} from Raw Tags, and then find a specific value; for example. + * Utilizing {@link NbtKeys} is also very helpful to track changes across versions of Minecraft. + */ +public class DataTileEntityUtils +{ + /** + * Get the Tile Entity Type from the Data Tag + * @param data - + * @return - + * @implNote In the future, BlockEntityType is utilized + */ + public static Optional getTileEntityType(@Nonnull CompoundData data) + // BlockEntityType type + { + if (data.contains(NbtKeys.ID, Constants.NBT.TAG_STRING)) + { + Identifier id = Identifier.of(data.getString(NbtKeys.ID)); + + if (id != null && RegistryUtils.isTileEntityValid(id)) + { + return Optional.of(id); + } + } + + return Optional.empty(); + } + + /** + * Write the Block Entity ID tag. + * @param te - + * @param dataIn - + * @return - + * @implNote In the future, BlockEntityType is utilized + */ + public static CompoundData setTileEntityType(Class te, @Nullable CompoundData dataIn) + // BlockEntityType type + { + CompoundData data = dataIn != null ? dataIn : new CompoundData(); + ResourceLocation rl = TileEntity.getKey(te); + + if (rl != null) + { + return data.putString(NbtKeys.ID, rl.toString()); + } + + return data; + } + + /** + * Get the Text Component of the Custom Name + * @param data - + * @return - + * @implNote In the future; the Text Serialization changes multiple times + */ + public static Optional getCustomName(@Nonnull CompoundData data, @Nullable String key) + // Component / @Nonnull RegistryAccess registry + { + if (key == null && data.contains(NbtKeys.CUSTOM_NAME, Constants.NBT.TAG_STRING)) + { + return Optional.of(new TextComponentString(data.getString(NbtKeys.CUSTOM_NAME))); + } + else if (data.contains(key, Constants.NBT.TAG_STRING)) + { + return Optional.of(new TextComponentString(data.getString(key))); + } + + return Optional.empty(); + } + + /** + * Get a RecordItem as an ItemStack from Data tag + * @param data - + * @return - + * @implNote After ~1.21, the Registry is required + */ + public static Optional getRecordItem(@Nonnull CompoundData data) + // @Nonnull RegistryAccess registry + { + if (data.contains(NbtKeys.RECORD_ITEM, Constants.NBT.TAG_COMPOUND)) + { + return DataTypeUtils.toItemStack(data.getCompound(NbtKeys.RECORD_ITEM)); + } + else if (data.contains(NbtKeys.RECORD, Constants.NBT.TAG_INT)) + { + try + { + final Item item = Item.getItemById(data.getInt(NbtKeys.RECORD)); + return Optional.of(new ItemStack(item)); + } + catch (Exception ignored) {} + } + + return Optional.empty(); + } + + /** + * Get Banner patterns and colors from Data tag + * @param data - + * @return - + */ + public static Optional getBannerPatterns(@Nonnull CompoundData data) + { + DyeColorCode color = DyeColorCode.WHITE; + List patterns = new ArrayList<>(); + List colors = new ArrayList<>(); + + if (data.contains(NbtKeys.BASE_COLOR, Constants.NBT.TAG_INT)) + { + color = DyeColorCode.getByMeta(data.getInt(NbtKeys.BASE_COLOR)); + } + + if (data.contains(NbtKeys.PATTERNS, Constants.NBT.TAG_COMPOUND)) + { + ListData listData = data.getList(NbtKeys.PATTERNS, Constants.NBT.TAG_COMPOUND); + + for (int i = 0; i < listData.size(); i++) + { + CompoundData entry = listData.getCompoundAt(i); + getEachBannerPattern(entry, patterns, colors); + } + } + + if (patterns.isEmpty() && colors.isEmpty() && color == DyeColorCode.WHITE) + { + return Optional.empty(); + } + + return Optional.of(new BannerPatternsData(color, patterns, colors)); + } + + /** + * Read each Banner Pattern from a List Entry + * @param data - + * @param patterns - + * @param colors - + */ + public static void getEachBannerPattern(@Nonnull CompoundData data, @Nonnull List patterns, @Nonnull List colors) + { + BannerPattern pattern = null; + int entryColor = -1; + + if (data.contains(NbtKeys.PATTERN, Constants.NBT.TAG_STRING)) + { + pattern = BannerPattern.byHash(data.getString(NbtKeys.PATTERN)); + } + if (data.contains(NbtKeys.COLOR, Constants.NBT.TAG_INT)) + { + entryColor = data.getInt(NbtKeys.COLOR); + } + + if (pattern != null) + { + patterns.add(pattern); + colors.add(DyeColorCode.getByMeta(entryColor)); + } + } + + public static class BannerPatternsData + { + private final DyeColorCode baseColor; + private final List patterns; + private final List colors; + + BannerPatternsData(DyeColorCode baseColor, List patterns, List colors) + { + this.baseColor = baseColor; + this.patterns = patterns; + this.colors = colors; + } + + public DyeColorCode baseColor() { return this.baseColor; } + + public List patterns() { return this.patterns; } + + public List colors() { return this.colors; } + } + + /** + * Get Beacon Data from Data Tag. + * @param data - + * @return - + */ + public static Optional getBeaconData(@Nonnull CompoundData data) + { + Potion pri = null; + Potion sec = null; + int levels = -1; + + if (data.contains(NbtKeys.PRIMARY_EFFECT, Constants.NBT.TAG_INT)) + { + pri = Potion.getPotionById(data.getInt(NbtKeys.PRIMARY_EFFECT)); + } + else if (data.contains(NbtKeys.NEW_PRIMARY_EFFECT, Constants.NBT.TAG_STRING)) + { + pri = Potion.getPotionFromResourceLocation(data.getString(NbtKeys.NEW_PRIMARY_EFFECT)); + } + + if (data.contains(NbtKeys.SECONDARY_EFFECT, Constants.NBT.TAG_INT)) + { + sec = Potion.getPotionById(data.getInt(NbtKeys.SECONDARY_EFFECT)); + } + else if (data.contains(NbtKeys.NEW_SECONDARY_EFFECT, Constants.NBT.TAG_STRING)) + { + sec = Potion.getPotionFromResourceLocation(data.getString(NbtKeys.NEW_SECONDARY_EFFECT)); + } + + if (data.contains(NbtKeys.LEVELS, Constants.NBT.TAG_INT)) + { + levels = data.getInt(NbtKeys.LEVELS); + } + + if (pri == null && sec == null) + { + return Optional.empty(); + } + + return Optional.of(new BeaconData(pri, sec, levels)); + } + + public static class BeaconData + { + // Potion -> StatusEffect / MobEffect + public static final Set VALID_EFFECTS = collectEffects(); + private final @Nullable Potion primaryEffect; + private final @Nullable Potion secondaryEffect; + private final int levels; + + public BeaconData(@Nullable Potion primary, @Nullable Potion secondary, int levels) + { + this.primaryEffect = isValidEffect(primary) ? primary : null; + this.secondaryEffect = isValidEffect(secondary) ? secondary : null; + this.levels = levels; + } + + public Optional primaryEffect() {return Optional.ofNullable(this.primaryEffect);} + + public Optional secondaryEffect() {return Optional.ofNullable(this.secondaryEffect);} + + public int levels() {return this.levels;} + + private static ImmutableSet collectEffects() + { + ImmutableSet.Builder set = new ImmutableSet.Builder<>(); + + for (Potion[] pot : TileEntityBeacon.EFFECTS_LIST) + { + set.addAll(Arrays.asList(pot)); + } + + return set.build(); + } + + public static boolean isValidEffect(@Nullable Potion effect) + { + if (effect == null) { return false; } + return VALID_EFFECTS.contains(effect); + } + } + + /** + * Get the End Gateway's Exit Portal from Data Tag. + * @param data - + * @return - + */ + public static Optional> getEndGatewayData(@Nonnull CompoundData data) + { + long age = -1; + BlockPos pos = null; + + if (data.contains(NbtKeys.AGE, Constants.NBT.TAG_LONG)) + { + age = data.getLong(NbtKeys.AGE); + } + + if (data.contains(NbtKeys.EXIT_PORTAL, Constants.NBT.TAG_COMPOUND)) + { + pos = DataTypeUtils.readPrefixedPosFromTag(data.getCompound(NbtKeys.EXIT_PORTAL), ""); + } + else if (data.contains(NbtKeys.NEW_EXIT, Constants.NBT.TAG_INT_ARRAY)) + { + pos = DataTypeUtils.readBlockPosFromArrayTag(data, NbtKeys.NEW_EXIT); + } + + if (pos == null && age < 0) + { + return Optional.empty(); + } + + return Optional.of(Pair.of(age, pos)); + } + + /** + * Read Sign Text from Data tag + * NOTE: This code makes NO attempts to "resolve" the various command codes + * @param data - + * @return - + * @implNote Signs from 1.20.2+ are two-sided and can be Waxed + */ + public static Optional getSignText(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + if (data.contains(NbtKeys.NEW_FRONT_TEXT, Constants.NBT.TAG_COMPOUND)) + { + CompoundData fd = data.getCompound(NbtKeys.NEW_FRONT_TEXT); + CompoundData bd = new CompoundData(); + boolean waxed = false; + + if (data.contains(NbtKeys.NEW_BACK_TEXT, Constants.NBT.TAG_COMPOUND)) + { + bd = fd.getCompound(NbtKeys.NEW_BACK_TEXT); + } + + if (data.contains(NbtKeys.NEW_WAXED, Constants.NBT.TAG_COMPOUND)) + { + waxed = data.getBoolean(NbtKeys.NEW_WAXED); + } + + SignTextData.Side front = getSignTextEachSide(fd).orElse(null); + SignTextData.Side back = getSignTextEachSide(bd).orElse(null); + + if (front == null && back == null) + { + return Optional.empty(); + } + + return Optional.of(new SignTextData(front, back, waxed)); + } + else + { + SignTextData.Side front = getSignTextLegacySide(data).orElse(null); + + if (front == null) + { + return Optional.empty(); + } + + return Optional.of(new SignTextData(front, null, false)); + } + } + + /** + * Get The Front Side of a Sign + * @param data - + * @return - + */ + public static Optional getSignTextLegacySide(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + ITextComponent[] front = SignTextData.emptyArray(); + ITextComponent[] filter = SignTextData.emptyArray(); + DyeColorCode color = DyeColorCode.BLACK; + boolean glow = false; + + if (data.contains(NbtKeys.COLOR, Constants.NBT.TAG_STRING)) + { + color = DyeColorCode.fromStringOrDefault(data.getString(NbtKeys.COLOR), DyeColorCode.BLACK); + } + if (data.contains(NbtKeys.SIGN_GLOWING, Constants.NBT.TAG_BYTE)) + { + glow = data.getBoolean(NbtKeys.SIGN_GLOWING); + } + + for (int i = 0; i < 4; i++) + { + final int index = i; + + if (data.contains(NbtKeys.SIGN_TEXT_PREFIX+index, Constants.NBT.TAG_STRING)) + { + DataTypeUtils.toTextComponent(data, NbtKeys.SIGN_TEXT_PREFIX+i).ifPresent(e -> front[index] = e); + } + + if (data.contains(NbtKeys.SIGN_FILTER_PREFIX+index, Constants.NBT.TAG_STRING)) + { + DataTypeUtils.toTextComponent(data, NbtKeys.SIGN_FILTER_PREFIX+i).ifPresent(e -> filter[index] = e); + } + else + { + filter[index] = front[index].createCopy(); + } + } + + if (front.length != 4) + { + return Optional.empty(); + } + + return Optional.of(new SignTextData.Side(front, filter, color, glow)); + } + + public static Optional getSignTextEachSide(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + ITextComponent[] text = SignTextData.emptyArray(); + ITextComponent[] filter = null; + DyeColorCode color = DyeColorCode.BLACK; + boolean glow = false; + + if (data.containsList(NbtKeys.NEW_SIGN_MESSAGES, Constants.NBT.TAG_STRING)) + { + ListData list = data.getListOrDefault(NbtKeys.NEW_SIGN_MESSAGES, Constants.NBT.TAG_STRING, new ListData(Constants.NBT.TAG_STRING)); + + for (int i = 0; i < list.size() && i < 4; i++) + { + text[i] = DataTypeUtils.toTextComponent(list.getStringAt(i)).orElse(SignTextData.EMPTY); + } + } + + if (data.containsList(NbtKeys.NEW_SIGN_FILTERED, Constants.NBT.TAG_STRING)) + { + ListData list = data.getListOrDefault(NbtKeys.NEW_SIGN_FILTERED, Constants.NBT.TAG_STRING, new ListData(Constants.NBT.TAG_STRING)); + filter = SignTextData.emptyArray(); + + for (int i = 0; i < list.size() && i < 4; i++) + { + filter[i] = DataTypeUtils.toTextComponent(list.getStringAt(i)).orElse(SignTextData.EMPTY); + } + } + + if (data.contains(NbtKeys.NEW_SIGN_COLOR, Constants.NBT.TAG_INT)) + { + color = DyeColorCode.getByMeta(data.getInt(NbtKeys.NEW_SIGN_COLOR)); + } + + if (data.contains(NbtKeys.NEW_SIGN_GLOW, Constants.NBT.TAG_BYTE)) + { + glow = data.getBoolean(NbtKeys.NEW_SIGN_GLOW); + } + + if (text.length != 4) + { + return Optional.empty(); + } + + return Optional.of(new SignTextData.Side(text, filter, color, glow)); + } + + /** + * Designed to be compliant with 1.20.2+ with a front / back {@link Side} + */ + public static class SignTextData + { + public static final ITextComponent EMPTY = new TextComponentString(""); + private final Side front; + private final @Nullable Side back; + private final boolean waxed; + + SignTextData(Side front, @Nullable Side back, boolean waxed) + { + this.front = front; + this.back = back; + this.waxed = waxed; + } + + public Side front() { return this.front; } + + public @Nullable Side back() { return this.back; } + + public boolean waxed() { return this.waxed; } + + public static ITextComponent[] emptyArray() + { + return new ITextComponent[]{EMPTY, EMPTY, EMPTY, EMPTY}; + } + + public static class Side + { + private final ITextComponent[] text; + private final ITextComponent[] filter; + private final DyeColorCode color; + private final boolean glow; + + Side(@Nonnull ITextComponent[] text, @Nullable ITextComponent[] filter, @Nullable DyeColorCode color, boolean glow) + { + this.text = text; + this.filter = filter != null ? filter : text.clone(); + this.color = color != null ? color : DyeColorCode.BLACK; + this.glow = glow; + } + + public List text() { return new ArrayList<>(Arrays.asList(this.text)); } + + public List filter() { return new ArrayList<>(Arrays.asList(this.filter)); } + + public DyeColorCode color() { return this.color != null ? this.color : DyeColorCode.BLACK; } + + public boolean glow() { return this.glow; } + } + } + + /** + * Get the Skull Data from Data tag + * @param data - + * @return - + */ + public static Optional getSkullData(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + ITextComponent customName = null; + Identifier sound = null; + + if (data.contains(NbtKeys.NEW_SKULL_CUSTOM_NAME, Constants.NBT.TAG_COMPOUND)) + { + customName = DataTypeUtils.toTextComponent(data, NbtKeys.NEW_SKULL_CUSTOM_NAME).orElse(null); + } + + if (data.contains(NbtKeys.NEW_NOTE, Constants.NBT.TAG_STRING)) + { + sound = Identifier.of(data.getStringOrDefault(NbtKeys.NEW_NOTE, "")); + } + + if (data.contains(NbtKeys.NEW_SKULL_PROFILE, Constants.NBT.TAG_COMPOUND)) + { + GameProfile profile = getNewSkullProfile(data.getCompound(NbtKeys.NEW_SKULL_PROFILE)).orElse(null); + UUID uuid; + + if (profile != null && profile.getId() != null) + { + uuid = profile.getId(); + } + else + { + uuid = new UUID(0L, 0L); + } + + return Optional.of(new SkullProfileData(sound, customName, uuid, profile)); + } + else if (data.contains(NbtKeys.NEW_SKULL_PROFILE, Constants.NBT.TAG_STRING)) + { + // It's possible, that it "could" be a simple String of the name; according to the code. + UUID uuid = new UUID(0L, 0L); + GameProfile profile = new GameProfile(uuid, data.getStringOrDefault(NbtKeys.NEW_SKULL_PROFILE, "")); + return Optional.of(new SkullProfileData(sound, customName, uuid, profile)); + } + else + { + return getLegacySkullProfile(data); + } + } + + /** + * Get Legacy Skull Data + * @param data - + * @return - + */ + public static Optional getLegacySkullProfile(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + int type = -1; + int rot = -1; + GameProfile profile = null; + Identifier sound = null; + + if (data.contains(NbtKeys.SKULL_TYPE, Constants.NBT.TAG_BYTE)) + { + type = data.getByte(NbtKeys.SKULL_TYPE); + } + if (data.contains(NbtKeys.SKULL_ROT, Constants.NBT.TAG_BYTE)) + { + rot = data.getByte(NbtKeys.SKULL_ROT); + } + if (data.contains(NbtKeys.NEW_NOTE, Constants.NBT.TAG_STRING)) + { + sound = Identifier.of(data.getStringOrDefault(NbtKeys.NEW_NOTE, "")); + } + if (type == 3) + { + if (data.contains(NbtKeys.SKULL_OWNER, Constants.NBT.TAG_COMPOUND)) + { + profile = NBTUtil.readGameProfileFromNBT(DataConverterNbt.toVanillaCompound(data.getCompound(NbtKeys.SKULL_OWNER))); + } + else if (data.contains(NbtKeys.SKULL_EXTRA_TYPE, Constants.NBT.TAG_STRING)) + { + final String name = data.getStringOrDefault(NbtKeys.SKULL_EXTRA_TYPE, ""); + + if (!name.isEmpty()) + { + profile = new GameProfile(new UUID(0L, 0L), name); + } + } + } + + if (profile == null && type == -1) + { + return Optional.empty(); + } + + return Optional.of(new SkullProfileData(type, rot, sound, profile)); + } + + /** + * Get the "New" Skull Data + * @param data - + * @return - + */ + public static Optional getNewSkullProfile(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + String name = null; + UUID uuid = null; + + if (data.contains(NbtKeys.NEW_SKULL_NAME, Constants.NBT.TAG_STRING)) + { + name = data.getString(NbtKeys.NEW_SKULL_NAME); + } + + if (data.contains(NbtKeys.ID, Constants.NBT.TAG_INT_ARRAY)) + { + uuid = DataTypeUtils.readUuidFromIntArray(data, NbtKeys.ID); + } + + if (name == null && uuid == null) + { + return Optional.empty(); + } + + // We aren't bothering with the "properties" tag here. + return Optional.of(new GameProfile(uuid, name)); + } + + public static class SkullProfileData + { + private final int type; + private final int rot; + private final @Nullable Identifier sound; + private final @Nullable ITextComponent name; + private final @Nullable UUID uuid; + private final GameProfile profile; + + // Legacy + SkullProfileData(int type, int rot, @Nullable Identifier sound, GameProfile profile) + { + this(type, rot, sound, null, profile != null ? profile.getId() : null, profile); + } + + // 1.20.6+ + SkullProfileData(@Nullable Identifier sound, @Nullable ITextComponent name, @Nullable UUID uuid, GameProfile profile) + { + this(-1, -1, sound, name, uuid, profile); + } + + SkullProfileData(int type, int rot, @Nullable Identifier sound, @Nullable ITextComponent name, @Nullable UUID uuid, GameProfile profile) + { + this.type = type; + this.rot = rot; + this.sound = sound; + this.name = name; + this.uuid = uuid; + this.profile = profile; + } + + public int type() { return this.type; } + + public int rot() { return this.rot; } + + public Optional sound() { return Optional.ofNullable(this.sound); } + + public Optional name() { return Optional.ofNullable(this.name); } + + public Optional uuid() { return Optional.ofNullable(this.uuid); } + + public GameProfile profile() { return this.profile; } + } + + /** + * Get Flower Pot Item from Data tag + * @param data - + * @return - + */ + public static Optional> getFlowerPotItem(@Nonnull CompoundData data) + { + Item item = null; + int damage = 0; + + try + { + if (data.contains(NbtKeys.ITEM, Constants.NBT.TAG_STRING)) + { + item = Item.getByNameOrId(data.getString(NbtKeys.ITEM)); + } + else if (data.contains(NbtKeys.ITEM, Constants.NBT.TAG_INT)) + { + item = Item.getItemById(data.getInt(NbtKeys.ITEM)); + } + } + catch (Exception ignored) {} + + if (data.contains(NbtKeys.POT_DATA, Constants.NBT.TAG_INT)) + { + damage = data.getInt(NbtKeys.POT_DATA); + } + + if (item == null && damage == 0) + { + return Optional.empty(); + } + + return Optional.of(Pair.of(item, damage)); + } + + /** + * Get Bed Color by Data tag + * @param data - + * @return - + */ + public static DyeColorCode getBedColor(@Nonnull CompoundData data) + { + DyeColorCode color = DyeColorCode.RED; + + if (data.contains(NbtKeys.BED_COLOR, Constants.NBT.TAG_INT)) + { + color = DyeColorCode.getByMeta(data.getInt(NbtKeys.BED_COLOR)); + } + + return color; + } + + /** + * Read a Comparator's Output Signal from a Data tag + * @param data - + * @return - + */ + public static int getComparatorOutput(@Nonnull CompoundData data) + { + int signal = 0; + + if (data.contains(NbtKeys.OUTPUT_SIGNAL, Constants.NBT.TAG_INT)) + { + signal = data.getInt(NbtKeys.OUTPUT_SIGNAL); + } + + return signal; + } + + /** + * Get a Lectern's Book and Page number. + * + * @param data - + * @return - + * @implNote Lecterns were added under 1.14.x + */ + public static Optional> getBook(@Nonnull CompoundData data) // @Nonnull RegistryAccess registry + { + ItemStack book = null; + int current = -1; + + if (data.contains(NbtKeys.ORG_BOOK, Constants.NBT.TAG_COMPOUND)) + { + book = DataTypeUtils.toItemStack(data.getCompound(NbtKeys.ORG_BOOK)).orElse(ItemStack.EMPTY); + } + + if (data.contains(NbtKeys.ORG_PAGE, Constants.NBT.TAG_INT)) + { + current = data.getInt(NbtKeys.ORG_PAGE); + } + + if (book == null && current < 0) + { + return Optional.empty(); + } + + return Optional.of(Pair.of(book, current)); + } + + /** + * Get a Furnaces 'Used Recipes' from Data Tag. + * @implNote This seems to have begun around 1.14.x, and is modified around 1.17.x + * @param data - + * @return - + */ + public static Optional getUsedRecipes(@Nonnull CompoundData data) + { + HashMap recipesUsed = new HashMap<>(); + + if (data.contains(NbtKeys.ORG_RECIPES_USED, Constants.NBT.TAG_COMPOUND)) + { + CompoundData recipes = data.getCompound(NbtKeys.ORG_RECIPES_USED); + Set keys = recipes.getKeys(); + + for (String key : keys) + { + Identifier id = Identifier.of(key); + if (id == null) { continue; } + + int amount = recipes.getIntOrDefault(key, -1); + recipesUsed.put(id, amount); + } + } + else if (data.contains(NbtKeys.RECIPES_USED_SIZE, Constants.NBT.TAG_SHORT)) + { + final int total = data.getShortOrDefault(NbtKeys.RECIPES_USED_SIZE, (short) 0); + + for (int i = 0; i < total; i++) + { + String rl = ""; + int count = -1; + + if (data.contains(NbtKeys.RECIPE_LOCATION_PREFIX+i, Constants.NBT.TAG_STRING)) + { + rl = data.getString(NbtKeys.RECIPE_LOCATION_PREFIX+i); + } + if (data.contains(NbtKeys.RECIPE_AMOUNT_PREFIX+i, Constants.NBT.TAG_INT)) + { + count = data.getInt(NbtKeys.RECIPE_AMOUNT_PREFIX+i); + } + + Identifier id = Identifier.of(rl); + + if (id != null && count > 0) + { + recipesUsed.put(id, count); + } + } + } + + if (recipesUsed.isEmpty()) + { + return Optional.empty(); + } + + return Optional.of(new UsedRecipeData(recipesUsed)); + } + + public static class UsedRecipeData + { + private final HashMap recipes; + + public UsedRecipeData(HashMap recipes) + { + this.recipes = recipes; + } + + public HashMap recipes() + { + return this.recipes; + } + } + + // todo add Bee's + + /** + * Read the Crafter's "locked slots" from Data Tag + * + * @param data - + * @return - + * @implNote This first appears under 1.21+ + */ + public static ImmutableSet getDisabledSlots(@Nonnull CompoundData data) + { + ImmutableSet.Builder builder = ImmutableSet.builder(); + + if (data.contains(NbtKeys.NEW_DISABLED_SLOTS, Constants.NBT.TAG_INT_ARRAY)) + { + int[] is = data.getIntArray(NbtKeys.NEW_DISABLED_SLOTS); + + for (int j : is) + { + builder.add(j); + } + } + + return builder.build(); + } +} diff --git a/src/main/java/malilib/util/game/MinecraftVersion.java b/src/main/java/malilib/util/game/MinecraftVersion.java index 54c39fe9b5..2aca9e2f6d 100644 --- a/src/main/java/malilib/util/game/MinecraftVersion.java +++ b/src/main/java/malilib/util/game/MinecraftVersion.java @@ -30,6 +30,8 @@ public boolean equals(Object o) return this.dataVersion == that.dataVersion; } + public static final MinecraftVersion MC_1_21_11 = new MinecraftVersion("1.21.11", 4671, 774); + public static final MinecraftVersion MC_1_21_10 = new MinecraftVersion("1.21.10", 4556, 773); public static final MinecraftVersion MC_1_21_8 = new MinecraftVersion("1.21.8", 4440, 772); public static final MinecraftVersion MC_1_21_7 = new MinecraftVersion("1.21.7", 4438, 772); public static final MinecraftVersion MC_1_21_6 = new MinecraftVersion("1.21.6", 4435, 771); diff --git a/src/main/java/malilib/util/game/wrap/RegistryUtils.java b/src/main/java/malilib/util/game/wrap/RegistryUtils.java index d2ca3b3b3b..95e9bfef90 100644 --- a/src/main/java/malilib/util/game/wrap/RegistryUtils.java +++ b/src/main/java/malilib/util/game/wrap/RegistryUtils.java @@ -7,14 +7,17 @@ import javax.annotation.Nullable; import net.minecraft.block.Block; +import net.minecraft.block.BlockJukebox; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityList; import net.minecraft.init.Blocks; import net.minecraft.init.Items; import net.minecraft.item.Item; +import net.minecraft.tileentity.TileEntity; import net.minecraft.util.ResourceLocation; +import malilib.mixin.access.TileEntityMixin; import malilib.util.data.Identifier; import malilib.util.world.BlockState; @@ -87,6 +90,11 @@ public static List getSortedBlockList() return blocks; } + public static boolean isBlockValid(ResourceLocation id) + { + return Block.REGISTRY.containsKey(id); + } + public static List getSortedBlockStatesList() { List states = new ArrayList<>(); @@ -166,4 +174,49 @@ public static List getSortedItemList() return items; } + + public static boolean isItemValid(ResourceLocation id) + { + return Item.REGISTRY.containsKey(id); + } + + @SuppressWarnings("unchecked") + public static Class getTileEntityById(ResourceLocation id) + { + TileEntity te = new BlockJukebox.TileEntityJukebox(); + + if (((TileEntityMixin) te).malilib_getTileEntityRegistry().containsKey(id)) + { + return (Class) ((TileEntityMixin) te).malilib_getTileEntityRegistry().getObject(id); + } + + return null; + } + + public static Class getTileEntityByStr(String name) + { + return getTileEntityById(new ResourceLocation(name)); + } + + public static ResourceLocation getTileEntityId(TileEntity te) + { + return ((TileEntityMixin) te).malilib_getTileEntityRegistry().getNameForObject(te.getClass()); + } + + public static String getTileEntityIdStr(TileEntity te) + { + ResourceLocation id = getTileEntityId(te); + return id != null ? id.toString() : "?"; + } + + public static Collection getRegisteredTileEntityIds() + { + TileEntity te = new BlockJukebox.TileEntityJukebox(); + return ((TileEntityMixin) te).malilib_getTileEntityRegistry().getKeys(); + } + + public static boolean isTileEntityValid(ResourceLocation id) + { + return getRegisteredTileEntityIds().contains(id); + } } diff --git a/src/main/java/malilib/util/inventory/DataInventoryUtils.java b/src/main/java/malilib/util/inventory/DataInventoryUtils.java new file mode 100644 index 0000000000..a8ab091535 --- /dev/null +++ b/src/main/java/malilib/util/inventory/DataInventoryUtils.java @@ -0,0 +1,84 @@ +package malilib.util.inventory; + +import java.util.Optional; +import javax.annotation.Nonnull; +import malilib.util.data.Constants; +import malilib.util.data.Identifier; +import malilib.util.data.tag.CompoundData; +import malilib.util.data.tag.ListData; +import malilib.util.game.DataEntityUtils; +import malilib.util.nbt.NbtKeys; + +public class DataInventoryUtils +{ + /** + * Read any Items tag from a Tile Entity into a {@link DataInventoryView} utilizing the Data Tag system. + * @implNote 1.21+ requires the use of 'DynamicRegistry' aka 'RegistryAccess' + * @param data - + * @return - + */ + public static Optional getItemsAsDataInventory(@Nonnull CompoundData data) + // @Nonnull RegistryAccess registry + { + if (data.containsList(NbtKeys.ITEMS, Constants.NBT.TAG_COMPOUND)) + { + DataInventoryView inv = DataInventoryView.fromDataList(data.getList(NbtKeys.ITEMS, Constants.NBT.TAG_COMPOUND), false).orElse(null); + if (inv == null || inv.isEmpty()) { return Optional.empty(); } + + return Optional.of(inv.sorted()); + } + + return Optional.empty(); + } + + /** + * Read any Inventory tag from an Entity into a {@link DataInventoryView} utilizing the Data Tag system. + * @implNote 1.21+ requires the use of 'DynamicRegistry' aka 'RegistryAccess' + * @param data - + * @return - + */ + public static Optional getInventoryAsDataInventory(@Nonnull CompoundData data) + // @Nonnull RegistryAccess registry + { + Identifier id = DataEntityUtils.getEntityType(data).orElse(null); + Identifier player = Identifier.of("player"); + boolean isPlayer = false; + + if (id != null && player != null) + { + isPlayer = id.equals(player); + } + + if (data.containsList(NbtKeys.INVENTORY, Constants.NBT.TAG_COMPOUND)) + { + ListData list = data.getList(NbtKeys.INVENTORY, Constants.NBT.TAG_COMPOUND); + DataInventoryView inv = DataInventoryView.fromDataList(list, !isPlayer).orElse(null); + if (inv == null || inv.isEmpty()) { return Optional.empty(); } + + return Optional.of(inv.sorted()); + } + + return Optional.empty(); + } + + /** + * Read any EnderItems tag from a Player into a {@link DataInventoryView} utilizing the Data Tag system. + * @implNote 1.21+ requires the use of 'DynamicRegistry' aka 'RegistryAccess' + * @param data - + * @return - + */ + public static Optional getEnderItemsAsDataInventory(@Nonnull CompoundData data) + // @Nonnull RegistryAccess registry + { + if (data.containsList(NbtKeys.ENDER_ITEMS, Constants.NBT.TAG_COMPOUND)) + { + ListData list = data.getList(NbtKeys.ENDER_ITEMS, Constants.NBT.TAG_COMPOUND); + DataInventoryView inv = DataInventoryView.fromDataList(list, false).orElse(null); + if (inv == null || inv.isEmpty()) { return Optional.empty(); } + + return Optional.of(inv.sorted()); + } + + return Optional.empty(); + } +} diff --git a/src/main/java/malilib/util/inventory/DataInventoryView.java b/src/main/java/malilib/util/inventory/DataInventoryView.java new file mode 100644 index 0000000000..08162a7ee1 --- /dev/null +++ b/src/main/java/malilib/util/inventory/DataInventoryView.java @@ -0,0 +1,656 @@ +package malilib.util.inventory; + +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.Nonnull; +import malilib.util.MathUtils; +import malilib.util.data.Constants; +import malilib.util.data.tag.CompoundData; +import malilib.util.data.tag.ListData; +import malilib.util.data.tag.converter.DataConverterNbt; +import malilib.util.game.wrap.DefaultedList; +import malilib.util.nbt.NbtKeys; + +import net.minecraft.inventory.Container; +import net.minecraft.inventory.IInventory; +import net.minecraft.inventory.InventoryBasic; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.text.TextComponentString; + +/** + * This is useful as an Abstract Inventory Holder for all Inventory types. + * This is primarily for use with {@link malilib.util.data.tag.DataView} in a predictable manner. + * In the future, it can then add in the future NbtReaderView support as well from 1.21.6+ + */ +public class DataInventoryView implements InventoryView +// implements AutoCloseable +{ + public static final Comparator COMPARATOR = new EntrySlotComparator(); + public static final int VILLAGER_SIZE = 8; + public static final int DEFAULT_SIZE = 27; + public static final int PLAYER_SIZE = 36; + public static final int DOUBLE_SIZE = 54; + public static final int MAX_SIZE = 256; + private HashSet items; + + private DataInventoryView() {} + + /** + * Create a new blank {@link DataInventoryView} of the size specified. + * + * @param size - + * @return - + */ + public static DataInventoryView create(int size) + { + DataInventoryView newInv = new DataInventoryView(); + size = getAdjustedSize(MathUtils.clamp(size, 1, MAX_SIZE)); + newInv.buildEmptyList(size); + return newInv; + } + + /** + * Common Function to try to get the "corrected" Inventory size based on + * an existing `list.size()` for example. + *
+ * + * @param size The Size to adjust. + * @return The Adjusted Size. + */ + public static int getAdjustedSize(int size) + { + if (size <= VILLAGER_SIZE) + { + return size; + } + else if (size <= DEFAULT_SIZE) + { + return DEFAULT_SIZE; + } + else if (size <= PLAYER_SIZE) + { + return PLAYER_SIZE; + } + else if (size <= DOUBLE_SIZE) + { + return DOUBLE_SIZE; + } + else + { + return Math.min(size, MAX_SIZE); + } + } + + private void buildEmptyList(int size) throws RuntimeException + { + if (this.items != null) + { + throw new RuntimeException("List not empty!"); + } + + this.items = new HashSet(); + + for (int i = 0; i < size; i++) + { + this.items.add(new EntrySlot(i, ItemStack.EMPTY)); + } + } + + /** + * This exists because an NBT List can have empty slots not accounted for in the middle of its current size; + * Such as an empty slot in the middle of a Hopper Minecart. This code fixes the problem. + * + * @param slotsUsed - + */ + private void verifySize(List slotsUsed, int maxSlot) + { + int size = Math.max(this.size(), maxSlot); + + size = getAdjustedSize(size); + + for (int i = 0; i < size; i++) + { + if (!slotsUsed.contains(i)) + { + this.items.add(new EntrySlot(i, ItemStack.EMPTY)); + } + } + } + + /** + * Resort this {@link DataInventoryView} by Slot ID. + */ + public DataInventoryView sorted() + { + if (this.size() > 0) + { + List sorted = new ArrayList<>(this.items); + sorted.sort(COMPARATOR); + this.items.clear(); + this.items.addAll(sorted); + } + + return this; + } + + public boolean isEmpty() + { + if (this.items == null || this.items.isEmpty()) + { + return true; + } + AtomicBoolean bool = new AtomicBoolean(true); + + this.items.forEach( + (slot) -> + { + if (!slot.stack().isEmpty()) + { + bool.set(false); + } + }); + + return bool.get(); + } + + public int size() + { + if (this.items == null) + { + return -1; + } + return this.items.size(); + } + + // @Override + public void close() throws Exception + { + this.items.clear(); + } + + /** + * Return this Inventory as a {@link DefaultedList} + * + * @return - + */ + public DefaultedList toVanillaList(int size) + { + if (this.isEmpty()) + { + return DefaultedList.empty(); + } + + size = getAdjustedSize(MathUtils.clamp(size, this.size(), MAX_SIZE)); + + DefaultedList list = DefaultedList.ofSize(size, ItemStack.EMPTY); + AtomicInteger i = new AtomicInteger(0); + + this.items.forEach( + (slot) -> + { + list.set(slot.slot(), slot.stack()); + i.getAndIncrement(); + }); + + return list; + } + + /** + * Create a new {@link DataInventoryView} from a {@link DefaultedList}; making all the slot numbers the stack index. + * + * @param list - + * @return - + */ + public static Optional fromVanillaList(@Nonnull DefaultedList list) + { + int size = list.size(); + if (size < 1) + { + return Optional.empty(); + } + + size = getAdjustedSize(MathUtils.clamp(size, 1, MAX_SIZE)); + DataInventoryView newInv = new DataInventoryView(); + newInv.items = new HashSet<>(); + + for (int i = 0; i < size; i++) + { + newInv.items.add(new EntrySlot(i, list.get(i))); + } + + return Optional.of(newInv); + } + + /** + * Convert this Inventory to a Vanilla Inventory object. + * Supports oversized Inventories (MAX_SIZE) and DoubleInventory (DOUBLE_SIZE); or defaults to (DEFAULT_SIZE) + * + * @return - + */ + // todo when Vanilla allows for a 'SimpleInventory' Object ... + public Optional toInventory(final int size) + { + if (this.isEmpty()) + { + return Optional.empty(); + } + + int sizeAdj = getAdjustedSize(MathUtils.clamp(size, this.size(), MAX_SIZE)); + InventoryBasic inv = new InventoryBasic(new TextComponentString("dataInventory"), sizeAdj); + AtomicInteger i = new AtomicInteger(0); + + this.items.forEach( + (slot) -> + { + inv.setInventorySlotContents(slot.slot(), slot.stack()); + i.getAndIncrement(); + }); + + return Optional.of(inv); + } + + /** + * Creates a new {@link DataInventoryView} from a vanilla Inventory object; making all the slot numbers the stack index. + * + * @param inv - + * @return - + */ + // todo when Vanilla allows for a 'SimpleInventory' Object ... + public static DataInventoryView fromInventory(@Nonnull IInventory inv) + { + DataInventoryView newInv = new DataInventoryView(); + List slotsUsed = new ArrayList<>(); + int size = inv.getSizeInventory(); + int maxSlot = 0; + + size = getAdjustedSize(MathUtils.clamp(size, 1, MAX_SIZE)); + newInv.items = new HashSet<>(); + + for (int i = 0; i < size; i++) + { + EntrySlot slot = new EntrySlot(i, inv.getStackInSlot(i)); + + newInv.items.add(slot); + slotsUsed.add(slot.slot()); + + if (slot.slot() > maxSlot) + { + maxSlot = slot.slot(); + } + } + + newInv.verifySize(slotsUsed, maxSlot); + + return newInv; + } + + /** + * Creates a new {@link DataInventoryView} from a vanilla Inventory object; making all the slot numbers the stack index. + * + * @param inv - + * @return - + */ + public static DataInventoryView fromInventory(@Nonnull Container inv) + { + DataInventoryView newInv = new DataInventoryView(); + List slotsUsed = new ArrayList<>(); + int size = inv.getInventory().size(); + int maxSlot = 0; + + size = getAdjustedSize(MathUtils.clamp(size, 1, MAX_SIZE)); + newInv.items = new HashSet<>(); + + for (int i = 0; i < size; i++) + { + EntrySlot slot = new EntrySlot(i, inv.getInventory().get(i)); + + newInv.items.add(slot); + slotsUsed.add(slot.slot()); + + if (slot.slot() > maxSlot) + { + maxSlot = slot.slot(); + } + } + + newInv.verifySize(slotsUsed, maxSlot); + + return newInv; + } + + /** + * Uses the newer Vanilla 'WriterView' interface to write this Inventory to it; using our 'NbtView' wrapper. + * @param registry RegistryAccess object + * @return - + * @implNote This is used after 1.21.6 + */ +// public @Nullable NbtView toNbtWriterView(@Nonnull RegistryAccess registry) +// { +// if (this.isEmpty()) +// { +// return null; +// } +// +// final int size = getAdjustedSize(this.size()); +// +// NbtView view = NbtView.getWriter(registry); +// NonNullList list = this.toVanillaList(size); +// +// ContainerHelper.saveAllItems(Objects.requireNonNull(view.getWriter()), list); +// +// return view; +// } + + /** + * Uses the newer Vanilla 'ReaderView' interface to create a new NbtInventory; using our 'NbtView' wrapper. + * @param view - + * @param size - + * @return - + * @implNote This is used after 1.21.6 + */ +// public static @Nullable DataInventory fromNbtReaderView(@Nonnull NbtView view, int size) +// { +// if (size < 1) +// { +// return null; +// } +// +// size = getAdjustedSize(MathUtils.clamp(size, 1, MAX_SIZE)); +// NonNullList list = NonNullList.withSize(size, ItemStack.EMPTY); +// +// ContainerHelper.saveAllItems(Objects.requireNonNull(view.getReader()), list); +// return fromVanillaList(list); +// } + + /** + * Converts the first {@link DataInventoryView} element to a single {@link CompoundData}. + * + * @return - + * @throws RuntimeException - + */ + public CompoundData toDataSingle() throws RuntimeException // (@Nonnull RegistryAccess registry) + { + if (this.size() > 1) + { + throw new RuntimeException("Inventory is too large for a single entry!"); + } + + EntrySlot slot = this.items.stream().findFirst().orElse(new EntrySlot(0, ItemStack.EMPTY)); + + if (!slot.stack().isEmpty()) + { + return slot.toData(); + } + + return new CompoundData(); + } + + /** + * Converts this {@link DataInventoryView} to a basic {@link ListData} with Slot information. + * + * @return - + * @throws RuntimeException - + */ + public ListData toDataList() // (@Nonnull RegistryAccess registry) + { + ListData list = new ListData(Constants.NBT.TAG_COMPOUND); + if (this.isEmpty()) + { + return list; + } + + this.items.forEach( + (slot) -> + { + if (!slot.stack().isEmpty()) + { + list.add(slot.toData()); + } + }); + + return list; + } + + /** + * Writes this {@link DataInventoryView} to a Data Type (List or Compound) using a key; with slot information. + * + * @param type - + * @param key - + * @return - + * @throws RuntimeException - + */ + public CompoundData toData(int type, String key) throws RuntimeException + // (@Nonnull RegistryAccess registry) + { + CompoundData data = new CompoundData(); + + if (type == Constants.NBT.TAG_LIST) + { + ListData list = this.toDataList(); + + if (list.isEmpty()) + { + return data; + } + + return data.put(key, list); + } + else if (type == Constants.NBT.TAG_COMPOUND) + { + return data.put(key, this.toDataSingle()); + } + + throw new RuntimeException("Unsupported Data Type!"); + } + + /** + * Creates a new {@link DataInventoryView} from a Data Type (List or Compound) using a key; retains slot information. + * + * @param data - + * @param key The Key of the Data to read + * @param noSlotId If the List doesn't include Slots, generate them using inventory index + * @return - + * @throws RuntimeException - + */ + public static Optional fromData(@Nonnull CompoundData data, String key, boolean noSlotId) + throws RuntimeException + // (@Nonnull RegistryAccess registry) + { + if (data.isEmpty()) + { + return Optional.empty(); + } + + if (data.containsList(key, Constants.NBT.TAG_COMPOUND)) + { + return fromDataList(data.getList(key, Constants.NBT.TAG_COMPOUND), noSlotId); + } + else if (data.contains(key, Constants.NBT.TAG_COMPOUND)) + { + return fromDataSingle(data.getCompound(key)); + } + else + { + throw new RuntimeException("Invalid Data Type!"); + } + } + + /** + * Creates a new {@link DataInventoryView} from a single-member {@link CompoundData} containing a single item with a slot number. + * + * @param data - + * @return - + * @throws RuntimeException - + */ + public static Optional fromDataSingle(@Nonnull CompoundData data) throws RuntimeException + // (@Nonnull RegistryAccess registry) + { + if (data.isEmpty()) + { + return Optional.empty(); + } + DataInventoryView newInv = new DataInventoryView(); + CompoundData tag = checkDataForIDOverrides(data); + + newInv.items = new HashSet<>(); + newInv.items.add(EntrySlot.fromData(tag)); + + return Optional.of(newInv); + } + + /** + * Creates a new {@link DataInventoryView} from an {@link ListData}; utilizing Slot information. + * + * @param list - + * @param noSlotId If the List doesn't include Slots, generate them using inventory index + * @return - + * @throws RuntimeException - + */ + public static Optional fromDataList(@Nonnull ListData list, boolean noSlotId) + throws RuntimeException + // (@Nonnull RegistryAccess registry) + { + if (list.isEmpty()) + { + return Optional.empty(); + } + else if (list.size() > MAX_SIZE) + { + throw new RuntimeException("Data List is too large!"); + } + + int size = list.size(); + size = getAdjustedSize(MathUtils.clamp(size, 1, MAX_SIZE)); + DataInventoryView newInv = new DataInventoryView(); + List slotsUsed = new ArrayList<>(); + int maxSlot = 0; + + newInv.items = new HashSet<>(); + + for (int i = 0; i < list.size(); i++) + { + CompoundData tag = checkDataForIDOverrides(list.getCompoundAt(i)); + EntrySlot slot; + + // Some lists, such as the "Inventory" tag does not include slot ID's + if (noSlotId) + { + slot = EntrySlot.fromData(tag); + slot.setSlot(i); + } + else + { + slot = EntrySlot.fromData(tag); + } + + newInv.items.add(slot); + slotsUsed.add(slot.slot()); + + if (slot.slot() > maxSlot) + { + maxSlot = slot.slot(); + } + } + + newInv.verifySize(slotsUsed, maxSlot); + + return Optional.of(newInv); + } + + /** + * Primarily for Broken NBT (Item ID) situations where the Server + * might not be equal in version over ViaVersion, and the like. + * Such problems arise under the DataInventory. + * + * @param in - + * @return - + */ + private static CompoundData checkDataForIDOverrides(CompoundData in) + { +// String id = in.getStringOrDefault(NbtKeys.ID, ""); +// +// if (NbtOverrides.ID_OVERRIDES.containsKey(id)) +// { +// id = NbtOverrides.ID_OVERRIDES.get(id); +// in.putString(NbtKeys.ID, id); +// } + + return in; + } + + @Override + public int getSize() + { + return this.size(); + } + + @Override + public ItemStack getStack(int slot) + { + AtomicReference result = new AtomicReference<>(ItemStack.EMPTY); + + this.items.forEach( + entry -> + { + if (entry.slot() == slot) + { + result.set(entry.stack().copy()); + } + }); + + return result.get(); + } + + /** + * Equivalence with ItemStackWithSlot from ~1.21.8+ + */ + public static class EntrySlot + { + private final ItemStack stack; + private int slot; + + public EntrySlot(int slot, ItemStack stack) + { + this.slot = slot; + this.stack = stack.copy(); + } + + public void setSlot(int slot) + { + this.slot = slot; + } + + public int slot() {return this.slot;} + + public ItemStack stack() {return this.stack;} + + // todo Use DataOps to Serialize ItemStack in the future + public CompoundData toData() // (@Nonnull RegistryAccess registry) + { + NBTTagCompound nbt = new NBTTagCompound(); + nbt.setByte(NbtKeys.SLOT, (byte) this.slot); + this.stack.writeToNBT(nbt); + return DataConverterNbt.fromVanillaCompound(nbt); + } + + // todo Use DataOps to Deserialize ItemStack in the future + public static EntrySlot fromData(CompoundData data) // (@Nonnull RegistryAccess registry) + { + final int slot = data.getByteOrDefault(NbtKeys.SLOT, (byte) 0) & 0xFF; + final ItemStack stack = new ItemStack(DataConverterNbt.toVanillaCompound(data)); + return new EntrySlot(slot, stack.copy()); + } + } + + public static class EntrySlotComparator implements Comparator + { + @Override + public int compare(EntrySlot o1, EntrySlot o2) + { + return Integer.compare(o1.slot(), o2.slot()); + } + } +} diff --git a/src/main/java/malilib/util/nbt/NbtKeys.java b/src/main/java/malilib/util/nbt/NbtKeys.java new file mode 100644 index 0000000000..fd0259a05b --- /dev/null +++ b/src/main/java/malilib/util/nbt/NbtKeys.java @@ -0,0 +1,539 @@ +package malilib.util.nbt; + +/** + * NBT Tag Constants Library -- + * This is meant to help keep track of NBT Key changes across Minecraft; and to use them while parsing NBT tags. + */ +public class NbtKeys +{ + // Generic + public static final String ID = "id"; // Entity, Tile Entity, Items, etc + public static final String POS = "Pos"; // Entity (List, Double) + public static final String CUSTOM_NAME = "CustomName"; // Tile Entity / Entity (Str) + public static final String COLOR = "Color"; // Banner Pattern, Shulker, Sign, Sheep (Byte) --> DyeColor + public static final String UUID = "UUID"; // All Over the place + public static final String UUID_MOST = "UUIDMost"; // UUID (Most) + public static final String UUID_LEAST = "UUIDLeast"; // UUID (Least) + + // Inventory + public static final String ITEMS = "Items"; // Brewing Stand, Furnace, Chest, Disp/Drop, Hopper, Shulker, Minecart, ChestedHorse, etc. + public static final String INVENTORY = "Inventory"; // Player, Villager, Zombified Pigmen, Pillagers, etc. (List, Comp) -> Slotted ItemStacks + public static final String SLOT = "Slot"; // Any Slotted Inventory + public static final String ITEM = "Item"; // Flower Pot, ItemEntity, Item Frame (Single Item Inventory) + public static final String LOOT_TABLE = "LootTable"; // Lootable Container (Str) + public static final String LOOT_TABLE_SEED = "LootTableSeed"; // Lootable Container (Long) + + // Item Tags General + public static final String COUNT = "Count"; // ItemStack (Byte) + public static final String DAMAGE = "Damage"; // ItemStack (Short) + public static final String TAG = "tag"; // ItemStack (Comp) + public static final String ENCHANTMENTS = "ench"; // ItemStack (List, Comp) --> + public static final String ENCH_ID = "id"; // ItemStack, Ench (Short) --> Enchantment + public static final String ENCH_LEVEL = "lvl"; // ItemStack, Ench (Short) + public static final String DISPLAY = "display"; // ItemStack (Comp) --> + public static final String DISP_NAME = "Name"; // display (Str) + public static final String DISP_LOC_NAME = "LocName"; // display (Str) + public static final String DISP_COLOR = "color"; // display (Int) + public static final String DISP_LORE = "Lore"; // display (List, Str) --> Text + public static final String DISP_MAP_COLOR = "MapColor"; // display (Int) + public static final String HIDE_FLAGS = "HideFlags"; // ItemStack (Int) + public static final String UNBREAKABLE = "Unbreakable"; // ItemStack (Bool) + public static final String REPAIR_COST = "RepairCost"; // ItemStack (Int) + public static final String CAN_DESTROY = "CanDestroy"; // ItemStack (List, Str) --> Block + public static final String CAN_PLACE_ON = "CanPlaceOn"; // ItemStack (List, Str) --> Block + public static final String ATTRIBUTE_MODIFIERS = "AttributeModifiers"; // ItemStack (List, Comp) --> AttributeModifier + public static final String ATTRIBUTE_NAME = "AttributeName"; // ItemStack, AttribMod (Str) + public static final String ATTRIBUTE_SLOT = "Slot"; // ItemStack, AttribMod (Str) + public static final String ATTR_MOD_NAME = "Name"; // ItemStack, AttribMod (Str) + public static final String ATTR_MOD_AMOUNT = "Amount"; // ItemStack, AttribMod (Double) + public static final String ATTR_MOD_OPERATION = "Operation"; // ItemStack, AttribMod (Int) + public static final String ATTR_MOD_UUID = "UUID"; // ItemStack, AttribMod (UUID) + public static final String BLOCK_ENTITY = "BlockEntityTag"; // ItemStack (Comp) + + // Item Tags Specific + public static final String ITEM_SKULL_OWNER = "SkullOwner"; // Skull (Str) --> GameProfile + public static final String MAP_SCALE_DIR = "map_scale_direction"; // Map (Int) + public static final String MAP_TRACKING_POS = "map_tracking_position"; // Map (Bool) + public static final String MAP_DECORATIONS = "Decorations"; // Map (List, Comp) + public static final String MAP_DECO_TYPE = "type"; // Map, Deco (Byte) + public static final String MAP_DECO_ID = "id"; // Map, Deco (Str) + public static final String MAP_DECO_X = "x"; // Map, Deco (Double) + public static final String MAP_DECO_Z = "z"; // Map, Deco (Double) + public static final String MAP_DECO_ROT = "rot"; // Map, Deco (Double) + public static final String STORED_ENCHANTMENTS = "StoredEnchantments"; // Enchanted Book (List, Comp) + public static final String FIREWORKS = "Fireworks"; // Firework (Comp) + public static final String FW_FLIGHT = "Flight"; // Firework (Byte) + public static final String FW_EXPLOSIONS = "Explosions"; // Firework (List, Comp) + public static final String FW_EXPLOSION = "Explosion"; // Firework Charge (Comp) + public static final String FW_EXP_TYPE = "Type"; // Firework Charge (Byte) + public static final String FW_EXP_COLORS = "Colors"; // Firework Charge (Int Array) + public static final String FW_EXP_FADE_COLORS = "FadeColors"; // Firework Charge (Int Array) + public static final String FW_EXP_TRAIL = "Trail"; // Firework Charge (Bool) + public static final String FW_EXP_FLICKER = "Flicker"; // Firework Charge (Bool) + public static final String BOOK_RECIPES = "Recipes"; // KnowledgeBook (List, Str) -> Recipe + public static final String ENTITY_TAG = "EntityTag"; // SpawnEgg (Comp) + public static final String BOOK_PAGES = "pages"; // WritableBook (List, Str) + public static final String BOOK_AUTHOR = "author"; // WrittenBook (Str) + public static final String BOOK_RESOLVED = "resolved"; // WrittenBook (Bool) + + // Tile/Block Entity + public static final String TE_POS_X = "x"; // [Base] (Int) + public static final String TE_POS_Y = "y"; // [Base] (Int) + public static final String TE_POS_Z = "z"; // [Base] (Int) + public static final String LOCK = "Lock"; // Lockable Container (Str) + public static final String BASE_COLOR = "Base"; // Banner (Int) + public static final String PATTERNS = "Patterns"; // Banner (LIST: Color, Pattern) + public static final String PATTERN = "Pattern"; // Banner, Pattern + public static final String PRIMARY_EFFECT = "Primary"; // Beacon (Int) + public static final String SECONDARY_EFFECT = "Secondary"; // Beacon (Int) + public static final String LEVELS = "Levels"; // Beacon (Int) + public static final String BED_COLOR = "color"; // Dye Color (Int) + public static final String BREW_TIME = "BrewTime"; // Bewing Stand (Short) + public static final String FUEL = "Fuel"; // Brewing Stand (Byte) + public static final String OUTPUT_SIGNAL = "OutputSignal"; // Comparator (Int) + public static final String PORTAL_AGE = "Age"; // End Gateway (Long) + public static final String EXIT_PORTAL = "ExitPortal"; // End Gateway (Comp/Pos) + public static final String EXACT_TELEPORT = "ExactTeleport"; // End Gateway (Bool) + public static final String POT_DATA = "Data"; // Flower Pot (Int) + public static final String BURN_TIME = "BurnTime"; // Furnace (Short) + public static final String COOK_TIME_SPENT = "CookTime"; // Furnace (Short) + public static final String COOK_TIME_TOTAL = "CookTimeTotal"; // Furnace (Short) + public static final String RECIPES_USED_SIZE = "RecipesUsedSize"; // Furnace (Short) + public static final String RECIPE_LOCATION_PREFIX = "RecipeLocation"; // Furnace (Str) + public static final String RECIPE_AMOUNT_PREFIX = "RecipeAmount"; // Furnace (Int) + public static final String TRANSFER_COOLDOWN = "TransferCooldown"; // Hopper, Hopper MC (Int) + public static final String NOTE = "note"; // Note Block (Byte) + public static final String SIGN_TEXT_PREFIX = "Text"; // Sign (Str) + public static final String SIGN_FILTER_PREFIX = "FilteredText"; // Sign (Str) + public static final String SIGN_GLOWING = "GlowingText"; // Sign (Bool) + public static final String COMMAND_STATS = "CommandStats"; // Sign (Compound, Dumb) + public static final String RECORD_ITEM = "RecordItem"; // Jukebox (Comp) --> ItemStack (Single Item Inventory) + public static final String RECORD = "Record"; // Jukebox (Int) --> ItemStack (By Item ID) + + // Command Block, (Minecart also) + public static final String POWERED = "powered"; // Command, Note Block, Creeper (Bool) + public static final String COND_MET = "conditionMet"; // Bool + public static final String AUTO = "auto"; // Bool + public static final String COMMAND = "Command"; // Str + public static final String SUCCESS_COUNT = "SuccessCount"; // Int + public static final String TRACK_OUTPUT = "TrackOutput"; // Bool + public static final String LAST_OUTPUT = "LastOutput"; // Str + public static final String UPD_LAST_EXEC = "UpdateLastExecution"; // Bool + public static final String LAST_EXEC = "LastExecution"; // Long + + // Monster Spawner, (Minecart also) + public static final String SPAWN_POTENTIALS = "SpawnPotentials"; // (List, Compound) + public static final String SPAWN_ID = "id"; // (Potentials, Str) + public static final String SPAWN_ENTITY = "Entity"; // (Potentials, Compound) + public static final String SPAWN_WEIGHT = "Weight"; // (Potentials, Int) + public static final String SPAWN_DATA = "SpawnData"; // Compound + public static final String SPAWN_DELAY = "Delay"; // Short + public static final String MIN_SPAWN_DELAY = "MinSpawnDelay"; // (Numeric, Short) + public static final String MAX_SPAWN_DELAY = "MaxSpawnDelay"; // Short + public static final String SPAWN_COUNT = "SpawnCount"; // Short + public static final String MAX_NEARBY_ENT = "MaxNearbyEntities"; // (Numeric, Short) + public static final String REQ_PLAYER_RANGE = "RequiredPlayerRange"; // Short + public static final String SPAWN_RANGE = "SpawnRange"; // (Numeric, Short) + + // Piston + public static final String BLOCK_ID = "blockId"; // Int + public static final String BLOCK_DATA = "blockData"; // Int + public static final String FACING = "facing"; // Int + public static final String PROGRESS = "progress"; // Float + public static final String EXTENDING = "extending"; // Bool + public static final String SOURCE = "source"; // Bool + + // Skull + public static final String SKULL_TYPE = "SkullType"; // Skull (Byte) + public static final String SKULL_ROT = "Rot"; // Skull (Byte) + public static final String SKULL_OWNER = "Owner"; // Skull (Comp, Game Profile) + public static final String SKULL_EXTRA_TYPE = "ExtraType"; // Skull (Str, Game Profile Name) + public static final String PROFILE_NAME = "Name"; // Profile (Str) + public static final String PROFILE_ID = "Id"; // Profile (Str) + public static final String PROFILE_PROPS = "Properties"; // Profile (Compound) -> (Keyed List) + public static final String PROFILE_VALUE = "Value"; // Profile, Properties List (Str) + public static final String PROFILE_SIGNATURE = "Signature"; // Profile, Properties List (Str) + + // Entity [Base] + public static final String MOTION = "Motion"; // [Base] Double List + public static final String ROTATION = "Rotation"; // [Base] Float List + public static final String FALL_DISTANCE = "FallDistance"; // [Base] Float + public static final String FIRE = "Fire"; // [Base] Short + public static final String AIR = "Air"; // [Base] Short + public static final String ON_GROUND = "OnGround"; // [Base] Bool + public static final String DIMENSION = "Dimension"; // [Base] Int + public static final String INVULNERABLE = "Invulnerable"; // [Base] Bool + public static final String PORTAL_COOLDOWN = "PortalCooldown"; // [Base] Int + public static final String CUSTOM_NAME_VISIBLE = "CustomNameVisible"; // [Base] Bool + public static final String SILENT = "Silent"; // [Base] Bool + public static final String NO_GRAVITY = "NoGravity"; // [Base] Bool + public static final String GLOWING = "Glowing"; // [Base] Bool + public static final String COMMAND_TAGS = "Tags"; // [Base] String List + public static final String PASSENGERS = "Passengers"; // [Base] Compound List (Entity NBT) + public static final String AGE = "Age"; // ItemEntity, XP (Short) + public static final String ITEM_HEALTH = "Health"; // ItemEntity, XP (Short) + public static final String PICKUP_DELAY = "PickupDelay"; // ItemEntity (Short) + public static final String THROWER = "Thrower"; // ItemEntity (Str) + public static final String OWNER = "Owner"; // ItemEntity (Str) + public static final String FUSE = "Fuse"; // TNT Primed, Creeper (Short) + public static final String FANGS_WARMUP = "Warmup"; // Fangs (Int) + public static final String FANGS_OWNER = "OwnerUUID"; // Fangs, Effect Cloud (UUID) + public static final String BEAM_TARGET = "BeamTarget"; // End Crystal (Comp, Pos) + public static final String SHOW_BOTTOM = "ShowBottom"; // End Crystal (Bool) + public static final String MC_CUSTOM_DISPLAY = "CustomDisplayTile"; // Minecart (Bool) + public static final String MC_DISPLAY_TITLE = "DisplayTile"; // Minecart (Str) -> Block + public static final String MC_DISPLAY_DATA = "DisplayData"; // Minecart (Int) + public static final String MC_DISPLAY_OFFSET = "DisplayOffset"; // Minecart (Int) + public static final String MC_TNT_FUSE = "TNTFuse"; // TNT Minecart (Int) + public static final String MC_FURNACE_PUSH_X = "PushX"; // Furnace Minecart (Double) + public static final String MC_FURNACE_PUSH_Z = "PushZ"; // Furnace Minecart (Double) + public static final String MC_FURNACE_FUEL = "Fuel"; // Furnace Minecart (Short) + public static final String MC_HOPPER_ENABLED = "Enabled"; // Hopper Minecart (Bool) + public static final String FALLING_BLOCK = "Block"; // FB (Str) -> Block + public static final String FALLING_DATA = "Data"; // FB (Byte) + public static final String FALLING_TIME = "Time"; // FB (Int) + public static final String FALLING_DROP_ITEM = "DropItem"; // FB (Bool) + public static final String FALLING_HURT_ENT = "HurtEntities"; // FB (Bool) + public static final String FALLING_HURT_AMT = "FallHurtAmount"; // FB (Float) + public static final String FALLING_HURT_MAX = "FallHurtMax"; // FB (Int) + public static final String TILE_ENT_DATA = "TileEntityData"; // FB (Compound) -> TE + public static final String SH_BUL_STEPS = "Steps"; // SH BUL (Int) + public static final String SH_BUL_TXD = "TXD"; // SH BUL (Double) + public static final String SH_BUL_TYD = "TYD"; // SH BUL (Double) + public static final String SH_BUL_TZD = "TZD"; // SH BUL (Double) + public static final String SH_BUL_DIRECTION = "Dir"; // SH BUL (Int) + public static final String SH_BUL_OWNER = "Owner"; // SH BUL (Compound) + public static final String SH_BUL_OWNER_POS_X = "X"; // SH BUL (Owner, Int) + public static final String SH_BUL_OWNER_POS_Y = "Y"; // SH BUL (Owner, Int) + public static final String SH_BUL_OWNER_POS_Z = "Z"; // SH BUL (Owner, Int) + public static final String SH_BUL_TARGET = "Target"; // SH BUL (Compound) + public static final String SH_BUL_TARGET_POS_X = "X"; // SH BUL (Target, Int) + public static final String SH_BUL_TARGET_POS_Y = "Y"; // SH BUL (Target, Int) + public static final String SH_BUL_TARGET_POS_Z = "Z"; // SH BUL (Target, Int) + public static final String FIREWORKS_LIFE = "Life"; // FW (Int) + public static final String FIREWORKS_LIFE_TIME = "LifeTime"; // FW (Int) + public static final String FIREWORKS_ITEM = "FireworksItem"; // FW (Compound) -> ItemStack + public static final String TYPE = "Type"; // Boat (Str) + public static final String PROJ_TILE_X = "xTile"; // Arrow, Projectile (Int) + public static final String PROJ_TILE_Y = "yTile"; // Arrow, Projectile (Int) + public static final String PROJ_TILE_Z = "zTile"; // Arrow, Projectile (Int) + public static final String PROJ_IN_TILE = "inTile"; // Arrow, Projectile (Str) + public static final String PROJ_SHAKE = "shake"; // Arrow, Projectile (Byte) + public static final String PROJ_IN_GROUND = "inGround"; // Arrow, Projectile (Byte) + public static final String PROJ_POTION = "Potion"; // Potion (Compound) -> ItemStack + public static final String ARROW_LIFE = "life"; // Arrow (Short) + public static final String ARROW_IN_DATA = "inData"; // Arrow (Byte) + public static final String ARROW_DAMAGE = "damage"; // Arrow (Double) + public static final String ARROW_PICKUP = "pickup"; // Arrow (Byte) + public static final String ARROW_PLAYER = "player"; // Arrow (Bool) + public static final String ARROW_CRIT = "crit"; // Arrow (Bool) + public static final String ARROW_DURATION = "Duration"; // Arrow, Spectral (Int) + public static final String ARROW_POTION = "Potion"; // Arrow, Tipped (Str) + public static final String ARROW_COLOR = "Color"; // Arrow, Tipped (Int) + public static final String ARROW_EFFECTS = "CustomPotionEffects"; // Arrow, Tipped (List, Comp) -> PotionEffect + public static final String EXP_VALUE = "Value"; // EXP Orb (Short) + public static final String EFF_AGE = "Age"; // Eff Cloud (Int) + public static final String EFF_DURATION = "Duration"; // Eff Cloud (Int) + public static final String EFF_WAIT_TIME = "WaitTime"; // Eff Cloud (Int) + public static final String EFF_RE_APP_DELAY = "ReapplicationDelay"; // Eff Cloud (Int) + public static final String EFF_DURATION_ON_USE = "DurationOnUse"; // Eff Cloud (Int) + public static final String EFF_RADIUS_ON_USE = "RadiusOnUse"; // Eff Cloud (Float) + public static final String EFF_RADIUS_TICK = "RadiusPerTick"; // Eff Cloud (Float) + public static final String EFF_RADIUS = "Radius"; // Eff Cloud (Float) + public static final String EFF_PARTICLE = "Particle"; // Eff Cloud (Str) -> ParticleType + public static final String EFF_PARTICLE_1 = "ParticleParam1"; // Eff Cloud (Int) + public static final String EFF_PARTICLE_2 = "ParticleParam2"; // Eff Cloud (Int) + public static final String EFF_COLOR = "Color"; // Eff Cloud (Int) + public static final String EFF_POTION_TYPE = "Potion"; // Eff Cloud (Str) -> PotionType + public static final String EFF_EFFECTS = "Effects"; // Eff Cloud (List, Comp) -> PotionEffect + public static final String EFFECT_ID = "Id"; // [Base] PotionEffect (Byte) + public static final String EFFECT_AMPLIFIER = "Amplifier"; // [Base] PotionEffect (Byte) + public static final String EFFECT_DURATION = "Duration"; // [Base] PotionEffect (Int) + public static final String EFFECT_AMBIENT = "Ambient"; // [Base] PotionEffect (Bool) + public static final String EFFECT_SHOW_PART = "ShowParticles"; // [Base] PotionEffect (Bool) + public static final String FACING_2 = "Facing"; // Hanging (Byte) + public static final String TILE_X = "TileX"; // Hanging (Int) -> Pos + public static final String TILE_Y = "TileY"; // Hanging (Int) -> Pos + public static final String TILE_Z = "TileZ"; // Hanging (Int) -> Pos + public static final String ITEM_ROTATION = "ItemRotation"; // Item Frame (Byte) + public static final String ITEM_DROP_CHANCE = "ItemDropChance"; // Item Frame (Float) + public static final String PAINTING_TYPE = "Motive"; // Painting (Str) + public static final String EXPLOSION_POWER = "ExplosionPower"; // Large Fireball, Ghast (Int) + public static final String HEALTH = "Health"; // [LivingBase] (Float) + public static final String HURT_TIME = "HurtTime"; // [LivingBase] (Short) + public static final String HURT_BY_TS = "HurtByTimestamp"; // [LivingBase] (Int) + public static final String DEATH_TIME = "DeathTime"; // [LivingBase] (Short) + public static final String ABSORB_AMT = "AbsorptionAmount"; // [LivingBase] (Float) + public static final String ATTRIBUTES = "Attributes"; // [LivingBase] (List, Comp) -- + public static final String ATTRIB_NAME = "Name"; // [LivingBase] Attrib (Str) -> AttribInstance + public static final String ATTRIB_BASE = "Base"; // [LivingBase] AttribInstance (Double) + public static final String ATTRIB_MODIFIERS = "Modifiers"; // [LivingBase] AttribInstance (List, Comp) -> AttribModifier + public static final String MODIFIER_UUID = "UUID"; // [LivingBase] AttribModifier (UUID) + public static final String MODIFIER_AMOUNT = "Amount"; // [LivingBase] AttribModifier (Double) + public static final String ACTIVE_EFFECTS = "ActiveEffects"; // [LivingBase] (List, Comp) -> PotionEffect + public static final String FALL_FLYING = "FallFlying"; // [LivingBase] (Bool) + public static final String TEAM = "Team"; // [LivingBase] (Str) + public static final String ARMOR_ITEMS = "ArmorItems"; // AS, Living (List, Comp) -> ItemStack + public static final String HAND_ITEMS = "HandItems"; // AS, Living (List, Comp) -> ItemStack + public static final String INVISIBLE = "Invisible"; // AS (Bool) + public static final String AS_SMALL = "Small"; // AS (Bool) + public static final String AS_SHOW_ARMS = "ShowArms"; // AS (Bool) + public static final String AS_DISABLED_SLOTS = "DisabledSlots"; // AS (Int) + public static final String AS_NO_BASE_PLATE = "NoBasePlate"; // AS (Bool) + public static final String AS_MARKER = "Marker"; // AS (Bool) + public static final String AS_POSE = "Pose"; // AS (Compound) + public static final String AS_POSE_HEAD = "Head"; // AS (List, Float) [x y z] + public static final String AS_POSE_BODY = "Body"; // AS (List, Float) [x y z] + public static final String AS_POSE_L_ARM = "LeftArm"; // AS (List, Float) [x y z] + public static final String AS_POSE_R_ARM = "RightArm"; // AS (List, Float) [x y z] + public static final String AS_POSE_L_LEG = "LeftLeg"; // AS (List, Float) [x y z] + public static final String AS_POSE_R_LEG = "RightLeg"; // AS (List, Float) [x y z] + public static final String CAN_PICKUP_LOOT = "CanPickUpLoot"; // Living (Bool) + public static final String PERSISTENCE = "PersistenceRequired"; // Living (Bool) + public static final String ARMOR_DROP_CHANCES = "ArmorDropChances"; // Living (List, Float) + public static final String HAND_DROP_CHANCES = "HandDropChances"; // Living (List, Float) + public static final String LEASHED = "Leashed"; // Living (Bool) + public static final String LEASH = "Leash"; // Living (Compound) + public static final String LEFT_HANDED = "LeftHanded"; // Living (Bool) + public static final String DEATH_LOOT = "DeathLootTable"; // Living (Str) + public static final String DEATH_LOOT_SEED = "DeathLootTableSeed"; // Living (Long) + public static final String NO_AI = "NoAI"; // Living (Bool) + public static final String BAT_FLAGS = "BatFlags"; // Bat (Byte) + public static final String DRAGON_PHASE = "DragonPhase"; // Dragon (Int) + public static final String SIZE = "Size"; // Slime (Int) + public static final String WAS_ON_GROUND = "wasOnGround"; // Slime (Bool) + public static final String PLAYER_CREATED = "PlayerCreated"; // Iron Golem (Bool) + public static final String PUMPKIN = "Pumpkin"; // Snow Golem (Bool) + public static final String SHULKER_ATTACH_FACE = "AttachFace"; // Shulker (Byte) + public static final String SHULKER_PEEK = "Peek"; // Shulker (Byte) + public static final String SHULKER_APX = "APX"; // Shulker (Int) + public static final String SHULKER_APY = "APY"; // Shulker (Int) + public static final String SHULKER_APZ = "APZ"; // Shulker (Int) + public static final String EXPLOSION_RADIUS = "ExplosionRadius"; // Creeper (Byte) + public static final String IGNITED = "ignited"; // Creeper (Bool) + public static final String IS_BABY = "IsBaby"; // Zombie (Bool) + public static final String CAN_BREAK_DOORS = "CanBreakDoors"; // Zombie (Bool) + public static final String ANGER = "Anger"; // Pig Zombie (Short) + public static final String HURT_BY = "HurtBy"; // Pig Zombie (Str) --> UUID + public static final String PROFESSION = "Profession"; // Zombie Villager, Villager (Int) + public static final String ZOMBIE_CONVERSION = "ConversionTime"; // Zombie Villager (Int) + public static final String CONVERSION_PLAYER = "ConversionPlayer"; // Zombie Villager (UUID) + public static final String JOHNNY = "Johnny"; // Vindicator (Bool) + public static final String SPELL_TICKS = "SpellTicks"; // Spell Caster (Int) + public static final String CARRIED = "carried"; // Enderman (Short) + public static final String CARRIED_DATA = "carriedData"; // Enderman (Short) + public static final String LIFE_TIME = "Lifetime"; // Endermite (Int) + public static final String PLAYER_SPAWNED = "PlayerSpawned"; // Endermite (Bool) + public static final String INVUL_TIME = "Invul"; // Wither (Int) + public static final String LIFE_TICKS = "LifeTicks"; // Vex (Int) + public static final String BOUND_X = "BoundX"; // Vex (Int) + public static final String BOUND_Y = "BoundY"; // Vex (Int) + public static final String BOUND_Z = "BoundZ"; // Vex (Int) + public static final String GROWING_AGE = "Age"; // Agable (Int) + public static final String FORCED_AGE = "ForcedAge"; // Agable (Int) + public static final String RICHES = "Riches"; // Villager (Int) + public static final String CAREER = "Career"; // Villager (Int) + public static final String CAREER_LEVEL = "CareerLevel"; // Villager (Int) + public static final String WILLING = "Willing"; // Villager (Bool) + public static final String TRADE_OFFERS = "Offers"; // Villager (Comp) --> Recipes + public static final String TRADE_RECIPES = "Recipes"; // Villager (List, Comp) --> MerchantRecipe + public static final String TRADE_BUY = "buy"; // MerchantRecipe (Comp) --> ItemStack + public static final String TRADE_SELL = "sell"; // MerchantRecipe (Comp) --> ItemStack + public static final String TRADE_BUY_B = "buyB"; // MerchantRecipe (Comp) --> ItemStack + public static final String TRADE_USES = "uses"; // MerchantRecipe (Int) + public static final String TRADE_MAX_USES = "maxUses"; // MerchantRecipe (Int) + public static final String TRADE_REWARD_EXP = "rewardExp"; // MerchantRecipe (Bool) + public static final String IN_LOVE = "InLove"; // Animal (Int) + public static final String LOVE_CAUSE = "LoveCause"; // Animal (UUID) + public static final String SHEARED = "Sheared"; // Sheep (Bool) + public static final String SHEEP_COLOR = "Color"; // Sheep (Byte) + public static final String SADDLED = "Saddle"; // Pig (Bool) + public static final String CHICKEN_JOCKEY = "IsChickenJockey"; // Chicken (Bool) + public static final String EGG_LAY_TIME = "EggLayTime"; // Chicken (Int) + public static final String RABBIT_TYPE = "RabbitType"; // Rabbit (Int) + public static final String MORE_CARROT_TICKS = "MoreCarrotTicks"; // Rabbit (Int) + public static final String TAMABLE_OWNER = "OwnerUUID"; // Tamable (Str) --> UUID + public static final String SITTING = "Sitting"; // Tamable (Bool) + public static final String OCELOT_TYPE = "CatType"; // Ocelot (Int) + public static final String ANGRY = "Angry"; // Wolf (Bool) + public static final String COLLAR_COLOR = "CollarColor"; // Wolf (Byte) --> DyeColor + public static final String VARIANT = "Variant"; // Parrot, Horse, Llama (Int) + public static final String EATING_HAY = "EatingHaystack"; // AbstractHorse (Bool) + public static final String HORSE_BRED = "Bred"; // AbstractHorse (Bool) + public static final String HORSE_TEMPER = "Temper"; // AbstractHorse (Int) + public static final String HORSE_TAME = "Tame"; // AbstractHorse (Bool) + public static final String SADDLE = "SaddleItem"; // AbstractHorse (Compound) -> ItemStack (Inv Slot 0) + public static final String SKELETON_TRAP = "SkeletonTrap"; // Skeleton Horse (Bool) + public static final String SKELETON_TRAP_TIME = "SkeletonTrapTime"; // Skeleton Horse (Int) + public static final String HORSE_ARMOR = "ArmorItem"; // Horse (Compound) -> ItemStack (Inv Slot 1) + public static final String CHESTED_HORSE = "ChestedHorse"; // AbstractChestedHorse (Bool) + public static final String STRENGTH = "Strength"; // Llama (Int) + public static final String LLAMA_DECOR = "DecorItem"; // Llama (Comp) -> ItemStack (Inv Slot 1) + + // Player [Base] + public static final String DATA_VERSION = "DataVersion"; // Int (Schema ID) + public static final String SEL_ITEM_SLOT = "SelectedItemSlot"; // Int + public static final String SLEEPING = "Sleeping"; // Bool + public static final String SLEEP_TIMER = "SleepTimer"; // Short + public static final String EXP_PROGRESS = "XpP"; // Float + public static final String EXP_LEVEL = "XpLevel"; // Int + public static final String EXP_TOTAL = "XpTotal"; // Int + public static final String EXP_SEED = "XpSeed"; // Int + public static final String SCORE = "Score"; // Int + public static final String SPAWN_X = "SpawnX"; // Int + public static final String SPAWN_Y = "SpawnY"; // Int + public static final String SPAWN_Z = "SpawnZ"; // Int + public static final String SPAWN_FORCED = "SpawnForced"; // Bool + public static final String FOOD_LEVEL = "foodLevel"; // Int + public static final String FOOD_TIMER = "foodTickTimer"; // Int + public static final String FOOD_SATURATION = "foodSaturationLevel"; // Float + public static final String FOOD_EXHAUSTION = "foodExhaustionLevel"; // Float + public static final String ABILITIES = "abilities"; // Compound --> + public static final String ABL_INVULNERABLE = "invulnerable"; // Bool + public static final String ABL_FLYING = "flying"; // Bool + public static final String ABL_MAY_FLY = "mayfly"; // Bool + public static final String ABL_INST_BUILD = "instabuild"; // Bool + public static final String ABL_FLY_SPEED = "flySpeed"; // Float + public static final String ABL_WALK_SPEED = "walkSpeed"; // Float + public static final String ABL_MAY_BUILD = "mayBuild"; // Bool + public static final String ENDER_ITEMS = "EnderItems"; // List, Comp -> Slotted ItemStacks + public static final String LEFT_SHOULDER = "ShoulderEntityLeft"; // Comp -> Entity + public static final String RIGHT_SHOULDER = "ShoulderEntityRight"; // Comp -> Entity + + // Server Player + public static final String GAME_TYPE = "playerGameType"; // Int + public static final String SEEN_CREDITS = "seenCredits"; // Bool + public static final String NETHER_POSITION = "enteredNetherPosition"; // Comp --> [x y z] + public static final String NETHER_POS_X = "x"; // Int + public static final String NETHER_POS_Y = "y"; // Int + public static final String NETHER_POS_Z = "z"; // Int + public static final String ROOT_VEHICLE = "RootVehicle"; // Comp --> + public static final String ROOT_ENTITY = "Entity"; // Comp --> Entity + public static final String ROOT_ATTACH = "Attach"; // UUID + public static final String RECIPE_BOOK = "recipeBook"; // Compound --> + public static final String RB_IS_GUI_OPEN = "isGuiOpen"; // Bool + public static final String RB_IS_FIL_CRAFT = "isFilteringCraftable"; // Bool + public static final String RECIPES = "recipes"; // List, String --> Recipe + + // Other/Newer Tags (There are tons more not listed possibly) + // 1.13.x ? + public static final String ORG_GOT_FISH = "GotFish"; // Dolphin + public static final String ORG_CAN_FIND_TREASURE = "CanFindTreasure"; + public static final String ORG_TREASURE_X = "TreasurePosX"; + public static final String ORG_TREASURE_Y = "TreasurePosY"; + public static final String ORG_TREASURE_Z = "TreasurePosZ"; + public static final String ORG_MOISTNESS = "Moistness"; + public static final String ORG_DROWNED_CONVERSION = "DrownedConversionTime"; // Drowned + public static final String ORG_IN_WATER = "InWaterTime"; + public static final String ORG_PHANTOM_AX = "AX"; // Phantom + public static final String ORG_PHANTOM_AY = "AY"; + public static final String ORG_PHANTOM_AZ = "AZ"; + public static final String ORG_PHANTOM_SIZE = "Size"; + public static final String ORG_HOME_POS_X = "HomePosX"; // Turtle + public static final String ORG_HOME_POS_Y = "HomePosY"; + public static final String ORG_HOME_POS_Z = "HomePosZ"; + public static final String ORG_TRAVEL_POS_X = "TravelPosX"; + public static final String ORG_TRAVEL_POS_Y = "TravelPosY"; + public static final String ORG_TRAVEL_POS_Z = "TravelPosZ"; + public static final String ORG_HAS_EGG = "HasEgg"; + // 1.14.x ? + public static final String ORG_TYPE = "type"; // Villager + public static final String ORG_VILLAGER_DATA = "VillagerData"; + public static final String ORG_VILLAGER_PROFESSION = "profession"; + public static final String ORG_MAIN_GENE = "MainGene"; + public static final String ORG_HIDDEN_GENE = "HiddenGene"; + public static final String ORG_TRADE_SP_PRICE = "specialPrice"; + public static final String ORG_TRADE_DEMAND = "demand"; + public static final String ORG_TRADE_PRICE_MUL = "priceMultiplier"; + public static final String ORG_TRADE_XP = "xp"; + // 1.15.x ? + public static final String ORG_BEES = "Bees"; // Bees + public static final String ORG_BOOK = "Book"; // Lectern + public static final String ORG_PAGE = "Page"; + public static final String ORG_RECIPES_USED = "RecipesUsed"; + public static final String ORG_EXP_COUNT = "Count"; + public static final String ORG_DISPLAY_STATE = "DisplayState"; // AbstractMinecartEntity + public static final String ORG_BLOCK_STATE = "BlockStateTag"; + public static final String ORG_TICKS_FROZEN = "TicksFrozen"; + public static final String ORG_HAS_VISUAL_FIRE = "HasVisualFire"; + public static final String ORG_FLIPPED_ROTATION = "FlippedRotation"; + public static final String ORG_HAS_TICKED = "HasTicked"; + public static final String ORG_FALLING_BLOCK_STATE = "BlockState"; + public static final String ORG_FALLING_TE_DATA = "TileEntityData"; + public static final String ORG_BRAIN = "Brain"; + public static final String ORG_ITEM_FIXED = "Fixed"; + public static final String ORG_BUCKET_VARIANT = "BucketVariantTag"; + public static final String ORG_ENCHANTMENTS = "Enchantments"; + // 1.17.x ? + public static final String ORG_STRAY_CONVERSION = "StrayConversionTime"; + // 1.18.x ? + // 1.19.x ? + // 1.20.x ? + public static final String NEW_FRONT_TEXT = "front_text"; // Sign + public static final String NEW_BACK_TEXT = "back_text"; + public static final String NEW_SIGN_MESSAGES = "messages"; + public static final String NEW_SIGN_FILTERED = "filtered_messages"; + public static final String NEW_SIGN_COLOR = "color"; + public static final String NEW_SIGN_GLOW = "has_glowing_text"; + public static final String NEW_NOTE = "note_block_sound"; + // 24w09a/1.20.5+ (Data Components) + public static final String NEW_COMPONENTS = "components"; + public static final String NEW_COUNT = "count"; + public static final String NEW_BEES = "bees"; + public static final String NEW_SKULL_PROFILE = "profile"; + public static final String NEW_SKULL_CUSTOM_NAME = "custom_name"; + public static final String NEW_SKULL_PROPERTIES = "properties"; + public static final String NEW_SKULL_NAME = "name"; + public static final String NEW_SKULL_VALUE = "value"; + public static final String NEW_SKULL_SIGNATURE = "signature"; + public static final String NEW_TAGS = "tags"; + public static final String NEW_MAP_ID = "map"; + public static final String NEW_ITEM = "item"; + public static final String NEW_SLEEPING_POS = "sleeping_pos"; + public static final String NEW_FLOWER = "flower_pos"; + public static final String NEW_EXIT = "exit_portal"; + public static final String NEW_WAXED = "is_waxed"; + public static final String NEW_NEXT_WEATHER_AGE = "next_weather_age"; + public static final String NEW_WEATHER_STATE = "weather_state"; + public static final String NEW_VIBRATION = "last_vibration_frequency"; + public static final String NEW_LISTENER = "listener"; + public static final String NEW_PRIMARY_EFFECT = "primary_effect"; + public static final String NEW_SECONDARY_EFFECT = "secondary_effect"; + public static final String NEW_DISABLED_SLOTS = "disabled_slots"; + public static final String NEW_MEMORIES = "memories"; + public static final String NEW_CUSTOM_DATA = "data"; + // 24w18a?/1.21+ (Attributes / Enchantments) + public static final String NEW_ATTRIB = "attributes"; + public static final String NEW_BODY_ARMOR = "body_armor_item"; + // 1.21.2+ (RecipeBook) + public static final String NEW_DISPLAYED = "toBeDisplayed"; + // 1.21.4-pre2 (AbstractFurnaces) + public static final String NEW_COOK_TIME_SPENT = "cooking_time_spent"; + public static final String NEW_COOK_TIME_TOTAL = "cooking_total_time"; + public static final String NEW_BURN_TIME = "lit_time_remaining"; + public static final String NEW_BURN_TIME_TOTAL = "lit_total_time"; + // 25w03a/1.21.5+ (Equipment Data Components / New tags / Entity Variants) + public static final String NEW_EQUIPMENT = "equipment"; + public static final String NEW_ANIMAL_ARMOR = "body"; + public static final String NEW_SADDLE = "saddle"; + public static final String NEW_FACING = "facing"; + public static final String NEW_FALL_DISTANCE = "fall_distance"; + public static final String NEW_TNT_FUSE = "fuse"; // TNT + public static final String NEW_TNT_BLOCK_STATE = "block_state"; + public static final String NEW_EXPLOSION_POWER = "explosion_power"; + public static final String NEW_EXPLOSION_SPEED_FACT = "explosion_speed_factor"; + public static final String NEW_BEAM_TARGET = "beam_target"; // EndCrystalEntity + public static final String NEW_ATTACHED_BLOCK_POS = "block_pos"; + public static final String NEW_SOUND_VARIANT = "sound_variant"; + public static final String NEW_EFFECTS = "active_effects"; + public static final String NEW_VARIANT = "variant"; + public static final String NEW_CUSTOM_PARTICLE = "custom_particle"; // AreaEffectCloudEntity + public static final String NEW_POTION_CONTENTS = "potion_contents"; + public static final String NEW_POTION_DURATION = "potion_duration_scale"; + // 1.21.6+ + public static final String NEW_LEASH = "leash"; + public static final String NEW_HOME_RADIUS = "home_radius"; // MobEntity + public static final String NEW_HOME_POS = "home_pos"; + public static final String NEW_OMINOUS_TIMER = "spawn_item_after_ticks"; + public static final String NEW_LOCATOR_ICON = "locator_bar_icon"; +} diff --git a/src/main/resources/mixins.malilib.json b/src/main/resources/mixins.malilib.json index d35c34b6b2..d08e3e0fe3 100644 --- a/src/main/resources/mixins.malilib.json +++ b/src/main/resources/mixins.malilib.json @@ -9,6 +9,7 @@ "access.GuiContainerMixin", "access.NBTBaseMixin", "access.NBTTagLongArrayMixin", + "access.TileEntityMixin", "command.GuiScreenMixin", "command.TabCompleterMixin", "event.MinecraftMixin",