package carpet.script; import carpet.CarpetServer; import carpet.CarpetSettings; // import carpet.helpers.FeatureGenerator; todo import carpet.fakes.MinecraftServerInterface; import carpet.script.exception.CarpetExpressionException; import carpet.script.exception.ExpressionException; import carpet.script.exception.InternalExpressionException; import carpet.script.Fluff.TriFunction; import carpet.script.value.BlockValue; import carpet.script.value.EntityValue; import carpet.script.value.LazyListValue; import carpet.script.value.ListValue; import carpet.script.value.NumericValue; import carpet.script.value.StringValue; import carpet.script.value.Value; import carpet.utils.BlockInfo; import carpet.utils.Messenger; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.entity.EntityType; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.command.arguments.ParticleArgumentType; import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.client.network.packet.PlaySoundIdS2CPacket; import net.minecraft.particle.ParticleEffect; import net.minecraft.block.BlockPlacementEnvironment; import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.state.property.Property; import net.minecraft.state.StateFactory; import net.minecraft.predicate.entity.EntityPredicates; import net.minecraft.util.Identifier; import net.minecraft.sound.SoundCategory; import net.minecraft.util.math.BoundingBox; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; import net.minecraft.util.registry.Registry; import net.minecraft.world.LightType; import net.minecraft.world.World; import net.minecraft.server.world.ServerWorld; import net.minecraft.world.Heightmap; import org.apache.commons.lang3.tuple.Pair; import java.util.ArrayList; import java.util.Arrays; 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.function.BiFunction; import java.util.function.Predicate; import java.util.stream.Collectors; import static java.lang.Math.abs; import static java.lang.Math.max; import static java.lang.Math.min; import static java.lang.Math.sqrt; /** *

Minecraft specific API and scarpet language add-ons and commands

*

Here is the gist of the Minecraft related functions. Otherwise the CarpetScript could live without Minecraft.

*

Dimension issues

*

One note, which is important is that most of the calls for entities and blocks * would refer to the current dimension of the caller, meaning, that if we for example * list all the players using player('all') function, if a player is in the * other dimension, calls to entities and blocks around that player would point incorrectly. * Moreover, running commandblocks in the spawn chunks would mean that commands will always * refer to the overworld blocks and entities. * In case you would want to run commands across all dimensions, just run three of them, using * /execute in overworld/the_nether/the_end run script run ... and query * players using player('*'), which only returns players in current dimension.

