diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index b4b1d64b..8b4e712b 100644 --- a/docs/scarpet/api/Auxiliary.md +++ b/docs/scarpet/api/Auxiliary.md @@ -130,6 +130,30 @@ Consult section about container operations in `Expression` to learn about possib Excapes all the special characters in the string or nbt tag and returns a string that can be stored in nbt directly as a string value. +### parse_nbt(tag) + +Converts NBT tag to a scarpet value, which you can navigate through much better. + +Converts: + - Compound tags into maps with string keys + - List tags into list values + - Numbers (Ints, Floats, Doubles, Longs) into a number + - Rest is converted to strings. + +### encode_nbt(expr, force?) + +Encodes value of the expression as an NBT tag. By default (or when `force` is false), it will only allow +to encode values that are guaranteed to return the same value when applied the resulting tag to `parse_nbt()`. +Supported types that can reliably convert back and forth to and from NBT values are: + - Maps with string keywords + - Lists of items of the same type (scarpet will take care of unifying value types if possible) + - Numbers (encoded as Ints -> Longs -> Doubles, as needed) + - Strings + +Other value types will only be converted to tags (including NBT tags) if `force` is true. They would require +extra treatment when loading them back from NBT, but using `force` true will always produce output / never +produce an exception. + ### `print(expr)` Displays the result of the expression to the chat. Overrides default `scarpet` behaviour of sending everyting to stderr. diff --git a/docs/scarpet/api/Entities.md b/docs/scarpet/api/Entities.md index a0524460..cc9084d5 100644 --- a/docs/scarpet/api/Entities.md +++ b/docs/scarpet/api/Entities.md @@ -532,6 +532,15 @@ Modifies directly player raw hunger components. Has no effect on non-players adds exhaustion value to the current player exhaustion level - that's the method you probably want to use to manipulate how much 'food' some action cost. +### `modify(e, 'nbt_merge', partial_tag)` + +Merges a partial tag into the entity data and reloads the entity from its updated tag. Cannot be applied to players + +### `modify(e, 'nbt', tag)` + +Reloads the entity from a supplied tag. Better get a valid entity tag, what can go wrong. Wonder what would happen if +transplant rabbit's brain into a villager. Cannot be applied to players. + ## Entity Events There is a number of events that happen to entities that you can attach your own code to in the form of event handlers. diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java index 2ac378c7..b69a5267 100644 --- a/src/main/java/carpet/script/CarpetExpression.java +++ b/src/main/java/carpet/script/CarpetExpression.java @@ -2949,6 +2949,24 @@ public class CarpetExpression return (cc, tt) -> parsed; }); + this.expr.addLazyFunction("encode_nbt", -1, (c, t, lv) -> { + int argSize = lv.size(); + if (argSize==0 || argSize > 2) throw new InternalExpressionException("'encode_nbt' requires 1 or 2 parameters"); + Value v = lv.get(0).evalValue(c); + boolean force = (argSize > 1) && lv.get(1).evalValue(c).getBoolean(); + Tag tag; + try + { + tag = v.toTag(force); + } + catch (NBTSerializableValue.IncompatibleTypeException ignored) + { + throw new InternalExpressionException("cannot reliably encode to a tag the value of '"+ignored.val.getPrettyString()+"'"); + } + Value tagValue = new NBTSerializableValue(tag); + return (cc, tt) -> tagValue; + }); + //"overridden" native call that prints to stderr this.expr.addLazyFunction("print", -1, (c, t, lv) -> { diff --git a/src/main/java/carpet/script/value/BlockValue.java b/src/main/java/carpet/script/value/BlockValue.java index 0f921b01..ca9e5ee1 100644 --- a/src/main/java/carpet/script/value/BlockValue.java +++ b/src/main/java/carpet/script/value/BlockValue.java @@ -12,7 +12,10 @@ import net.minecraft.block.Blocks; import net.minecraft.item.ItemPlacementContext; import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.StringTag; +import net.minecraft.nbt.Tag; import net.minecraft.server.world.ServerWorld; +import net.minecraft.state.property.Property; import net.minecraft.util.GlobalPos; import net.minecraft.util.Hand; import net.minecraft.util.hit.BlockHitResult; @@ -24,8 +27,10 @@ import net.minecraft.world.World; import net.minecraft.world.chunk.WorldChunk; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.stream.Collectors; @@ -180,6 +185,33 @@ public class BlockValue extends Value public ServerWorld getWorld() { return world;} + @Override + public Tag toTag(boolean force) + { + if (!force) throw new NBTSerializableValue.IncompatibleTypeException(this); + // follows falling block convertion + CompoundTag tag = new CompoundTag(); + CompoundTag state = new CompoundTag(); + BlockState s = getBlockState(); + state.put("Name", StringTag.of(Registry.BLOCK.getId(s.getBlock()).toString())); + Collection> properties = s.getProperties(); + if (!properties.isEmpty()) + { + CompoundTag props = new CompoundTag(); + for (Property p: properties) + { + props.put(p.getName(), StringTag.of(s.get(p).toString().toLowerCase(Locale.ROOT))); + } + state.put("Properties", props); + } + tag.put("BlockState", state); + CompoundTag dataTag = getData(); + if (dataTag != null) + { + tag.put("TileEntityData", dataTag); + } + return tag; + } public enum SpecificDirection { UP("up",0.5, 0.0, 0.5, Direction.UP), diff --git a/src/main/java/carpet/script/value/EntityValue.java b/src/main/java/carpet/script/value/EntityValue.java index 557ee4eb..e4922827 100644 --- a/src/main/java/carpet/script/value/EntityValue.java +++ b/src/main/java/carpet/script/value/EntityValue.java @@ -1,6 +1,5 @@ package carpet.script.value; -import carpet.CarpetSettings; import carpet.fakes.EntityInterface; import carpet.fakes.ItemEntityInterface; import carpet.fakes.LivingEntityInterface; @@ -12,10 +11,10 @@ import carpet.script.CarpetContext; import carpet.script.EntityEventsGroup; import carpet.script.argument.Vector3Argument; import carpet.script.exception.InternalExpressionException; -import carpet.utils.Messenger; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.exceptions.CommandSyntaxException; -import net.minecraft.entity.player.HungerManager; +import net.minecraft.nbt.StringTag; +import net.minecraft.nbt.Tag; import net.minecraft.network.packet.s2c.play.EntityPassengersSetS2CPacket; import net.minecraft.network.packet.s2c.play.EntityPositionS2CPacket; import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket; @@ -61,6 +60,7 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.UUID; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Predicate; @@ -976,22 +976,37 @@ public class EntityValue extends Value if(e instanceof PlayerEntity) ((PlayerEntity) e).getHungerManager().setSaturationLevelClient((float)NumericValue.asNumber(v).getLong()); }); - // gamemode [check] - // spectate [check] - // "fire" [check] - // "extinguish" [set fire ticks to 0] - // "silent" [check] - // "gravity" [check] - // "invulnerable" [check] + put("nbt", (e, v) -> { + if (!(e instanceof PlayerEntity)) + { + UUID uUID = e.getUuid(); + Value tagValue = NBTSerializableValue.fromValue(v); + if (tagValue instanceof NBTSerializableValue) + { + e.fromTag(((NBTSerializableValue) tagValue).getCompoundTag()); + e.setUuid(uUID); + } + } + }); + put("nbt_merge", (e, v) -> { + if (!(e instanceof PlayerEntity)) + { + UUID uUID = e.getUuid(); + Value tagValue = NBTSerializableValue.fromValue(v); + if (tagValue instanceof NBTSerializableValue) + { + CompoundTag nbttagcompound = e.toTag((new CompoundTag())); + nbttagcompound.copyFrom(((NBTSerializableValue) tagValue).getCompoundTag()); + e.fromTag(nbttagcompound); + e.setUuid(uUID); + } + } + }); + // "dimension" [] // "item" [] // "count", [] - // "age", [check] // "effect_"name [] - // "hold" [inventory_set?] - // "hold_offhand" [inventory_set?] - // "jump" [check] - // "nbt" <-big one, for now use run('data merge entity ... }}; public void setEvent(CarpetContext cc, String eventName, FunctionValue fun, List args) @@ -1001,4 +1016,14 @@ public class EntityValue extends Value throw new InternalExpressionException("Unknown entity event: " + eventName); ((EntityInterface)entity).getEventContainer().addEvent(event, cc, fun, args); } + + @Override + public Tag toTag(boolean force) + { + if (!force) throw new NBTSerializableValue.IncompatibleTypeException(this); + CompoundTag tag = new CompoundTag(); + tag.put("Data", getEntity().toTag( new CompoundTag())); + tag.put("Name", StringTag.of(Registry.ENTITY_TYPE.getId(entity.getType()).toString())); + return tag; + } } diff --git a/src/main/java/carpet/script/value/FormattedTextValue.java b/src/main/java/carpet/script/value/FormattedTextValue.java index 501e3181..68983085 100644 --- a/src/main/java/carpet/script/value/FormattedTextValue.java +++ b/src/main/java/carpet/script/value/FormattedTextValue.java @@ -1,6 +1,9 @@ package carpet.script.value; +import net.minecraft.nbt.StringTag; +import net.minecraft.nbt.Tag; import net.minecraft.text.BaseText; +import net.minecraft.text.Text; public class FormattedTextValue extends StringValue { @@ -37,4 +40,11 @@ public class FormattedTextValue extends StringValue { return text; } + + @Override + public Tag toTag(boolean force) + { + if (!force) throw new NBTSerializableValue.IncompatibleTypeException(this); + return StringTag.of(Text.Serializer.toJson(text)); + } } diff --git a/src/main/java/carpet/script/value/FrameworkValue.java b/src/main/java/carpet/script/value/FrameworkValue.java index 882c79ec..a432974e 100644 --- a/src/main/java/carpet/script/value/FrameworkValue.java +++ b/src/main/java/carpet/script/value/FrameworkValue.java @@ -1,5 +1,7 @@ package carpet.script.value; +import net.minecraft.nbt.Tag; + public abstract class FrameworkValue extends Value { @Override @@ -25,4 +27,10 @@ public abstract class FrameworkValue extends Value { throw new UnsupportedOperationException("Scarpet language component cannot be used as map key"); } + + @Override + public Tag toTag(boolean force) + { + throw new UnsupportedOperationException("Scarpet language component cannot be serialized to the tag"); + } } diff --git a/src/main/java/carpet/script/value/FunctionValue.java b/src/main/java/carpet/script/value/FunctionValue.java index 3b872ccf..45791fad 100644 --- a/src/main/java/carpet/script/value/FunctionValue.java +++ b/src/main/java/carpet/script/value/FunctionValue.java @@ -13,6 +13,8 @@ import carpet.script.exception.ExpressionException; import carpet.script.exception.InternalExpressionException; import carpet.script.exception.ReturnStatement; import carpet.script.exception.ThrowStatement; +import net.minecraft.nbt.StringTag; +import net.minecraft.nbt.Tag; import java.util.ArrayList; import java.util.Collections; @@ -94,6 +96,8 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction return name.hashCode()+(int)variant; } + + @Override public boolean equals(Object o) { @@ -231,4 +235,11 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction { return args; } + + @Override + public Tag toTag(boolean force) + { + if (!force) throw new NBTSerializableValue.IncompatibleTypeException(this); + return StringTag.of(getString()); + } } diff --git a/src/main/java/carpet/script/value/GlobalValue.java b/src/main/java/carpet/script/value/GlobalValue.java index 697d630d..c38afb68 100644 --- a/src/main/java/carpet/script/value/GlobalValue.java +++ b/src/main/java/carpet/script/value/GlobalValue.java @@ -1,6 +1,7 @@ package carpet.script.value; import carpet.script.exception.InternalExpressionException; +import net.minecraft.nbt.Tag; public class GlobalValue extends Value { @@ -28,4 +29,10 @@ public class GlobalValue extends Value { throw new UnsupportedOperationException("Global value cannot be used as a map key"); } + + @Override + public Tag toTag(boolean force) + { + throw new UnsupportedOperationException("Global value cannot be serialized to the tag"); + } } diff --git a/src/main/java/carpet/script/value/LazyListValue.java b/src/main/java/carpet/script/value/LazyListValue.java index e57d7110..2edd637d 100644 --- a/src/main/java/carpet/script/value/LazyListValue.java +++ b/src/main/java/carpet/script/value/LazyListValue.java @@ -1,6 +1,8 @@ package carpet.script.value; import carpet.script.exception.InternalExpressionException; +import net.minecraft.nbt.StringTag; +import net.minecraft.nbt.Tag; import java.util.ArrayList; import java.util.Iterator; @@ -152,4 +154,11 @@ public abstract class LazyListValue extends AbstractListValue implements Iterato { return ("i"+getString()).hashCode(); } + + @Override + public Tag toTag(boolean force) + { + if (!force) throw new NBTSerializableValue.IncompatibleTypeException(this); + return StringTag.of(getString()); + } } diff --git a/src/main/java/carpet/script/value/ListValue.java b/src/main/java/carpet/script/value/ListValue.java index e5307ae1..4aca8e41 100644 --- a/src/main/java/carpet/script/value/ListValue.java +++ b/src/main/java/carpet/script/value/ListValue.java @@ -3,13 +3,23 @@ package carpet.script.value; import carpet.script.LazyValue; import carpet.script.exception.InternalExpressionException; import net.minecraft.item.ItemStack; +import net.minecraft.nbt.AbstractNumberTag; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.DoubleTag; +import net.minecraft.nbt.IntTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.LongTag; +import net.minecraft.nbt.StringTag; +import net.minecraft.nbt.Tag; import net.minecraft.util.registry.Registry; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.EnumSet; import java.util.Iterator; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @@ -454,4 +464,65 @@ public class ListValue extends AbstractListValue implements ContainerValueInterf { return items.hashCode(); } + + private enum TagTypeCompat + { + INT, + LONG, + DBL, + LIST, + MAP, + STRING; + private static TagTypeCompat getType(Tag tag) + { + if (tag instanceof IntTag) return INT; + if (tag instanceof LongTag) return LONG; + if (tag instanceof DoubleTag) return DBL; + if (tag instanceof ListTag) return LIST; + if (tag instanceof CompoundTag) return MAP; + return STRING; + } + } + + + @Override + public Tag toTag(boolean force) + { + int argSize = items.size(); + if (argSize == 0) return new ListTag(); + ListTag tag = new ListTag(); + if (argSize ==1) + { + tag.add(items.get(0).toTag(force)); + return tag; + } + // figuring out the types + List tags= new ArrayList<>(); + items.forEach(v -> tags.add(v.toTag(force))); + Set cases = EnumSet.noneOf(TagTypeCompat.class); + tags.forEach(t -> cases.add(TagTypeCompat.getType(t))); + if (cases.size()==1) // well, one type of items + { + tag.addAll(tags); + return tag; + } + if (cases.contains(TagTypeCompat.LIST) + || cases.contains(TagTypeCompat.MAP) + || cases.contains(TagTypeCompat.STRING)) // incompatible types + { + if (!force) throw new NBTSerializableValue.IncompatibleTypeException(this); + tags.forEach(t -> tag.add(StringTag.of(t.asString()))); + return tag; + } + // only numbers / mixed types + if (cases.contains(TagTypeCompat.DBL)) + { + tags.forEach(t -> tag.add(DoubleTag.of(((AbstractNumberTag)t).getDouble()))); + } + else + { + tags.forEach(t -> tag.add(LongTag.of(((AbstractNumberTag)t).getLong()))); + } + return tag; + } } diff --git a/src/main/java/carpet/script/value/MapValue.java b/src/main/java/carpet/script/value/MapValue.java index 61965e4e..f6332b4d 100644 --- a/src/main/java/carpet/script/value/MapValue.java +++ b/src/main/java/carpet/script/value/MapValue.java @@ -1,6 +1,8 @@ package carpet.script.value; import carpet.script.exception.InternalExpressionException; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; import java.util.ArrayList; import java.util.HashMap; @@ -226,4 +228,16 @@ public class MapValue extends AbstractListValue implements ContainerValueInterfa { return map.hashCode(); } + + @Override + public Tag toTag(boolean force) + { + CompoundTag tag = new CompoundTag() ; + map.forEach( (k, v) -> + { + if (!force && !(k instanceof StringValue)) throw new NBTSerializableValue.IncompatibleTypeException(k); + tag.put(k.getString(), v.toTag(force)); + }); + return tag; + } } diff --git a/src/main/java/carpet/script/value/NBTSerializableValue.java b/src/main/java/carpet/script/value/NBTSerializableValue.java index 63461472..c2ff37e0 100644 --- a/src/main/java/carpet/script/value/NBTSerializableValue.java +++ b/src/main/java/carpet/script/value/NBTSerializableValue.java @@ -638,4 +638,23 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa { return "nbt"; } + + + @Override + public Tag toTag(boolean force) + { + if (!force) throw new NBTSerializableValue.IncompatibleTypeException(this); + ensureOwnership(); + return getTag(); + } + + public static class IncompatibleTypeException extends RuntimeException + { + private IncompatibleTypeException() {} + public Value val; + public IncompatibleTypeException(Value val) + { + this.val = val; + } + }; } diff --git a/src/main/java/carpet/script/value/NullValue.java b/src/main/java/carpet/script/value/NullValue.java index eeef2f0d..18ffcec1 100644 --- a/src/main/java/carpet/script/value/NullValue.java +++ b/src/main/java/carpet/script/value/NullValue.java @@ -1,5 +1,8 @@ package carpet.script.value; +import net.minecraft.nbt.StringTag; +import net.minecraft.nbt.Tag; + public class NullValue extends NumericValue // TODO check nonsingleton code { @Override @@ -50,4 +53,11 @@ public class NullValue extends NumericValue // TODO check nonsingleton code { return 0; } + + @Override + public Tag toTag(boolean force) + { + if (!force) throw new NBTSerializableValue.IncompatibleTypeException(this); + return StringTag.of("null"); + } } diff --git a/src/main/java/carpet/script/value/NumericValue.java b/src/main/java/carpet/script/value/NumericValue.java index 2ae575fb..482d9fab 100644 --- a/src/main/java/carpet/script/value/NumericValue.java +++ b/src/main/java/carpet/script/value/NumericValue.java @@ -1,6 +1,10 @@ package carpet.script.value; import carpet.script.exception.InternalExpressionException; +import net.minecraft.nbt.DoubleTag; +import net.minecraft.nbt.IntTag; +import net.minecraft.nbt.LongTag; +import net.minecraft.nbt.Tag; import org.apache.commons.lang3.StringUtils; import java.math.BigDecimal; @@ -204,4 +208,20 @@ public class NumericValue extends Value { return (int)getLong(); } + + @Override + public Tag toTag(boolean force) + { + long longValue = getLong(); + if (value == (double)longValue) + { + if (abs(value) < Integer.MAX_VALUE-2) + return IntTag.of((int)longValue); + return LongTag.of(getLong()); + } + else + { + return DoubleTag.of(value); + } + } } diff --git a/src/main/java/carpet/script/value/StringValue.java b/src/main/java/carpet/script/value/StringValue.java index eba0cd16..c494865d 100644 --- a/src/main/java/carpet/script/value/StringValue.java +++ b/src/main/java/carpet/script/value/StringValue.java @@ -1,5 +1,8 @@ package carpet.script.value; +import net.minecraft.nbt.StringTag; +import net.minecraft.nbt.Tag; + public class StringValue extends Value { private String str; @@ -30,4 +33,10 @@ public class StringValue extends Value { return "string"; } + + @Override + public Tag toTag(boolean force) + { + return StringTag.of(str); + } } diff --git a/src/main/java/carpet/script/value/ThreadValue.java b/src/main/java/carpet/script/value/ThreadValue.java index 98ac7623..e4ee5d50 100644 --- a/src/main/java/carpet/script/value/ThreadValue.java +++ b/src/main/java/carpet/script/value/ThreadValue.java @@ -6,6 +6,7 @@ import carpet.script.LazyValue; import carpet.script.Tokenizer; import carpet.script.exception.ExpressionException; import carpet.script.exception.InternalExpressionException; +import net.minecraft.nbt.Tag; import java.util.HashMap; import java.util.List; @@ -144,4 +145,10 @@ public class ThreadValue extends Value locks.clear(); } + @Override + public Tag toTag(boolean force) + { + if (!force) throw new NBTSerializableValue.IncompatibleTypeException(this); + return getValue().toTag(true); + } } diff --git a/src/main/java/carpet/script/value/Value.java b/src/main/java/carpet/script/value/Value.java index f97fb8d5..82bdd664 100644 --- a/src/main/java/carpet/script/value/Value.java +++ b/src/main/java/carpet/script/value/Value.java @@ -1,6 +1,7 @@ package carpet.script.value; import carpet.script.exception.InternalExpressionException; +import net.minecraft.nbt.Tag; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -203,4 +204,6 @@ public abstract class Value implements Comparable, Cloneable throw new InternalExpressionException("Cannot make a copy of value: "+this); } } + + public abstract Tag toTag(boolean force); }