465 lines
14 KiB
Java
465 lines
14 KiB
Java
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<Tag> 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<LazyValue> 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<Value> 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<String,ItemStackArgument> 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<Tag> targets;
|
|
try
|
|
{
|
|
targets = nbtPath.getOrInit(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.getOrInit(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<Tag> 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<String, NbtPathArgumentType.NbtPath> 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";
|
|
}
|
|
}
|