*/ public class CarpetExpression { private ServerCommandSource source; private BlockPos origin; private Expression expr; private static long tickStart = 0L; private static boolean stopAll = false; /** *

script stop/script resume command

*
*

* /script stop allows to stop execution of any script currently running that calls the * gametick() function which * allows the game loop to regain control of the game and process other commands. This will also make sure * that all current and future programs will stop their execution. Execution of all programs will be * prevented until /script resume command is called. *

*

Lets look at the following example. This is a program computes Fibonacci number in a recursive manner:

*
     * fib(n) -> if(n<3, 1, fib(n-1)+fib(n-2) ); fib(8)
     * 
*

That's really bad way of doing it, because the higher number we need to compute the compute requirements will rise * exponentially with n. It takes a little over 50 milliseconds to do fib(24), so above one tick, * but about a minute to do fib(40). Calling fib(40) will not only freeze the game, but also you woudn't be able to interrupt * its execution. We can modify the script as follows

*
fib(n) -> ( gametick(50); if(n<3, 1, fib(n-1)+fib(n-2) ) ); fib(40)
*

But this would never finish as such call would finish after ~ 2^40 ticks. To make our computations * responsive, yet able to respond to user interactions, other commands, as well as interrupt execution, * we could do the following:

*
fib(n) -> ( if(n==23, gametick(50) ); if(n<3, 1, fib(n-1)+fib(n-2) ) ); fib(40)
*

This would slow down the computation of fib(40) from a minute to two, but allows the game to keep continue running * and be responsive to commands, using about half of each tick to advance the computation. * Obviously depending on the problem, and available hardware, certain things can take * more or less time to execute, so portioning of work with calling gametick should be balanced in each * case separately

*
* @param doStop . */ public static void BreakExecutionOfAllScriptsWithCommands(boolean doStop) { //unused - accessed via CarpetScriptServer } 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 = BlockValue.fromParams(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) { Value retval = new StringValue(test.apply( ((BlockValue) v0).getBlockState(), ((BlockValue) v0).getPos())); return (c_, t_) -> retval; } BlockValue block = BlockValue.fromParams(cc, params, 0).block; Value retval = new StringValue(test.apply(block.getBlockState(), block.getPos())); 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 = BlockValue.fromParams(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.getValue(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 Map particleCache = new HashMap<>(); private ParticleEffect getParticleData(String name) { ParticleEffect particle = particleCache.get(name); if (particle != null) return particle; try { particle = ParticleArgumentType.readParameters(new StringReader(name)); } catch (CommandSyntaxException e) { throw new InternalExpressionException("No such particle: "+name); } particleCache.put(name, particle); return particle; } private int drawParticleLine(ServerWorld world, ParticleEffect particle, Vec3d from, Vec3d to, double density) { double lineLengthSq = from.squaredDistanceTo(to); if (lineLengthSq == 0) return 0; Vec3d incvec = to.subtract(from).multiply(2*density/sqrt(lineLengthSq)); int pcount = 0; for (Vec3d delta = new Vec3d(0.0,0.0,0.0); delta.lengthSquared()Blocks / World API *
*

Specifying blocks

*

block(x, y, z), block(state)

*

Returns either a block from specified location, or block with a specific state * (as used by /setblock command), so allowing for block properties etc. * Blocks can be referenced everywhere by its simple name, but its only used in its default state

*
     * block('air')  => air
     * block('iron_trapdoor[half=top]')  => iron_trapdoor
     * block(0,0,0) == block('bedrock')  => 1
     * 
*

World Manipulation

*

All the functions below can be used with block value, queried with coord triple, or 3-long list. * All pos in the functions referenced below refer to either method of passing block position

*

set(pos, block, property?, value?, ...)

*

First part of the set function is either a coord triple, list of three numbers, or other block * with coordinates. Second part, block is either block value as a result of block() function * string value indicating the block name, and optional property - value pairs for extra block properties. * If block is specified only by name, then if a destination block is the same the set operation * is skipped, otherwise is executed, for other potential extra properties

*

The returned value is either the block state that has been set, or false if block setting was skipped

*
     * set(0,5,0,'bedrock')  => bedrock
     * set(l(0,5,0), 'bedrock')  => bedrock
     * set(block(0,5,0), 'bedrock')  => bedrock
     * scan(0,5,0,0,0,0,set(_,'bedrock'))  => 1
     * set(pos(players()), 'bedrock')  => bedrock
     * set(0,0,0,'bedrock')  => 0   // or 1 in overworlds generated in 1.8 and before
     * scan(0,100,0,20,20,20,set(_,'glass'))
     *     // filling the area with glass
     * scan(0,100,0,20,20,20,set(_,block('glass')))
     *     // little bit faster due to internal caching of block state selectors
     * b = block('glass'); scan(0,100,0,20,20,20,set(_,b))
     *     // yet another option, skips all parsing
     * set(x,y,z,'iron_trapdoor')  // sets bottom iron trapdoor
     * set(x,y,z,'iron_trapdoor[half=top]')  // Incorrect. sets bottom iron trapdoor - no parsing of properties
     * set(x,y,z,'iron_trapdoor','half','top') // correct - top trapdoor
     * set(x,y,z,block('iron_trapdoor[half=top]')) // also correct, block() provides extra parsing
     * 
*

update(pos)

*

Causes a block update at position.

*

block_tick(pos)

*

Causes a block to tick at position.

*

random_tick(pos)

*

Causes a random tick at position.

* *

Block and World querying

* *

pos(block), pos(entity)

*

Returns a triple of coordinates of a specified block or entity. Technically entities are queried with * query function and the same can be achieved with query(entity,'pos'), but for simplicity * pos allows to pass all positional objects.

*
     *     pos(block(0,5,0))  => l(0,5,0)
     *     pos(players()) => l(12.3, 45.6, 32.05)
     *     pos(block('stone'))  => Error: Cannot fetch position of an unrealized block
     * 
*

property(pos, name)

*

Returns property of block at pos, or specified by block argument. If a block doesn't * have that property, null value is returned. Returned values are always strings. It is expected from * the user to know what to expect and convert values to numbers using number() function or booleans * using bool() function.

*
     *     set(x,y,z,'iron_trapdoor','half','top'); property(x,y,z,'half')  => top
     *     set(x,y,z,'air'); property(x,y,z,'half')  => null
     *     property(block('iron_trapdoor[half=top]'),'half')  => top
     *     property(block('iron_trapdoor[half=top]'),'powered')  => false
     *     bool(property(block('iron_trapdoor[half=top]'),'powered'))  => 0
     * 
* *

solid(pos)

*

Boolean function, true if the block is solid

*

air(pos)

*

Boolean function, true if a block is air.... or cave air... * or void air.... or any other air they come up with.

*

liquid(pos)

*

Boolean function, true if the block is liquid, or liquidlogged

*

flammable(pos)

*

Boolean function, true if the block is flammable

*

transparent(pos)

*

Boolean function, true if the block is transparent

*

opacity(pos)

*

Numeric, returning opacity level of a block

*

blocks_daylight(pos)

*

Boolean function, true if the block blocks daylight

*

emitted_light(pos)

*

Numeric, returning light level emitted from block

*

light(pos)

*

Integer function, returning total light level at position

*

block_light(pos)

*

Integer function, returning block light at position. From torches and other light sources.

*

sky_light(pos)

*

Numeric function, returning sky light at position. From the sky access.

*

see_sky(pos)

*

Boolean function, returning true if the block can see sky.

*

hardness(pos)

*

Numeric function, indicating hardness of a block.

*

blast_resistance(pos)

*

Numeric function, indicating blast_resistance of a block.

*

top(type, x, z)

*

Returns the Y value of the topmost block at given x, z coords, according to the * heightmap specified by type. Valid options are:

*
    *
  • light: topmost light blocking block
  • *
  • motion: topmost motion blocking block
  • *
  • terrain: topmost motion blocking block except leaves
  • *
  • ocean_floor: topmost non-water block
  • *
  • surface: topmost surface block
  • *
*
     * top('motion', x, z)  => 63
     * top('ocean_floor', x, z)  => 41
     * 
*

loaded(pos)

*

Boolean function, true if the block is loaded. Normally scarpet doesn't check if operates on * loaded area - the game will automatically load missing blocks. We see this as advantage. * Vanilla fill/clone commands only check the specified corners for loadness.

*
     * loaded(pos(players()))  => 1
     * loaded(100000,100,1000000)  => 0
     * 
*

loaded_ep(pos)

*

Boolean function, true if the block is loaded and entity processing, as per 1.13.2

*

suffocates(pos)

*

Boolean function, true if the block causes suffocation.

*

power(pos)

*

Numeric function, returning redstone power level at position.

*

ticks_randomly(pos)

*

Boolean function, true if the block ticks randomly.

*

blocks_movement(pos)

*

Boolean function, true if block at position blocks movement.

*

block_sound(pos)

*

Returns the name of sound type made by the block at position. One of:

*
    *
  • wood
  • *
  • gravel
  • *
  • grass
  • *
  • stone
  • *
  • metal
  • *
  • glass
  • *
  • wool
  • *
  • sand
  • *
  • snow
  • *
  • ladder
  • *
  • anvil
  • *
  • slime
  • *
  • sea_grass
  • *
  • coral
  • *
*

material(pos)

*

Returns the name of material of the block at position. very useful to target a group of blocks. One of:

*
    *
  • air
  • *
  • void
  • *
  • portal
  • *
  • carpet
  • *
  • plant
  • *
  • water_plant
  • *
  • vine
  • *
  • sea_grass
  • *
  • water
  • *
  • bubble_column
  • *
  • lava
  • *
  • snow_layer
  • *
  • fire
  • *
  • redstone_bits
  • *
  • cobweb
  • *
  • redstone_lamp
  • *
  • clay
  • *
  • dirt
  • *
  • grass
  • *
  • packed_ice
  • *
  • sand
  • *
  • sponge
  • *
  • wood
  • *
  • wool
  • *
  • tnt
  • *
  • leaves
  • *
  • glass
  • *
  • ice
  • *
  • cactus
  • *
  • stone
  • *
  • iron
  • *
  • snow
  • *
  • anvil
  • *
  • barrier
  • *
  • piston
  • *
  • coral
  • *
  • gourd
  • *
  • dragon_egg
  • *
  • cake
  • *
*

map_colour(pos)

*

Returns the map colour of a block at position. One of:

*
    *
  • air
  • *
  • grass
  • *
  • sand
  • *
  • wool
  • *
  • tnt
  • *
  • ice
  • *
  • iron
  • *
  • foliage
  • *
  • snow
  • *
  • clay
  • *
  • dirt
  • *
  • stone
  • *
  • water
  • *
  • wood
  • *
  • quartz
  • *
  • adobe
  • *
  • magenta
  • *
  • light_blue
  • *
  • yellow
  • *
  • lime
  • *
  • pink
  • *
  • gray
  • *
  • light_gray
  • *
  • cyan
  • *
  • purple
  • *
  • blue
  • *
  • brown
  • *
  • green
  • *
  • red
  • *
  • black
  • *
  • gold
  • *
  • diamond
  • *
  • lapis
  • *
  • emerald
  • *
  • obsidian
  • *
  • netherrack
  • *
  • white_terracotta
  • *
  • orange_terracotta
  • *
  • magenta_terracotta
  • *
  • light_blue_terracotta
  • *
  • yellow_terracotta
  • *
  • lime_terracotta
  • *
  • pink_terracotta
  • *
  • gray_terracotta
  • *
  • light_gray_terracotta
  • *
  • cyan_terracotta
  • *
  • purple_terracotta
  • *
  • blue_terracotta
  • *
  • brown_terracotta
  • *
  • green_terracotta
  • *
  • red_terracotta
  • *
  • black_terracotta
  • *
*
*/ public 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"); } if (lv.size() == 1) { Value retval = BlockValue.fromCommandExpression(lv.get(0).evalValue(cc).getString()); return (c_, t_) -> retval; //return new BlockValue(Registry.field_212618_g.get(new Identifier(lv.get(0).getString())).getDefaultState(), origin); } Value retval = BlockValue.fromParams(cc, lv, 0).block; return (c_, t_) -> retval; }); 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.x), new NumericValue(e.y), new NumericValue(e.z)); return(c_, t_) -> retval; } else { throw new InternalExpressionException("pos works only with a block or an entity type"); } }); this.expr.addLazyFunction("solid", -1, (c, t, lv) -> booleanStateTest(c, "solid", lv, (s, p) -> s.isFullBoundsCubeForCulling())); 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(w.getLightLevel(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("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("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); } int x; int z; Value v1 = lv.get(1).evalValue(c); if (v1 instanceof BlockValue) { BlockPos inpos = ((BlockValue)v1).getPos(); x = inpos.getX(); z = inpos.getZ(); } else { x = (int) NumericValue.asNumber(lv.get(1).evalValue(c)).getLong(); z = (int) NumericValue.asNumber(lv.get(2).evalValue(c)).getLong(); } 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().isBlockLoaded(BlockValue.fromParams((CarpetContext) c, lv, 0).block.getPos()) ? Value.TRUE : Value.FALSE; return (c_, t_) -> retval; }); this.expr.addLazyFunction("loaded_ep", -1, (c, t, lv) -> { BlockPos pos = BlockValue.fromParams((CarpetContext)c, lv, 0).block.getPos(); Value retval = ((CarpetContext)c).s.getWorld().isAreaLoaded(pos.getX() - 32, 0, pos.getZ() - 32, pos.getX() + 32, 0, pos.getZ() + 32) ? Value.TRUE : Value.FALSE; return (c_, t_) -> retval; }); 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) -> { World w = ((CarpetContext)c).s.getWorld(); s.onRandomTick(w, p, w.random); return true; })); this.expr.addLazyFunction("random_tick", -1, (c, t, lv) -> booleanStateTest(c, "random_tick", lv, (s, p) -> { World w = ((CarpetContext)c).s.getWorld(); if (s.hasRandomTicks() || s.getFluidState().hasRandomTicks()) s.onRandomTick(w, p, w.random); return true; })); this.expr.addLazyFunction("set", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; World world = cc.s.getWorld(); if (lv.size() < 2 || lv.size() % 2 == 1) throw new InternalExpressionException("set block should have at least 2 params and odd attributes"); BlockValue.LocatorResult locator = BlockValue.fromParams(cc, lv, 0); Value blockArg = lv.get(locator.offset).evalValue(cc); BlockValue bv = ((blockArg instanceof BlockValue)) ? (BlockValue) blockArg : BlockValue.fromString(blockArg.getString()); if (bv == BlockValue.NULL) throw new InternalExpressionException("block to set to should be a valid block"); BlockState bs = bv.getBlockState(); BlockState targetBlockState = world.getBlockState(locator.block.getPos()); if (lv.size()==1+locator.offset && !(blockArg instanceof BlockValue)) // no reqs for properties if (targetBlockState.getBlock() == bs.getBlock()) return (c_, t_) -> Value.FALSE; StateFactory states = bs.getBlock().getStateFactory(); for (int i = 1+locator.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 " + blockArg.getString()); String paramValue = lv.get(i + 1).evalValue(c).getString(); bs = setProperty(property, paramString, paramValue, bs); } cc.s.getWorld().setBlockState(locator.block.getPos(), bs, 2 | (CarpetSettings.getBool("fillUpdates") ? 0 : 1024)); Value retval = new BlockValue(bs, world, locator.block.getPos()); return (c_, t_) -> retval; }); 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) -> { BlockValue.LocatorResult locator = BlockValue.fromParams((CarpetContext) c, lv, 0); BlockState state = locator.block.getBlockState(); String tag = lv.get(locator.offset).evalValue(c).getString(); StateFactory states = state.getBlock().getStateFactory(); Property property = states.getProperty(tag); if (property == null) return LazyValue.NULL; Value retval = new StringValue(state.get(property).toString()); return (_c, _t ) -> retval; }); } /** *

Entity API

*
*

Entity Selection

*

Entities have to be fetched before using them. Entities can also change their state between calls to the script * if game happens either in between separate calls to the programs, or if the program calls game_tick * on its own. In this case - entities would need to be re-fetched, or the code should account for entities getting dead

*

player(), player(type), player(name)

*

* With no arguments, it returns the calling player or the player closest to the caller. Note that the main context * will receive p variable pointing to this player. With type or name specified * it will try first to match a type, returning a list of players matching a type, and if this fails, will assume its * player name query retuning player with that name, or null if no player was found. * With 'all', list of all players in the game, in all dimensions, so end user needs to be cautious, that * you might be referring to wrong blocks and entities around the player in question. * WIth type = '*' it returns all players in caller dimension, 'survival' returns all survival * and adventure players, 'creative' returns all creative players, 'spectating' returns all spectating * players, and '!spectating', all not-spectating players. If all fails, * with name, the player in question, if is logged in.

*

entity_id(uuid), entity_id(id)

*

Fetching entities wither by their ID obtained via entity ~ 'id', which is unique * for a dimension and current world run, or by UUID, obtained via entity ~ 'uuid'. * It returns null if no such entity * is found. Safer way to 'store' entities between calls, as missing entities will be returning null. * Both calls using UUID or numerical ID are O(1), but obviously using UUIDs takes more memory and compute.

*

entity_list(type)

*

Returns global lists of entities in the current dimension of a specified type. Currently the following selectors are available:

*
    *
  • *: all
  • *
  • living
  • *
  • items
  • *
  • players
  • *
  • !players
  • *
* *

entity_area(type, cx, cy, cz, dx, dy, dz)

*

Returns entities of a specified type in an area centered on cx, cy, cz and * at most dx, dy, dz blocks away from the center point. Uses same selectors as entities_list

* *

entity_selector(selector)

*

Returns entities satisfying given vanilla entity selector. Most complex among all the methods of selecting * entities, but the most capable. Selectors are cached so should be as fast as other methods of selecting entities.

* *

Entity Manipulation

* *

Unlike with blocks, that use plethora of vastly different querying functions, entities are queried with * query function and altered via modify function. Type of information needed or * values to be modified are different for each call

*

query(e,'removed')

*

Boolean. True if the entity is removed

*

query(e,'id')

*

Returns numerical id of the entity. Most efficient way to keep track of entites in a script. Ids are only unique * within current game session (ids are not preserved between restarts), and dimension (each dimension has its own ids * which can overlap.

*

query(e,'uuid')

*

Returns UUID (unique id) of the entity. Can be used to access entities with the other vanilla commands and remains unique * regardless of the dimension, and is preserved between game restarts. * Apparently players cannot be accessed via UUID, but name instead.

*
     * map(entities_area('*',x,y,z,30,30,30),run('kill '+query(_,'id'))) // doesn't kill the player
     * 
*

query(e,'pos')

*

Triple of entity position

*

query(e,'location')

*

Quin-tuple of entity position (x, y, and z coords), and rotation (yaw, pitch)

*

query(e,'x'), query(e,'y'), query(e,'z')

*

Respective entity coordinate

*

query(e,'pitch'), query(e,'yaw')

*

Pitch and Yaw or where entity is looking.

*

query(e,'motion')

*

Triple of entity motion vector, l(motion_x, motion_y, motion_z)

*

query(e,'motion_x'), query(e,'motion_y'), query(e,'motion_z')

*

Respective component of the motion vector

*

query(e,'name'), query(e,'custom_name'), query(e,'type')

*

String of entity name

*
     * query(e,'name')  => Leatherworker
     * query(e,'custom_name')  => null
     * query(e,'type')  => villager
     * 
*

query(e,'is_riding')

*

Boolean. True if riding another entity.

*

query(e,'is_ridden')

*

Boolean. True if another entity is riding it.

*

query(e,'passengers')

*

List of entities riding the entity.

*

query(e,'mount')

*

Entity that e rides.

*

query(e,'tags')

*

List of entity tags.

*

query(e,'has_tags',tag)

*

Boolean, True if the entity is marked with tag.

*

query(e,'is_burning')

*

Boolean, True if the entity is burning.

*

query(e,'fire')

*

Number of remaining ticks of being on fire.

*

query(e,'silent')

*

Boolean, True if the entity is silent.

*

query(e,'gravity')

*

Boolean, True if the entity is affected by gravity, like most entities do.

*

query(e,'immune_to_fire')

*

Boolean, True if the entity is immune to fire.

*

query(e,'dimension')

*

Name of the dimension entity is in.

*

query(e,'height')

*

Height of the entity.

*

query(e,'width')

*

Width of the entity.

*

query(e,'eye_height')

*

Eye height of the entity.

*

query(e,'age')

*

Age, in ticks, of the entity, i.e. how long it existed.

*

query(e,'item')

*

Name of the item if its an item entity, null otherwise

*

query(e,'count')

*

Number of items in a stack from item entity, null otherwise

*

query(e,'is_baby')

*

Boolean, true if its a baby.

*

query(e,'target')

*

Returns mob's attack target or null if none or not applicable.

*

query(e,'home')

*

Returns creature's home position or null if none or not applicable.

*

query(e,'sneaking')

*

Boolean, true if entity is sneaking.

*

query(e,'sprinting')

*

Boolean, true if entity is sprinting.

*

query(e,'swimming')

*

Boolean, true if entity is swimming.

*

query(e,'jumping')

*

Boolean, true if entity is jumping.

*

query(e,'gamemode')

*

String with gamemode, or null if not a player.

*

query(e,'gamemode_id')

*

Good'ol gamemode id, or null if not a player.

*

query(e,'effect',name?)

*

Without extra arguments, it returns list of effect active on a living entity. * Each entry is a triple of short effect name, amplifier, and remaining duration. * With an argument, if the living entity has not that potion active, returns null, otherwise * return a tuple of amplifier and remaining duration

*
     * query(p,'effect')  => [[haste, 0, 177], [speed, 0, 177]]
     * query(p,'effect','haste')  => [0, 177]
     * query(p,'effect','resistance')  => null
     * 
*

query(e,'health')

*

Number indicating remaining entity health, or null if not applicable.

*

query(e,'holds',slot?)

*

Returns triple of short name, stack count, and NBT of item held in slot. * Available options for slot are:

*
    *
  • main
  • *
  • offhand
  • *
  • head
  • *
  • chest
  • *
  • legs
  • *
  • feet
  • *
*

If slot is not specified, it defaults to the main hand.

*

query(e,'nbt',path?)

*

Returns full NBT of the entity. If path is specified, it fetches only that portion of the NBT, * that corresponds to the path. For specification of path attribute, consult * vanilla /data get entity command.

*

Note that calls to nbt are considerably more expensive comparing to other * calls in Minecraft API, and should only be used when there is no other option. Also returned * NBT is just a string to any retrieval of information post can currently only be done with matching * operator ~. With time we are hoping not to support the 'nbt' call better, * but rather to fill the API, so that 'nbt' calls are not needed

*

Entity Modification

*

Like with entity querying, entity modifications happen through one function. Most position and movements * modifications don't work currently on players as their position is controlled by clients.

*

Currently there is no ability to modify NBT directly, but you could always use run('data modify entity

*

modify(e,'remove')

*

Removes (not kills) entity from the game.

*

modify(e,'kill')

*

Kills the entity.

*

modify(e, 'pos', x, y, z), modify(e, 'pos', l(x,y,z) )

*

Moves the entity to a specified coords.

*

modify(e, 'location', x, y, z, yaw, pitch), modify(e, 'location', l(x, y, z, yaw, pitch) )

*

Changes full location vector all at once.

*

modify(e, 'x', x), modify(e, 'y', y), modify(e, 'z', z)

*

Moves the entity in.... one direction.

*

modify(e, 'pitch', pitch), modify(e, 'yaw', yaw)

*

Changes entity's pitch or yaw.

*

modify(e, 'move', x, y, z), modify(e, 'move', l(x,y,z) )

*

Moves the entity by a vector from its current location.

*

modify(e, 'motion', x, y, z), modify(e, 'motion', l(x,y,z) )

*

Sets the motion vector (where and how much entity is moving).

*

modify(e, 'motion_z', x), modify(e, 'motion_y', y), modify(e, 'motion_z', z)

*

Sets the corresponding component of the motion vector.

*

modify(e, 'accelerate', x, y, z), modify(e, 'accelerate', l(x, y, z) )

*

Adds a vector to the motion vector. Most realistic way to apply a force to an entity.

*

modify(e, 'custom_name'), modify(e, 'custom_name', name )

*

Sets a custom name for an entity. No argument sets it empty, not removes it. Vanilla doesn't allow removing * of attributes.

*

modify(e, 'dismount')

*

Dismounts riding entity.

*

modify(e, 'mount', other)

*

Mounts the entity to the other.

*

modify(e, 'drop_passengers')

*

Shakes off all passengers.

*

modify(e, 'mount_passengers', passenger, ? ...), modify(e, 'mount_passengers', l(passengers) )

*

Mounts on all listed entities on e.

*

modify(e, 'tag', tag, ? ...), modify(e, 'tag', l(tags) )

*

Adds tag / tags to the entity.

*

modify(e, 'clear_tag', tag, ? ...), modify(e, 'clear_tag', l(tags) )

*

Removes tag from entity.

*

modify(e, 'talk')

*

Make noises.

*

modify(e, 'home', null), modify(e, 'home', block, distance?), modify(e, 'home', x, y, z, distance?)

*

Sets AI to stay around the home position, within distance blocks from it. distance * defaults to 16 blocks. null removes it. May not work fully with mobs that have this AI built in, like * Villagers.

*
*/ public 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("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() ); BoundingBox area = new BoundingBox(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(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_entity 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_entity 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_entity 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 get 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 lv.get(0); }); } /** *

Iterating over larger areas of blocks

*
*

These functions help scan larger areas of blocks without using generic loop functions, * like nested loop.

*

*

scan(cx, cy, cz, dx, dy, dz, px?, py?, pz?, expr)

*

Evaluates expression over area of blocks defined by its center (cx, cy, cz), * expanded in all directions by dx, dy, dz blocks, or optionally in negative with d coords, * and p coords in positive values. expr receives _x, _y, _z * as coords of current analyzed block and _ which represents the block itself.

*

volume(x1, y1, z1, x2, y2, z2, expr)

*

Evaluates expression for each block in the area, the same as the scanfunction, but using two opposite * corners of the rectangular cuboid. Any corners can be specified, its like you would do with /fill command

*

neighbours(x, y, z), neighbours(block), neighbours(l(x,y,z))

*

Returns the list of 6 neighbouring blocks to the argument. Commonly used with other loop functions like for

*
     * for(neighbours(x,y,z),air(_)) => 4 // number of air blocks around a block
     * 
*

rect(cx, cy, cz, dx?, dy?, dz?, px?, py?, pz?)

*

returns an iterator, just like range function that iterates over rectangular cubarea of blocks. If * only center point is specified, it iterates over 27 blocks. If d arguments are specified, expands selection * of respective number of blocks in each direction. If p arguments are specified, it uses d for * negative offset, and p for positive.

*

diamond(cx, cy, cz, radius?, height?)

*

Iterates over a diamond like area of blocks. With no radius and height, its 7 blocks centered around the middle * (block + neighbours). With a radius specified, it expands shape on x and z coords, and with a custom height, on y. Any of these can be * zero as well. radius of 0 makes a stick, height of 0 makes a diamond shape pad.

*
*/ public void API_IteratingOverAreasOfBlocks() { this.expr.addLazyFunction("scan", -1, (c, t, lv) -> { int lvsise = lv.size(); if (lvsise != 7 && lvsise != 10) throw new InternalExpressionException("scan takes 2, or 3 triples of coords, and the expression"); int cx = (int) NumericValue.asNumber(lv.get(0).evalValue(c)).getLong(); int cy = (int) NumericValue.asNumber(lv.get(1).evalValue(c)).getLong(); int cz = (int) NumericValue.asNumber(lv.get(2).evalValue(c)).getLong(); int xrange = (int) NumericValue.asNumber(lv.get(3).evalValue(c)).getLong(); int yrange = (int) NumericValue.asNumber(lv.get(4).evalValue(c)).getLong(); int zrange = (int) NumericValue.asNumber(lv.get(5).evalValue(c)).getLong(); int xprange = xrange; int yprange = yrange; int zprange = zrange; LazyValue expr; if (lvsise == 7) { expr = lv.get(6); } else { xprange = (int) NumericValue.asNumber(lv.get(6).evalValue(c)).getLong(); yprange = (int) NumericValue.asNumber(lv.get(7).evalValue(c)).getLong(); zprange = (int) NumericValue.asNumber(lv.get(8).evalValue(c)).getLong(); expr = lv.get(9); } //saving outer scope LazyValue _x = c.getVariable("_x"); LazyValue _y = c.getVariable("_y"); LazyValue _z = c.getVariable("_z"); LazyValue __ = c.getVariable("_"); int sCount = 0; 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); if (expr.evalValue(c, Context.BOOLEAN).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; 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); if (expr.evalValue(c, Context.BOOLEAN).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 = BlockValue.fromParams((CarpetContext) c, lv,0).block.getPos(); World 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() { int minx = cx-sminx; int miny = cy-sminy; int minz = cz-sminz; int maxx = cx+smaxx; int maxy = cy+smaxy; int maxz = cz+smaxz; int x; int y; int z; { x = minx; y = miny; z = minz; } @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 } }; }); 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 = new ListValue(Arrays.asList( 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 = 0; int curpos = 0; { } @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; } }; } else { return (c_, t_) -> new LazyListValue() { int curradius = 0; int curpos = 0; int curheight = -height; @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; } }; } }); } //TODO sounds /** *

Auxiliary aspects

*
*

Collection of other methods that control smaller, yet still important aspects of the game

*

Sounds

*

sound(name, pos, volume?, pitch?)

*

Plays a specific sound name, at block or position pos, with optional * volume and modified pitch. pos can be either a block, triple of coords, * or a list of thee numbers. Uses the same options as a corresponding playsound command.

*

Particles

*

particle(name, pos, count?. spread?, speed?, playername?)

*

Renders a cloud of particles name centered around pos position, by default * count 10 of them, default speed of 0, and to all players nearby, but these * options can be changed via optional arguments. Follow vanilla /particle command on details on those * options. Valid particle names are for example * 'angry_villager', 'item diamond', 'block stone', 'dust 0.8 0.1 0.1 4'

*

particle_line(name, pos, pos2, density?)

*

Renders a line of particles from point pos to pos2 with supplied density (defaults 1), * which indicates how far part you would want particles to appear, so 0.1 means one every 10cm.

*

particle_rect(name, pos, pos2, density?)

*

Renders a cuboid of particles between point pos to pos2 with supplied density.

*

System function

*

print(expr)

*

Displays the result of the expression to the chat. Overrides default scarpet behaviour of * sending everyting to stderr.

*

run(expr)

*

Runs a command from the string result of the expr expression, and returns its success count

*

save()

*

Performs autosave, saves all chunks, player data, etc. Useful for programs where autosave is disabled * due to performance reasons and saves the world only on demand.

*

tick_time()

*

Returns game tick counter. Can be used to run certain operations every n-th ticks, or to count in-game time

*

game_tick(mstime?)

*

Causes game to run for one tick. By default runs it and returns control to the program, but can optionally * accept expected tick length, in milliseconds. You can't use it to permanently change the game speed, but * setting longer commands with custom tick speeds can be interrupted via /script stop command

*
     * loop(1000,tick())  // runs the game as fast as it can for 1000 ticks
     * loop(1000,tick(100)) // runs the game twice as slow for 1000 ticks
     * 
*

current_dimension()

*

Returns current dimension that scripts run in.

*

plop(pos, what)

*

Plops a structure or a feature at a given pos, so block, triple position coordinates * or a list of coordinates. To what gets plopped and exactly where it often depends on the * feature or structure itself. For example, all structures are chunk aligned, and often span multiple chunks. * Repeated calls to plop a structure in the same chunk would result either in the same strucuture generated on * top of each other, or with different state, but same position. Most * structures generate at specific altitudes, which are hardcoded, or with certain blocks around them. API will cancel * all extra position / biome / random requirements for structure / feature placement, but some hardcoded limitations * may still cause some of strucutures/features not to place. Some features require special blocks to be present, like * coral -> water or ice spikes -> snow block, and for some features, like fossils, placement is all sorts of * messed up.

*

* All generated structures will retain their properties, like mob spawning, however in many cases the world / dimension * itself has certain rules to spawn mobs, like plopping a nether fortress in the overworld will not spawn nether mobs * because nether mobs can spawn only in the nether, but plopped in the nether - will behave like a valid nether * fortress.

*

plop will not use world random number generator to generate structures and features, but its own. * This has a benefit that they will generate properly randomly, not the same time every time

*

Structure list:

*
    * *
  • monument: Ocean Monument. Generates at fixed Y coordinate, surrounds itself with water.
  • *
  • fortress: Nether Fortress. Altitude varies, but its bounded by the code.
  • *
  • mansion: Woodland Mansion
  • *
  • jungle_temple: Jungle Temple
  • *
  • desert_temple: Desert Temple. Generates at fixed Y altitude.
  • *
  • end_city: End City with Shulkers
  • *
  • igloo: Igloo
  • *
  • shipwreck: Shipwreck, version1?
  • *
  • shipwreck2: Shipwreck, version2?
  • *
  • witchhut
  • *
  • oceanruin, oceanruin_small, oceanruin_tall: Stone variants of ocean ruins.
  • *
  • oceanruin_warm, oceanruin_warm_small, oceanruin_warm_tall: Sandstone variants of ocean ruins.
  • *
  • treasure: A treasure chest. Yes, its a whole structure.
  • *
  • mineshaft: A mineshaft.
  • *
  • mineshaft_mesa: A Mesa (Badlands) version of a mineshaft.
  • *
  • village: Plains, oak village.
  • *
  • village_desert: Desert, sandstone village.
  • *
  • village_savanna: Savanna, acacia village.
  • *
  • village_taiga: Taiga, spruce village.
  • *
*

Feature list:

*
    *
  • oak
  • *
  • oak_large: oak with branches.
  • *
  • birch
  • *
  • birch_large: tall variant of birch tree.
  • *
  • shrub: low bushes that grow in jungles.
  • *
  • shrub_acacia: low bush but configured with acacia
  • *
  • shrub_snowy: low bush with white blocks
  • *
  • jungle: a tree
  • *
  • spruce_matchstick: tall spruce with minimal leafage.
  • *
  • dark_oak
  • *
  • acacia
  • *
  • spruce
  • *
  • oak_swamp: oak with more leaves and vines.
  • *
  • jungle_large: 2x2 jungle tree
  • *
  • spruce_matchstick_large: 2x2 spruce tree with minimal leafage
  • *
  • spruce_large: 2x2 spruce tree
  • *
  • well: desert well
  • *
  • grass: a few spots of tall grass
  • *
  • grass_jungle: little bushier grass feature
  • *
  • fern: a few random ferns
  • *
  • cactus: random cacti
  • *
  • dead_bush: a few random dead bushi
  • *
  • fossils: underground fossils, placement little wonky
  • *
  • mushroom_brown: large brown mushroom.
  • *
  • mushroom_red: large red mushroom.
  • *
  • ice_spike: ice spike. Require snow block below to place.
  • *
  • glowstone: glowstone cluster. Required netherrack above it.
  • *
  • melon: a patch of melons
  • *
  • pumpkin: a patch of pumpkins
  • *
  • sugarcane
  • *
  • lilypad
  • *
  • dungeon: Dungeon. These are hard to place, and fail often.
  • *
  • iceberg: Iceberg. Generate at sea level.
  • *
  • iceberg_blue: Blue ice iceberg.
  • *
  • lake
  • *
  • lava_lake
  • *
  • end_island
  • *
  • chorus: Chorus plant. Require endstone to place.
  • *
  • sea_grass: a patch of sea grass. Require water.
  • *
  • sea_grass_river: a variant.
  • *
  • kelp
  • *
  • coral_tree, coral_mushroom, coral_claw: various coral types, random color.
  • *
  • coral: random coral structure. Require water to spawn.
  • *
  • sea_pickle
  • *
  • boulder: A rocky, mossy formation from a giant taiga biome. Doesn't update client properly, * needs relogging.
  • *
*
*/ public 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()); BlockValue.VectorLocator locator = BlockValue.locateVec(cc, lv, 1); if (!(Registry.SOUND_EVENT.containsId(soundName))) throw new InternalExpressionException("No such sound: "+soundName.getPath()); float volume = 1.0F; float pitch = 1.0F; 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(); } } 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, SoundCategory.PLAYERS, 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(); BlockValue.VectorLocator locator = BlockValue.locateVec(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 = 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 = getParticleData(particleName); BlockValue.VectorLocator pos1 = BlockValue.locateVec(cc, lv, 1); BlockValue.VectorLocator pos2 = BlockValue.locateVec(cc, lv, pos1.offset); int offset = pos2.offset; double density = (lv.size() > offset)? NumericValue.asNumber(lv.get(offset).evalValue(c)).getDouble():1.0; if (density <= 0) { throw new InternalExpressionException("Particle density should be positive"); } Value retval = new NumericValue(drawParticleLine(world, particle, pos1.vec, pos2.vec, density)); return (c_, t_) -> retval; }); this.expr.addLazyFunction("particle_rect", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; ServerWorld world = cc.s.getWorld(); String particleName = lv.get(0).evalValue(c).getString(); ParticleEffect particle = getParticleData(particleName); BlockValue.VectorLocator pos1 = BlockValue.locateVec(cc, lv, 1); BlockValue.VectorLocator pos2 = BlockValue.locateVec(cc, lv, pos1.offset); int offset = pos2.offset; double density = 1.0; if (lv.size() > offset) { density = NumericValue.asNumber(lv.get(offset).evalValue(c)).getDouble(); } if (density <= 0) { throw new InternalExpressionException("Particle density should be positive"); } Vec3d a = pos1.vec; Vec3d b = pos2.vec; double ax = min(a.x, b.x); double ay = min(a.y, b.y); double az = min(a.z, b.z); double bx = max(a.x, b.x); double by = max(a.y, b.y); double bz = max(a.z, b.z); int pc = 0; pc += drawParticleLine(world, particle, new Vec3d(ax, ay, az), new Vec3d(ax, by, az), density); pc += drawParticleLine(world, particle, new Vec3d(ax, by, az), new Vec3d(bx, by, az), density); pc += drawParticleLine(world, particle, new Vec3d(bx, by, az), new Vec3d(bx, ay, az), density); pc += drawParticleLine(world, particle, new Vec3d(bx, ay, az), new Vec3d(ax, ay, az), density); pc += drawParticleLine(world, particle, new Vec3d(ax, ay, bz), new Vec3d(ax, by, bz), density); pc += drawParticleLine(world, particle, new Vec3d(ax, by, bz), new Vec3d(bx, by, bz), density); pc += drawParticleLine(world, particle, new Vec3d(bx, by, bz), new Vec3d(bx, ay, bz), density); pc += drawParticleLine(world, particle, new Vec3d(bx, ay, bz), new Vec3d(ax, ay, bz), density); pc += drawParticleLine(world, particle, new Vec3d(ax, ay, az), new Vec3d(ax, ay, bz), density); pc += drawParticleLine(world, particle, new Vec3d(ax, by, az), new Vec3d(ax, by, bz), density); pc += drawParticleLine(world, particle, new Vec3d(bx, by, az), new Vec3d(bx, by, bz), density); pc += drawParticleLine(world, particle, new Vec3d(bx, ay, az), new Vec3d(bx, ay, bz), density); int particleCount = pc; return (c_, t_) -> new NumericValue(particleCount); }); //"overridden" native call that prints to stderr this.expr.addLazyFunction("print", 1, (c, t, lv) -> { ServerCommandSource s = ((CarpetContext)c).s; if (s.getEntity() instanceof PlayerEntity) { Messenger.m((PlayerEntity) s.getEntity(), "w "+ lv.get(0).evalValue(c).getString()); } else { Messenger.m(s, "w "); } return lv.get(0); // pass through for variables }); 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(), 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("game_tick", -1, (c, t, lv) -> { ServerCommandSource s = ((CarpetContext)c).s; ((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 Expression.ExitStatement(Value.NULL); return (cc, tt) -> Value.TRUE; }); this.expr.addLazyFunction("current_dimension", 0, (c, t, lv) -> { ServerCommandSource s = ((CarpetContext)c).s; Value retval = new StringValue(s.getWorld().dimension.getType().toString().replaceFirst("minecraft:","")); return (cc, tt) -> retval; }); /*this.expr.addLazyFunction("plop", 4, (c, t, lv) ->{ BlockValue.LocatorResult locator = BlockValue.fromParams((CarpetContext)c, lv, 0); Boolean res = FeatureGenerator.spawn(lv.get(locator.offset).evalValue(c).getString(), ((CarpetContext)c).s.getWorld(), locator.block.getPos()); if (res == null) return (c_, t_) -> Value.NULL; return (c_, t_) -> new NumericValue(res); });*/ } /** *

