package carpet.script; import carpet.CarpetServer; import carpet.fakes.ChunkTicketManagerInterface; import carpet.fakes.MinecraftServerInterface; import carpet.fakes.BiomeArrayInterface; import carpet.fakes.ServerChunkManagerInterface; import carpet.fakes.ServerLightingProviderInterface; import carpet.fakes.StatTypeInterface; import carpet.fakes.ThreadedAnvilChunkStorageInterface; import carpet.helpers.FeatureGenerator; import carpet.mixins.PointOfInterest_scarpetMixin; import carpet.script.Fluff.TriFunction; import carpet.script.argument.BlockArgument; import carpet.script.argument.FunctionArgument; import carpet.script.argument.Vector3Argument; import carpet.script.bundled.Module; import carpet.CarpetSettings; import carpet.script.exception.BreakStatement; import carpet.script.exception.CarpetExpressionException; import carpet.script.exception.ContinueStatement; import carpet.script.exception.ExitStatement; import carpet.script.exception.ExpressionException; import carpet.script.exception.InternalExpressionException; import carpet.script.utils.ShapeDispatcher; import carpet.script.value.BlockValue; import carpet.script.value.EntityValue; import carpet.script.value.FormattedTextValue; import carpet.script.value.FunctionValue; import carpet.script.value.LazyListValue; import carpet.script.value.ListValue; import carpet.script.value.MapValue; import carpet.script.value.NBTSerializableValue; import carpet.script.value.NullValue; import carpet.script.value.NumericValue; import carpet.script.value.StringValue; import carpet.script.value.ThreadValue; import carpet.script.value.Value; import carpet.utils.BlockInfo; import carpet.utils.Messenger; import com.google.common.collect.Sets; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.exceptions.CommandSyntaxException; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.LongSet; import net.minecraft.block.BarrierBlock; import net.minecraft.block.BedrockBlock; import net.minecraft.block.Block; import net.minecraft.block.BlockPlacementEnvironment; import net.minecraft.block.BlockState; import net.minecraft.block.CommandBlock; import net.minecraft.block.JigsawBlock; import net.minecraft.block.StructureBlock; import net.minecraft.block.entity.BlockEntity; import net.minecraft.enchantment.EnchantmentHelper; import net.minecraft.entity.FallingBlockEntity; import net.minecraft.item.BlockItem; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; import net.minecraft.item.MiningToolItem; import net.minecraft.item.ShearsItem; import net.minecraft.item.SwordItem; import net.minecraft.item.TridentItem; import net.minecraft.network.packet.s2c.play.ContainerSlotUpdateS2CPacket; import net.minecraft.network.packet.s2c.play.PlaySoundIdS2CPacket; import net.minecraft.command.arguments.ItemStackArgument; import net.minecraft.enchantment.Enchantments; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.ItemEntity; import net.minecraft.entity.LightningEntity; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.SpawnType; import net.minecraft.entity.decoration.ArmorStandEntity; import net.minecraft.entity.mob.MobEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.StringTag; import net.minecraft.nbt.Tag; import net.minecraft.particle.ParticleEffect; import net.minecraft.predicate.entity.EntityPredicates; import net.minecraft.scoreboard.Scoreboard; import net.minecraft.scoreboard.ScoreboardCriterion; import net.minecraft.scoreboard.ScoreboardObjective; import net.minecraft.scoreboard.ScoreboardPlayerScore; import net.minecraft.server.MinecraftServer; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ChunkTicket; import net.minecraft.server.world.ChunkTicketType; import net.minecraft.server.world.ServerWorld; import net.minecraft.sound.BlockSoundGroup; import net.minecraft.sound.SoundCategory; import net.minecraft.stat.Stat; import net.minecraft.stat.StatType; import net.minecraft.state.StateManager; import net.minecraft.state.property.Property; import net.minecraft.structure.StructurePiece; import net.minecraft.structure.StructureStart; import net.minecraft.text.LiteralText; import net.minecraft.util.ActionResult; import net.minecraft.util.Clearable; import net.minecraft.util.Identifier; import net.minecraft.util.InvalidIdentifierException; import net.minecraft.util.SortedArraySet; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Box; import net.minecraft.util.math.ChunkPos; import net.minecraft.util.math.Direction; import net.minecraft.util.math.EulerAngle; import net.minecraft.util.math.BlockBox; import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3i; import net.minecraft.util.registry.Registry; import net.minecraft.world.gen.feature.Feature; import net.minecraft.world.gen.feature.StructureFeature; import net.minecraft.world.poi.PointOfInterest; import net.minecraft.world.poi.PointOfInterestStorage; import net.minecraft.world.poi.PointOfInterestType; import net.minecraft.world.Heightmap; import net.minecraft.world.LightType; import net.minecraft.world.World; import net.minecraft.world.biome.Biome; import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.ChunkStatus; import net.minecraft.world.chunk.WorldChunk; import net.minecraft.world.dimension.DimensionType; import net.minecraft.world.gen.ChunkRandom; import org.apache.commons.lang3.tuple.Pair; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletionException; import java.util.function.BiFunction; import java.util.function.Predicate; import java.util.stream.Collectors; import static carpet.script.utils.WorldTools.canHasChunk; import static java.lang.Math.abs; import static java.lang.Math.max; import static java.lang.Math.min; public class CarpetExpression { private final ServerCommandSource source; private final BlockPos origin; private final Expression expr; public Expression getExpr() {return expr;} private static long tickStart = 0L; // dummy entity for dummy requirements in the loot tables (see snowball) private static FallingBlockEntity DUMMY_ENTITY = new FallingBlockEntity(EntityType.FALLING_BLOCK, null); public static final String MARKER_STRING = "__scarpet_marker"; private static boolean stopAll = false; private static final Map DIRECTION_MAP = Arrays.stream(Direction.values()).collect(Collectors.toMap(Direction::getName, (direction) -> direction)); private LazyValue booleanStateTest( Context c, String name, List params, BiFunction test ) { CarpetContext cc = (CarpetContext) c; if (params.size() == 0) { throw new InternalExpressionException("'" + name + "' requires at least one parameter"); } Value v0 = params.get(0).evalValue(c); if (v0 instanceof BlockValue) { Value retval = test.apply(((BlockValue) v0).getBlockState(), ((BlockValue) v0).getPos()) ? Value.TRUE : Value.FALSE; return (c_, t_) -> retval; } BlockValue block = BlockArgument.findIn(cc, params, 0).block; Value retval = test.apply(block.getBlockState(), block.getPos()) ? Value.TRUE : Value.FALSE; return (c_, t_) -> retval; } private LazyValue stateStringQuery( Context c, String name, List params, BiFunction test ) { CarpetContext cc = (CarpetContext) c; if (params.size() == 0) { throw new InternalExpressionException("'" + name + "' requires at least one parameter"); } Value v0 = params.get(0).evalValue(c); if (v0 instanceof BlockValue) { String strVal = test.apply( ((BlockValue) v0).getBlockState(), ((BlockValue) v0).getPos()); Value retval = strVal != null ? new StringValue(strVal) : Value.NULL; return (c_, t_) -> retval; } BlockValue block = BlockArgument.findIn(cc, params, 0).block; String strVal = test.apply(block.getBlockState(), block.getPos()); Value retval = strVal != null ? new StringValue(strVal) : Value.NULL; return (c_, t_) -> retval; } private LazyValue genericStateTest( Context c, String name, List params, TriFunction test ) { CarpetContext cc = (CarpetContext) c; if (params.size() == 0) { throw new InternalExpressionException("'" + name + "' requires at least one parameter"); } Value v0 = params.get(0).evalValue(c); if (v0 instanceof BlockValue) { try { Value retval = test.apply(((BlockValue) v0).getBlockState(), ((BlockValue) v0).getPos(), cc.s.getWorld()); return (_c, _t) -> retval; } catch (NullPointerException ignored) { throw new InternalExpressionException("'" + name + "' function requires a block that is positioned in the world"); } } BlockValue block = BlockArgument.findIn(cc, params, 0).block; Value retval = test.apply(block.getBlockState(), block.getPos(), cc.s.getWorld()); return (c_, t_) -> retval; } private > BlockState setProperty(Property property, String name, String value, BlockState bs) { Optional optional = property.parse(value); if (optional.isPresent()) { bs = bs.with(property, optional.get()); } else { throw new InternalExpressionException(value + " is not a valid value for property " + name); } return bs; } private static void forceChunkUpdate(BlockPos pos, ServerWorld world) { Chunk chunk = world.getChunk(pos); chunk.setShouldSave(true); for (int i = 0; i<16; i++) { BlockPos section = new BlockPos((pos.getX()>>4 <<4)+8, 16*i, (pos.getZ()>>4 <<4)+8); world.getChunkManager().markForUpdate(section); world.getChunkManager().markForUpdate(section.east()); world.getChunkManager().markForUpdate(section.west()); world.getChunkManager().markForUpdate(section.north()); } } private boolean tryBreakBlock_copy_from_ServerPlayerInteractionManager(ServerPlayerEntity player, BlockPos blockPos_1) { //this could be done little better, by hooking up event handling not in try_break_block but wherever its called // so we can safely call it here // but that woudl do for now. BlockState blockState_1 = player.world.getBlockState(blockPos_1); if (!player.getMainHandStack().getItem().canMine(blockState_1, player.world, blockPos_1, player)) { return false; } else { BlockEntity blockEntity_1 = player.world.getBlockEntity(blockPos_1); Block block_1 = blockState_1.getBlock(); if ((block_1 instanceof CommandBlock || block_1 instanceof StructureBlock || block_1 instanceof JigsawBlock) && !player.isCreativeLevelTwoOp()) { player.world.updateListeners(blockPos_1, blockState_1, blockState_1, 3); return false; } else if (player.canMine(player.world, blockPos_1, player.interactionManager.getGameMode())) { return false; } else { block_1.onBreak(player.world, blockPos_1, blockState_1, player); boolean boolean_1 = player.world.removeBlock(blockPos_1, false); if (boolean_1) { block_1.onBroken(player.world, blockPos_1, blockState_1); } if (player.isCreative()) { return true; } else { ItemStack itemStack_1 = player.getMainHandStack(); boolean boolean_2 = player.isUsingEffectiveTool(blockState_1); itemStack_1.postMine(player.world, blockState_1, blockPos_1, player); if (boolean_1 && boolean_2) { ItemStack itemStack_2 = itemStack_1.isEmpty() ? ItemStack.EMPTY : itemStack_1.copy(); block_1.afterBreak(player.world, player, blockPos_1, blockState_1, blockEntity_1, itemStack_2); } return true; } } } } private static Value structureToValue(StructureStart structure) { if (structure == null || structure == StructureStart.DEFAULT) return Value.NULL; List pieces = new ArrayList<>(); for (StructurePiece piece : structure.getChildren()) { BlockBox box = piece.getBoundingBox(); pieces.add(ListValue.of( new StringValue( NBTSerializableValue.nameFromRegistryId(Registry.STRUCTURE_PIECE.getId(piece.getType()))), (piece.getFacing()== null)?Value.NULL: new StringValue(piece.getFacing().getName()), ListValue.fromTriple(box.minX, box.minY, box.minZ), ListValue.fromTriple(box.maxX, box.maxY, box.maxZ) )); }; BlockBox boundingBox = structure.getBoundingBox(); Map ret = new HashMap<>(); ret.put(new StringValue("box"), ListValue.of( ListValue.fromTriple(boundingBox.minX, boundingBox.minY, boundingBox.minZ), ListValue.fromTriple(boundingBox.maxX, boundingBox.maxY, boundingBox.maxZ) )); ret.put(new StringValue("pieces"), ListValue.wrap(pieces)); return MapValue.wrap(ret); } private void API_BlockManipulation() { this.expr.addLazyFunction("block", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext) c; if (lv.size() == 0) { throw new InternalExpressionException("Block requires at least one parameter"); } BlockValue retval = BlockArgument.findIn(cc, lv, 0, true).block; // fixing block state and data retval.getBlockState(); retval.getData(); return (c_, t_) -> retval; }); this.expr.addLazyFunction("block_data", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext) c; if (lv.size() == 0) { throw new InternalExpressionException("Block requires at least one parameter"); } CompoundTag tag = BlockArgument.findIn(cc, lv, 0, true).block.getData(); if (tag == null) return (c_, t_) -> Value.NULL; Value retval = new NBTSerializableValue(tag); return (c_, t_) -> retval; }); // poi_get(pos, radius?, type?, occupation?) this.expr.addLazyFunction("poi", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext) c; if (lv.size() == 0) throw new InternalExpressionException("'poi' requires at least one parameter"); BlockArgument locator = BlockArgument.findIn(cc, lv, 0, false); BlockPos pos = locator.block.getPos(); PointOfInterestStorage store = cc.s.getWorld().getPointOfInterestStorage(); if (lv.size() == locator.offset) { PointOfInterestType poiType = store.getType(pos).orElse(null); if (poiType == null) return LazyValue.NULL; // this feels wrong, but I don't want to mix-in more than I really need to. // also distance adds 0.5 to each point which screws up accurate distance calculations // you shoudn't be using POI with that in mind anyways, so I am not worried about it. PointOfInterest poi = store.get( poiType.getCompletionCondition(), pos, 1, PointOfInterestStorage.OccupationStatus.ANY ).filter(p -> p.getPos().equals(pos)).findFirst().orElse(null); if (poi == null) return LazyValue.NULL; Value ret = ListValue.of( new StringValue(poi.getType().toString()), new NumericValue(poiType.getTicketCount() - ((PointOfInterest_scarpetMixin)poi).getFreeTickets()) ); return (_c, _t) -> ret; } long radius = NumericValue.asNumber(lv.get(locator.offset+0).evalValue(c)).getLong(); if (radius < 0) return ListValue.lazyEmpty(); Predicate condition = PointOfInterestType.ALWAYS_TRUE; if (locator.offset + 1 < lv.size()) { String poiType = lv.get(locator.offset+1).evalValue(c).getString().toLowerCase(Locale.ROOT); if (!"any".equals(poiType)) { PointOfInterestType type = Registry.POINT_OF_INTEREST_TYPE.get(new Identifier(poiType)); if (type == PointOfInterestType.UNEMPLOYED && !"unemployed".equals(poiType)) return LazyValue.NULL; condition = (tt) -> tt == type; } } PointOfInterestStorage.OccupationStatus status = PointOfInterestStorage.OccupationStatus.ANY; if (locator.offset + 2 < lv.size()) { String statusString = lv.get(locator.offset+2).evalValue(c).getString().toLowerCase(Locale.ROOT); if ("occupied".equals(statusString)) status = PointOfInterestStorage.OccupationStatus.IS_OCCUPIED; else if ("available".equals(statusString)) status = PointOfInterestStorage.OccupationStatus.HAS_SPACE; else return LazyValue.NULL; } Value ret = ListValue.wrap(store.get(condition, pos, (int)radius, status).sorted(Comparator.comparingDouble(p -> p.getPos().getSquaredDistance(pos))).map(p -> ListValue.of( new StringValue(p.getType().toString()), new NumericValue(p.getType().getTicketCount() - ((PointOfInterest_scarpetMixin)p).getFreeTickets()), ListValue.of(new NumericValue(p.getPos().getX()), new NumericValue(p.getPos().getY()), new NumericValue(p.getPos().getZ())) ) ).collect(Collectors.toList())); return (c_, t_) -> ret; }); //poi_set(pos, null) poi_set(pos, type, occupied?, this.expr.addLazyFunction("set_poi", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext) c; if (lv.size() == 0) throw new InternalExpressionException("'set_poi' requires at least one parameter"); BlockArgument locator = BlockArgument.findIn(cc, lv, 0, false); BlockPos pos = locator.block.getPos(); if (lv.size() < locator.offset) throw new InternalExpressionException("'set_poi' requires the new poi type or null, after position argument"); Value poi = lv.get(locator.offset+0).evalValue(c); PointOfInterestStorage store = cc.s.getWorld().getPointOfInterestStorage(); if (poi == Value.NULL) { // clear poi information if (store.getType(pos).isPresent()) { store.remove(pos); return LazyValue.TRUE; } return LazyValue.FALSE; } String poiTypeString = poi.getString().toLowerCase(Locale.ROOT); PointOfInterestType type = Registry.POINT_OF_INTEREST_TYPE.get(new Identifier(poiTypeString)); // solving lack of null with defaulted registries if (type == PointOfInterestType.UNEMPLOYED && !"unemployed".equals(poiTypeString)) throw new InternalExpressionException("Unknown POI type: "+poiTypeString); int occupancy = 0; if (locator.offset + 1 < lv.size()) { occupancy = (int)NumericValue.asNumber(lv.get(locator.offset + 1).evalValue(c)).getLong(); if (occupancy < 0) throw new InternalExpressionException("Occupancy cannot be negative"); } if (store.getType(pos).isPresent()) store.remove(pos); store.add(pos, type); // setting occupancy for a // again - don't want to mix in unnecessarily - peeps not gonna use it that often so not worries about it. if (occupancy > 0) { int finalO = occupancy; store.get((tt) -> tt==type, pos, 1, PointOfInterestStorage.OccupationStatus.ANY ).filter(p -> p.getPos().equals(pos)).findFirst().ifPresent(p -> { for (int i=0; i < finalO; i++) ((PointOfInterest_scarpetMixin)p).callReserveTicket(); }); } return LazyValue.TRUE; }); this.expr.addLazyFunction("pos", 1, (c, t, lv) -> { Value arg = lv.get(0).evalValue(c); if (arg instanceof BlockValue) { BlockPos pos = ((BlockValue) arg).getPos(); if (pos == null) throw new InternalExpressionException("Cannot fetch position of an unrealized block"); Value retval = ListValue.of(new NumericValue(pos.getX()), new NumericValue(pos.getY()), new NumericValue(pos.getZ())); return (c_, t_) -> retval; } else if (arg instanceof EntityValue) { Entity e = ((EntityValue) arg).getEntity(); if (e == null) throw new InternalExpressionException("Null entity"); Value retval = ListValue.of(new NumericValue(e.getX()), new NumericValue(e.getY()), new NumericValue(e.getZ())); return (c_, t_) -> retval; } else { throw new InternalExpressionException("'pos' works only with a block or an entity type"); } }); this.expr.addLazyFunction("pos_offset", -1, (c, t, lv) -> { BlockArgument locator = BlockArgument.findIn((CarpetContext)c, lv, 0); BlockPos pos = locator.block.getPos(); if (lv.size() <= locator.offset) throw new InternalExpressionException("'pos_offset' needs at least position, and direction"); String directionString = lv.get(locator.offset).evalValue(c).getString(); Direction dir = DIRECTION_MAP.get(directionString); if (dir == null) throw new InternalExpressionException("Unknown direction: "+directionString); int howMuch = 1; if (lv.size() > locator.offset+1) howMuch = (int) NumericValue.asNumber(lv.get(locator.offset+1).evalValue(c)).getLong(); BlockPos retpos = pos.offset(dir, howMuch); Value ret = ListValue.of(new NumericValue(retpos.getX()), new NumericValue(retpos.getY()), new NumericValue(retpos.getZ())); return (cc, tt) -> ret; }); this.expr.addLazyFunction("solid", -1, (c, t, lv) -> genericStateTest(c, "solid", lv, (s, p, w) -> new NumericValue(s.isSimpleFullBlock(w, p)))); this.expr.addLazyFunction("air", -1, (c, t, lv) -> booleanStateTest(c, "air", lv, (s, p) -> s.isAir())); this.expr.addLazyFunction("liquid", -1, (c, t, lv) -> booleanStateTest(c, "liquid", lv, (s, p) -> !s.getFluidState().isEmpty())); this.expr.addLazyFunction("flammable", -1, (c, t, lv) -> booleanStateTest(c, "flammable", lv, (s, p) -> s.getMaterial().isBurnable())); this.expr.addLazyFunction("transparent", -1, (c, t, lv) -> booleanStateTest(c, "transparent", lv, (s, p) -> !s.getMaterial().isSolid())); /*this.expr.addLazyFunction("opacity", -1, (c, t, lv) -> genericStateTest(c, "opacity", lv, (s, p, w) -> new NumericValue(s.getOpacity(w, p)))); this.expr.addLazyFunction("blocks_daylight", -1, (c, t, lv) -> genericStateTest(c, "blocks_daylight", lv, (s, p, w) -> new NumericValue(s.propagatesSkylightDown(w, p))));*/ // investigate this.expr.addLazyFunction("emitted_light", -1, (c, t, lv) -> genericStateTest(c, "emitted_light", lv, (s, p, w) -> new NumericValue(s.getLuminance()))); this.expr.addLazyFunction("light", -1, (c, t, lv) -> genericStateTest(c, "light", lv, (s, p, w) -> new NumericValue(Math.max(w.getLightLevel(LightType.BLOCK, p), w.getLightLevel(LightType.SKY, p))))); this.expr.addLazyFunction("block_light", -1, (c, t, lv) -> genericStateTest(c, "block_light", lv, (s, p, w) -> new NumericValue(w.getLightLevel(LightType.BLOCK, p)))); this.expr.addLazyFunction("sky_light", -1, (c, t, lv) -> genericStateTest(c, "sky_light", lv, (s, p, w) -> new NumericValue(w.getLightLevel(LightType.SKY, p)))); this.expr.addLazyFunction("see_sky", -1, (c, t, lv) -> genericStateTest(c, "see_sky", lv, (s, p, w) -> new NumericValue(w.isSkyVisible(p)))); this.expr.addLazyFunction("brightness", -1, (c, t, lv) -> genericStateTest(c, "brightness", lv, (s, p, w) -> new NumericValue(w.getBrightness(p)))); this.expr.addLazyFunction("hardness", -1, (c, t, lv) -> genericStateTest(c, "hardness", lv, (s, p, w) -> new NumericValue(s.getHardness(w, p)))); this.expr.addLazyFunction("blast_resistance", -1, (c, t, lv) -> genericStateTest(c, "blast_resistance", lv, (s, p, w) -> new NumericValue(s.getBlock().getBlastResistance()))); this.expr.addLazyFunction("in_slime_chunk", -1, (c, t, lv) -> { BlockPos pos = BlockArgument.findIn((CarpetContext)c, lv, 0).block.getPos(); ChunkPos chunkPos = new ChunkPos(pos); Value ret = new NumericValue(ChunkRandom.create( chunkPos.x, chunkPos.z, ((CarpetContext)c).s.getWorld().getSeed(), 987234911L ).nextInt(10) == 0); return (_c, _t) -> ret; }); this.expr.addLazyFunction("top", -1, (c, t, lv) -> { String type = lv.get(0).evalValue(c).getString().toLowerCase(Locale.ROOT); Heightmap.Type htype; switch (type) { //case "light": htype = Heightmap.Type.LIGHT_BLOCKING; break; //investigate case "motion": htype = Heightmap.Type.MOTION_BLOCKING; break; case "terrain": htype = Heightmap.Type.MOTION_BLOCKING_NO_LEAVES; break; case "ocean_floor": htype = Heightmap.Type.OCEAN_FLOOR; break; case "surface": htype = Heightmap.Type.WORLD_SURFACE; break; default: throw new InternalExpressionException("Unknown heightmap type: "+type); } BlockArgument locator = BlockArgument.findIn((CarpetContext)c, lv, 1); BlockPos pos = locator.block.getPos(); int x = pos.getX(); int z = pos.getZ(); Value retval = new NumericValue(((CarpetContext)c).s.getWorld().getChunk(x >> 4, z >> 4).sampleHeightmap(htype, x & 15, z & 15) + 1); return (c_, t_) -> retval; //BlockPos pos = new BlockPos(x,y,z); //return new BlockValue(source.getWorld().getBlockState(pos), pos); }); this.expr.addLazyFunction("loaded", -1, (c, t, lv) -> { Value retval = ((CarpetContext) c).s.getWorld().isChunkLoaded(BlockArgument.findIn((CarpetContext) c, lv, 0).block.getPos()) ? Value.TRUE : Value.FALSE; return (c_, t_) -> retval; }); // Deprecated, use loaded_status as more indicative this.expr.addLazyFunction("loaded_ep", -1, (c, t, lv) -> { BlockPos pos = BlockArgument.findIn((CarpetContext)c, lv, 0).block.getPos(); Value retval = ((CarpetContext)c).s.getWorld().getChunkManager().shouldTickChunk(new ChunkPos(pos))?Value.TRUE : Value.FALSE; return (c_, t_) -> retval; }); this.expr.addLazyFunction("loaded_status", -1, (c, t, lv) -> { BlockPos pos = BlockArgument.findIn((CarpetContext)c, lv, 0).block.getPos(); WorldChunk chunk = ((CarpetContext)c).s.getWorld().getChunkManager().getWorldChunk(pos.getX()>>4, pos.getZ()>>4, false); if (chunk == null) return LazyValue.ZERO; Value retval = new NumericValue(chunk.getLevelType().ordinal()); return (c_, t_) -> retval; }); this.expr.addLazyFunction("is_chunk_generated", -1, (c, t, lv) -> { BlockArgument locator = BlockArgument.findIn((CarpetContext)c, lv, 0); BlockPos pos = locator.block.getPos(); boolean force = false; if (lv.size() > locator.offset) force = lv.get(locator.offset).evalValue(c, Context.BOOLEAN).getBoolean(); Value retval = new NumericValue(canHasChunk(((CarpetContext)c).s.getWorld(), new ChunkPos(pos), null, force)); return (c_, t_) -> retval; }); this.expr.addLazyFunction("generation_status", -1, (c, t, lv) -> { BlockArgument blockArgument = BlockArgument.findIn((CarpetContext)c, lv, 0); BlockPos pos = blockArgument.block.getPos(); boolean forceLoad = false; if (lv.size() > blockArgument.offset) forceLoad = lv.get(blockArgument.offset).evalValue(c, Context.BOOLEAN).getBoolean(); Chunk chunk = ((CarpetContext)c).s.getWorld().getChunk(pos.getX()>>4, pos.getZ()>>4, ChunkStatus.EMPTY, forceLoad); if (chunk == null) return LazyValue.NULL; Value retval = new StringValue(chunk.getStatus().getId()); return (c_, t_) -> retval; }); this.expr.addLazyFunction("chunk_tickets", -1, (c, t, lv) -> { ServerWorld world = ((CarpetContext) c).s.getWorld(); Long2ObjectOpenHashMap>> levelTickets = ( (ChunkTicketManagerInterface) ((ServerChunkManagerInterface) world.getChunkManager()) .getCMTicketManager() ).getTicketsByPosition(); List res = new ArrayList<>(); if (lv.size() == 0) { for (long key : levelTickets.keySet()) { ChunkPos chpos = new ChunkPos(key); for (ChunkTicket ticket : levelTickets.get(key)) { res.add(ListValue.of( new StringValue(ticket.getType().toString()), new NumericValue(33 - ticket.getLevel()), new NumericValue(chpos.x), new NumericValue(chpos.z) )); } } } else { BlockArgument blockArgument = BlockArgument.findIn((CarpetContext) c, lv, 0); BlockPos pos = blockArgument.block.getPos(); SortedArraySet> tickets = levelTickets.get(new ChunkPos(pos).toLong()); if (tickets != null) { for (ChunkTicket ticket : tickets) { res.add(ListValue.of( new StringValue(ticket.getType().toString()), new NumericValue(33 - ticket.getLevel()) )); } } } res.sort(Comparator.comparing(e -> ((ListValue) e).getItems().get(1)).reversed()); Value returnValue = ListValue.wrap(res); return (_c, _t) -> returnValue; }); this.expr.addLazyFunction("suffocates", -1, (c, t, lv) -> genericStateTest(c, "suffocates", lv, (s, p, w) -> new NumericValue(s.canSuffocate(w, p)))); this.expr.addLazyFunction("power", -1, (c, t, lv) -> genericStateTest(c, "power", lv, (s, p, w) -> new NumericValue(w.getReceivedRedstonePower(p)))); this.expr.addLazyFunction("ticks_randomly", -1, (c, t, lv) -> booleanStateTest(c, "ticks_randomly", lv, (s, p) -> s.hasRandomTicks())); this.expr.addLazyFunction("update", -1, (c, t, lv) -> booleanStateTest(c, "update", lv, (s, p) -> { ((CarpetContext) c).s.getWorld().updateNeighbor(p, s.getBlock(), p); return true; })); this.expr.addLazyFunction("block_tick", -1, (c, t, lv) -> booleanStateTest(c, "block_tick", lv, (s, p) -> { ServerWorld w = ((CarpetContext)c).s.getWorld(); s.randomTick(w, p, w.random); return true; })); this.expr.addLazyFunction("random_tick", -1, (c, t, lv) -> booleanStateTest(c, "random_tick", lv, (s, p) -> { ServerWorld w = ((CarpetContext)c).s.getWorld(); if (s.hasRandomTicks() || s.getFluidState().hasRandomTicks()) s.randomTick(w, p, w.random); return true; })); this.expr.addLazyFunction("without_updates", 1, (c, t, lv) -> { boolean previous = CarpetSettings.impendingFillSkipUpdates; try { CarpetSettings.impendingFillSkipUpdates = true; Value ret = lv.get(0).evalValue(c, t); return (cc, tt) -> ret; } finally { CarpetSettings.impendingFillSkipUpdates = previous; } }); this.expr.addLazyFunction("set", -1, (c, t, lv) -> { 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()) { StateManager states = sourceBlockState.getBlock().getStateManager(); for (int i = sourceLocator.offset; i < lv.size(); i += 2) { String paramString = lv.get(i).evalValue(c).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(); sourceBlockState = setProperty(property, paramString, paramValue, sourceBlockState); } } CompoundTag data = sourceLocator.block.getData(); if (sourceBlockState == targetBlockState && data == null) return (c_, t_) -> Value.FALSE; BlockState finalSourceBlockState = sourceBlockState; BlockPos targetPos = targetLocator.block.getPos(); cc.s.getMinecraftServer().submitAndJoin( () -> { Clearable.clear(world.getBlockEntity(targetPos)); world.setBlockState(targetPos, finalSourceBlockState, 2); if (data != null) { BlockEntity be = world.getBlockEntity(targetPos); if (be != null) { CompoundTag destTag = data.copy(); destTag.putInt("x", targetPos.getX()); destTag.putInt("y", targetPos.getY()); destTag.putInt("z", targetPos.getZ()); be.fromTag(destTag); be.markDirty(); } } }); Value retval = new BlockValue(finalSourceBlockState, world, targetLocator.block.getPos()); return (c_, t_) -> retval; }); this.expr.addLazyFunction("destroy", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; ServerWorld world = cc.s.getWorld(); BlockArgument locator = BlockArgument.findIn(cc, lv, 0); BlockState state = locator.block.getBlockState(); if (state.isAir()) return (c_, t_) -> Value.FALSE; BlockPos where = locator.block.getPos(); BlockEntity be = world.getBlockEntity(where); long how = 0; Item item = Items.DIAMOND_PICKAXE; boolean playerBreak = false; if (lv.size() > locator.offset) { Value val = lv.get(locator.offset).evalValue(c); if (val instanceof NumericValue) { how = ((NumericValue) val).getLong(); } else { playerBreak = true; String itemString = val.getString(); item = Registry.ITEM.get(new Identifier(itemString)); if (item == Items.AIR && !itemString.equals("air")) throw new InternalExpressionException("Incorrect item: " + itemString); } } CompoundTag tag = null; if (lv.size() > locator.offset+1) { if (!playerBreak) throw new InternalExpressionException("tag is not necessary with 'destroy' with no item"); Value tagValue = lv.get(locator.offset+1).evalValue(c); if (tagValue instanceof NullValue) tag = null; else if (tagValue instanceof NBTSerializableValue) tag = ((NBTSerializableValue) tagValue).getCompoundTag(); else { NBTSerializableValue readTag = NBTSerializableValue.parseString(tagValue.getString()); if (readTag == null) throw new InternalExpressionException("Incorrect tag: "+tagValue.getString()); tag = readTag.getCompoundTag(); } } ItemStack tool = new ItemStack(item, 1); if (tag != null) tool.setTag(tag); if (playerBreak && state.getHardness(world, where) < 0.0) return LazyValue.FALSE; boolean removed = world.removeBlock(where, false); if (!removed) return LazyValue.FALSE; world.playLevelEvent(null, 2001, where, Block.getRawIdFromState(state)); boolean toolBroke = false; boolean dropLoot = true; if (playerBreak) { boolean isUsingEffectiveTool = state.getMaterial().canBreakByHand() || tool.isEffectiveOn(state); //postMine() durability from item classes float hardness = state.getHardness(world, where); int damageAmount = 0; if ((item instanceof MiningToolItem && hardness > 0.0) || item instanceof ShearsItem) { damageAmount = 1; } else if (item instanceof TridentItem || item instanceof SwordItem) { damageAmount = 2; } toolBroke = damageAmount>0 && tool.damage(damageAmount, world.getRandom(), null); if (!isUsingEffectiveTool) dropLoot = false; } if (dropLoot) { if (how < 0 || (tag != null && EnchantmentHelper.getLevel(Enchantments.SILK_TOUCH, tool) > 0)) { Block.dropStack(world, where, new ItemStack(state.getBlock())); } else { if (how > 0) tool.addEnchantment(Enchantments.FORTUNE, (int) how); Block.dropStacks(state, world, where, be, DUMMY_ENTITY, tool); } } if (!playerBreak) // no tool info - block brokwn return (c_, t_) -> Value.TRUE; if (toolBroke) return LazyValue.NULL; Tag outtag = tool.getTag(); if (outtag == null) return LazyValue.TRUE; Value ret = new NBTSerializableValue(outtag); return (_c, _t) -> ret; }); this.expr.addLazyFunction("harvest", -1, (c, t, lv) -> { if (lv.size()<2) throw new InternalExpressionException("'harvest' takes at least 2 parameters: entity and block, or position, to harvest"); CarpetContext cc = (CarpetContext)c; World world = cc.s.getWorld(); Value entityValue = lv.get(0).evalValue(cc); if (!(entityValue instanceof EntityValue)) return (c_, t_) -> Value.FALSE; Entity e = ((EntityValue) entityValue).getEntity(); if (!(e instanceof ServerPlayerEntity)) return (c_, t_) -> Value.FALSE; ServerPlayerEntity player = (ServerPlayerEntity)e; BlockArgument locator = BlockArgument.findIn(cc, lv, 1); BlockPos where = locator.block.getPos(); BlockState state = locator.block.getBlockState(); Block block = state.getBlock(); boolean success = false; if (!((block instanceof BedrockBlock || block instanceof BarrierBlock) && player.interactionManager.isSurvivalLike())) success = tryBreakBlock_copy_from_ServerPlayerInteractionManager(player, where); if (success) world.playLevelEvent(null, 2001, where, Block.getRawIdFromState(state)); return success ? LazyValue.TRUE : LazyValue.FALSE; }); // TODO rename to use_item this.expr.addLazyFunction("place_item", -1, (c, t, lv) -> { if (lv.size()<2) throw new InternalExpressionException("'place_item' takes at least 2 parameters: item and block, or position, to place onto"); CarpetContext cc = (CarpetContext) c; String itemString = lv.get(0).evalValue(c).getString(); Vector3Argument locator = Vector3Argument.findIn(cc, lv, 1); ItemStackArgument stackArg = NBTSerializableValue.parseItem(itemString); BlockPos where = new BlockPos(locator.vec); String facing = "up"; if (lv.size() > locator.offset) facing = lv.get(locator.offset).evalValue(c).getString(); boolean sneakPlace = false; if (lv.size() > locator.offset+1) sneakPlace = lv.get(locator.offset+1).evalValue(c).getBoolean(); BlockValue.PlacementContext ctx; try { ctx = BlockValue.PlacementContext.from(cc.s.getWorld(), where, facing, sneakPlace, stackArg.createStack(1, false)); } catch (CommandSyntaxException e) { throw new InternalExpressionException(e.getMessage()); } if (!(stackArg.getItem() instanceof BlockItem)) { ActionResult useResult = ctx.getStack().useOnBlock(ctx); if (useResult == ActionResult.CONSUME || useResult == ActionResult.SUCCESS) { return LazyValue.TRUE; } } else { // not sure we need special case for block items, since useOnBlock can do that as well BlockItem blockItem = (BlockItem) stackArg.getItem(); if (!ctx.canPlace()) return (_c, _t) -> Value.FALSE; BlockState placementState = blockItem.getBlock().getPlacementState(ctx); if (placementState != null) { if (placementState.canPlaceAt(cc.s.getWorld(), where)) { cc.s.getWorld().setBlockState(where, placementState, 2); BlockSoundGroup blockSoundGroup = placementState.getSoundGroup(); cc.s.getWorld().playSound(null, where, blockSoundGroup.getPlaceSound(), SoundCategory.BLOCKS, (blockSoundGroup.getVolume() + 1.0F) / 2.0F, blockSoundGroup.getPitch() * 0.8F); return (_c, _t) -> Value.TRUE; } } } return (_c, _t) -> Value.FALSE; }); this.expr.addLazyFunction("blocks_movement", -1, (c, t, lv) -> booleanStateTest(c, "blocks_movement", lv, (s, p) -> !s.canPlaceAtSide(((CarpetContext) c).s.getWorld(), p, BlockPlacementEnvironment.LAND))); this.expr.addLazyFunction("block_sound", -1, (c, t, lv) -> stateStringQuery(c, "block_sound", lv, (s, p) -> BlockInfo.soundName.get(s.getSoundGroup()))); this.expr.addLazyFunction("material", -1, (c, t, lv) -> stateStringQuery(c, "material", lv, (s, p) -> BlockInfo.materialName.get(s.getMaterial()))); this.expr.addLazyFunction("map_colour", -1, (c, t, lv) -> stateStringQuery(c, "map_colour", lv, (s, p) -> BlockInfo.mapColourName.get(s.getTopMaterialColor(((CarpetContext)c).s.getWorld(), p)))); this.expr.addLazyFunction("property", -1, (c, t, lv) -> { BlockArgument locator = BlockArgument.findIn((CarpetContext) c, lv, 0); BlockState state = locator.block.getBlockState(); if (lv.size() <= locator.offset) throw new InternalExpressionException("'property' requires to specify a property to query"); String tag = lv.get(locator.offset).evalValue(c).getString(); StateManager states = state.getBlock().getStateManager(); Property property = states.getProperty(tag); if (property == null) return LazyValue.NULL; Value retval = new StringValue(state.get(property).toString().toLowerCase(Locale.ROOT)); return (_c, _t) -> retval; }); this.expr.addLazyFunction("block_properties", -1, (c, t, lv) -> { BlockArgument locator = BlockArgument.findIn((CarpetContext) c, lv, 0); BlockState state = locator.block.getBlockState(); StateManager states = state.getBlock().getStateManager(); Value res = ListValue.wrap(states.getProperties().stream().map( p -> new StringValue(p.getName())).collect(Collectors.toList()) ); return (_c, _t) -> res; }); this.expr.addLazyFunction("biome", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; BlockArgument locator = BlockArgument.findIn(cc, lv, 0); ServerWorld world = cc.s.getWorld(); BlockPos pos = locator.block.getPos(); Biome biome = world.getBiome(pos); Value res = new StringValue(biome.getTranslationKey().replaceFirst("^biome\\.minecraft\\.", "")); return (_c, _t) -> res; }); this.expr.addLazyFunction("set_biome", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; BlockArgument locator = BlockArgument.findIn(cc, lv, 0); if (lv.size() == locator.offset) throw new InternalExpressionException("'set_biome' needs a biome name as an argument"); String biomeName = lv.get(locator.offset+0).evalValue(c).getString(); Biome biome = Registry.BIOME.get(new Identifier(biomeName)); if (biome == null) throw new InternalExpressionException("Unknown biome: "+biomeName); boolean doImmediateUpdate = true; if (lv.size() > locator.offset+1) { doImmediateUpdate = lv.get(locator.offset+1).evalValue(c).getBoolean(); } ServerWorld world = cc.s.getWorld(); BlockPos pos = locator.block.getPos(); Chunk chunk = world.getChunk(pos.getX() >> 4, pos.getZ() >> 4, ChunkStatus.BIOMES); ((BiomeArrayInterface)chunk.getBiomeArray()).setBiomeAtIndex(pos, world, biome); if (doImmediateUpdate) forceChunkUpdate(pos, world); return LazyValue.TRUE; }); this.expr.addLazyFunction("reload_chunk", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; BlockPos pos = BlockArgument.findIn(cc, lv, 0).block.getPos(); ServerWorld world = cc.s.getWorld(); cc.s.getMinecraftServer().submitAndJoin( () -> forceChunkUpdate(pos, world)); return LazyValue.TRUE; }); this.expr.addLazyFunction("structure_references", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; BlockArgument locator = BlockArgument.findIn(cc, lv, 0); ServerWorld world = cc.s.getWorld(); BlockPos pos = locator.block.getPos(); Map references = world.getChunk(pos.getX() >> 4, pos.getZ() >> 4, ChunkStatus.STRUCTURE_REFERENCES).getStructureReferences(); if (lv.size() == locator.offset) { List referenceList = references.entrySet().stream(). filter(e -> e.getValue()!= null && !e.getValue().isEmpty()). map(e -> new StringValue(FeatureGenerator.structureToFeature.get(e.getKey()).get(0))).collect(Collectors.toList()); return (_c, _t ) -> ListValue.wrap(referenceList); } String simpleStructureName = lv.get(locator.offset).evalValue(c).getString().toLowerCase(Locale.ROOT); String structureName = FeatureGenerator.featureToStructure.get(simpleStructureName); if (structureName == null) return LazyValue.NULL; LongSet structureReferences = references.get(structureName); if (structureReferences == null || structureReferences.isEmpty()) return ListValue.lazyEmpty(); Value ret = ListValue.wrap(structureReferences.stream().map(l -> ListValue.of( new NumericValue(16*ChunkPos.getPackedX(l)), Value.ZERO, new NumericValue(16*ChunkPos.getPackedZ(l)))).collect(Collectors.toList())); return (_c, _t) -> ret; }); this.expr.addLazyFunction("structure_eligibility", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; BlockArgument locator = BlockArgument.findIn(cc, lv, 0); ServerWorld world = cc.s.getWorld(); BlockPos pos = locator.block.getPos(); StructureFeature structure = null; boolean needSize = false; if (lv.size() > locator.offset) { Value requested = lv.get(locator.offset+0).evalValue(c); if (!(requested instanceof NullValue)) { String reqString = requested.getString(); String structureName = FeatureGenerator.featureToStructure.get(reqString); if (structureName == null) throw new InternalExpressionException("Unknown structure: " + reqString); structure = Feature.STRUCTURES.get(structureName.toLowerCase(Locale.ROOT)); if (structure == null) throw new InternalExpressionException("Unsupported structure: " + structureName); } if (lv.size() > locator.offset+1) { needSize = lv.get(locator.offset+1).evalValue(c).getBoolean(); } } if (structure != null) { StructureStart start = FeatureGenerator.shouldStructureStartAt(world, pos, structure, needSize); if (start == null) return LazyValue.NULL; if (!needSize) return LazyValue.TRUE; Value ret = structureToValue(start); return (_c, _t) -> ret; } Map ret = new HashMap<>(); for(StructureFeature str : Feature.STRUCTURES.values()) { StructureStart start = FeatureGenerator.shouldStructureStartAt(world, pos, str, needSize); if (start == null) continue; Value key = new StringValue(FeatureGenerator.structureToFeature.get(str.getName()).get(0)); ret.put(key, (!needSize)?Value.NULL:structureToValue(start)); } Value retMap = MapValue.wrap(ret); return (_c, _t) -> retMap; }); this.expr.addLazyFunction("structures", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; BlockArgument locator = BlockArgument.findIn(cc, lv, 0); ServerWorld world = cc.s.getWorld(); BlockPos pos = locator.block.getPos(); Map structures = world.getChunk(pos.getX() >> 4, pos.getZ() >> 4, ChunkStatus.STRUCTURE_STARTS).getStructureStarts(); if (lv.size() == locator.offset) { Map structureList = new HashMap<>(); for (Map.Entry entry : structures.entrySet()) { StructureStart start = entry.getValue(); if (start == StructureStart.DEFAULT) continue; BlockBox box = start.getBoundingBox(); structureList.put( new StringValue(FeatureGenerator.structureToFeature.get(entry.getKey()).get(0)), ListValue.of(ListValue.fromTriple(box.minX, box.minY, box.minZ), ListValue.fromTriple(box.maxX, box.maxY, box.maxZ)) ); } Value ret = MapValue.wrap(structureList); return (_c, _t) -> ret; } String structureName = lv.get(locator.offset).evalValue(c).getString().toLowerCase(Locale.ROOT); StructureStart start = structures.get(FeatureGenerator.featureToStructure.get(structureName)); Value ret = structureToValue(start); return (_c, _t) -> ret; }); this.expr.addLazyFunction("set_structure", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; BlockArgument locator = BlockArgument.findIn(cc, lv, 0); ServerWorld world = cc.s.getWorld(); BlockPos pos = locator.block.getPos(); if (lv.size() == locator.offset) throw new InternalExpressionException("'set_structure requires at least position and a structure name"); String structureName = lv.get(locator.offset).evalValue(c).getString().toLowerCase(Locale.ROOT); String structureId = FeatureGenerator.featureToStructure.get(structureName); if (structureId == null) throw new InternalExpressionException("Unknown structure: "+structureName); // good 'ol pointer Value[] result = new Value[]{Value.NULL}; // technically a world modification. Even if we could let it slide, we will still park it ((CarpetContext) c).s.getMinecraftServer().submitAndJoin(() -> { Map structures = world.getChunk(pos).getStructureStarts(); if (lv.size() == locator.offset + 1) { Boolean res = FeatureGenerator.gridStructure(structureName, ((CarpetContext) c).s.getWorld(), locator.block.getPos()); if (res == null) return; result[0] = res?Value.TRUE:Value.FALSE; return; } Value newValue = lv.get(locator.offset+1).evalValue(c); if (newValue instanceof NullValue) // remove structure { if (!structures.containsKey(structureId)) { return; } StructureStart start = structures.get(structureId); ChunkPos structureChunkPos = new ChunkPos(start.getChunkX(), start.getChunkZ()); BlockBox box = start.getBoundingBox(); for (int chx = box.minX / 16; chx <= box.maxX / 16; chx++) { for (int chz = box.minZ / 16; chz <= box.maxZ / 16; chz++) { ChunkPos chpos = new ChunkPos(chx, chz); // getting a chunk will convert it to full, allowing to modify references Map references = world.getChunk(chpos.getCenterBlockPos()).getStructureReferences(); if (references.containsKey(structureId) && references.get(structureId) != null) references.get(structureId).remove(structureChunkPos.toLong()); } } structures.remove(structureId); result[0] = Value.TRUE; } }); Value ret = result[0]; // preventing from lazy evaluating of the result in case a future completes later return (_c, _t) -> ret; }); this.expr.addLazyFunction("reset_chunk", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; List requestedChunks = new ArrayList<>(); if (lv.size() == 1) { //either one block or list of chunks Value first = lv.get(0).evalValue(c); if (first instanceof ListValue) { List listVal = ((ListValue) first).getItems(); int offset = 0; BlockArgument locator = BlockArgument.findInValues(cc, listVal, 0, false, false); requestedChunks.add(new ChunkPos(locator.block.getPos())); while (listVal.size() > locator.offset) { locator = BlockArgument.findInValues(cc, listVal, locator.offset, false, false); requestedChunks.add(new ChunkPos(locator.block.getPos())); } } else { BlockArgument locator = BlockArgument.findInValues(cc, Collections.singletonList(first), 0, false, false); requestedChunks.add(new ChunkPos(locator.block.getPos())); } } else { BlockArgument locator = BlockArgument.findIn(cc, lv, 0); ChunkPos from = new ChunkPos(locator.block.getPos()); if (lv.size() > locator.offset) { locator = BlockArgument.findIn(cc, lv, locator.offset); ChunkPos to = new ChunkPos(locator.block.getPos()); int xmax = Math.max(from.x, to.x); int zmax = Math.max(from.z, to.z); for (int x = Math.min(from.x, to.x); x <= xmax; x++) for (int z = Math.min(from.z, to.z); z <= zmax; z++) { requestedChunks.add(new ChunkPos(x,z)); } CarpetSettings.LOG.error("Regenerating from "+Math.min(from.x, to.x)+", "+Math.min(from.z, to.z)+" to "+Math.max(from.x, to.x)+", "+Math.max(from.z, to.z)); } else { requestedChunks.add(from); } } ServerWorld world = cc.s.getWorld(); Value [] result = new Value[]{Value.NULL}; ((CarpetContext)c).s.getMinecraftServer().submitAndJoin( () -> { Map report = ((ThreadedAnvilChunkStorageInterface) world.getChunkManager().threadedAnvilChunkStorage).regenerateChunkRegion(requestedChunks); for (ChunkPos chpos: requestedChunks) { if (world.getChunk(chpos.x, chpos.z, ChunkStatus.FULL, false) != null) { forceChunkUpdate(chpos.getCenterBlockPos(), world); } } result[0] = MapValue.wrap(report.entrySet().stream().collect(Collectors.toMap( e -> new StringValue((String)((Map.Entry) e).getKey()), e -> new NumericValue((Integer)((Map.Entry) e).getValue()) ))); }); return (_c, _t) -> result[0]; }); this.expr.addLazyFunction("inhabited_time", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; BlockArgument locator = BlockArgument.findIn(cc, lv, 0); BlockPos pos = locator.block.getPos(); Value ret = new NumericValue(cc.s.getWorld().getChunk(pos).getInhabitedTime()); return (_c, _t) -> ret; }); this.expr.addLazyFunction("add_chunk_ticket", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; BlockArgument locator = BlockArgument.findIn(cc, lv, 0); BlockPos pos = locator.block.getPos(); if (lv.size() != locator.offset+2) throw new InternalExpressionException("'add_chunk_ticket' requires block position, ticket type and radius"); String type = lv.get(locator.offset).evalValue(c).getString(); ChunkTicketType ticket = ticketTypes.get(type.toLowerCase(Locale.ROOT)); if (ticket == null) throw new InternalExpressionException("Unknown ticket type: "+type); int radius = NumericValue.asNumber(lv.get(locator.offset+1).evalValue(c)).getInt(); if (radius < 1 || radius > 32) throw new InternalExpressionException("Ticket radius should be between 1 and 32 chunks"); // due to types we will wing it: ChunkPos target = new ChunkPos(pos); if (ticket == ChunkTicketType.PORTAL) cc.s.getWorld().getChunkManager().addTicket(ChunkTicketType.PORTAL, target, radius, pos); else if (ticket == ChunkTicketType.POST_TELEPORT) cc.s.getWorld().getChunkManager().addTicket(ChunkTicketType.POST_TELEPORT, target, radius, 1); else cc.s.getWorld().getChunkManager().addTicket(ChunkTicketType.UNKNOWN, target, radius, target); Value ret = new NumericValue(ticket.getExpiryTicks()); return (_c, _t) -> ret; }); } private Map> ticketTypes = new HashMap>(){{ put("portal", ChunkTicketType.PORTAL); put("teleport", ChunkTicketType.POST_TELEPORT); put("unknown", ChunkTicketType.UNKNOWN); }}; private static String getScoreboardKeyFromValue(Value keyValue) { if (keyValue instanceof EntityValue) { Entity e = ((EntityValue) keyValue).getEntity(); if (e instanceof PlayerEntity) { return e.getName().getString(); } else { return e.getUuidAsString(); } } else { return keyValue.getString(); } } private void API_Scoreboard() { // scoreboard(player,'objective') // scoreboard(player, objective, newValue) this.expr.addLazyFunction("scoreboard", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; Scoreboard scoreboard = cc.s.getMinecraftServer().getScoreboard(); if (lv.size()==0) { Value ret = ListValue.wrap(scoreboard.getObjectiveNames().stream().map(StringValue::new).collect(Collectors.toList())); return (_c, _t) -> ret; } String objectiveName = lv.get(0).evalValue(c).getString(); ScoreboardObjective objective = scoreboard.getObjective(objectiveName); if (objective == null) throw new InternalExpressionException("Unknown objective: "+objectiveName); if (lv.size()==1) { Value ret = ListValue.wrap(scoreboard.getAllPlayerScores(objective).stream().map(s -> new StringValue(s.getPlayerName())).collect(Collectors.toList())); return (_c, _t) -> ret; } String key = getScoreboardKeyFromValue(lv.get(1).evalValue(c)); if (!scoreboard.playerHasObjective(key, objective) && lv.size()==2) return LazyValue.NULL; ScoreboardPlayerScore scoreboardPlayerScore = scoreboard.getPlayerScore(key, objective); Value retval = new NumericValue(scoreboardPlayerScore.getScore()); if (lv.size() > 2) { scoreboardPlayerScore.setScore(NumericValue.asNumber(lv.get(2).evalValue(c)).getInt()); } return (_c, _t) -> retval; }); this.expr.addLazyFunction("scoreboard_remove", -1, (c, t, lv)-> { 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) { scoreboard.removeObjective(objective); return LazyValue.TRUE; } String key = getScoreboardKeyFromValue(lv.get(1).evalValue(c)); if (!scoreboard.playerHasObjective(key, objective)) return LazyValue.NULL; ScoreboardPlayerScore scoreboardPlayerScore = scoreboard.getPlayerScore(key, objective); Value previous = new NumericValue(scoreboardPlayerScore.getScore()); scoreboard.resetPlayerScore(key, objective); return (c_, t_) -> previous; }); // objective_add('lvl','level') // objective_add('counter') this.expr.addLazyFunction("scoreboard_add", -1, (c, t, lv)-> { CarpetContext cc = (CarpetContext)c; Scoreboard scoreboard = cc.s.getMinecraftServer().getScoreboard(); if (lv.size() == 0 || lv.size()>2) throw new InternalExpressionException("'scoreboard_add' should have one or two parameters"); String objectiveName = lv.get(0).evalValue(c).getString(); ScoreboardCriterion criterion; if (lv.size() == 1 ) { criterion = ScoreboardCriterion.DUMMY; } else { String critetionName = lv.get(1).evalValue(c).getString(); criterion = ScoreboardCriterion.createStatCriterion(critetionName).orElse(null); if (criterion==null) { throw new InternalExpressionException("Unknown scoreboard criterion: "+critetionName); } } ScoreboardObjective objective = scoreboard.getObjective(objectiveName); if (objective != null) return LazyValue.FALSE; scoreboard.addObjective(objectiveName, criterion, new LiteralText(objectiveName), criterion.getCriterionType()); return LazyValue.TRUE; }); this.expr.addLazyFunction("scoreboard_display", 2, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; Scoreboard scoreboard = cc.s.getMinecraftServer().getScoreboard(); String location = lv.get(0).evalValue(c).getString(); int slot = Scoreboard.getDisplaySlotId(location); if (slot < 0) throw new InternalExpressionException("Invalid objective slot: "+location); Value target = lv.get(1).evalValue(c); if (target instanceof NullValue) { scoreboard.setObjectiveSlot(slot, null); return (_c, _t) -> new NumericValue(slot); } String objectiveString = target.getString(); ScoreboardObjective objective = scoreboard.getObjective(objectiveString); if (objective == null) throw new InternalExpressionException("Objective doesn't exist: "+objectiveString); scoreboard.setObjectiveSlot(slot, objective); return (_c, _t) -> new NumericValue(slot); }); } private void API_InventoryManipulation() { this.expr.addLazyFunction("stack_limit", 1, (c, t, lv) -> { ItemStackArgument item = NBTSerializableValue.parseItem(lv.get(0).evalValue(c).getString()); Value res = new NumericValue(item.getItem().getMaxCount()); return (_c, _t) -> res; }); this.expr.addLazyFunction("item_category", 1, (c, t, lv) -> { ItemStackArgument item = NBTSerializableValue.parseItem(lv.get(0).evalValue(c).getString()); Value res = new StringValue(item.getItem().getGroup().getName()); return (_c, _t) -> res; }); this.expr.addLazyFunction("inventory_size", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext) c; NBTSerializableValue.InventoryLocator inventoryLocator = NBTSerializableValue.locateInventory(cc, lv, 0); if (inventoryLocator == null) return (_c, _t) -> Value.NULL; Value res = new NumericValue(inventoryLocator.inventory.getInvSize()); return (_c, _t) -> res; }); this.expr.addLazyFunction("inventory_has_items", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext) c; NBTSerializableValue.InventoryLocator inventoryLocator = NBTSerializableValue.locateInventory(cc, lv, 0); if (inventoryLocator == null) return (_c, _t) -> Value.NULL; Value res = new NumericValue(!inventoryLocator.inventory.isInvEmpty()); return (_c, _t) -> res; }); //inventory_get(, ) -> item_triple this.expr.addLazyFunction("inventory_get", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext) c; NBTSerializableValue.InventoryLocator inventoryLocator = NBTSerializableValue.locateInventory(cc, lv, 0); if (inventoryLocator == null) return (_c, _t) -> Value.NULL; if (lv.size() == inventoryLocator.offset) { List fullInventory = new ArrayList<>(); for (int i = 0, maxi = inventoryLocator.inventory.getInvSize(); i < maxi; i++) { fullInventory.add(ListValue.fromItemStack(inventoryLocator.inventory.getInvStack(i))); } Value res = ListValue.wrap(fullInventory); return (_c, _t) -> res; } int slot = (int)NumericValue.asNumber(lv.get(inventoryLocator.offset).evalValue(c)).getLong(); slot = NBTSerializableValue.validateSlot(slot, inventoryLocator.inventory); if (slot == inventoryLocator.inventory.getInvSize()) return (_c, _t) -> Value.NULL; Value res = ListValue.fromItemStack(inventoryLocator.inventory.getInvStack(slot)); return (_c, _t) -> res; }); //inventory_set(, , , , ) this.expr.addLazyFunction("inventory_set", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext) c; NBTSerializableValue.InventoryLocator inventoryLocator = NBTSerializableValue.locateInventory(cc, lv, 0); if (inventoryLocator == null) return (_c, _t) -> Value.NULL; if (lv.size() < inventoryLocator.offset+2) throw new InternalExpressionException("'inventory_set' requires at least slot number and new stack size, and optional new item"); int slot = (int) NumericValue.asNumber(lv.get(inventoryLocator.offset+0).evalValue(c)).getLong(); slot = NBTSerializableValue.validateSlot(slot, inventoryLocator.inventory); if (slot == inventoryLocator.inventory.getInvSize()) return (_c, _t) -> Value.NULL; int count = (int) NumericValue.asNumber(lv.get(inventoryLocator.offset+1).evalValue(c)).getLong(); if (count == 0) { // clear slot ItemStack removedStack = inventoryLocator.inventory.removeInvStack(slot); syncPlayerInventory(inventoryLocator, slot); //Value res = ListValue.fromItemStack(removedStack); // that tuple will be read only but cheaper if noone cares return (_c, _t) -> ListValue.fromItemStack(removedStack); } if (lv.size() < inventoryLocator.offset+3) { ItemStack previousStack = inventoryLocator.inventory.getInvStack(slot); ItemStack newStack = previousStack.copy(); newStack.setCount(count); inventoryLocator.inventory.setInvStack(slot, newStack); syncPlayerInventory(inventoryLocator, slot); return (_c, _t) -> ListValue.fromItemStack(previousStack); } CompoundTag nbt = null; // skipping one argument if (lv.size() > inventoryLocator.offset+3) { Value nbtValue = lv.get(inventoryLocator.offset+3).evalValue(c); if (nbtValue instanceof NBTSerializableValue) nbt = ((NBTSerializableValue)nbtValue).getCompoundTag(); else if (nbtValue instanceof NullValue) nbt = null; else nbt = new NBTSerializableValue(nbtValue.getString()).getCompoundTag(); } ItemStackArgument newitem = NBTSerializableValue.parseItem( lv.get(inventoryLocator.offset+2).evalValue(c).getString(), nbt ); ItemStack previousStack = inventoryLocator.inventory.getInvStack(slot); try { inventoryLocator.inventory.setInvStack(slot, newitem.createStack(count, false)); syncPlayerInventory(inventoryLocator, slot); } catch (CommandSyntaxException e) { throw new InternalExpressionException(e.getMessage()); } return (_c, _t) -> ListValue.fromItemStack(previousStack); }); //inventory_find(, or null (first empty slot), ) -> or null this.expr.addLazyFunction("inventory_find", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext) c; NBTSerializableValue.InventoryLocator inventoryLocator = NBTSerializableValue.locateInventory(cc, lv, 0); if (inventoryLocator == null) return (_c, _t) -> Value.NULL; ItemStackArgument itemArg = null; if (lv.size() > inventoryLocator.offset) { Value secondArg = lv.get(inventoryLocator.offset+0).evalValue(c); if (!(secondArg instanceof NullValue)) itemArg = NBTSerializableValue.parseItem(secondArg.getString()); } int startIndex = 0; if (lv.size() > inventoryLocator.offset+1) { startIndex = (int) NumericValue.asNumber(lv.get(inventoryLocator.offset+1).evalValue(c)).getLong(); } startIndex = NBTSerializableValue.validateSlot(startIndex, inventoryLocator.inventory); for (int i = startIndex, maxi = inventoryLocator.inventory.getInvSize(); i < maxi; i++) { ItemStack stack = inventoryLocator.inventory.getInvStack(i); if ( (itemArg == null && stack.isEmpty()) || (itemArg != null && itemArg.getItem().equals(stack.getItem())) ) { Value res = new NumericValue(i); return (_c, _t) -> res; } } return (_c, _t) -> Value.NULL; }); //inventory_remove(, , ) -> bool this.expr.addLazyFunction("inventory_remove", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext) c; NBTSerializableValue.InventoryLocator inventoryLocator = NBTSerializableValue.locateInventory(cc, lv, 0); if (inventoryLocator == null) return (_c, _t) -> Value.NULL; if (lv.size() <= inventoryLocator.offset) throw new InternalExpressionException("'inventory_remove' requires at least an item to be removed"); ItemStackArgument searchItem = NBTSerializableValue.parseItem(lv.get(inventoryLocator.offset).evalValue(c).getString()); int amount = 1; if (lv.size() > inventoryLocator.offset+1) { amount = (int)NumericValue.asNumber(lv.get(inventoryLocator.offset+1).evalValue(c)).getLong(); } // not enough if (((amount == 1) && (!inventoryLocator.inventory.containsAnyInInv(Sets.newHashSet(searchItem.getItem())))) || (inventoryLocator.inventory.countInInv(searchItem.getItem()) < amount)) { return (_c, _t) -> Value.FALSE; } for (int i = 0, maxi = inventoryLocator.inventory.getInvSize(); i < maxi; i++) { ItemStack stack = inventoryLocator.inventory.getInvStack(i); if (stack.isEmpty()) continue; if (!stack.getItem().equals(searchItem.getItem())) continue; int left = stack.getCount()-amount; if (left > 0) { stack.setCount(left); inventoryLocator.inventory.setInvStack(i, stack); syncPlayerInventory(inventoryLocator, i); return (_c, _t) -> Value.TRUE; } else { inventoryLocator.inventory.removeInvStack(i); syncPlayerInventory(inventoryLocator, i); amount -= stack.getCount(); } } if (amount > 0) throw new InternalExpressionException("Something bad happened - cannot pull all items from inventory"); return (_c, _t) -> Value.TRUE; }); //inventory_drop(, , ) -> entity_item (and sets slot) or null if cannot this.expr.addLazyFunction("drop_item", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext) c; NBTSerializableValue.InventoryLocator inventoryLocator = NBTSerializableValue.locateInventory(cc, lv, 0); if (inventoryLocator == null) return (_c, _t) -> Value.NULL; if (lv.size() == inventoryLocator.offset) throw new InternalExpressionException("Slot number is required for inventory_drop"); int slot = (int)NumericValue.asNumber(lv.get(inventoryLocator.offset).evalValue(c)).getLong(); slot = NBTSerializableValue.validateSlot(slot, inventoryLocator.inventory); if (slot == inventoryLocator.inventory.getInvSize()) return (_c, _t) -> Value.NULL; int amount = 0; if (lv.size() > inventoryLocator.offset+1) amount = (int)NumericValue.asNumber(lv.get(inventoryLocator.offset+1).evalValue(c)).getLong(); if (amount < 0) throw new InternalExpressionException("Cannot throw negative number of items"); ItemStack stack = inventoryLocator.inventory.getInvStack(slot); if (stack == null || stack.isEmpty()) return (_c, _t) -> Value.ZERO; if (amount == 0) amount = stack.getCount(); ItemStack droppedStack = inventoryLocator.inventory.takeInvStack(slot, amount); if (droppedStack.isEmpty()) { return (_c, _t) -> Value.ZERO; } Object owner = inventoryLocator.owner; ItemEntity item; if (owner instanceof PlayerEntity) { item = ((PlayerEntity) owner).dropItem(droppedStack, false, true); } else if (owner instanceof LivingEntity) { LivingEntity villager = (LivingEntity)owner; // stolen from LookTargetUtil.give((VillagerEntity)owner, droppedStack, (LivingEntity) owner); double double_1 = villager.getY() - 0.30000001192092896D + (double)villager.getStandingEyeHeight(); item = new ItemEntity(villager.world, villager.getX(), double_1, villager.getZ(), droppedStack); Vec3d vec3d_1 = villager.getRotationVec(1.0F).normalize().multiply(0.3);// new Vec3d(0, 0.3, 0); item.setVelocity(vec3d_1); item.setToDefaultPickupDelay(); cc.s.getWorld().spawnEntity(item); } else { Vec3d point = new Vec3d(inventoryLocator.position); item = new ItemEntity(cc.s.getWorld(), point.x+0.5, point.y+0.5, point.z+0.5, droppedStack); item.setToDefaultPickupDelay(); cc.s.getWorld().spawnEntity(item); } Value res = new NumericValue(item.getStack().getCount()); return (_c, _t) -> res; }); } private void syncPlayerInventory(NBTSerializableValue.InventoryLocator inventory, int int_1) { if (inventory.owner instanceof ServerPlayerEntity && !inventory.isEnder) { ServerPlayerEntity player = (ServerPlayerEntity) inventory.owner; player.networkHandler.sendPacket(new ContainerSlotUpdateS2CPacket( -2, int_1, inventory.inventory.getInvStack(int_1) )); } } private void API_EntityManipulation() { this.expr.addLazyFunction("player", -1, (c, t, lv) -> { if (lv.size() ==0) { Entity callingEntity = ((CarpetContext)c).s.getEntity(); if (callingEntity instanceof PlayerEntity) { Value retval = new EntityValue(callingEntity); return (_c, _t) -> retval; } Vec3d pos = ((CarpetContext)c).s.getPosition(); PlayerEntity closestPlayer = ((CarpetContext)c).s.getWorld().getClosestPlayer(pos.x, pos.y, pos.z, -1.0, EntityPredicates.VALID_ENTITY); if (closestPlayer != null) { Value retval = new EntityValue(closestPlayer); return (_c, _t) -> retval; } return (_c, _t) -> Value.NULL; } String playerName = lv.get(0).evalValue(c).getString(); Value retval = Value.NULL; if ("all".equalsIgnoreCase(playerName)) { retval = ListValue.wrap( ((CarpetContext)c).s.getMinecraftServer().getPlayerManager().getPlayerList(). stream().map(EntityValue::new).collect(Collectors.toList())); } else if ("*".equalsIgnoreCase(playerName)) { retval = ListValue.wrap( ((CarpetContext)c).s.getWorld().getPlayers(). stream().map(EntityValue::new).collect(Collectors.toList())); } else if ("survival".equalsIgnoreCase(playerName)) { retval = ListValue.wrap( ((CarpetContext)c).s.getWorld().getPlayers((p) -> p.interactionManager.isSurvivalLike()). stream().map(EntityValue::new).collect(Collectors.toList())); } else if ("creative".equalsIgnoreCase(playerName)) { retval = ListValue.wrap( ((CarpetContext)c).s.getWorld().getPlayers(PlayerEntity::isCreative). stream().map(EntityValue::new).collect(Collectors.toList())); } else if ("spectating".equalsIgnoreCase(playerName)) { retval = ListValue.wrap( ((CarpetContext)c).s.getWorld().getPlayers(PlayerEntity::isSpectator). stream().map(EntityValue::new).collect(Collectors.toList())); } else if ("!spectating".equalsIgnoreCase(playerName)) { retval = ListValue.wrap( ((CarpetContext)c).s.getWorld().getPlayers((p) -> !p.isSpectator()). stream().map(EntityValue::new).collect(Collectors.toList())); } else { ServerPlayerEntity player = ((CarpetContext) c).s.getMinecraftServer().getPlayerManager().getPlayer(playerName); if (player != null) retval = new EntityValue(player); } Value finalVar = retval; return (cc, tt) -> finalVar; }); this.expr.addLazyFunction("spawn", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; if (lv.size() < 2) throw new InternalExpressionException("'spawn' function takes mob name, and position to spawn"); String entityString = lv.get(0).evalValue(c).getString(); Identifier entityId; try { entityId = Identifier.fromCommandInput(new StringReader(entityString)); EntityType type = Registry.ENTITY_TYPE.getOrEmpty(entityId).orElse(null); if (type == null || !type.isSummonable()) return LazyValue.NULL; } catch (CommandSyntaxException exception) { return LazyValue.NULL; } Vector3Argument position = Vector3Argument.findIn(cc, lv, 1); if (position.fromBlock) position.vec = position.vec.subtract(0, 0.5, 0); CompoundTag tag = new CompoundTag(); boolean hasTag = false; if (lv.size() > position.offset) { 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(); } } tag.putString("id", entityId.toString()); Vec3d vec3d = position.vec; if (EntityType.getId(EntityType.LIGHTNING_BOLT).equals(entityId)) { LightningEntity lightningEntity_1 = new LightningEntity(cc.s.getWorld(), vec3d.x, vec3d.y, vec3d.z, false); cc.s.getWorld().addLightning(lightningEntity_1); return LazyValue.NULL; } else { ServerWorld serverWorld = cc.s.getWorld(); Entity entity_1 = EntityType.loadEntityWithPassengers(tag, serverWorld, (entity_1x) -> { entity_1x.refreshPositionAndAngles(vec3d.x, vec3d.y, vec3d.z, entity_1x.yaw, entity_1x.pitch); return !serverWorld.tryLoadEntity(entity_1x) ? null : entity_1x; }); if (entity_1 == null) { return LazyValue.NULL; } else { if (!hasTag && entity_1 instanceof MobEntity) { ((MobEntity)entity_1).initialize(serverWorld, serverWorld.getLocalDifficulty(entity_1.getBlockPos()), SpawnType.COMMAND, null, null); } Value res = new EntityValue(entity_1); return (_c, _t) -> res; } } }); this.expr.addLazyFunction("entity_id", 1, (c, t, lv) -> { Value who = lv.get(0).evalValue(c); Entity e; if (who instanceof NumericValue) { e = ((CarpetContext)c).s.getWorld().getEntityById((int)((NumericValue) who).getLong()); } else { e = ((CarpetContext)c).s.getWorld().getEntity(UUID.fromString(who.getString())); } if (e==null) { return LazyValue.NULL; } return (cc, tt) -> new EntityValue(e); }); this.expr.addLazyFunction("entity_list", 1, (c, t, lv) -> { String who = lv.get(0).evalValue(c).getString(); Pair, Predicate> pair = EntityValue.getPredicate(who); if (pair == null) { throw new InternalExpressionException("Unknown entity selection criterion: "+who); } List entityList = ((CarpetContext)c).s.getWorld().getEntities(pair.getKey(), pair.getValue()); Value retval = ListValue.wrap(entityList.stream().map(EntityValue::new).collect(Collectors.toList())); return (_c, _t ) -> retval; }); this.expr.addLazyFunction("entity_area", 7, (c, t, lv) -> { BlockPos center = new BlockPos( NumericValue.asNumber(lv.get(1).evalValue(c)).getDouble(), NumericValue.asNumber(lv.get(2).evalValue(c)).getDouble(), NumericValue.asNumber(lv.get(3).evalValue(c)).getDouble() ); Box area = new Box(center).expand( NumericValue.asNumber(lv.get(4).evalValue(c)).getDouble(), NumericValue.asNumber(lv.get(5).evalValue(c)).getDouble(), NumericValue.asNumber(lv.get(6).evalValue(c)).getDouble() ); String who = lv.get(0).evalValue(c).getString(); Pair, Predicate> pair = EntityValue.getPredicate(who); if (pair == null) { throw new InternalExpressionException("Unknown entity selection criterion: "+who); } List entityList = ((CarpetContext)c).s.getWorld().getEntities((EntityType) pair.getKey(), area, pair.getValue()); Value retval = ListValue.wrap(entityList.stream().map(EntityValue::new).collect(Collectors.toList())); return (_c, _t ) -> retval; }); this.expr.addLazyFunction("entity_selector", -1, (c, t, lv) -> { String selector = lv.get(0).evalValue(c).getString(); List retlist = new ArrayList<>(); for (Entity e: EntityValue.getEntitiesFromSelector(((CarpetContext)c).s, selector)) { retlist.add(new EntityValue(e)); } return (c_, t_) -> ListValue.wrap(retlist); }); this.expr.addLazyFunction("query", -1, (c, t, lv) -> { if (lv.size()<2) { throw new InternalExpressionException("'query' takes entity as a first argument, and queried feature as a second"); } Value v = lv.get(0).evalValue(c); if (!(v instanceof EntityValue)) throw new InternalExpressionException("First argument to query should be an entity"); String what = lv.get(1).evalValue(c).getString(); Value retval; if (lv.size()==2) retval = ((EntityValue) v).get(what, null); else if (lv.size()==3) retval = ((EntityValue) v).get(what, lv.get(2).evalValue(c)); else retval = ((EntityValue) v).get(what, ListValue.wrap(lv.subList(2, lv.size()).stream().map((vv) -> vv.evalValue(c)).collect(Collectors.toList()))); return (cc, tt) -> retval; }); // or update this.expr.addLazyFunction("modify", -1, (c, t, lv) -> { if (lv.size()<2) { throw new InternalExpressionException("'modify' takes entity as a first argument, and queried feature as a second"); } Value v = lv.get(0).evalValue(c); if (!(v instanceof EntityValue)) throw new InternalExpressionException("First argument to modify should be an entity"); String what = lv.get(1).evalValue(c).getString(); if (lv.size()==2) ((EntityValue) v).set(what, null); else if (lv.size()==3) ((EntityValue) v).set(what, lv.get(2).evalValue(c)); else ((EntityValue) v).set(what, ListValue.wrap(lv.subList(2, lv.size()).stream().map((vv) -> vv.evalValue(c)).collect(Collectors.toList()))); return (cc, tt) -> v; }); // or update this.expr.addLazyFunction("entity_event", -1, (c, t, lv) -> { if (lv.size()<3) throw new InternalExpressionException("'entity_event' requires at least 3 arguments, entity, event to be handled, and function name, with optional arguments"); Value v = lv.get(0).evalValue(c); if (!(v instanceof EntityValue)) throw new InternalExpressionException("First argument to entity_event should be an entity"); String what = lv.get(1).evalValue(c).getString(); Value functionValue = lv.get(2).evalValue(c); if (functionValue instanceof NullValue) functionValue = null; else if (!(functionValue instanceof FunctionValue)) { String name = functionValue.getString(); functionValue = c.host.getAssertFunction(this.expr.module, name); } FunctionValue function = (FunctionValue)functionValue; List args = null; if (lv.size()==4) args = Collections.singletonList(lv.get(3).evalValue(c)); else if (lv.size()>4) { args = lv.subList(3, lv.size()).stream().map((vv) -> vv.evalValue(c)).collect(Collectors.toList()); } ((EntityValue) v).setEvent((CarpetContext)c, what, function, args); return LazyValue.NULL; }); } private void API_IteratingOverAreasOfBlocks() { this.expr.addLazyFunction("scan", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; BlockArgument centerLocator = BlockArgument.findIn(cc, lv, 0); Vector3Argument rangeLocator = Vector3Argument.findIn(cc, lv, centerLocator.offset); BlockPos center = centerLocator.block.getPos(); Vec3i range; if (rangeLocator.fromBlock) { range = new Vec3i( abs(rangeLocator.vec.x - center.getX()), abs(rangeLocator.vec.y - center.getY()), abs(rangeLocator.vec.z - center.getZ()) ); //if (lv.size() > rangeLocator.offset+1) throw new InternalExpressionException("'scan' takes two block positions, and the expression") } else { range = new Vec3i(abs(rangeLocator.vec.x), abs(rangeLocator.vec.y), abs(rangeLocator.vec.z)); } Vec3i upperRange = range; if (lv.size() > rangeLocator.offset+1) // +1 cause we still need the expression { rangeLocator = Vector3Argument.findIn(cc, lv, rangeLocator.offset); if (rangeLocator.fromBlock) { upperRange = new Vec3i( abs(rangeLocator.vec.x - center.getX()), abs(rangeLocator.vec.y - center.getY()), abs(rangeLocator.vec.z - center.getZ()) ); //if (lv.size() > rangeLocator.offset+1) throw new InternalExpressionException("'scan' takes two block positions, and the expression") } else { upperRange = new Vec3i(abs(rangeLocator.vec.x), abs(rangeLocator.vec.y), abs(rangeLocator.vec.z)); } } if (lv.size() != rangeLocator.offset+1) { throw new InternalExpressionException("'scan' takes two, or three block positions, and an expression: "+lv.size()+" "+rangeLocator.offset); } LazyValue expr = lv.get(rangeLocator.offset); int cx = center.getX(); int cy = center.getY(); int cz = center.getZ(); int xrange = range.getX(); int yrange = range.getY(); int zrange = range.getZ(); int xprange = upperRange.getX(); int yprange = upperRange.getY(); int zprange = upperRange.getZ(); //saving outer scope LazyValue _x = c.getVariable("_x"); LazyValue _y = c.getVariable("_y"); LazyValue _z = c.getVariable("_z"); LazyValue __ = c.getVariable("_"); int sCount = 0; outer:for (int y=cy-yrange; y <= cy+yprange; y++) { int yFinal = y; c.setVariable("_y", (c_, t_) -> new NumericValue(yFinal).bindTo("_y")); for (int x=cx-xrange; x <= cx+xprange; x++) { int xFinal = x; c.setVariable("_x", (c_, t_) -> new NumericValue(xFinal).bindTo("_x")); for (int z=cz-zrange; z <= cz+zprange; z++) { int zFinal = z; c.setVariable("_z", (c_, t_) -> new NumericValue(zFinal).bindTo("_z")); Value blockValue = BlockValue.fromCoords(((CarpetContext)c), xFinal,yFinal,zFinal).bindTo("_"); c.setVariable( "_", (cc_, t_c) -> blockValue); Value result; try { result = expr.evalValue(c, t); } catch (ContinueStatement notIgnored) { result = notIgnored.retval; } catch (BreakStatement notIgnored) { break outer; } if (t != Context.VOID && result.getBoolean()) { sCount += 1; } } } } //restoring outer scope c.setVariable("_x", _x); c.setVariable("_y", _y); c.setVariable("_z", _z); c.setVariable("_", __); int finalSCount = sCount; return (c_, t_) -> new NumericValue(finalSCount); }); this.expr.addLazyFunction("volume", 7, (c, t, lv) -> { int xi = (int) NumericValue.asNumber(lv.get(0).evalValue(c)).getLong(); int yi = (int) NumericValue.asNumber(lv.get(1).evalValue(c)).getLong(); int zi = (int) NumericValue.asNumber(lv.get(2).evalValue(c)).getLong(); int xj = (int) NumericValue.asNumber(lv.get(3).evalValue(c)).getLong(); int yj = (int) NumericValue.asNumber(lv.get(4).evalValue(c)).getLong(); int zj = (int) NumericValue.asNumber(lv.get(5).evalValue(c)).getLong(); int minx = min(xi, xj); int miny = min(yi, yj); int minz = min(zi, zj); int maxx = max(xi, xj); int maxy = max(yi, yj); int maxz = max(zi, zj); LazyValue expr = lv.get(6); //saving outer scope LazyValue _x = c.getVariable("_x"); LazyValue _y = c.getVariable("_y"); LazyValue _z = c.getVariable("_z"); LazyValue __ = c.getVariable("_"); int sCount = 0; outer:for (int y=miny; y <= maxy; y++) { int yFinal = y; c.setVariable("_y", (c_, t_) -> new NumericValue(yFinal).bindTo("_y")); for (int x=minx; x <= maxx; x++) { int xFinal = x; c.setVariable("_x", (c_, t_) -> new NumericValue(xFinal).bindTo("_x")); for (int z=minz; z <= maxz; z++) { int zFinal = z; c.setVariable("_z", (c_, t_) -> new NumericValue(zFinal).bindTo("_z")); Value blockValue = BlockValue.fromCoords(((CarpetContext)c), xFinal,yFinal,zFinal).bindTo("_"); c.setVariable( "_", (cc_, t_c) -> blockValue); Value result; try { result = expr.evalValue(c, t); } catch (ContinueStatement notIgnored) { result = notIgnored.retval; } catch (BreakStatement notIgnored) { break outer; } if (t != Context.VOID && result.getBoolean()) { sCount += 1; } } } } //restoring outer scope c.setVariable("_x", _x); c.setVariable("_y", _y); c.setVariable("_z", _z); c.setVariable("_", __); int finalSCount = sCount; return (c_, t_) -> new NumericValue(finalSCount); }); this.expr.addLazyFunction("neighbours", -1, (c, t, lv)-> { BlockPos center = BlockArgument.findIn((CarpetContext) c, lv,0).block.getPos(); ServerWorld world = ((CarpetContext) c).s.getWorld(); List neighbours = new ArrayList<>(); neighbours.add(new BlockValue(null, world, center.up())); neighbours.add(new BlockValue(null, world, center.down())); neighbours.add(new BlockValue(null, world, center.north())); neighbours.add(new BlockValue(null, world, center.south())); neighbours.add(new BlockValue(null, world, center.east())); neighbours.add(new BlockValue(null, world, center.west())); Value retval = ListValue.wrap(neighbours); return (c_, t_) -> retval; }); this.expr.addLazyFunction("rect", -1, (c, t, lv)-> { if (lv.size() != 3 && lv.size() != 6 && lv.size() != 9) { throw new InternalExpressionException("Rectangular region should be specified with 3, 6, or 9 coordinates"); } int cx; int cy; int cz; int sminx; int sminy; int sminz; int smaxx; int smaxy; int smaxz; try { cx = (int)((NumericValue) lv.get(0).evalValue(c)).getLong(); cy = (int)((NumericValue) lv.get(1).evalValue(c)).getLong(); cz = (int)((NumericValue) lv.get(2).evalValue(c)).getLong(); if (lv.size()==3) // only done this way because of stupid Java lambda final reqs { sminx = 1; sminy = 1; sminz = 1; smaxx = 1; smaxy = 1; smaxz = 1; } else if (lv.size()==6) { sminx = (int) ((NumericValue) lv.get(3).evalValue(c)).getLong(); sminy = (int) ((NumericValue) lv.get(4).evalValue(c)).getLong(); sminz = (int) ((NumericValue) lv.get(5).evalValue(c)).getLong(); smaxx = sminx; smaxy = sminy; smaxz = sminz; } else // size == 9 { sminx = (int) ((NumericValue) lv.get(3).evalValue(c)).getLong(); sminy = (int) ((NumericValue) lv.get(4).evalValue(c)).getLong(); sminz = (int) ((NumericValue) lv.get(5).evalValue(c)).getLong(); smaxx = (int)((NumericValue) lv.get(6).evalValue(c)).getLong(); smaxy = (int)((NumericValue) lv.get(7).evalValue(c)).getLong(); smaxz = (int)((NumericValue) lv.get(8).evalValue(c)).getLong(); } } catch (ClassCastException exc) { throw new InternalExpressionException("Attempted to pass a non-number to rect"); } CarpetContext cc = (CarpetContext)c; return (c_, t_) -> new LazyListValue() { final int minx = cx-sminx; final int miny = cy-sminy; final int minz = cz-sminz; final int maxx = cx+smaxx; final int maxy = cy+smaxy; final int maxz = cz+smaxz; int x; int y; int z; { reset(); } @Override public boolean hasNext() { return y <= maxy; } @Override public Value next() { Value r = BlockValue.fromCoords(cc, x,y,z); //possibly reroll context x++; if (x > maxx) { x = minx; z++; if (z > maxz) { z = minz; y++; // hasNext should fail if we went over } } return r; } @Override public void fatality() { // possibly return original x, y, z super.fatality(); } @Override public void reset() { x = minx; y = miny; z = minz; } @Override public String getString() { return String.format(Locale.ROOT, "rect[(%d,%d,%d),..,(%d,%d,%d)]",minx, miny, minz, maxx, maxy, maxz); } }; }); this.expr.addLazyFunction("diamond", -1, (c, t, lv)-> { CarpetContext cc = (CarpetContext)c; if (lv.size() != 3 && lv.size() != 4 && lv.size() != 5) { throw new InternalExpressionException("'diamond' region should be specified with 3 to 5 coordinates"); } int cx; int cy; int cz; int width; int height; try { cx = (int)((NumericValue) lv.get(0).evalValue(c)).getLong(); cy = (int)((NumericValue) lv.get(1).evalValue(c)).getLong(); cz = (int)((NumericValue) lv.get(2).evalValue(c)).getLong(); if (lv.size()==3) { Value retval = ListValue.of( BlockValue.fromCoords(cc, cx, cy-1, cz), BlockValue.fromCoords(cc, cx, cy, cz), BlockValue.fromCoords(cc, cx-1, cy, cz), BlockValue.fromCoords(cc, cx, cy, cz-1), BlockValue.fromCoords(cc, cx+1, cy, cz), BlockValue.fromCoords(cc, cx, cy, cz+1), BlockValue.fromCoords(cc, cx, cy+1, cz) ); return (_c, _t ) -> retval; } else if (lv.size()==4) { width = (int) ((NumericValue) lv.get(3).evalValue(c)).getLong(); height = 0; } else // size == 5 { width = (int) ((NumericValue) lv.get(3).evalValue(c)).getLong(); height = (int) ((NumericValue) lv.get(4).evalValue(c)).getLong(); } } catch (ClassCastException exc) { throw new InternalExpressionException("Attempted to pass a non-number to diamond"); } if (height == 0) { return (c_, t_) -> new LazyListValue() { int curradius; int curpos; { reset(); } @Override public boolean hasNext() { return curradius <= width; } @Override public Value next() { if (curradius == 0) { curradius = 1; return BlockValue.fromCoords(cc, cx, cy, cz); } // x = 3-|i-6| // z = |( (i-3)%12-6|-3 Value block = BlockValue.fromCoords(cc, cx+(curradius-abs(curpos-2*curradius)), cy, cz-curradius+abs( abs(curpos-curradius)%(4*curradius) -2*curradius )); curpos++; if (curpos>=curradius*4) { curradius++; curpos = 0; } return block; } @Override public void reset() { curradius = 0; curpos = 0; } @Override public String getString() { return String.format(Locale.ROOT, "diamond[(%d,%d,%d),%d,0]",cx, cy, cz, width); } }; } else { return (c_, t_) -> new LazyListValue() { int curradius; int curpos; int curheight; { reset(); } @Override public boolean hasNext() { return curheight <= height; } @Override public Value next() { if (curheight == -height || curheight == height) { return BlockValue.fromCoords(cc, cx, cy+curheight++, cz); } if (curradius == 0) { curradius++; return BlockValue.fromCoords(cc, cx, cy+curheight, cz); } // x = 3-|i-6| // z = |( (i-3)%12-6|-3 Value block = BlockValue.fromCoords(cc, cx+(curradius-abs(curpos-2*curradius)), cy+curheight, cz-curradius+abs( abs(curpos-curradius)%(4*curradius) -2*curradius )); curpos++; if (curpos>=curradius*4) { curradius++; curpos = 0; if (curradius>width -abs(width*curheight/height)) { curheight++; curradius = 0; curpos = 0; } } return block; } @Override public void reset() { curradius = 0; curpos = 0; curheight = -height; } @Override public String getString() { return String.format(Locale.ROOT, "diamond[(%d,%d,%d),%d,%d]",cx, cy, cz, width, height); } }; } }); } private static Map mixerMap = Arrays.stream(SoundCategory.values()).collect(Collectors.toMap(SoundCategory::getName, k -> k)); private void API_Interapperability() { //todo doc and test all this.expr.addLazyFunction("loaded_apps", 0, (c, t, lv) -> { // return a set of all loaded apps Value ret = new MapValue(((CarpetScriptHost) c.host).getScriptServer().modules.keySet().stream().map(StringValue::new).collect(Collectors.toSet())); return (cc, tt) -> ret; }); this.expr.addLazyFunction("is_app_loaded", 1, (c, t, lv) -> { Value ret = new NumericValue(((CarpetScriptHost) c.host).getScriptServer().modules.containsKey(lv.get(0).evalValue(c).getString())); return (cc, tt) -> ret; }); } public static String recognizeResource(Value value) { String origfile = value.getString(); 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()) { throw new InternalExpressionException("Cannot use "+origfile+" as resource name - must have some letters and numbers"); } return file; } private void API_AuxiliaryAspects() { this.expr.addLazyFunction("sound", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; Identifier soundName = new Identifier(lv.get(0).evalValue(c).getString()); Vector3Argument locator = Vector3Argument.findIn(cc, lv, 1); if (Registry.SOUND_EVENT.get(soundName) == null) throw new InternalExpressionException("No such sound: "+soundName.getPath()); float volume = 1.0F; float pitch = 1.0F; SoundCategory mixer = SoundCategory.MASTER; if (lv.size() > 0+locator.offset) { volume = (float) NumericValue.asNumber(lv.get(0+locator.offset).evalValue(c)).getDouble(); if (lv.size() > 1+locator.offset) { pitch = (float) NumericValue.asNumber(lv.get(1+locator.offset).evalValue(c)).getDouble(); if (lv.size() > 2+locator.offset) { String mixerName = lv.get(2+locator.offset).evalValue(c).getString(); mixer = mixerMap.get(mixerName.toLowerCase(Locale.ROOT)); if (mixer == null) throw new InternalExpressionException(mixerName +" is not a valid mixer name"); } } } Vec3d vec = locator.vec; double d0 = Math.pow(volume > 1.0F ? (double)(volume * 16.0F) : 16.0D, 2.0D); int count = 0; for (ServerPlayerEntity player : cc.s.getWorld().getPlayers( (p) -> p.squaredDistanceTo(vec) < d0)) { count++; player.networkHandler.sendPacket(new PlaySoundIdS2CPacket(soundName, mixer, vec, volume, pitch)); } int totalPlayed = count; return (_c, _t) -> new NumericValue(totalPlayed); }); this.expr.addLazyFunction("particle", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; MinecraftServer ms = cc.s.getMinecraftServer(); ServerWorld world = cc.s.getWorld(); Vector3Argument locator = Vector3Argument.findIn(cc, lv, 1); String particleName = lv.get(0).evalValue(c).getString(); int count = 10; double speed = 0; float spread = 0.5f; ServerPlayerEntity player = null; if (lv.size() > locator.offset) { count = (int) NumericValue.asNumber(lv.get(locator.offset).evalValue(c)).getLong(); if (lv.size() > 1+locator.offset) { spread = (float) NumericValue.asNumber(lv.get(1+locator.offset).evalValue(c)).getDouble(); if (lv.size() > 2+locator.offset) { speed = NumericValue.asNumber(lv.get(2 + locator.offset).evalValue(c)).getDouble(); if (lv.size() > 3 + locator.offset) // should accept entity as well as long as it is player { player = ms.getPlayerManager().getPlayer(lv.get(3 + locator.offset).evalValue(c).getString()); } } } } ParticleEffect particle = ShapeDispatcher.getParticleData(particleName); Vec3d vec = locator.vec; if (player == null) { for (PlayerEntity p : (world.getPlayers())) { world.spawnParticles((ServerPlayerEntity)p, particle, true, vec.x, vec.y, vec.z, count, spread, spread, spread, speed); } } else { world.spawnParticles(player, particle, true, vec.x, vec.y, vec.z, count, spread, spread, spread, speed); } return (c_, t_) -> Value.TRUE; }); this.expr.addLazyFunction("particle_line", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; ServerWorld world = cc.s.getWorld(); String particleName = lv.get(0).evalValue(c).getString(); ParticleEffect particle = ShapeDispatcher.getParticleData(particleName); Vector3Argument pos1 = Vector3Argument.findIn(cc, lv, 1); Vector3Argument pos2 = Vector3Argument.findIn(cc, lv, pos1.offset); double density = 1.0; ServerPlayerEntity player = null; if (lv.size() > pos2.offset+0 ) { density = NumericValue.asNumber(lv.get(pos2.offset+0).evalValue(c)).getDouble(); if (density <= 0) { throw new InternalExpressionException("Particle density should be positive"); } if (lv.size() > pos2.offset+1) { Value playerValue = lv.get(pos2.offset+1).evalValue(c); if (playerValue instanceof EntityValue) { Entity e = ((EntityValue) playerValue).getEntity(); if (!(e instanceof ServerPlayerEntity)) throw new InternalExpressionException("'particle_line' player argument has to be a player"); player = (ServerPlayerEntity) e; } else { player = cc.s.getMinecraftServer().getPlayerManager().getPlayer(playerValue.getString()); } } } Value retval = new NumericValue(ShapeDispatcher.drawParticleLine( (player == null)?world.getPlayers():Collections.singletonList(player), particle, pos1.vec, pos2.vec, density)); return (c_, t_) -> retval; }); this.expr.addLazyFunction("particle_box", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; ServerWorld world = cc.s.getWorld(); String particleName = lv.get(0).evalValue(c).getString(); ParticleEffect particle = ShapeDispatcher.getParticleData(particleName); Vector3Argument pos1 = Vector3Argument.findIn(cc, lv, 1); Vector3Argument pos2 = Vector3Argument.findIn(cc, lv, pos1.offset); double density = 1.0; ServerPlayerEntity player = null; if (lv.size() > pos2.offset+0 ) { density = NumericValue.asNumber(lv.get(pos2.offset+0).evalValue(c)).getDouble(); if (density <= 0) { throw new InternalExpressionException("Particle density should be positive"); } if (lv.size() > pos2.offset+1) { Value playerValue = lv.get(pos2.offset+1).evalValue(c); if (playerValue instanceof EntityValue) { Entity e = ((EntityValue) playerValue).getEntity(); if (!(e instanceof ServerPlayerEntity)) throw new InternalExpressionException("'particle_box' player argument has to be a player"); player = (ServerPlayerEntity) e; } else { player = cc.s.getMinecraftServer().getPlayerManager().getPlayer(playerValue.getString()); } } } Vec3d a = pos1.vec; Vec3d b = pos2.vec; Vec3d from = new Vec3d(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z)); Vec3d to = new Vec3d(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z)); int particleCount = ShapeDispatcher.Box.particleMesh( player==null?world.getPlayers():Collections.singletonList(player), particle, density, from, to ); return (c_, t_) -> new NumericValue(particleCount); }); // deprecated this.expr.alias("particle_rect", "particle_box"); this.expr.addLazyFunction("draw_shape", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; ServerPlayerEntity player[] = {null}; List>> shapes = new ArrayList<>(); if (lv.size() == 1) // bulk { Value specLoad = lv.get(0).evalValue(c); if (!(specLoad instanceof ListValue)) throw new InternalExpressionException("In bulk mode - shapes need to be provided as a list of shape specs"); for (Value list : ((ListValue) specLoad).getItems()) { if (!(list instanceof ListValue)) throw new InternalExpressionException("In bulk mode - shapes need to be provided as a list of shape specs"); shapes.add( ShapeDispatcher.fromFunctionArgs(cc, ((ListValue) list).getItems(), player)); } } else { List params = new ArrayList<>(); for (LazyValue v : lv) params.add(v.evalValue(c)); shapes.add(ShapeDispatcher.fromFunctionArgs(cc, params, player)); } ShapeDispatcher.sendShape( (player[0]==null)?cc.s.getWorld().getPlayers():Collections.singletonList(player[0]), shapes ); return LazyValue.TRUE; }); this.expr.addLazyFunction("create_marker", -1, (c, t, lv) ->{ CarpetContext cc = (CarpetContext)c; BlockState targetBlock = null; Vector3Argument pointLocator; boolean interactable = true; String name; try { Value nameValue = lv.get(0).evalValue(c); name = nameValue instanceof NullValue ? "" : nameValue.getString(); pointLocator = Vector3Argument.findIn(cc, lv, 1, true); if (lv.size()>pointLocator.offset) { BlockArgument blockLocator = BlockArgument.findIn(cc, lv, pointLocator.offset, true, true); if (blockLocator.block != null) targetBlock = blockLocator.block.getBlockState(); if (lv.size() > blockLocator.offset) { interactable = lv.get(blockLocator.offset).evalValue(c, Context.BOOLEAN).getBoolean(); } } } catch (IndexOutOfBoundsException e) { throw new InternalExpressionException("'create_marker' requires a name and three coordinates, with optional direction, and optional block on its head"); } ArmorStandEntity armorstand = new ArmorStandEntity(EntityType.ARMOR_STAND, cc.s.getWorld()); armorstand.refreshPositionAndAngles( pointLocator.vec.x, pointLocator.vec.y - ((targetBlock==null)?(armorstand.getHeight()+0.41):(armorstand.getHeight()-0.3)), pointLocator.vec.z, (float)pointLocator.yaw, (float) pointLocator.pitch ); armorstand.addScoreboardTag(MARKER_STRING+"_"+((cc.host.getName()==null)?"":cc.host.getName())); armorstand.addScoreboardTag(MARKER_STRING); if (targetBlock != null) armorstand.equipStack(EquipmentSlot.HEAD, new ItemStack(targetBlock.getBlock().asItem())); if (!name.isEmpty()) { armorstand.setCustomName(new LiteralText(name)); armorstand.setCustomNameVisible(true); } armorstand.setHeadRotation(new EulerAngle((int)pointLocator.pitch,0,0)); armorstand.setNoGravity(true); armorstand.setInvisible(true); armorstand.setInvulnerable(true); armorstand.getDataTracker().set(ArmorStandEntity.ARMOR_STAND_FLAGS, (byte)(interactable?8 : 16|8)); cc.s.getWorld().spawnEntity(armorstand); EntityValue result = new EntityValue(armorstand); return (_c, _t) -> result; }); this.expr.addLazyFunction("remove_all_markers", 0, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; int total = 0; String markerName = MARKER_STRING+"_"+((cc.host.getName()==null)?"":cc.host.getName()); for (Entity e : cc.s.getWorld().getEntities(EntityType.ARMOR_STAND, (as) -> as.getScoreboardTags().contains(markerName))) { total ++; e.remove(); } int finalTotal = total; return (_cc, _tt) -> new NumericValue(finalTotal); }); 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; return (cc, tt) -> ret; }); this.expr.addLazyFunction("escape_nbt", 1, (c, t, lv) -> { Value v = lv.get(0).evalValue(c); String string = v.getString(); Value ret = new StringValue(StringTag.escape(string)); return (cc, tt) -> ret; }); //"overridden" native call that prints to stderr this.expr.addLazyFunction("print", 1, (c, t, lv) -> { ServerCommandSource s = ((CarpetContext)c).s; Value res = lv.get(0).evalValue(c); if (res instanceof FormattedTextValue) { s.sendFeedback(((FormattedTextValue) res).getText(), false); } else { if (s.getEntity() instanceof PlayerEntity) { Messenger.m((PlayerEntity) s.getEntity(), "w " + res.getString()); } else { Messenger.m(s, "w " + res.getString()); } } return (_c, _t) -> res; // pass through for variables }); this.expr.addLazyFunction("format", -1, (c, t, lv) -> { if (lv.size() == 0 ) throw new InternalExpressionException("'format' requires at least one component"); List values = lv.stream().map(lazy -> lazy.evalValue(c)).collect(Collectors.toList()); if (values.get(0) instanceof ListValue && values.size()==1) values = ((ListValue) values.get(0)).getItems(); Value ret = new FormattedTextValue(Messenger.c(values.stream().map(Value::getString).toArray())); return (cc, tt) -> ret; }); //"overidden" native call to cancel if on main thread this.expr.addLazyFunction("task_join", 1, (c, t, lv) -> { if (((CarpetContext)c).s.getMinecraftServer().isOnThread()) throw new InternalExpressionException("'task_join' cannot be called from main thread to avoid deadlocks"); Value v = lv.get(0).evalValue(c); if (!(v instanceof ThreadValue)) throw new InternalExpressionException("'task_join' could only be used with a task value"); Value ret = ((ThreadValue) v).join(); return (_c, _t) -> ret; }); this.expr.addLazyFunctionWithDelegation("task_dock", 1, (c, t, expr, tok, lv) -> { CarpetContext cc = (CarpetContext)c; MinecraftServer server = cc.s.getMinecraftServer(); if (server.isOnThread()) return lv.get(0); // pass through for on thread tasks Value[] result = new Value[]{Value.NULL}; RuntimeException[] internal = new RuntimeException[]{null}; try { ((CarpetContext) c).s.getMinecraftServer().submitAndJoin(() -> { try { result[0] = lv.get(0).evalValue(c, t); } catch (ExpressionException exc) { internal[0] = exc; } catch (InternalExpressionException exc) { internal[0] = new ExpressionException(c, expr, tok, exc.getMessage(), exc.stack); } catch (ArithmeticException exc) { internal[0] = new ExpressionException(c, expr, tok, "Your math is wrong, "+exc.getMessage()); } }); } catch (CompletionException exc) { throw new InternalExpressionException("Error while executing docked task section, internal stack trace is gone"); } if (internal[0] != null) { throw internal[0]; } Value ret = result[0]; // preventing from lazy evaluating of the result in case a future completes later return (_c, _t) -> ret; // pass through placeholder // implmenetation should dock the task on the main thread. }); this.expr.addLazyFunction("run", 1, (c, t, lv) -> { BlockPos target = ((CarpetContext)c).origin; Vec3d posf = new Vec3d((double)target.getX()+0.5D,(double)target.getY(),(double)target.getZ()+0.5D); ServerCommandSource s = ((CarpetContext)c).s; Value retval = new NumericValue(s.getMinecraftServer().getCommandManager().execute( s.withPosition(posf).withSilent().withLevel(CarpetSettings.runPermissionLevel), lv.get(0).evalValue(c).getString())); return (c_, t_) -> retval; }); this.expr.addLazyFunction("save", 0, (c, t, lv) -> { ServerCommandSource s = ((CarpetContext)c).s; s.getMinecraftServer().getPlayerManager().saveAllPlayerData(); s.getMinecraftServer().save(true,true,true); s.getWorld().getChunkManager().tick(() -> true); CarpetSettings.LOG.warn("Saved chunks"); return (cc, tt) -> Value.TRUE; }); this.expr.addLazyFunction("tick_time", 0, (c, t, lv) -> { Value time = new NumericValue(((CarpetContext) c).s.getMinecraftServer().getTicks()); return (cc, tt) -> time; }); this.expr.addLazyFunction("world_time", 0, (c, t, lv) -> { Value time = new NumericValue(((CarpetContext) c).s.getWorld().getTime()); return (cc, tt) -> time; }); this.expr.addLazyFunction("day_time", -1, (c, t, lv) -> { Value time = new NumericValue(((CarpetContext) c).s.getWorld().getTimeOfDay()); if (lv.size() > 0) { long newTime = NumericValue.asNumber(lv.get(0).evalValue(c)).getLong(); if (newTime < 0) newTime = 0; ((CarpetContext) c).s.getWorld().setTimeOfDay(newTime); } return (cc, tt) -> time; }); this.expr.addLazyFunction("last_tick_times", -1, (c, t, lv) -> { //assuming we are in the tick world section // might be off one tick when run in the off tasks or asynchronously. int currentReportedTick = ((CarpetContext) c).s.getMinecraftServer().getTicks()-1; List ticks = new ArrayList<>(100); final long[] tickArray = ((CarpetContext) c).s.getMinecraftServer().lastTickLengths; for (int i=currentReportedTick+100; i > currentReportedTick; i--) { ticks.add(new NumericValue(((double)tickArray[i % 100])/1000000.0)); } Value ret = ListValue.wrap(ticks); return (cc, tt) -> ret; }); this.expr.addLazyFunction("game_tick", -1, (c, t, lv) -> { ServerCommandSource s = ((CarpetContext)c).s; if (!s.getMinecraftServer().isOnThread()) throw new InternalExpressionException("Unable to run ticks from threads"); ((MinecraftServerInterface)s.getMinecraftServer()).forceTick( () -> System.nanoTime()- CarpetServer.scriptServer.tickStart<50000000L); if (lv.size()>0) { long ms_total = NumericValue.asNumber(lv.get(0).evalValue(c)).getLong(); long end_expected = CarpetServer.scriptServer.tickStart+ms_total*1000000L; long wait = end_expected-System.nanoTime(); if (wait > 0L) { try { Thread.sleep(wait/1000000L); } catch (InterruptedException ignored) { } } } CarpetServer.scriptServer.tickStart = System.nanoTime(); // for the next tick Thread.yield(); if(CarpetServer.scriptServer.stopAll) throw new ExitStatement(Value.NULL); return (cc, tt) -> Value.TRUE; }); this.expr.addLazyFunction("seed", -1, (c, t, lv) -> { ServerCommandSource s = ((CarpetContext)c).s; Value ret = new NumericValue(s.getWorld().getSeed()); return (cc, tt) -> ret; }); this.expr.addLazyFunction("relight", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext) c; BlockArgument locator = BlockArgument.findIn(cc, lv, 0); BlockPos pos = locator.block.getPos(); ServerWorld world = cc.s.getWorld(); ((ServerLightingProviderInterface)world.getChunkManager().getLightingProvider()).resetLight(world.getChunk(pos), new ChunkPos(pos)); //light(world.getChunk(pos), false); forceChunkUpdate(pos, world); world.getChunkManager().getLightingProvider().light(world.getChunk(pos), false); return LazyValue.TRUE; }); this.expr.addLazyFunction("current_dimension", 0, (c, t, lv) -> { ServerCommandSource s = ((CarpetContext)c).s; Value retval = new StringValue(NBTSerializableValue.nameFromRegistryId(Registry.DIMENSION_TYPE.getId(s.getWorld().dimension.getType()))); return (cc, tt) -> retval; }); this.expr.addLazyFunction("view_distance", 0, (c, t, lv) -> { ServerCommandSource s = ((CarpetContext)c).s; Value retval = new NumericValue(s.getMinecraftServer().getPlayerManager().getViewDistance()); return (cc, tt) -> retval; }); this.expr.addLazyFunction("in_dimension", 2, (c, t, lv) -> { ServerCommandSource outerSource = ((CarpetContext)c).s; Value dimensionValue = lv.get(0).evalValue(c); ServerCommandSource innerSource = outerSource; if (dimensionValue instanceof EntityValue) { innerSource = outerSource.withWorld((ServerWorld) ((EntityValue)dimensionValue).getEntity().getEntityWorld()); } else if (dimensionValue instanceof BlockValue) { BlockValue bv = (BlockValue)dimensionValue; if (bv.getWorld() != null) { innerSource = outerSource.withWorld(bv.getWorld()); } else { throw new InternalExpressionException("'in_dimension' accepts only world-localized block arguments"); } } else { String dimString = dimensionValue.getString().toLowerCase(Locale.ROOT); switch (dimString) { case "nether": case "the_nether": innerSource = outerSource.withWorld(outerSource.getMinecraftServer().getWorld(DimensionType.THE_NETHER)); break; case "end": case "the_end": innerSource = outerSource.withWorld(outerSource.getMinecraftServer().getWorld(DimensionType.THE_END)); break; case "overworld": case "over_world": innerSource = outerSource.withWorld(outerSource.getMinecraftServer().getWorld(DimensionType.OVERWORLD)); break; default: throw new InternalExpressionException("Incorrect dimension string: "+dimString); } } ((CarpetContext) c).s = innerSource; Value retval = lv.get(1).evalValue(c); ((CarpetContext) c).s = outerSource; return (cc, tt) -> retval; }); this.expr.addLazyFunction("plop", 4, (c, t, lv) ->{ BlockArgument locator = BlockArgument.findIn((CarpetContext)c, lv, 0); if (lv.size() <= locator.offset) throw new InternalExpressionException("'plop' needs extra argument indicating what to plop"); String what = lv.get(locator.offset).evalValue(c).getString(); Value [] result = new Value[]{Value.NULL}; ((CarpetContext)c).s.getMinecraftServer().submitAndJoin( () -> { Boolean res = FeatureGenerator.spawn(what, ((CarpetContext) c).s.getWorld(), locator.block.getPos()); if (res == null) return; if (what.equalsIgnoreCase("boulder")) // there might be more of those forceChunkUpdate(locator.block.getPos(), ((CarpetContext) c).s.getWorld()); result[0] = new NumericValue(res); }); Value ret = result[0]; // preventing from lazy evaluating of the result in case a future completes later return (_c, _t) -> ret; }); this.expr.addLazyFunction("schedule", -1, (c, t, lv) -> { if (lv.size()<2) throw new InternalExpressionException("'schedule' should have at least 2 arguments, delay and call name"); long delay = NumericValue.asNumber(lv.get(0).evalValue(c)).getLong(); FunctionArgument functionArgument = FunctionArgument.findIn(c, this.expr.module, lv, 1, true, true); CarpetServer.scriptServer.events.scheduleCall( (CarpetContext) c, functionArgument.function, functionArgument.resolveArgs(c, Context.NONE), delay ); return (c_, t_) -> Value.TRUE; }); this.expr.addLazyFunction("logger", 1, (c, t, lv) -> { Value res = lv.get(0).evalValue(c); CarpetSettings.LOG.error(res.getString()); return (_c, _t) -> res; // pass through for variables }); this.expr.addLazyFunction("read_file", 2, (c, t, lv) -> { String resource = recognizeResource(lv.get(0).evalValue(c)); String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT); boolean shared = origtype.startsWith("shared_"); String type = shared ? origtype.substring(7) : origtype; //len(shared_) if (!type.equals("raw") && !type.equals("text") && !type.equals("nbt")) throw new InternalExpressionException("Unsupported file type: "+origtype); Value retVal; if (type.equals("nbt")) { Tag state = ((CarpetScriptHost)((CarpetContext)c).host).readFileTag(resource, shared); if (state == null) return LazyValue.NULL; retVal = new NBTSerializableValue(state); } else { List content = ((CarpetScriptHost) ((CarpetContext) c).host).readTextResource(resource, shared); if (content == null) return LazyValue.NULL; retVal = ListValue.wrap(content.stream().map(StringValue::new).collect(Collectors.toList())); } return (cc, tt) -> retVal; }); this.expr.addLazyFunction("delete_file", 2, (c, t, lv) -> { String resource = recognizeResource(lv.get(0).evalValue(c)); String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT); boolean shared = origtype.startsWith("shared_"); String type = shared ? origtype.substring(7) : origtype; //len(shared_) if (!type.equals("raw") && !type.equals("text") && !type.equals("nbt")) throw new InternalExpressionException("Unsupported file type: "+origtype); boolean success = ((CarpetScriptHost)((CarpetContext)c).host).removeResourceFile(resource, shared, type); return success?LazyValue.TRUE:LazyValue.FALSE; }); this.expr.addLazyFunction("write_file", -1, (c, t, lv) -> { if (lv.size() < 3) throw new InternalExpressionException("'write_file' requires three or more arguments"); String resource = recognizeResource(lv.get(0).evalValue(c)); String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT); boolean shared = origtype.startsWith("shared_"); String type = shared ? origtype.substring(7) : origtype; //len(shared_) if (!type.equals("raw") && !type.equals("text") && !type.equals("nbt")) throw new InternalExpressionException("Unsupported file type: "+origtype); boolean success; if (type.equals("nbt")) { Value val = lv.get(2).evalValue(c); NBTSerializableValue tagValue = (val instanceof NBTSerializableValue) ? (NBTSerializableValue) val : new NBTSerializableValue(val.getString()); Tag tag = tagValue.getTag(); success = ((CarpetScriptHost)((CarpetContext)c).host).writeTagFile(tag, resource, shared); } else { List data = new ArrayList<>(); if (lv.size()==3) { Value val = lv.get(2).evalValue(c); if (val instanceof ListValue) { List lval = ((ListValue) val).getItems(); lval.forEach(v -> data.add(v.getString())); } else { data.add(val.getString()); } } else { for(int i = 2; i < lv.size(); i++) { data.add(lv.get(i).evalValue(c).getString()); } } success = ((CarpetScriptHost) ((CarpetContext) c).host).appendLogFile(resource, shared, type, data); } return success?LazyValue.TRUE:LazyValue.FALSE; }); //write_file this.expr.addLazyFunction("load_app_data", -1, (c, t, lv) -> { String file = null; boolean shared = false; if (lv.size()>0) { file = recognizeResource(lv.get(0).evalValue(c)); if (lv.size() > 1) { shared = lv.get(1).evalValue(c).getBoolean(); } } Tag state = ((CarpetScriptHost)((CarpetContext)c).host).readFileTag(file, shared); if (state == null) return (cc, tt) -> Value.NULL; Value retVal = new NBTSerializableValue(state); return (cc, tt) -> retVal; }); this.expr.addLazyFunction("store_app_data", -1, (c, t, lv) -> { if (lv.size() == 0) throw new InternalExpressionException("'store_app_data' needs NBT tag and an optional file"); Value val = lv.get(0).evalValue(c); String file = null; boolean shared = false; if (lv.size()>1) { file = recognizeResource(lv.get(1).evalValue(c)); if (lv.size() > 2) { shared = lv.get(2).evalValue(c).getBoolean(); } } NBTSerializableValue tagValue = (val instanceof NBTSerializableValue) ? (NBTSerializableValue) val : new NBTSerializableValue(val.getString()); Tag tag = tagValue.getTag(); boolean success = ((CarpetScriptHost)((CarpetContext)c).host).writeTagFile(tag, file, shared); return success?LazyValue.TRUE:LazyValue.FALSE; }); this.expr.addLazyFunction("statistic", 3, (c, t, lv) -> { Value playerValue = lv.get(0).evalValue(c); CarpetContext cc = (CarpetContext)c; ServerPlayerEntity player = EntityValue.getPlayerByValue(cc.s.getMinecraftServer(), playerValue); if (player == null) return LazyValue.NULL; Identifier category; Identifier statName; try { category = new Identifier(lv.get(1).evalValue(c).getString()); statName = new Identifier(lv.get(2).evalValue(c).getString()); } catch (InvalidIdentifierException e) { return LazyValue.NULL; } StatType type = Registry.STAT_TYPE.get(category); if (type == null) return LazyValue.NULL; Stat stat = getStat(type, statName); if (stat == null) return LazyValue.NULL; return (_c, _t) -> new NumericValue(player.getStatHandler().getStat(stat)); }); } private Stat getStat(StatType type, Identifier id) { T key = type.getRegistry().get(id); if (key == null || !((StatTypeInterface)type).hasStatCreated(key)) return null; return type.getOrCreateStat(key); } /** *

.

* @param expression expression * @param source source * @param origin origin */ public CarpetExpression(Module module, String expression, ServerCommandSource source, BlockPos origin) { this.origin = origin; this.source = source; this.expr = new Expression(expression); this.expr.asAModule(module); API_BlockManipulation(); API_EntityManipulation(); API_InventoryManipulation(); API_IteratingOverAreasOfBlocks(); API_AuxiliaryAspects(); API_Scoreboard(); API_Interapperability(); } public boolean fillAndScanCommand(ScriptHost host, int x, int y, int z) { if (CarpetServer.scriptServer.stopAll) return false; try { Context context = new CarpetContext(host, source, origin). with("x", (c, t) -> new NumericValue(x - origin.getX()).bindTo("x")). with("y", (c, t) -> new NumericValue(y - origin.getY()).bindTo("y")). with("z", (c, t) -> new NumericValue(z - origin.getZ()).bindTo("z")). with("_", (c, t) -> new BlockValue(null, source.getWorld(), new BlockPos(x, y, z)).bindTo("_")); Entity e = source.getEntity(); if (e==null) { Value nullPlayer = Value.NULL.reboundedTo("p"); context.with("p", (cc, tt) -> nullPlayer ); } else { Value playerValue = new EntityValue(e).bindTo("p"); context.with("p", (cc, tt) -> playerValue); } return this.expr.eval(context).getBoolean(); } catch (ExpressionException e) { throw new CarpetExpressionException(e.getMessage(), e.stack); } catch (ArithmeticException ae) { throw new CarpetExpressionException("Math doesn't compute... "+ae.getMessage(), null); } } public String scriptRunCommand(ScriptHost host, BlockPos pos) { if (CarpetServer.scriptServer.stopAll) return "SCRIPTING PAUSED"; try { Context context = new CarpetContext(host, source, origin). with("x", (c, t) -> new NumericValue(pos.getX() - origin.getX()).bindTo("x")). with("y", (c, t) -> new NumericValue(pos.getY() - origin.getY()).bindTo("y")). with("z", (c, t) -> new NumericValue(pos.getZ() - origin.getZ()).bindTo("z")); Entity e = source.getEntity(); if (e==null) { Value nullPlayer = Value.NULL.reboundedTo("p"); context.with("p", (cc, tt) -> nullPlayer ); } else { Value playerValue = new EntityValue(e).bindTo("p"); context.with("p", (cc, tt) -> playerValue); } return this.expr.eval(context).getString(); } catch (ExpressionException e) { throw new CarpetExpressionException(e.getMessage(), e.stack); } catch (ArithmeticException ae) { throw new CarpetExpressionException("Math doesn't compute... "+ae.getMessage(), null); } } }