package carpet.script.value; import carpet.script.CarpetContext; import carpet.script.LazyValue; import carpet.script.exception.InternalExpressionException; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.minecraft.block.entity.HopperBlockEntity; import net.minecraft.command.arguments.ItemStackArgument; import net.minecraft.command.arguments.ItemStringReader; import net.minecraft.command.arguments.NbtPathArgumentType; import net.minecraft.entity.Entity; import net.minecraft.entity.passive.VillagerEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.inventory.Inventory; import net.minecraft.item.ItemStack; import net.minecraft.nbt.AbstractListTag; import net.minecraft.nbt.AbstractNumberTag; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.StringNbtReader; import net.minecraft.nbt.Tag; import net.minecraft.util.math.BlockPos; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; public class NBTSerializableValue extends Value implements ContainerValueInterface { private String nbtString = null; private Tag nbtTag = null; private Supplier nbtSupplier = null; public NBTSerializableValue(ItemStack stack) { nbtSupplier = () -> stack.hasTag() ? stack.getTag(): new CompoundTag(); } public NBTSerializableValue(String nbtString) { nbtSupplier = () -> { try { return (new StringNbtReader(new StringReader(nbtString))).parseTag(); } catch (CommandSyntaxException e) { throw new InternalExpressionException("Incorrect nbt data: "+nbtString); } }; } public static NBTSerializableValue parseString(String nbtString) { Tag tag; try { tag = (new StringNbtReader(new StringReader(nbtString))).parseTag(); } catch (CommandSyntaxException e) { return null; } NBTSerializableValue value = new NBTSerializableValue(tag); value.nbtString = nbtString; return value; } public Value clone() { return new NBTSerializableValue(getTag().copy()); } public NBTSerializableValue(Tag tag) { nbtTag = tag; } public static InventoryLocator locateInventory(CarpetContext c, List params, int offset) { try { Inventory inv = null; Value v1 = params.get(0 + offset).evalValue(c); if (v1 instanceof EntityValue) { Entity e = ((EntityValue) v1).getEntity(); if (e instanceof PlayerEntity) inv = ((PlayerEntity) e).inventory; else if (e instanceof Inventory) inv = (Inventory) e; else if (e instanceof VillagerEntity) inv = ((VillagerEntity) e).getInventory(); if (inv == null) return null; return new InventoryLocator(e, e.getBlockPos(), inv, offset + 1); } else if (v1 instanceof BlockValue) { BlockPos pos = ((BlockValue) v1).getPos(); if (pos == null) throw new InternalExpressionException("Block to access inventory needs to be positioned in the world"); inv = HopperBlockEntity.getInventoryAt(c.s.getWorld(), pos); if (inv == null) return null; return new InventoryLocator(pos, pos, inv, offset + 1); } else if (v1 instanceof ListValue) { List args = ((ListValue) v1).getItems(); BlockPos pos = new BlockPos( NumericValue.asNumber(args.get(0)).getDouble(), NumericValue.asNumber(args.get(1)).getDouble(), NumericValue.asNumber(args.get(2)).getDouble()); inv = HopperBlockEntity.getInventoryAt(c.s.getWorld(), pos); if (inv == null) return null; return new InventoryLocator(pos, pos, inv, offset + 1); } BlockPos pos = new BlockPos( NumericValue.asNumber(v1).getDouble(), NumericValue.asNumber(params.get(1 + offset).evalValue(c)).getDouble(), NumericValue.asNumber(params.get(2 + offset).evalValue(c)).getDouble()); inv = HopperBlockEntity.getInventoryAt(c.s.getWorld(), pos); if (inv == null) return null; return new InventoryLocator(pos, pos, inv, offset + 3); } catch (IndexOutOfBoundsException e) { throw new InternalExpressionException("Inventory should be defined either by three coordinates, a block value, or an entity"); } } private static Map itemCache = new HashMap<>(); public static ItemStackArgument parseItem(String itemString) { return parseItem(itemString, null); } public static ItemStackArgument parseItem(String itemString, CompoundTag customTag) { try { ItemStackArgument res = itemCache.get(itemString); if (res != null) if (customTag == null) return res; else return new ItemStackArgument(res.getItem(), customTag); ItemStringReader parser = (new ItemStringReader(new StringReader(itemString), false)).consume(); res = new ItemStackArgument(parser.getItem(), parser.getTag()); itemCache.put(itemString, res); if (itemCache.size()>64000) itemCache.clear(); if (customTag == null) return res; else return new ItemStackArgument(res.getItem(), customTag); } catch (CommandSyntaxException e) { throw new InternalExpressionException("Incorrect item: "+itemString); } } public static int validateSlot(int slot, Inventory inv) { int invSize = inv.getInvSize(); if (slot < 0) slot = invSize + slot; if (slot < 0 || slot >= invSize) return inv.getInvSize(); // outside of inventory return slot; } private static Value decodeTag(Tag t) { if (t instanceof CompoundTag) return new NBTSerializableValue(t); if (t instanceof AbstractNumberTag) return new NumericValue(((AbstractNumberTag) t).getDouble()); // more can be done here return new StringValue(t.asString()); } private Tag getTag() { if (nbtTag == null) nbtTag = nbtSupplier.get(); return nbtTag; } private void replaceTag(Tag newTag) { nbtTag = newTag; nbtString = null; nbtSupplier = null; // just to be sure } @Override public boolean equals(final Object o) { if (o instanceof NBTSerializableValue) return getTag().equals(((NBTSerializableValue) o).getTag()); return super.equals((Value)o); } @Override public String getString() { if (nbtString == null) nbtString = getTag().toString(); return nbtString; } @Override public boolean getBoolean() { return true; } public CompoundTag getCompoundTag() { try { return (CompoundTag) getTag(); } catch (ClassCastException e) { throw new InternalExpressionException(getString()+" is not a valid compound tag"); } } @Override public Value put(Value where, Value value) { return put(where, value, new StringValue("replace")); } @Override public Value put(Value where, Value value, Value conditions) { /// WIP NbtPathArgumentType.NbtPath path = cachePath(where.getString()); Tag tagToInsert = value instanceof NBTSerializableValue ? ((NBTSerializableValue) value).getTag() : new NBTSerializableValue(value.getString()).getTag(); Tag modifiedTag; if (conditions instanceof NumericValue) { modifiedTag = modify_insert((int)((NumericValue) conditions).getLong(), path, tagToInsert); } else { String ops = conditions.getString(); if (ops.equalsIgnoreCase("merge")) { modifiedTag = modify_merge(path, tagToInsert); } else if (ops.equalsIgnoreCase("replace")) { modifiedTag = modify_replace(path, tagToInsert); } else { return this; } } if (modifiedTag != null) { replaceTag(modifiedTag); } return this; } private Tag modify_insert(int index, NbtPathArgumentType.NbtPath nbtPath, Tag newElement) { return modify_insert(index, nbtPath, newElement, this.getTag().copy()); } private Tag modify_insert(int index, NbtPathArgumentType.NbtPath nbtPath, Tag newElement, Tag currentTag) { Collection targets; try { targets = nbtPath.putIfAbsent(currentTag, ListTag::new); } catch (CommandSyntaxException e) { return null; } boolean modified = false; for (Tag target : targets) { if (!(target instanceof AbstractListTag)) { continue; } try { AbstractListTag targetList = (AbstractListTag) target; if (!targetList.addTag(index < 0 ? targetList.size() + index + 1 : index, newElement.copy())) return null; modified = true; } catch (IndexOutOfBoundsException ignored) { } } return modified?currentTag:null; } private Tag modify_merge(NbtPathArgumentType.NbtPath nbtPath, Tag replacement) //nbtPathArgumentType$NbtPath_1, list_1) { if (!(replacement instanceof CompoundTag)) { return getTag(); } Tag originalTag = getTag().copy(); try { for (Tag target : nbtPath.putIfAbsent(originalTag, CompoundTag::new)) { if (!(target instanceof CompoundTag)) { continue; } ((CompoundTag) target).copyFrom((CompoundTag) replacement); } } catch (CommandSyntaxException ignored) { } return originalTag; } private Tag modify_replace(NbtPathArgumentType.NbtPath nbtPath, Tag replacement) //nbtPathArgumentType$NbtPath_1, list_1) { Tag originalTag = getTag().copy(); String pathText = nbtPath.toString(); if (pathText.endsWith("]")) // workaround for array replacement or item in the array replacement { if (nbtPath.remove(originalTag)==0) return null; Pattern pattern = Pattern.compile("\\[[^\\[]*]$"); Matcher matcher = pattern.matcher(pathText); if (!matcher.find()) // malformed path { return null; } String arrAccess = matcher.group(); int pos; if (arrAccess.length()==2) // we just removed entire array pos = 0; else { try { pos = Integer.parseInt(arrAccess.substring(1, arrAccess.length() - 1)); } catch (NumberFormatException e) { return null; } } NbtPathArgumentType.NbtPath newPath = cachePath(pathText.substring(0, pathText.length()-arrAccess.length())); return modify_insert(pos,newPath,replacement, originalTag); } try { nbtPath.put(originalTag, () -> replacement); } catch (CommandSyntaxException e) { return null; } return originalTag; } @Override public Value get(Value value) { NbtPathArgumentType.NbtPath path = cachePath(value.getString()); try { List tags = path.get(getTag()); if (tags.size()==0) return Value.NULL; if (tags.size()==1) return NBTSerializableValue.decodeTag(tags.get(0)); return ListValue.wrap(tags.stream().map(NBTSerializableValue::decodeTag).collect(Collectors.toList())); } catch (CommandSyntaxException ignored) { } return Value.NULL; } @Override public boolean has(Value where) { return cachePath(where.getString()).count(getTag()) > 0; } @Override public Value delete(Value where) { NbtPathArgumentType.NbtPath path = cachePath(where.getString()); Tag tag = getTag().copy(); int removed = path.remove(tag); if (removed > 0) replaceTag(tag); return new NumericValue(removed); } public static class InventoryLocator { public Object owner; public BlockPos position; public Inventory inventory; public int offset; InventoryLocator(Object owner, BlockPos pos, Inventory i, int o) { this.owner = owner; position = pos; inventory = i; offset = o; } } private static Map pathCache = new HashMap<>(); private static NbtPathArgumentType.NbtPath cachePath(String arg) { NbtPathArgumentType.NbtPath res = pathCache.get(arg); if (res != null) return res; try { res = NbtPathArgumentType.nbtPath().method_9362(new StringReader(arg)); } catch (CommandSyntaxException exc) { throw new InternalExpressionException("Incorrect nbt path: "+arg); } if (pathCache.size() > 1024) pathCache.clear(); pathCache.put(arg, res); return res; } @Override public String getTypeString() { return "nbt"; } }