diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index a1c136d2..0562a0fe 100644 --- a/docs/scarpet/api/Auxiliary.md +++ b/docs/scarpet/api/Auxiliary.md @@ -200,7 +200,7 @@ With the specified `resource` in the scripts folder, of a specific `type`, write content, or deletes the resource. Resource is identified by a path to the file. -A path can contain letters, numbers and folder separator: `'/'`. Any other characters are stripped +A path can contain letters, numbers, characters `-`, `+`, or `_`, and a folder separator: `'/'`. Any other characters are stripped from the name. Empty descriptors are invalid. Do not add file extensions to the descriptor - extensions are inferred based on the `type` of the file. diff --git a/docs/scarpet/api/BlocksAndWorldAccess.md b/docs/scarpet/api/BlocksAndWorldAccess.md index 522faff2..6ef5f75b 100644 --- a/docs/scarpet/api/BlocksAndWorldAccess.md +++ b/docs/scarpet/api/BlocksAndWorldAccess.md @@ -36,11 +36,14 @@ print(b); // 'stone', block was evaluated 'eagerly' but call to `block` All the functions below can be used with block value, queried with coord triple, or 3-long list. All `pos` in the functions referenced below refer to either method of passing block position. -### `set(pos, block, property?, value?, ...)` +### `set(pos, block, property?, value?, ..., block_data?)`, `set(pos, block, l(property?, value?, ...), block_data?)` First part of the `set` function is either a coord triple, list of three numbers, or other block with coordinates. Second part, `block` is either block value as a result of `block()` function string value indicating the block name, -and optional `property - value` pairs for extra block properties. If `block` is specified only by name, then if a +and optional `property - value` pairs for extra block properties. Optional `block_data` include the block data to +be set for the target block. + +If `block` is specified only by name, then if a destination block is the same the `set` operation is skipped, otherwise is executed, for other potential extra properties. @@ -64,6 +67,7 @@ set(x,y,z,'iron_trapdoor[half=top]') // Incorrect. sets bottom iron trapdoor - set(x,y,z,'iron_trapdoor','half','top') // correct - top trapdoor set(x,y,z, block('iron_trapdoor[half=top]')) // also correct, block() provides extra parsing set(x,y,z,'hopper[facing=north]{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:16b}]}') // extra block data +set(x,y,z,'hopper', l('facing', 'north'), nbt('{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:16b}]}') ) // same ### `without_updates(expr)` diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java index 91676b21..63f91aac 100644 --- a/src/main/java/carpet/script/CarpetExpression.java +++ b/src/main/java/carpet/script/CarpetExpression.java @@ -769,27 +769,55 @@ public class CarpetExpression { CarpetContext cc = (CarpetContext)c; ServerWorld world = cc.s.getWorld(); - if (lv.size() < 2 || lv.size() % 2 == 1) - throw new InternalExpressionException("'set' should have at least 2 params and odd attributes"); BlockArgument targetLocator = BlockArgument.findIn(cc, lv, 0); BlockArgument sourceLocator = BlockArgument.findIn(cc, lv, targetLocator.offset, true); BlockState sourceBlockState = sourceLocator.block.getBlockState(); BlockState targetBlockState = world.getBlockState(targetLocator.block.getPos()); - if (sourceLocator.offset < lv.size()) + CompoundTag data = null; + if (lv.size() > sourceLocator.offset) { - StateManager states = sourceBlockState.getBlock().getStateManager(); - for (int i = sourceLocator.offset; i < lv.size(); i += 2) + List args = new ArrayList<>(); + for (int i = sourceLocator.offset, m = lv.size(); i < m; i++) { - String paramString = lv.get(i).evalValue(c).getString(); + args.add(lv.get(i).evalValue(c)); + } + if (args.get(0) instanceof ListValue) + { + if (args.size() == 2) + { + Value dataValue = NBTSerializableValue.fromValue( args.get(1)); + if (dataValue instanceof NBTSerializableValue) + { + data = ((NBTSerializableValue) dataValue).getCompoundTag(); + } + } + args = ((ListValue) args.get(0)).getItems(); + } + else + { + if ((args.size() & 1) == 1) + { + Value dataValue = NBTSerializableValue.fromValue( args.get(args.size()-1)); + if (dataValue instanceof NBTSerializableValue) + { + data = ((NBTSerializableValue) dataValue).getCompoundTag(); + } + } + } + StateManager states = sourceBlockState.getBlock().getStateManager(); + for (int i = 0; i < args.size()-1; i += 2) + { + String paramString = args.get(i).getString(); Property property = states.getProperty(paramString); if (property == null) throw new InternalExpressionException("Property " + paramString + " doesn't apply to " + sourceLocator.block.getString()); - String paramValue = lv.get(i + 1).evalValue(c).getString(); + String paramValue = args.get(i + 1).getString(); sourceBlockState = setProperty(property, paramString, paramValue, sourceBlockState); } } - CompoundTag data = sourceLocator.block.getData(); + if (data == null) data = sourceLocator.block.getData(); + CompoundTag finalData = data; if (sourceBlockState == targetBlockState && data == null) return (c_, t_) -> Value.FALSE; @@ -799,12 +827,12 @@ public class CarpetExpression { Clearable.clear(world.getBlockEntity(targetPos)); world.setBlockState(targetPos, finalSourceBlockState, 2); - if (data != null) + if (finalData != null) { BlockEntity be = world.getBlockEntity(targetPos); if (be != null) { - CompoundTag destTag = data.copy(); + CompoundTag destTag = finalData.copy(); destTag.putInt("x", targetPos.getX()); destTag.putInt("y", targetPos.getY()); destTag.putInt("z", targetPos.getZ()); @@ -913,7 +941,7 @@ public class CarpetExpression Tag outtag = tool.getTag(); if (outtag == null) return LazyValue.TRUE; - Value ret = new NBTSerializableValue(outtag); + Value ret = new NBTSerializableValue(() -> outtag); return (_c, _t) -> ret; }); @@ -1419,13 +1447,14 @@ public class CarpetExpression }); this.expr.addLazyFunction("scoreboard_remove", -1, (c, t, lv)-> { + if (lv.size()==0) throw new InternalExpressionException("'scoreboard_remove' requires at least one parameter"); CarpetContext cc = (CarpetContext)c; Scoreboard scoreboard = cc.s.getMinecraftServer().getScoreboard(); String objectiveName = lv.get(0).evalValue(c).getString(); ScoreboardObjective objective = scoreboard.getObjective(objectiveName); if (objective == null) return LazyValue.FALSE; - if (lv.size() == 0) + if (lv.size() == 1) { scoreboard.removeObjective(objective); return LazyValue.TRUE; @@ -1973,11 +2002,9 @@ public class CarpetExpression Value nbt = lv.get(position.offset).evalValue(c); NBTSerializableValue v = (nbt instanceof NBTSerializableValue) ? (NBTSerializableValue) nbt : NBTSerializableValue.parseString(nbt.getString()); - if (v != null) - { - hasTag = true; - tag = v.getCompoundTag(); - } + hasTag = true; + tag = v.getCompoundTag(); + } tag.putString("id", entityId.toString()); Vec3d vec3d = position.vec; @@ -2638,7 +2665,7 @@ public class CarpetExpression public static String recognizeResource(Value value) { String origfile = value.getString(); - String file = origfile.toLowerCase(Locale.ROOT).replaceAll("[^A-Za-z0-9/]", ""); + String file = origfile.toLowerCase(Locale.ROOT).replaceAll("[^A-Za-z0-9\\-+_/]", ""); file = Arrays.stream(file.split("/+")).filter(s -> !s.isEmpty()).collect(Collectors.joining("/")); if (file.isEmpty()) { @@ -2914,12 +2941,7 @@ public class CarpetExpression }); this.expr.addLazyFunction("nbt", 1, (c, t, lv) -> { - Value v = lv.get(0).evalValue(c); - if (v instanceof NBTSerializableValue) - return (cc, tt) -> v; - Value ret = NBTSerializableValue.parseString(v.getString()); - if (ret == null) - return LazyValue.NULL; + Value ret = NBTSerializableValue.fromValue(lv.get(0).evalValue(c)); return (cc, tt) -> ret; }); @@ -2930,6 +2952,20 @@ public class CarpetExpression return (cc, tt) -> ret; }); + this.expr.addLazyFunction("parse_nbt", 1, (c, t, lv) -> { + Value v = lv.get(0).evalValue(c); + if (v instanceof NBTSerializableValue) + { + Value parsed = ((NBTSerializableValue) v).toValue(); + return (cc, tt) -> parsed; + } + NBTSerializableValue ret = NBTSerializableValue.parseString(v.getString()); + if (ret == null) + return LazyValue.NULL; + Value parsed = ret.toValue(); + return (cc, tt) -> parsed; + }); + //"overridden" native call that prints to stderr this.expr.addLazyFunction("print", -1, (c, t, lv) -> { diff --git a/src/main/java/carpet/script/value/NBTSerializableValue.java b/src/main/java/carpet/script/value/NBTSerializableValue.java index 11b73ea2..fcf2009f 100644 --- a/src/main/java/carpet/script/value/NBTSerializableValue.java +++ b/src/main/java/carpet/script/value/NBTSerializableValue.java @@ -33,6 +33,7 @@ import net.minecraft.util.Identifier; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Box; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -48,6 +49,7 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa private String nbtString = null; private Tag nbtTag = null; private Supplier nbtSupplier = null; + private boolean owned = false; private NBTSerializableValue() {} @@ -64,11 +66,18 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa throw new InternalExpressionException("Incorrect NBT data: "+nbtString); } }; + owned = true; } public NBTSerializableValue(Tag tag) { nbtTag = tag; + owned = true; + } + + public NBTSerializableValue(Supplier tagSupplier) + { + nbtSupplier = tagSupplier; } public static Value fromStack(ItemStack stack) @@ -100,7 +109,7 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa } catch (CommandSyntaxException e) { - return null; + throw new InternalExpressionException("Incorrect NBT tag: nbtString"); } NBTSerializableValue value = new NBTSerializableValue(tag); value.nbtString = nbtString; @@ -115,6 +124,7 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa NBTSerializableValue copy = new NBTSerializableValue(nbtTag); copy.nbtSupplier = this.nbtSupplier; copy.nbtString = this.nbtString; + copy.owned = this.owned; return copy; } @@ -122,9 +132,8 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa public Value deepcopy() { NBTSerializableValue copy = (NBTSerializableValue) clone(); - // same fields except when tag is set - need to copy it. - if (copy.nbtTag != null) - copy.nbtTag = copy.getTag().copy(); + copy.owned = false; + ensureOwnership(); return copy; } @@ -290,7 +299,50 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa private static Value decodeTag(Tag t) { if (t instanceof CompoundTag) - return new NBTSerializableValue(t); + return new NBTSerializableValue(() -> t); + if (t instanceof AbstractNumberTag) + return new NumericValue(((AbstractNumberTag) t).getDouble()); + // more can be done here + return new StringValue(t.asString()); + } + + public Value toValue() + { + return decodeTagDeep(this.getTag()); + } + + public static Value fromValue(Value v) + { + if (v instanceof NBTSerializableValue) + return v; + if (v instanceof NullValue) + return Value.NULL; + return NBTSerializableValue.parseString(v.getString()); + } + + + private static Value decodeTagDeep(Tag t) + { + if (t instanceof CompoundTag) + { + Map pairs = new HashMap<>(); + CompoundTag ctag = (CompoundTag)t; + for (String key: ctag.getKeys()) + { + pairs.put(new StringValue(key), decodeTagDeep(ctag.get(key))); + } + return MapValue.wrap(pairs); + } + if (t instanceof AbstractListTag) + { + List elems = new ArrayList<>(); + AbstractListTag ltag = (AbstractListTag)t; + for (Tag elem: ltag) + { + elems.add(decodeTagDeep(elem)); + } + return ListValue.wrap(elems); + } if (t instanceof AbstractNumberTag) return new NumericValue(((AbstractNumberTag) t).getDouble()); // more can be done here @@ -304,13 +356,6 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa return nbtTag; } - private void replaceTag(Tag newTag) - { - nbtTag = newTag; - nbtString = null; - nbtSupplier = null; // just to be sure - } - @Override public boolean equals(final Object o) { @@ -346,6 +391,7 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa { try { + ensureOwnership(); return (CompoundTag) getTag(); } catch (ClassCastException e) @@ -364,12 +410,12 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa public boolean put(Value where, Value value, Value conditions) { /// WIP - + ensureOwnership(); NbtPathArgumentType.NbtPath path = cachePath(where.getString()); Tag tagToInsert = value instanceof NBTSerializableValue ? ((NBTSerializableValue) value).getTag() : new NBTSerializableValue(value.getString()).getTag(); - Tag modifiedTag; + boolean modifiedTag; if (conditions instanceof NumericValue) { modifiedTag = modify_insert((int)((NumericValue) conditions).getLong(), path, tagToInsert); @@ -390,22 +436,17 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa return false; } } - if (modifiedTag != null) - { - replaceTag(modifiedTag); - return true; - } - return false; + return modifiedTag; } - private Tag modify_insert(int index, NbtPathArgumentType.NbtPath nbtPath, Tag newElement) + private boolean modify_insert(int index, NbtPathArgumentType.NbtPath nbtPath, Tag newElement) { - return modify_insert(index, nbtPath, newElement, this.getTag().copy()); + return modify_insert(index, nbtPath, newElement, this.getTag()); } - private Tag modify_insert(int index, NbtPathArgumentType.NbtPath nbtPath, Tag newElement, Tag currentTag) + private boolean modify_insert(int index, NbtPathArgumentType.NbtPath nbtPath, Tag newElement, Tag currentTag) { Collection targets; try @@ -414,7 +455,7 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa } catch (CommandSyntaxException e) { - return null; + return false; } boolean modified = false; @@ -428,27 +469,27 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa { AbstractListTag targetList = (AbstractListTag) target; if (!targetList.addTag(index < 0 ? targetList.size() + index + 1 : index, newElement.copy())) - return null; + return false; modified = true; } catch (IndexOutOfBoundsException ignored) { } } - return modified?currentTag:null; + return modified; } - private Tag modify_merge(NbtPathArgumentType.NbtPath nbtPath, Tag replacement) //nbtPathArgumentType$NbtPath_1, list_1) + private boolean modify_merge(NbtPathArgumentType.NbtPath nbtPath, Tag replacement) //nbtPathArgumentType$NbtPath_1, list_1) { if (!(replacement instanceof CompoundTag)) { - return getTag(); + return false; } - Tag originalTag = getTag().copy(); + Tag ownTag = getTag(); try { - for (Tag target : nbtPath.getOrInit(originalTag, CompoundTag::new)) + for (Tag target : nbtPath.getOrInit(ownTag, CompoundTag::new)) { if (!(target instanceof CompoundTag)) { @@ -457,23 +498,26 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa ((CompoundTag) target).copyFrom((CompoundTag) replacement); } } - catch (CommandSyntaxException ignored) { } - return originalTag; + catch (CommandSyntaxException ignored) + { + return false; + } + return true; } - private Tag modify_replace(NbtPathArgumentType.NbtPath nbtPath, Tag replacement) //nbtPathArgumentType$NbtPath_1, list_1) + private boolean modify_replace(NbtPathArgumentType.NbtPath nbtPath, Tag replacement) //nbtPathArgumentType$NbtPath_1, list_1) { - Tag originalTag = getTag().copy(); + Tag tag = getTag(); String pathText = nbtPath.toString(); if (pathText.endsWith("]")) // workaround for array replacement or item in the array replacement { - if (nbtPath.remove(originalTag)==0) - return null; + if (nbtPath.remove(tag)==0) + return false; Pattern pattern = Pattern.compile("\\[[^\\[]*]$"); Matcher matcher = pattern.matcher(pathText); if (!matcher.find()) // malformed path { - return null; + return false; } String arrAccess = matcher.group(); int pos; @@ -487,21 +531,21 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa } catch (NumberFormatException e) { - return null; + return false; } } NbtPathArgumentType.NbtPath newPath = cachePath(pathText.substring(0, pathText.length()-arrAccess.length())); - return modify_insert(pos,newPath,replacement, originalTag); + return modify_insert(pos,newPath,replacement, tag); } try { - nbtPath.put(originalTag, () -> replacement); + nbtPath.put(tag, () -> replacement); } catch (CommandSyntaxException e) { - return null; + return false; } - return originalTag; + return true; } @Override @@ -527,13 +571,23 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa return cachePath(where.getString()).count(getTag()) > 0; } + private void ensureOwnership() + { + if (!owned) + { + nbtTag = getTag().copy(); + nbtString = null; + nbtSupplier = null; // just to be sure + owned = true; + } + } + @Override public boolean delete(Value where) { NbtPathArgumentType.NbtPath path = cachePath(where.getString()); - Tag tag = getTag().copy(); - int removed = path.remove(tag); - if (removed > 0) replaceTag(tag); + ensureOwnership(); + int removed = path.remove(getTag()); return removed > 0; }