.

* @param expression expression * @param source source * @param origin origin */ public CarpetExpression(String expression, ServerCommandSource source, BlockPos origin) { this.origin = origin; this.source = source; this.expr = new Expression(expression); API_BlockManipulation(); API_EntityManipulation(); API_IteratingOverAreasOfBlocks(); API_AuxiliaryAspects(); } /** *

/script scan, /script fill and /script outline commands

*
*

These commands can be used to evaluate an expression over an area of blocks. They all need to have specified * the origin of the analyzed area (which is used as referenced (0,0,0), and two corners of an area to analyzed. If * you would want that the script block coordinates refer to the actual world coordinates, use origin of 0 0 0, * or if it doesn't matter, duplicating coordinates of one of the corners is the easiest way.

*

These commands, unlike raw /script run are limited by vanilla fill / clone command * limit of 32k blocks, which can be altered with carpet mod's own /carpet fillLimit command.

*

*

/script scan origin<x y z> corner<x y z> corner<x y z> expr

*

Evaluates expression for each point in the area and returns number of successes (result was positive). Since * the command by itself doesn't affect the area, the effects would be in side effects.

*

/script fill origin<x y z> corner<x y z> corner<x y z> "expr" <block> (? replace <replacement>)

*

Think of it as a regular fill command, that sets blocks based on whether a result of the command was successful. * Note that the expression is in quotes. Thankfully string constants in scarpet use single quotes. Can be used * to fill complex geometric shapes.

*

/script outline origin<x y z> corner<x y z> corner<x y z> "expr" <block> (? replace <replacement>)

*

Similar to fill command it evaluates an expression for each block in the area, but in this case setting blocks * where condition was true and any of the neighbouring blocks were evaluated negatively. This allows to create surface areas, * like sphere for example, without resorting to various rounding modes and tricks.

*

Here is an example of seven ways to draw a sphere of radius of 32 blocks around 0 100 0:

*
     * /script outline 0 100 0 -40 60 -40 40 140 40 "x*x+y*y+z*z <  32*32" white_stained_glass replace air
     * /script outline 0 100 0 -40 60 -40 40 140 40 "x*x+y*y+z*z <= 32*32" white_stained_glass replace air
     * /script outline 0 100 0 -40 60 -40 40 140 40 "x*x+y*y+z*z <  32.5*32.5" white_stained_glass replace air
     * /script fill    0 100 0 -40 60 -40 40 140 40 "floor(sqrt(x*x+y*y+z*z)) == 32" white_stained_glass replace air
     * /script fill    0 100 0 -40 60 -40 40 140 40 "round(sqrt(x*x+y*y+z*z)) == 32" white_stained_glass replace air
     * /script fill    0 100 0 -40 60 -40 40 140 40 "ceil(sqrt(x*x+y*y+z*z)) == 32" white_stained_glass replace air
     * /draw sphere 0 100 0 32 white_stained_glass replace air
     *
     * fluffy ball round(sqrt(x*x+y*y+z*z)-rand(abs(y)))==32
     *
     * 
*

The last method is the one that world edit is using (part of carpet mod). It turns out that the outline method with 32.5 radius, * fill method with round function and draw command are equivalent

*
* @param x . * @param y . * @param z . * @return . */ 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()); } catch (ArithmeticException ae) { throw new CarpetExpressionException("math doesn't compute... "+ae.getMessage()); } } /** *

/script run command

*
*

primary way to input commands. The command executes in the context, position, and dimension of the executing * player, commandblock, etc... The command receives 4 variables, x, y, z * and p indicating position and the executing entity of the command. * It is advisable to use /execute in ... at ... as ... run script run ... or similar, to simulate running * commands in a different scope

*
* @param pos . * @return . */ 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()); } catch (ArithmeticException ae) { throw new CarpetExpressionException("math doesn't compute... "+ae.getMessage()); } } /** *

/script invoke / invokepoint / invokearea, /script globals commands

*
*

invoke family of commands provide convenient way to invoke stored procedures (i.e. functions * that has been defined previously by any running script. To view current stored procedure set, * run /script globals, to define a new stored procedure, just run a /script run function(a,b) -> ( ... ) * command with your procedure once, and to forget a procedure, use undef function: * /script run undef('function')

*

*

/script invoke <fun> <args?> ...

*

Equivalent of running /script run fun(args, ...), but you get the benefit of getting the tab completion of the * command name, and lower permission level required to run these (since player is not capable of running any custom code * in this case, only this that has been executed before by an operator). Arguments will be checked for validity, and * you can only pass simple values as arguments (strings, numbers, or null value). Use quotes to include * whitespaces in argument strings.

*

Command will check provided arguments with required arguments (count) and fail if not enough or too much arguments * are provided. Operators defining functions are advised to use descriptive arguments names, as these will be visible * for invokers and form the base of understanding what each argument does.

*

invoke family of commands will tab complete any stored function that does not start with '_', * it will still allow to run procedures starting with '_' but not suggest them, and ban execution of any * hidden stored procedures, so ones that start with '__'. In case operator needs to use subroutines * for convenience and don't want to expose them to the invoke callers, they can use this mechanic.

*
     * /script run example_function(const, phrase, price) -> print(const+' '+phrase+' '+price)
     * /script invoke example_function pi costs 5
     * 
*

/script invokepoint <fun> <coords x y z> <args?> ...

*

It is equivalent to invoke except it assumes that the first three arguments are coordinates, and provides * coordinates tab completion, with looking at... mechanics for convenience. All other arguments are expected * at the end

*

/script invokearea <fun> <coords x y z> <coords x y z> <args?> ...

*

It is equivalent to invoke except it assumes that the first three arguments are one set of ccordinates, * followed by the second set of coordinates, providing tab completion, with looking at... mechanics for convenience, * followed by any other required arguments

*
* @return . */ public static String invokeGlobalFunctionCommand() { return "Yeah"; } }