diff --git a/src/main/java/carpet/mixins/ArmorStandEntity_scarpetMarkerMixin.java b/src/main/java/carpet/mixins/ArmorStandEntity_scarpetMarkerMixin.java index 34e5ea2d..ef3af312 100644 --- a/src/main/java/carpet/mixins/ArmorStandEntity_scarpetMarkerMixin.java +++ b/src/main/java/carpet/mixins/ArmorStandEntity_scarpetMarkerMixin.java @@ -1,8 +1,8 @@ package carpet.mixins; import carpet.CarpetServer; -import carpet.script.CarpetExpression; import carpet.CarpetSettings; +import carpet.script.api.Auxiliary; import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.decoration.ArmorStandEntity; @@ -32,9 +32,9 @@ public abstract class ArmorStandEntity_scarpetMarkerMixin extends LivingEntity { if ((CarpetSettings.scriptsAutoload || !CarpetSettings.commandScript.equals("false")) && !world.isClient) { - if (getScoreboardTags().contains(CarpetExpression.MARKER_STRING)) + if (getScoreboardTags().contains(Auxiliary.MARKER_STRING)) { - String prefix = CarpetExpression.MARKER_STRING+"_"; + String prefix = Auxiliary.MARKER_STRING+"_"; Optional owner = getScoreboardTags().stream().filter(s -> s.startsWith(prefix)).findFirst(); if (owner.isPresent()) { diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java index 73a21a73..2c847a57 100644 --- a/src/main/java/carpet/script/CarpetExpression.java +++ b/src/main/java/carpet/script/CarpetExpression.java @@ -1,179 +1,27 @@ package carpet.script; import carpet.CarpetServer; -import carpet.fakes.BiomeArrayInterface; -import carpet.fakes.ChunkGeneratorInterface; -import carpet.fakes.ChunkTicketManagerInterface; -import carpet.fakes.IngredientInterface; -import carpet.fakes.MinecraftServerInterface; -import carpet.fakes.RecipeManagerInterface; -import carpet.fakes.ServerChunkManagerInterface; -import carpet.fakes.SpawnHelperInnerInterface; -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.api.Apps; +import carpet.script.api.Auxiliary; +import carpet.script.api.BlockIterators; +import carpet.script.api.Entities; +import carpet.script.api.Inventories; +import carpet.script.api.Monitoring; +import carpet.script.api.Scoreboards; +import carpet.script.api.WorldAccess; 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.utils.WorldTools; 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.script.value.ValueConversions; -import carpet.utils.BlockInfo; -import carpet.utils.Messenger; -import carpet.utils.SpawnReporter; -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 it.unimi.dsi.fastutil.objects.Object2IntMap; -import net.minecraft.block.Block; -import net.minecraft.block.BlockState; -import net.minecraft.block.Blocks; -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.entity.SpawnGroup; -import net.minecraft.entity.ai.pathing.NavigationType; -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.ChunkDataS2CPacket; -import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket; -import net.minecraft.network.packet.s2c.play.PlaySoundIdS2CPacket; -import net.minecraft.command.argument.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.LivingEntity; -import net.minecraft.entity.SpawnReason; -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.recipe.AbstractCookingRecipe; -import net.minecraft.recipe.CuttingRecipe; -import net.minecraft.recipe.Recipe; -import net.minecraft.recipe.RecipeType; -import net.minecraft.recipe.ShapedRecipe; -import net.minecraft.recipe.ShapelessRecipe; -import net.minecraft.recipe.SpecialCraftingRecipe; -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.collection.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.MathHelper; -import net.minecraft.util.math.Vec3d; -import net.minecraft.util.math.Vec3i; -import net.minecraft.util.registry.BuiltinRegistries; -import net.minecraft.util.registry.DefaultedRegistry; -import net.minecraft.util.registry.Registry; -import net.minecraft.util.registry.RegistryKey; -import net.minecraft.world.gen.chunk.ChunkGenerator; -import net.minecraft.world.gen.feature.ConfiguredStructureFeature; -import net.minecraft.world.gen.feature.StructureFeature; -import net.minecraft.world.SpawnHelper; -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.gen.ChunkRandom; -import org.apache.commons.lang3.tuple.Pair; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -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 java.util.stream.Stream; - -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; @@ -182,3456 +30,6 @@ public class CarpetExpression public ServerCommandSource getSource() {return source;} public BlockPos getOrigin() {return origin;} - 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 final Map DIRECTION_MAP = Arrays.stream(Direction.values()).collect(Collectors.toMap(Direction::getName, (direction) -> direction)); - - private static 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 static 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 static 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 static > 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) - { - WorldChunk worldChunk = world.getChunkManager().getWorldChunk(pos.getX()>>4, pos.getZ()>>4, false); - if (worldChunk != null) - { - int vd = world.getServer().getPlayerManager().getViewDistance() * 16; - int vvd = vd * vd; - List nearbyPlayers = world.getPlayers(p -> pos.getSquaredDistance(p.getX(), pos.getY(), p.getZ(), true) < vvd); - if (!nearbyPlayers.isEmpty()) - { - ChunkDataS2CPacket packet = new ChunkDataS2CPacket(worldChunk, 65535); - ChunkPos chpos = new ChunkPos(pos); - nearbyPlayers.forEach(p -> p.networkHandler.sendPacket(packet)); - } - } - } - - private static 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 would 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.isBlockBreakingRestricted(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 static void BooYah(ChunkGenerator generator) - { - synchronized (generator) - { - ((ChunkGeneratorInterface)generator).initStrongholds(); - } - } - private static void API_BlockManipulation(Expression expression) - { - expression.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; - }); - - expression.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?, column_mode?) - expression.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.getInCircle( - 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; - } - int radius = NumericValue.asNumber(lv.get(locator.offset+0).evalValue(c)).getInt(); - if (radius < 0) return ListValue.lazyEmpty(); - Predicate condition = PointOfInterestType.ALWAYS_TRUE; - PointOfInterestStorage.OccupationStatus status = PointOfInterestStorage.OccupationStatus.ANY; - boolean inColumn = false; - 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; - } - 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 if (!("any".equals(statusString))) - throw new InternalExpressionException( - "Incorrect POI occupation status "+status+ " use `any`, " + "`occupied` or `available`" - ); - if (locator.offset + 3 < lv.size()) - { - inColumn = lv.get(locator.offset+3).evalValue(c, Context.BOOLEAN).getBoolean(); - } - } - } - Stream pois = inColumn? - store.getInSquare(condition, pos, radius, status): - store.getInCircle(condition, pos, radius, status); - Value ret = ListValue.wrap(pois.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?, - expression.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.getInSquare((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; - }); - - - expression.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"); - } - }); - - expression.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; - }); - - expression.addLazyFunction("solid", -1, (c, t, lv) -> - genericStateTest(c, "solid", lv, (s, p, w) -> new NumericValue(s.isSolidBlock(w, p)))); // isSimpleFullBlock - - expression.addLazyFunction("air", -1, (c, t, lv) -> - booleanStateTest(c, "air", lv, (s, p) -> s.isAir())); - - expression.addLazyFunction("liquid", -1, (c, t, lv) -> - booleanStateTest(c, "liquid", lv, (s, p) -> !s.getFluidState().isEmpty())); - - expression.addLazyFunction("flammable", -1, (c, t, lv) -> - booleanStateTest(c, "flammable", lv, (s, p) -> s.getMaterial().isBurnable())); - - expression.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 - - expression.addLazyFunction("emitted_light", -1, (c, t, lv) -> - genericStateTest(c, "emitted_light", lv, (s, p, w) -> new NumericValue(s.getLuminance()))); - - expression.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))))); - - expression.addLazyFunction("block_light", -1, (c, t, lv) -> - genericStateTest(c, "block_light", lv, (s, p, w) -> new NumericValue(w.getLightLevel(LightType.BLOCK, p)))); - - expression.addLazyFunction("sky_light", -1, (c, t, lv) -> - genericStateTest(c, "sky_light", lv, (s, p, w) -> new NumericValue(w.getLightLevel(LightType.SKY, p)))); - - expression.addLazyFunction("see_sky", -1, (c, t, lv) -> - genericStateTest(c, "see_sky", lv, (s, p, w) -> new NumericValue(w.isSkyVisible(p)))); - - expression.addLazyFunction("brightness", -1, (c, t, lv) -> - genericStateTest(c, "brightness", lv, (s, p, w) -> new NumericValue(w.getBrightness(p)))); - - expression.addLazyFunction("hardness", -1, (c, t, lv) -> - genericStateTest(c, "hardness", lv, (s, p, w) -> new NumericValue(s.getHardness(w, p)))); - - expression.addLazyFunction("blast_resistance", -1, (c, t, lv) -> - genericStateTest(c, "blast_resistance", lv, (s, p, w) -> new NumericValue(s.getBlock().getBlastResistance()))); - - expression.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.getSlimeRandom( - chunkPos.x, chunkPos.z, - ((CarpetContext)c).s.getWorld().getSeed(), - 987234911L - ).nextInt(10) == 0); - return (_c, _t) -> ret; - }); - - - expression.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); - }); - - expression.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 - expression.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; - }); - - expression.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; - }); - - expression.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; - }); - - expression.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; - }); - - expression.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; - }); - - expression.addLazyFunction("suffocates", -1, (c, t, lv) -> - genericStateTest(c, "suffocates", lv, (s, p, w) -> new NumericValue(s.shouldSuffocate(w, p)))); // canSuffocate - - expression.addLazyFunction("power", -1, (c, t, lv) -> - genericStateTest(c, "power", lv, (s, p, w) -> new NumericValue(w.getReceivedRedstonePower(p)))); - - expression.addLazyFunction("ticks_randomly", -1, (c, t, lv) -> - booleanStateTest(c, "ticks_randomly", lv, (s, p) -> s.hasRandomTicks())); - - expression.addLazyFunction("update", -1, (c, t, lv) -> - booleanStateTest(c, "update", lv, (s, p) -> - { - ((CarpetContext) c).s.getWorld().updateNeighbor(p, s.getBlock(), p); - return true; - })); - - expression.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; - })); - - expression.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; - })); - - expression.addLazyFunction("without_updates", 1, (c, t, lv) -> - { - boolean previous = CarpetSettings.impendingFillSkipUpdates; - if (previous) return lv.get(0); - Value [] result = new Value[]{Value.NULL}; - ((CarpetContext)c).s.getMinecraftServer().submitAndJoin( () -> - { - try - { - CarpetSettings.impendingFillSkipUpdates = true; - result[0] = lv.get(0).evalValue(c, t); - } - finally - { - CarpetSettings.impendingFillSkipUpdates = previous; - } - }); - return (cc, tt) -> result[0]; - }); - - expression.addLazyFunction("set", -1, (c, t, lv) -> - { - CarpetContext cc = (CarpetContext)c; - ServerWorld world = cc.s.getWorld(); - 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()); - CompoundTag data = null; - if (lv.size() > sourceLocator.offset) - { - List args = new ArrayList<>(); - for (int i = sourceLocator.offset, m = lv.size(); i < m; i++) - { - args.add(lv.get(i).evalValue(c)); - } - if (args.get(0) instanceof ListValue) - { - if (args.size() == 2) - { - Value dataValue = NBTSerializableValue.fromValue( args.get(1)); - if (dataValue instanceof NBTSerializableValue) - { - data = ((NBTSerializableValue) dataValue).getCompoundTag(); - } - } - args = ((ListValue) args.get(0)).getItems(); - } - else - { - if ((args.size() & 1) == 1) - { - Value dataValue = NBTSerializableValue.fromValue( args.get(args.size()-1)); - if (dataValue instanceof NBTSerializableValue) - { - data = ((NBTSerializableValue) dataValue).getCompoundTag(); - } - } - } - StateManager states = sourceBlockState.getBlock().getStateManager(); - for (int i = 0; i < args.size()-1; i += 2) - { - String paramString = args.get(i).getString(); - Property property = states.getProperty(paramString); - if (property == null) - throw new InternalExpressionException("Property " + paramString + " doesn't apply to " + sourceLocator.block.getString()); - String paramValue = args.get(i + 1).getString(); - sourceBlockState = setProperty(property, paramString, paramValue, sourceBlockState); - } - } - - if (data == null) data = sourceLocator.block.getData(); - CompoundTag finalData = data; - - 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 (finalData != null) - { - BlockEntity be = world.getBlockEntity(targetPos); - if (be != null) - { - CompoundTag destTag = finalData.copy(); - destTag.putInt("x", targetPos.getX()); - destTag.putInt("y", targetPos.getY()); - destTag.putInt("z", targetPos.getZ()); - be.fromTag(finalSourceBlockState, destTag); - be.markDirty(); - } - } - }); - Value retval = new BlockValue(finalSourceBlockState, world, targetLocator.block.getPos()); - return (c_, t_) -> retval; - }); - - expression.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.syncWorldEvent(null, 2001, where, Block.getRawIdFromState(state)); - - boolean toolBroke = false; - boolean dropLoot = true; - if (playerBreak) - { - boolean isUsingEffectiveTool = !state.isToolRequired() || 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; - - }); - - expression.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 == Blocks.BEDROCK || block == Blocks.BARRIER) && player.interactionManager.isSurvivalLike())) - success = tryBreakBlock_copy_from_ServerPlayerInteractionManager(player, where); - if (success) - world.syncWorldEvent(null, 2001, where, Block.getRawIdFromState(state)); - return success ? LazyValue.TRUE : LazyValue.FALSE; - }); - - // TODO rename to use_item - expression.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; - }); - - expression.addLazyFunction("blocks_movement", -1, (c, t, lv) -> - booleanStateTest(c, "blocks_movement", lv, (s, p) -> - !s.canPathfindThrough(((CarpetContext) c).s.getWorld(), p, NavigationType.LAND))); - - expression.addLazyFunction("block_sound", -1, (c, t, lv) -> - stateStringQuery(c, "block_sound", lv, (s, p) -> - BlockInfo.soundName.get(s.getSoundGroup()))); - - expression.addLazyFunction("material", -1, (c, t, lv) -> - stateStringQuery(c, "material", lv, (s, p) -> - BlockInfo.materialName.get(s.getMaterial()))); - - expression.addLazyFunction("map_colour", -1, (c, t, lv) -> - stateStringQuery(c, "map_colour", lv, (s, p) -> - BlockInfo.mapColourName.get(s.getTopMaterialColor(((CarpetContext)c).s.getWorld(), p)))); - - expression.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; - }); - - expression.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; - }); - - expression.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); - // in locatebiome - Identifier biomeId = cc.s.getMinecraftServer().getRegistryManager().get(Registry.BIOME_KEY).getId(biome); - Value res = new StringValue(NBTSerializableValue.nameFromRegistryId(biomeId)); - return (_c, _t) -> res; - }); - - expression.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(); - // from locatebiome command code - Biome biome = cc.s.getMinecraftServer().getRegistryManager().get(Registry.BIOME_KEY).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; - }); - - expression.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; - }); - - expression.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, LongSet> references = world.getChunk(pos.getX() >> 4, pos.getZ() >> 4, ChunkStatus.STRUCTURE_REFERENCES).getStructureReferences(); - if (lv.size() == locator.offset) - { - //CarpetSettings.LOG.error(references.keySet().stream().collect(Collectors.joining(","))); - List referenceList = references.entrySet().stream(). - filter(e -> e.getValue()!= null && !e.getValue().isEmpty()). - map(e -> new StringValue(NBTSerializableValue.nameFromRegistryId(Registry.STRUCTURE_FEATURE.getId(e.getKey())))).collect(Collectors.toList()); - return (_c, _t ) -> ListValue.wrap(referenceList); - } - String simpleStructureName = lv.get(locator.offset).evalValue(c).getString().toLowerCase(Locale.ROOT); - //CarpetSettings.LOG.error(FeatureGenerator.featureToStructure.keySet().stream().collect(Collectors.joining(","))); - //CarpetSettings.LOG.error(FeatureGenerator.featureToStructure.values().stream().collect(Collectors.joining(","))); - StructureFeature structureName = Registry.STRUCTURE_FEATURE.get(new Identifier(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; - }); - - expression.addLazyFunction("structure_eligibility", -1, (c, t, lv) -> - {// TODO rename structureName to class - CarpetContext cc = (CarpetContext)c; - BlockArgument locator = BlockArgument.findIn(cc, lv, 0); - - ServerWorld world = cc.s.getWorld(); - - // well, because - BooYah(world.getChunkManager().getChunkGenerator()); - - 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(); - structure = Registry.STRUCTURE_FEATURE.get(new Identifier(reqString)); - if (structure == null) throw new InternalExpressionException("Unknown structure: " + reqString); - } - 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 : StructureFeature.STRUCTURES.values()) - { - StructureStart start = FeatureGenerator.shouldStructureStartAt(world, pos, str, needSize); - if (start == null) continue; - - Value key = new StringValue(NBTSerializableValue.nameFromRegistryId(Registry.STRUCTURE_FEATURE.getId(str))); - ret.put(key, (!needSize)?Value.NULL:structureToValue(start)); - } - Value retMap = MapValue.wrap(ret); - return (_c, _t) -> retMap; - }); - - expression.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, StructureStart> 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, StructureStart> entry : structures.entrySet()) - { - StructureStart start = entry.getValue(); - if (start == StructureStart.DEFAULT) - continue; - BlockBox box = start.getBoundingBox(); - structureList.put( - new StringValue(NBTSerializableValue.nameFromRegistryId(Registry.STRUCTURE_FEATURE.getId(entry.getKey()))), - 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(Registry.STRUCTURE_FEATURE.get(new Identifier(structureName))); - Value ret = structureToValue(start); - return (_c, _t) -> ret; - }); - - expression.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); - ConfiguredStructureFeature configuredStructure = FeatureGenerator.resolveConfiguredStructure(structureName, world, pos); - if (configuredStructure == 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, StructureStart> structures = world.getChunk(pos).getStructureStarts(); - if (lv.size() == locator.offset + 1) - { - Boolean res = FeatureGenerator.plopGrid(configuredStructure, ((CarpetContext) c).s.getWorld(), locator.block.getPos()); - //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 - { - StructureFeature structure = configuredStructure.feature; - if (!structures.containsKey(structure)) - { - return; - } - StructureStart start = structures.get(structure); - 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, LongSet> references = - world.getChunk(chpos.getStartPos()).getStructureReferences(); - if (references.containsKey(structure) && references.get(structure) != null) - references.get(structure).remove(structureChunkPos.toLong()); - } - } - structures.remove(structure); - 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; - }); - - expression.addLazyFunction("custom_dimension", -1, (c, t, lv) -> - { - if (lv.size() == 0) throw new InternalExpressionException("'custom_dimension' requires at least one argument"); - CarpetContext cc = (CarpetContext)c; - String worldKey = lv.get(0).evalValue(c).getString(); - - Long seed = null; - if (lv.size() > 1) - { - String seedKey = lv.get(1).evalValue(c).getString(); - try - { - seed = Long.parseLong(seedKey); - } - catch (NumberFormatException ignored) - { - throw new InternalExpressionException("Incorrect number format for seed: " + seedKey); - } - } - - boolean success = WorldTools.createWorld(cc.s.getMinecraftServer(), worldKey, seed); - if (!success) return LazyValue.FALSE; - CarpetServer.settingsManager.notifyPlayersCommandsChanged(); - return LazyValue.TRUE; - }); - - - expression.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.getStartPos(), 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]; - }); - - expression.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; - }); - - expression.addLazyFunction("spawn_potential", -1, (c, t, lv) -> - { - CarpetContext cc = (CarpetContext)c; - BlockArgument locator = BlockArgument.findIn(cc, lv, 0); - BlockPos pos = locator.block.getPos(); - double required_charge = 1; - if (lv.size() > locator.offset) - required_charge = NumericValue.asNumber(lv.get(locator.offset).evalValue(c)).getDouble(); - SpawnHelper.Info charger = cc.s.getWorld().getChunkManager().getSpawnInfo(); - if (charger == null) return LazyValue.NULL; - Value ret = new NumericValue( - ((SpawnHelperInnerInterface)charger).getPotentialCalculator(). - calculate(pos, required_charge ) - ); - return (_c, _t) -> ret; - }); - - expression.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) // portal - cc.s.getWorld().getChunkManager().addTicket(ChunkTicketType.PORTAL, target, radius, pos); - else if (ticket == ChunkTicketType.POST_TELEPORT) // 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 final static Map> ticketTypes = new HashMap>(){{ - put("portal", ChunkTicketType.PORTAL); - put("teleport", ChunkTicketType.POST_TELEPORT); - put("unknown", ChunkTicketType.UNKNOWN); // 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 static void API_Scoreboard(Expression expression) - { - // scoreboard(player,'objective') - // scoreboard(player, objective, newValue) - expression.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; - }); - - expression.addLazyFunction("scoreboard_remove", -1, (c, t, lv)-> { - if (lv.size()==0) throw new InternalExpressionException("'scoreboard_remove' requires at least one parameter"); - CarpetContext cc = (CarpetContext)c; - Scoreboard scoreboard = cc.s.getMinecraftServer().getScoreboard(); - String objectiveName = lv.get(0).evalValue(c).getString(); - ScoreboardObjective objective = scoreboard.getObjective(objectiveName); - if (objective == null) - return LazyValue.FALSE; - if (lv.size() == 1) - { - 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') - - expression.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; - }); - - expression.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 static void API_InventoryManipulation(Expression expression) - { - expression.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; - }); - - expression.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; - }); - - expression.addLazyFunction("recipe_data", -1, (c, t, lv) -> - { - CarpetContext cc = (CarpetContext)c; - if (lv.size() < 1) throw new InternalExpressionException("'recipe_data' requires at least one argument"); - String recipeName = lv.get(0).evalValue(c).getString(); - RecipeType type = RecipeType.CRAFTING; - if (lv.size() > 1) - { - String recipeType = lv.get(1).evalValue(c).getString(); - try - { - type = Registry.RECIPE_TYPE.get(new Identifier(recipeType)); - } - catch (InvalidIdentifierException ignored) - { - throw new InternalExpressionException("Unknown crafting category: " + recipeType); - } - } - List> recipes; - try - { - recipes = ((RecipeManagerInterface) cc.s.getMinecraftServer().getRecipeManager()).getAllMatching(type, new Identifier(recipeName)); - } - catch (InvalidIdentifierException ignored) - { - return LazyValue.NULL; - } - if (recipes.isEmpty()) - return LazyValue.NULL; - List recipesOutput = new ArrayList<>(); - for (Recipe recipe: recipes) - { - ItemStack result = recipe.getOutput(); - List ingredientValue = new ArrayList<>(); - recipe.getPreviewInputs().forEach( - ingredient -> - { - // I am flattening ingredient lists per slot. - // consider recipe_data('wooden_sword','crafting') and ('iron_nugget', 'blasting') and notice difference - // in depths of lists. - List> stacks = ((IngredientInterface) (Object) ingredient).getRecipeStacks(); - if (stacks.isEmpty()) - { - ingredientValue.add(Value.NULL); - } - else - { - List alternatives = new ArrayList<>(); - stacks.forEach(col -> col.stream().map(ListValue::fromItemStack).forEach(alternatives::add)); - ingredientValue.add(ListValue.wrap(alternatives)); - } - } - ); - Value recipeSpec; - if (recipe instanceof ShapedRecipe) - { - recipeSpec = ListValue.of( - new StringValue("shaped"), - new NumericValue(((ShapedRecipe) recipe).getWidth()), - new NumericValue(((ShapedRecipe) recipe).getHeight()) - ); - } - else if (recipe instanceof ShapelessRecipe) - { - recipeSpec = ListValue.of(new StringValue("shapeless")); - } - else if (recipe instanceof AbstractCookingRecipe) - { - recipeSpec = ListValue.of( - new StringValue("smelting"), - new NumericValue(((AbstractCookingRecipe) recipe).getCookTime()), - new NumericValue(((AbstractCookingRecipe) recipe).getExperience()) - ); - } - else if (recipe instanceof CuttingRecipe) - { - recipeSpec = ListValue.of(new StringValue("cutting")); - } - else if (recipe instanceof SpecialCraftingRecipe) - { - recipeSpec = ListValue.of(new StringValue("special")); - } - else - { - recipeSpec = ListValue.of(new StringValue("custom")); - } - - recipesOutput.add(ListValue.of(ListValue.fromItemStack(result), ListValue.wrap(ingredientValue), recipeSpec)); - } - Value ret = ListValue.wrap(recipesOutput); - return (_c, _t) -> ret; - }); - - expression.addLazyFunction("crafting_remaining_item", 1, (c, t, lv) -> - { - String itemStr = lv.get(0).evalValue(c).getString(); - Item item; - try - { - Identifier id = new Identifier(itemStr); - item = Registry.ITEM.get(id); - if (item == Items.AIR && !id.getPath().equalsIgnoreCase("air")) - throw new InvalidIdentifierException("boo"); - } - catch (InvalidIdentifierException ignored) - { - throw new InternalExpressionException("Incorrect item: "+itemStr); - } - if (!item.hasRecipeRemainder()) return LazyValue.NULL; - Value ret = new StringValue(NBTSerializableValue.nameFromRegistryId(Registry.ITEM.getId(item.getRecipeRemainder()))); - return (_c, _t ) -> ret; - }); - - - expression.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.size()); - return (_c, _t) -> res; - }); - - expression.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.isEmpty()); - return (_c, _t) -> res; - }); - - //inventory_get(, ) -> item_triple - expression.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.size(); i < maxi; i++) - { - fullInventory.add(ListValue.fromItemStack(inventoryLocator.inventory.getStack(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.size()) - return (_c, _t) -> Value.NULL; - Value res = ListValue.fromItemStack(inventoryLocator.inventory.getStack(slot)); - return (_c, _t) -> res; - }); - - //inventory_set(, , , , ) - expression.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.size()) - 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.removeStack(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.getStack(slot); - ItemStack newStack = previousStack.copy(); - newStack.setCount(count); - inventoryLocator.inventory.setStack(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.getStack(slot); - try - { - inventoryLocator.inventory.setStack(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 - expression.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.size(); i < maxi; i++) - { - ItemStack stack = inventoryLocator.inventory.getStack(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 - expression.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.containsAny(Sets.newHashSet(searchItem.getItem())))) - || (inventoryLocator.inventory.count(searchItem.getItem()) < amount)) - { - return (_c, _t) -> Value.FALSE; - } - for (int i = 0, maxi = inventoryLocator.inventory.size(); i < maxi; i++) - { - ItemStack stack = inventoryLocator.inventory.getStack(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.setStack(i, stack); - syncPlayerInventory(inventoryLocator, i); - return (_c, _t) -> Value.TRUE; - } - else - { - inventoryLocator.inventory.removeStack(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 - expression.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.size()) - 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.getStack(slot); - if (stack == null || stack.isEmpty()) - return (_c, _t) -> Value.ZERO; - if (amount == 0) - amount = stack.getCount(); - ItemStack droppedStack = inventoryLocator.inventory.removeStack(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 = Vec3d.ofCenter(inventoryLocator.position); //pos+0.5v - item = new ItemEntity(cc.s.getWorld(), point.x, point.y, point.z, droppedStack); - item.setToDefaultPickupDelay(); - cc.s.getWorld().spawnEntity(item); - } - Value res = new NumericValue(item.getStack().getCount()); - return (_c, _t) -> res; - }); - } - private static void syncPlayerInventory(NBTSerializableValue.InventoryLocator inventory, int int_1) - { - if (inventory.owner instanceof ServerPlayerEntity && !inventory.isEnder) - { - ServerPlayerEntity player = (ServerPlayerEntity) inventory.owner; - player.networkHandler.sendPacket(new ScreenHandlerSlotUpdateS2CPacket( - -2, - int_1, - inventory.inventory.getStack(int_1) - )); - } - } - - private static void API_EntityManipulation(Expression expression) - { - expression.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; - }); - - expression.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()); - hasTag = true; - tag = v.getCompoundTag(); - - } - tag.putString("id", entityId.toString()); - Vec3d vec3d = position.vec; - - 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()), SpawnReason.COMMAND, null, null); - } - Value res = new EntityValue(entity_1); - return (_c, _t) -> res; - } - }); - - expression.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); - }); - - expression.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().getEntitiesByType(pair.getKey(), pair.getValue()); - Value retval = ListValue.wrap(entityList.stream().map(EntityValue::new).collect(Collectors.toList())); - return (_c, _t ) -> retval; - }); - - expression.addLazyFunction("entity_area", 7, (c, t, lv) -> { - Vec3d center = new Vec3d( - 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, 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().getEntitiesByType((EntityType) pair.getKey(), area, pair.getValue()); - Value retval = ListValue.wrap(entityList.stream().map(EntityValue::new).collect(Collectors.toList())); - return (_c, _t ) -> retval; - }); - - expression.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); - }); - - expression.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 - expression.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 - expression.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(expression.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 static void API_IteratingOverAreasOfBlocks(Expression expression) - { - expression.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); - }); - - expression.addLazyFunction("volume", -1, (c, t, lv) -> - { - CarpetContext cc = (CarpetContext)c; - - BlockArgument pos1Locator = BlockArgument.findIn(cc, lv, 0); - BlockArgument pos2Locator = BlockArgument.findIn(cc, lv, pos1Locator.offset); - BlockPos pos1 = pos1Locator.block.getPos(); - BlockPos pos2 = pos2Locator.block.getPos(); - - int x1 = pos1.getX(); - int y1 = pos1.getY(); - int z1 = pos1.getZ(); - int x2 = pos2.getX(); - int y2 = pos2.getY(); - int z2 = pos2.getZ(); - int minx = min(x1, x2); - int miny = min(y1, y2); - int minz = min(z1, z2); - int maxx = max(x1, x2); - int maxy = max(y1, y2); - int maxz = max(z1, z2); - LazyValue expr = lv.get(pos2Locator.offset); - - //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); - }); - - expression.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; - }); - - expression.addLazyFunction("rect", -1, (c, t, lv)-> - { - CarpetContext cc = (CarpetContext) c; - int cx, cy, cz; - int sminx, sminy, sminz; - int smaxx, smaxy, smaxz; - BlockArgument cposLocator = BlockArgument.findIn(cc, lv, 0); - BlockPos cpos = cposLocator.block.getPos(); - cx = cpos.getX(); - cy = cpos.getY(); - cz = cpos.getZ(); - if (lv.size() > cposLocator.offset) - { - Vector3Argument diffLocator = Vector3Argument.findIn(cc, lv, cposLocator.offset); - if (diffLocator.fromBlock) - { - sminx = MathHelper.floor(abs(diffLocator.vec.x - cx)); - sminy = MathHelper.floor(abs(diffLocator.vec.y - cx)); - sminz = MathHelper.floor(abs(diffLocator.vec.z - cx)); - } - else - { - sminx = MathHelper.floor(abs(diffLocator.vec.x)); - sminy = MathHelper.floor(abs(diffLocator.vec.y)); - sminz = MathHelper.floor(abs(diffLocator.vec.z)); - } - if (lv.size() > diffLocator.offset) - { - Vector3Argument posDiff = Vector3Argument.findIn(cc, lv, diffLocator.offset); - if (posDiff.fromBlock) - { - smaxx = MathHelper.floor(abs(posDiff.vec.x - cx)); - smaxy = MathHelper.floor(abs(posDiff.vec.y - cx)); - smaxz = MathHelper.floor(abs(posDiff.vec.z - cx)); - } - else - { - smaxx = MathHelper.floor(abs(posDiff.vec.x)); - smaxy = MathHelper.floor(abs(posDiff.vec.y)); - smaxz = MathHelper.floor(abs(posDiff.vec.z)); - } - } - else - { - smaxx = sminx; - smaxy = sminy; - smaxz = sminz; - } - } - else - { - sminx = 1; - sminy = 1; - sminz = 1; - smaxx = 1; - smaxy = 1; - smaxz = 1; - } - - 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); - } - }; - }); - - expression.addLazyFunction("diamond", -1, (c, t, lv)-> - { - CarpetContext cc = (CarpetContext)c; - - BlockArgument cposLocator=BlockArgument.findIn((CarpetContext)c,lv,0); - BlockPos cpos = cposLocator.block.getPos(); - - int cx; - int cy; - int cz; - int width; - int height; - try - { - cx = cpos.getX(); - cy = cpos.getY(); - cz = cpos.getZ(); - - if (lv.size()==cposLocator.offset) - { - 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()==1+cposLocator.offset) - { - width = (int) ((NumericValue) lv.get(cposLocator.offset).evalValue(c)).getLong(); - height = 0; - } - else if(lv.size()==2+cposLocator.offset) - { - width = (int) ((NumericValue) lv.get(cposLocator.offset).evalValue(c)).getLong(); - height = (int) ((NumericValue) lv.get(cposLocator.offset+1).evalValue(c)).getLong(); - } else{ - throw new InternalExpressionException("Incorrect number of arguments for 'diamond'"); - } - } - 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 final Map mixerMap = Arrays.stream(SoundCategory.values()).collect(Collectors.toMap(SoundCategory::getName, k -> k)); - - private static void API_Interapperability(Expression expression) - { - //todo doc and test all - expression.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; - }); - - expression.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 static void API_AuxiliaryAspects(Expression expression) - { - expression.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); - }); - - expression.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; - }); - - expression.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; - }); - - expression.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 - expression.alias("particle_rect", "particle_box"); - - - expression.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; - }); - - expression.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()); - double yoffset; - if (targetBlock == null && name.isEmpty()) - { - yoffset = 0.0; - } - else if (!interactable && targetBlock == null) - { - yoffset = -0.41; - } - else - { - if (targetBlock==null) - { - yoffset = -armorstand.getHeight()-0.41; - } - else - { - yoffset = -armorstand.getHeight()+0.3; - } - } - armorstand.refreshPositionAndAngles( - pointLocator.vec.x, - //pointLocator.vec.y - ((!interactable && targetBlock == null)?0.41f:((targetBlock==null)?(armorstand.getHeight()+0.41):(armorstand.getHeight()-0.3))), - pointLocator.vec.y + yoffset, - 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; - }); - - expression.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().getEntitiesByType(EntityType.ARMOR_STAND, (as) -> as.getScoreboardTags().contains(markerName))) - { - total ++; - e.remove(); - } - int finalTotal = total; - return (_cc, _tt) -> new NumericValue(finalTotal); - }); - - expression.addLazyFunction("nbt", 1, (c, t, lv) -> { - Value ret = NBTSerializableValue.fromValue(lv.get(0).evalValue(c)); - return (cc, tt) -> ret; - }); - - expression.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; - }); - - expression.addLazyFunction("parse_nbt", 1, (c, t, lv) -> { - Value v = lv.get(0).evalValue(c); - if (v instanceof NBTSerializableValue) - { - Value parsed = ((NBTSerializableValue) v).toValue(); - return (cc, tt) -> parsed; - } - NBTSerializableValue ret = NBTSerializableValue.parseString(v.getString()); - if (ret == null) - return LazyValue.NULL; - Value parsed = ret.toValue(); - return (cc, tt) -> parsed; - }); - - expression.addLazyFunction("encode_nbt", -1, (c, t, lv) -> { - int argSize = lv.size(); - if (argSize==0 || argSize > 2) throw new InternalExpressionException("'encode_nbt' requires 1 or 2 parameters"); - Value v = lv.get(0).evalValue(c); - boolean force = (argSize > 1) && lv.get(1).evalValue(c).getBoolean(); - Tag tag; - try - { - tag = v.toTag(force); - } - catch (NBTSerializableValue.IncompatibleTypeException ignored) - { - throw new InternalExpressionException("cannot reliably encode to a tag the value of '"+ignored.val.getPrettyString()+"'"); - } - Value tagValue = new NBTSerializableValue(tag); - return (cc, tt) -> tagValue; - }); - - //"overridden" native call that prints to stderr - expression.addLazyFunction("print", -1, (c, t, lv) -> - { - if (lv.size() == 0 || lv.size() > 2) throw new InternalExpressionException("'print' takes one or two arguments"); - ServerCommandSource s = ((CarpetContext)c).s; - Value res = lv.get(0).evalValue(c); - if (lv.size() == 2) - { - ServerPlayerEntity player = EntityValue.getPlayerByValue(s.getMinecraftServer(), res); - s = player.getCommandSource(); - res = lv.get(1).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()); - } - } - Value finalRes = res; - return (_c, _t) -> finalRes; // pass through for variables - }); - - expression.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 - expression.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; - }); - - expression.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. - }); - - expression.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; - try - { - 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; - } - catch (Exception ignored) {} - return LazyValue.NULL; - }); - - expression.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; - }); - - expression.addLazyFunction("tick_time", 0, (c, t, lv) -> - { - Value time = new NumericValue(((CarpetContext) c).s.getMinecraftServer().getTicks()); - return (cc, tt) -> time; - }); - - expression.addLazyFunction("world_time", 0, (c, t, lv) -> - { - Value time = new NumericValue(((CarpetContext) c).s.getWorld().getTime()); - return (cc, tt) -> time; - }); - - expression.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);// setTimeOfDay(newTime); - } - return (cc, tt) -> time; - }); - - expression.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; - }); - - - expression.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; - }); - - expression.addLazyFunction("seed", -1, (c, t, lv) -> { - ServerCommandSource s = ((CarpetContext)c).s; - Value ret = new NumericValue(s.getWorld().getSeed()); - return (cc, tt) -> ret; - }); - - expression.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(); - ((ThreadedAnvilChunkStorageInterface) world.getChunkManager().threadedAnvilChunkStorage).relightChunk(new ChunkPos(pos)); - forceChunkUpdate(pos, world); - return LazyValue.TRUE; - }); - - expression.addLazyFunction("current_dimension", 0, (c, t, lv) -> { - ServerCommandSource s = ((CarpetContext)c).s; - Value retval = ValueConversions.dimName(s.getWorld()); - return (cc, tt) -> retval; - }); - - expression.addLazyFunction("view_distance", 0, (c, t, lv) -> { - ServerCommandSource s = ((CarpetContext)c).s; - Value retval = new NumericValue(s.getMinecraftServer().getPlayerManager().getViewDistance()); - return (cc, tt) -> retval; - }); - - expression.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(World.NETHER)); //nether - break; - case "end": - case "the_end": - innerSource = outerSource.withWorld(outerSource.getMinecraftServer().getWorld(World.END)); //end - break; - case "overworld": - case "over_world": - innerSource = outerSource.withWorld(outerSource.getMinecraftServer().getWorld(World.OVERWORLD)); //ow - break; - default: - RegistryKey dim = null; - Identifier id = new Identifier(dimString); - // not using RegistryKey.of since that one creates on check - for (RegistryKey world : (outerSource.getMinecraftServer().getWorldRegistryKeys())) - { - if (id.equals(world.getValue())) - { - dim = world; - break; - } - } - if (dim == null) - throw new InternalExpressionException("Incorrect dimension string: "+dimString); - innerSource = outerSource.withWorld(outerSource.getMinecraftServer().getWorld(dim)); - } - }; - if (innerSource.getWorld() == outerSource.getWorld()) return lv.get(1); - Context newCtx = c.recreate(); - ((CarpetContext) newCtx).s = innerSource; - newCtx.variables = c.variables; - Value retval = lv.get(1).evalValue(newCtx); - return (cc, tt) -> retval; - }); - - expression.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.plop(what, ((CarpetContext) c).s.getWorld(), locator.block.getPos()); - - //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; - }); - - expression.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, expression.module, lv, 1, true, true); - - CarpetServer.scriptServer.events.scheduleCall( - (CarpetContext) c, - functionArgument.function, - functionArgument.resolveArgs(c, Context.NONE), - delay - ); - return (c_, t_) -> Value.TRUE; - }); - - expression.addLazyFunction("logger", -1, (c, t, lv) -> - { - //CarpetSettings.LOG.error("Standard Structures:"); - //CarpetSettings.LOG.error(Registry.STRUCTURE_FEATURE.getIds().stream().map(i -> "`'"+NBTSerializableValue.nameFromRegistryId(i)+"'`").sorted(). collect(Collectors.joining(", "))); - //CarpetSettings.LOG.error(""); - //CarpetSettings.LOG.error("Configured Structures:"); - //CarpetSettings.LOG.error(BuiltinRegistries.CONFIGURED_STRUCTURE_FEATURE.getIds().stream().map(i -> "`'"+NBTSerializableValue.nameFromRegistryId(i)+"'`").sorted().collect(Collectors.joining(", "))); - //CarpetSettings.LOG.error(""); - //CarpetSettings.LOG.error("Features:"); - //CarpetSettings.LOG.error(Registry.FEATURE.getIds().stream().map(i -> "`'"+NBTSerializableValue.nameFromRegistryId(i)+"'`").sorted().collect(Collectors.joining(", "))); - //CarpetSettings.LOG.error(""); - //CarpetSettings.LOG.error("Configured Features:"); - //CarpetSettings.LOG.error(BuiltinRegistries.CONFIGURED_FEATURE.getIds().stream().map(i -> "`'"+NBTSerializableValue.nameFromRegistryId(i)+"'`").sorted().collect(Collectors.joining(", "))); - //CarpetSettings.LOG.error(""); - //CarpetSettings.LOG.error("Custom:"); - //CarpetSettings.LOG.error(FeatureGenerator.featureMap.keySet().stream().map(i -> "`'"+i+"'`").sorted().collect(Collectors.joining(", "))); - - //CarpetSettings.LOG.error(Registry.ENTITY_TYPE.getIds().stream().sorted().map(ValueConversions::simplify).collect(Collectors.joining("`, `"))); - - Value res; - - if(lv.size()==1) - { - res = lv.get(0).evalValue(c); - CarpetSettings.LOG.info(res.getString()); - } - else if(lv.size()==2) - { - String level = lv.get(0).evalValue(c).getString().toLowerCase(Locale.ROOT); - res = lv.get(1).evalValue(c); - switch(level){ - case "error": CarpetSettings.LOG.error(res.getString()); break; - case "warn": CarpetSettings.LOG.warn(res.getString()); break; - case "debug": CarpetSettings.LOG.debug(res.getString()); break; - case "fatal": CarpetSettings.LOG.fatal(res.getString()); break; - case "info": CarpetSettings.LOG.info(res.getString()); break; - default: throw new InternalExpressionException("Unknown log level for 'logger': "+level); - } - } - else throw new InternalExpressionException("logger takes 1 or 2 arguments"); - - return (_c, _t) -> res; // pass through for variables - }); - - expression.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; - }); - - expression.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; - }); - - expression.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 - - expression.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; - }); - - expression.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; - }); - - expression.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 static void API_GaemMonitoring(Expression expression) - { - // game processed snooper functions - expression.addLazyFunction("get_mob_counts", -1, (c, t, lv) -> - { - CarpetContext cc = (CarpetContext)c; - ServerWorld world = cc.s.getWorld(); - SpawnHelper.Info info = world.getChunkManager().getSpawnInfo(); - if (info == null) return LazyValue.NULL; - Object2IntMap mobcounts = info.getGroupToCount(); - int chunks = info.getSpawningChunkCount(); - Value retVal; - if (lv.size() == 0) - { - Map retDict = new HashMap<>(); - for (SpawnGroup category: mobcounts.keySet()) - { - int currentCap = (int)(category.getCapacity() * chunks / SpawnReporter.currentMagicNumber()); // MAGIC_NUMBER - retDict.put( - new StringValue(category.asString().toLowerCase(Locale.ROOT)), - ListValue.of( - new NumericValue(mobcounts.getInt(category)), - new NumericValue(currentCap)) - ); - } - retVal = MapValue.wrap(retDict); - } - else - { - String catString = lv.get(0).evalValue(c).getString(); - SpawnGroup cat = SpawnGroup.byName(catString.toLowerCase(Locale.ROOT)); - if (cat == null) throw new InternalExpressionException("Unreconized mob category: "+catString); - retVal = ListValue.of( - new NumericValue(mobcounts.getInt(cat)), - new NumericValue((int)(cat.getCapacity() * chunks / SpawnReporter.currentMagicNumber())) - ); - } - return (_c, _t) -> retVal; - }); - } - - private static 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; @@ -3639,14 +37,14 @@ public class CarpetExpression this.expr = new Expression(expression); this.expr.asAModule(module); - API_BlockManipulation(this.expr); - API_EntityManipulation(this.expr); - API_InventoryManipulation(this.expr); - API_IteratingOverAreasOfBlocks(this.expr); - API_AuxiliaryAspects(this.expr); - API_Scoreboard(this.expr); - API_Interapperability(this.expr); - API_GaemMonitoring(this.expr); + WorldAccess.apply(this.expr); + Entities.apply(this.expr); + Inventories.apply(this.expr); + BlockIterators.apply(this.expr); + Auxiliary.apply(this.expr); + Scoreboards.apply(this.expr); + Apps.apply(this.expr); + Monitoring.apply(this.expr); CarpetServer.extensions.forEach(e -> e.scarpetApi(this)); } diff --git a/src/main/java/carpet/script/CarpetScriptHost.java b/src/main/java/carpet/script/CarpetScriptHost.java index 83d84770..4c22c493 100644 --- a/src/main/java/carpet/script/CarpetScriptHost.java +++ b/src/main/java/carpet/script/CarpetScriptHost.java @@ -1,6 +1,7 @@ package carpet.script; import carpet.CarpetServer; +import carpet.script.api.Auxiliary; import carpet.script.bundled.Module; import carpet.script.exception.CarpetExpressionException; import carpet.script.exception.ExpressionException; @@ -26,7 +27,6 @@ import java.math.BigInteger; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Locale; import java.util.Map; import static java.lang.Math.max; @@ -360,7 +360,7 @@ public class CarpetScriptHost extends ScriptHost } }); - String markerName = CarpetExpression.MARKER_STRING+"_"+((getName()==null)?"":getName()); + String markerName = Auxiliary.MARKER_STRING+"_"+((getName()==null)?"":getName()); for (ServerWorld world : scriptServer.server.getWorlds()) { for (Entity e : world.getEntitiesByType(EntityType.ARMOR_STAND, (as) -> as.getScoreboardTags().contains(markerName))) diff --git a/src/main/java/carpet/script/Context.java b/src/main/java/carpet/script/Context.java index 50548fb3..fb9b583e 100644 --- a/src/main/java/carpet/script/Context.java +++ b/src/main/java/carpet/script/Context.java @@ -24,12 +24,12 @@ public class Context public final ScriptHost host; - Context(ScriptHost host) + public Context(ScriptHost host) { this.host = host; } - LazyValue getVariable(String name) + public LazyValue getVariable(String name) { return variables.get(name); } @@ -39,7 +39,7 @@ public class Context variables.put(name, lv); } - void delVariable(String variable) + public void delVariable(String variable) { variables.remove(variable); } diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java index 95a96393..93cc3491 100644 --- a/src/main/java/carpet/script/Expression.java +++ b/src/main/java/carpet/script/Expression.java @@ -11,7 +11,6 @@ import carpet.script.Fluff.QuadFunction; import carpet.script.Fluff.QuinnFunction; import carpet.script.Fluff.SexFunction; import carpet.script.Fluff.TriFunction; -import carpet.script.argument.FunctionArgument; import carpet.script.bundled.Module; import carpet.script.exception.BreakStatement; import carpet.script.exception.ContinueStatement; @@ -20,79 +19,36 @@ import carpet.script.exception.ExpressionException; import carpet.script.exception.InternalExpressionException; import carpet.script.exception.ResolvedException; import carpet.script.exception.ReturnStatement; -import carpet.script.exception.ThrowStatement; -import carpet.script.utils.PerlinNoiseSampler; -import carpet.script.utils.SimplexNoiseSampler; -import carpet.script.value.AbstractListValue; -import carpet.script.value.ContainerValueInterface; -import carpet.script.value.FunctionSignatureValue; +import carpet.script.language.Arithmetic; +import carpet.script.language.ControlFlow; +import carpet.script.language.DataStructures; +import carpet.script.language.Functions; +import carpet.script.language.Loops; +import carpet.script.language.Operators; +import carpet.script.language.Sys; import carpet.script.value.FunctionValue; -import carpet.script.value.GlobalValue; -import carpet.script.value.LContainerValue; -import carpet.script.value.LazyListValue; -import carpet.script.value.ListValue; -import carpet.script.value.MapValue; -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 it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import org.apache.commons.lang3.text.WordUtils; import java.math.BigInteger; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; import java.util.Collections; -import java.util.GregorianCalendar; import java.util.HashMap; -import java.util.IllegalFormatException; -import java.util.Iterator; import java.util.List; -import java.util.Locale; import java.util.Map; -import java.util.Random; import java.util.Set; import java.util.Stack; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import static java.lang.Math.abs; import static java.lang.Math.max; import static java.lang.Math.min; public class Expression { - private static final Map precedence = new HashMap() {{ - put("attribute~:", 80); - put("unary+-!", 60); - put("exponent^", 40); - put("multiplication*/%", 30); - put("addition+-", 20); - put("compare>=><=<", 10); - put("equal==!=", 7); - put("and&&", 5); - put("or||", 4); - put("assign=<>", 3); - put("def->", 2); - put("nextop;", 1); - }}; - public static final Random randomizer = new Random(); - - public static final Value PI = new NumericValue( - "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679"); - - public static final Value euler = new NumericValue( - "2.71828182845904523536028747135266249775724709369995957496696762772407663"); - - // %[argument_index$][flags][width][.precision][t]conversion - private static final Pattern formatPattern = Pattern.compile("%(\\d+\\$)?([-#+ 0,(<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])"); - /** The current infix expression */ private String expression; String getCodeString() {return expression;} @@ -107,7 +63,6 @@ public class Expression return module == null?null:module.getName(); } - public void asATextSource() { allowNewlineSubstitutions = false; @@ -116,7 +71,6 @@ public class Expression public void asAModule(Module mi) { - module = mi; } @@ -288,7 +242,7 @@ public class Expression public void addUnaryOperator(String surface, boolean leftAssoc, Function fun) { - operators.put(surface+"u", new AbstractUnaryOperator(precedence.get("unary+-!"), leftAssoc) + operators.put(surface+"u", new AbstractUnaryOperator(Operators.precedence.get("unary+-!"), leftAssoc) { @Override public Value evalUnary(Value v1) @@ -414,1597 +368,6 @@ public class Expression } - private static void UserDefinedFunctionsAndControlFlow(Expression expression) // public just to get the javadoc right - { - // artificial construct to handle user defined functions and function definitions - expression.addLazyFunction("import", -1, (c, t, lv) -> - { - if (lv.size() < 1) throw new InternalExpressionException("'import' needs at least a module name to import, and list of values to import"); - String moduleName = lv.get(0).evalValue(c).getString(); - c.host.importModule(c, moduleName); - moduleName = moduleName.toLowerCase(Locale.ROOT); - if (lv.size() > 1) - c.host.importNames(c, expression.module, moduleName, lv.subList(1, lv.size()).stream().map((l) -> l.evalValue(c).getString()).collect(Collectors.toList())); - if (t == Context.VOID) - return LazyValue.NULL; - ListValue list = ListValue.wrap(c.host.availableImports(moduleName).map(StringValue::new).collect(Collectors.toList())); - return (cc, tt) -> list; - }); - - expression.addLazyFunctionWithDelegation("call",-1, (c, t, expr, tok, lv) -> { // adjust based on c - if (lv.size() == 0) - throw new InternalExpressionException("'call' expects at least function name to call"); - //lv.remove(lv.size()-1); // aint gonna cut it // maybe it will because of the eager eval changes - if (t != Context.SIGNATURE) // just call the function - { - FunctionArgument functionArgument = FunctionArgument.findIn(c, expression.module, lv, 0, false, true); - FunctionValue fun = functionArgument.function; - Value retval = fun.callInContext(expr, c, t, fun.getExpression(), fun.getToken(), functionArgument.args).evalValue(c); - return (cc, tt) -> retval; ///!!!! dono might need to store expr and token in statics? (e? t?) - } - // gimme signature - String name = lv.get(0).evalValue(c).getString(); - List args = new ArrayList<>(); - List globals = new ArrayList<>(); - for (int i = 1; i < lv.size(); i++) - { - Value v = lv.get(i).evalValue(c, Context.LOCALIZATION); - if (!v.isBound()) - { - throw new InternalExpressionException("Only variables can be used in function signature, not " + v.getString()); - } - if (v instanceof GlobalValue) - { - globals.add(v.boundVariable); - } - else - { - args.add(v.boundVariable); - } - } - Value retval = new FunctionSignatureValue(name, args, globals); - return (cc, tt) -> retval; - }); - expression.addLazyFunction("outer", 1, (c, t, lv) -> { - if (t != Context.LOCALIZATION) - throw new InternalExpressionException("Outer scoping of variables is only possible in function signatures"); - return (cc, tt) -> new GlobalValue(lv.get(0).evalValue(c)); - }); - - expression.addLazyBinaryOperator(";",precedence.get("nextop;"), true, (c, t, lv1, lv2) -> - { - lv1.evalValue(c, Context.VOID); - Value v2 = lv2.evalValue(c, t); - return (cc, tt) -> v2; - }); - - //assigns const procedure to the lhs, returning its previous value - expression.addLazyBinaryOperatorWithDelegation("->", precedence.get("def->"), false, (c, type, e, t, lv1, lv2) -> - { - if (type == Context.MAPDEF) - { - Value result = ListValue.of(lv1.evalValue(c), lv2.evalValue(c)); - return (cc, tt) -> result; - } - Value v1 = lv1.evalValue(c, Context.SIGNATURE); - if (!(v1 instanceof FunctionSignatureValue)) - throw new InternalExpressionException("'->' operator requires a function signature on the LHS"); - FunctionSignatureValue sign = (FunctionSignatureValue) v1; - Value result = expression.addContextFunction(c, sign.getName(), e, t, sign.getArgs(), sign.getGlobals(), lv2); - return (cc, tt) -> result; - }); - - expression.addFunction("exit", (lv) -> { throw new ExitStatement(lv.size()==0?Value.NULL:lv.get(0)); }); - expression.addFunction("return", (lv) -> { throw new ReturnStatement(lv.size()==0?Value.NULL:lv.get(0));} ); - expression.addFunction("throw", (lv)-> {throw new ThrowStatement(lv.size()==0?Value.NULL:lv.get(0)); }); - - expression.addLazyFunction("try", -1, (c, t, lv) -> - { - if (lv.size()==0) - throw new InternalExpressionException("'try' needs at least an expression block"); - try - { - Value retval = lv.get(0).evalValue(c, t); - return (c_, t_) -> retval; - } - catch (ThrowStatement ret) - { - if (lv.size() == 1) - return (c_, t_) -> Value.NULL; - LazyValue __ = c.getVariable("_"); - c.setVariable("_", (__c, __t) -> ret.retval.reboundedTo("_")); - Value val = lv.get(1).evalValue(c, t); - c.setVariable("_",__); - return (c_, t_) -> val; - } - }); - - // if(cond1, expr1, cond2, expr2, ..., ?default) => value - expression.addLazyFunction("if", -1, (c, t, lv) -> - { - if ( lv.size() < 2 ) - throw new InternalExpressionException("'if' statement needs to have at least one condition and one case"); - for (int i=0; i ret; - } - } - if (lv.size()%2 == 1) - { - Value ret = lv.get(lv.size() - 1).evalValue(c, t); - return (cc, tt) -> ret; - } - return (cc, tt) -> Value.NULL; - }); - } - - private static void Operators(Expression expression) - { - expression.addBinaryOperator("+", precedence.get("addition+-"), true, Value::add); - expression.addBinaryOperator("-", precedence.get("addition+-"), true, Value::subtract); - expression.addBinaryOperator("*", precedence.get("multiplication*/%"), true, Value::multiply); - expression.addBinaryOperator("/", precedence.get("multiplication*/%"), true, Value::divide); - expression.addBinaryOperator("%", precedence.get("multiplication*/%"), true, (v1, v2) -> - new NumericValue(NumericValue.asNumber(v1).getDouble() % NumericValue.asNumber(v2).getDouble())); - expression.addBinaryOperator("^", precedence.get("exponent^"), false, (v1, v2) -> - new NumericValue(Math.pow(NumericValue.asNumber(v1).getDouble(), NumericValue.asNumber(v2).getDouble()))); - - expression.addLazyBinaryOperator("&&", precedence.get("and&&"), false, (c, t, lv1, lv2) -> - { - Value v1 = lv1.evalValue(c, Context.BOOLEAN); - if (!v1.getBoolean()) return (cc, tt) -> v1; - return lv2; - }); - - expression.addLazyBinaryOperator("||", precedence.get("or||"), false, (c, t, lv1, lv2) -> - { - Value v1 = lv1.evalValue(c, Context.BOOLEAN); - if (v1.getBoolean()) return (cc, tt) -> v1; - return lv2; - }); - - expression.addBinaryOperator("~", precedence.get("attribute~:"), true, Value::in); - - expression.addBinaryOperator(">", precedence.get("compare>=><=<"), false, (v1, v2) -> - v1.compareTo(v2) > 0 ? Value.TRUE : Value.FALSE); - expression.addBinaryOperator(">=", precedence.get("compare>=><=<"), false, (v1, v2) -> - v1.compareTo(v2) >= 0 ? Value.TRUE : Value.FALSE); - expression.addBinaryOperator("<", precedence.get("compare>=><=<"), false, (v1, v2) -> - v1.compareTo(v2) < 0 ? Value.TRUE : Value.FALSE); - expression.addBinaryOperator("<=", precedence.get("compare>=><=<"), false, (v1, v2) -> - v1.compareTo(v2) <= 0 ? Value.TRUE : Value.FALSE); - expression.addBinaryOperator("==", precedence.get("equal==!="), false, (v1, v2) -> - v1.equals(v2) ? Value.TRUE : Value.FALSE); - expression.addBinaryOperator("!=", precedence.get("equal==!="), false, (v1, v2) -> - v1.equals(v2) ? Value.FALSE : Value.TRUE); - - expression.addLazyBinaryOperator("=", precedence.get("assign=<>"), false, (c, t, lv1, lv2) -> - { - Value v1 = lv1.evalValue(c, Context.LVALUE); - Value v2 = lv2.evalValue(c); - if (v1 instanceof ListValue.ListConstructorValue && v2 instanceof ListValue) - { - List ll = ((ListValue)v1).getItems(); - List rl = ((ListValue)v2).getItems(); - if (ll.size() < rl.size()) throw new InternalExpressionException("Too many values to unpack"); - if (ll.size() > rl.size()) throw new InternalExpressionException("Too few values to unpack"); - for (Value v: ll) v.assertAssignable(); - Iterator li = ll.iterator(); - Iterator ri = rl.iterator(); - while(li.hasNext()) - { - String lname = li.next().getVariable(); - Value vval = ri.next().reboundedTo(lname); - expression.setAnyVariable(c, lname, (cc, tt) -> vval); - } - return (cc, tt) -> Value.TRUE; - } - if (v1 instanceof LContainerValue) - { - ContainerValueInterface container = ((LContainerValue) v1).getContainer(); - if (container == null) - return (cc, tt) -> Value.NULL; - Value address = ((LContainerValue) v1).getAddress(); - if (!(container.put(address, v2))) return (cc, tt) -> Value.NULL; - return (cc, tt) -> v2; - } - v1.assertAssignable(); - String varname = v1.getVariable(); - Value copy = v2.reboundedTo(varname); - LazyValue boundedLHS = (cc, tt) -> copy; - expression.setAnyVariable(c, varname, boundedLHS); - return boundedLHS; - }); - - expression.addLazyBinaryOperator("+=", precedence.get("assign=<>"), false, (c, t, lv1, lv2) -> - { - Value v1 = lv1.evalValue(c, Context.LVALUE); - Value v2 = lv2.evalValue(c); - if (v1 instanceof ListValue.ListConstructorValue && v2 instanceof ListValue) - { - List ll = ((ListValue)v1).getItems(); - List rl = ((ListValue)v2).getItems(); - if (ll.size() < rl.size()) throw new InternalExpressionException("Too many values to unpack"); - if (ll.size() > rl.size()) throw new InternalExpressionException("Too few values to unpack"); - for (Value v: ll) v.assertAssignable(); - Iterator li = ll.iterator(); - Iterator ri = rl.iterator(); - while(li.hasNext()) - { - Value lval = li.next(); - String lname = lval.getVariable(); - Value result = lval.add(ri.next()).bindTo(lname); - expression.setAnyVariable(c, lname, (cc, tt) -> result); - } - return (cc, tt) -> Value.TRUE; - } - if (v1 instanceof LContainerValue) - { - ContainerValueInterface cvi = ((LContainerValue) v1).getContainer(); - if (cvi == null) - { - throw new InternalExpressionException("Failed to resolve left hand side of the += operation"); - } - Value key = ((LContainerValue) v1).getAddress(); - Value value = cvi.get(key); - if (value instanceof ListValue || value instanceof MapValue) - { - ((AbstractListValue) value).append(v2); - return (cc, tt) -> value; - } - else - { - Value res = value.add(v2); - cvi.put(key, res); - return (cc, tt) -> res; - } - } - v1.assertAssignable(); - String varname = v1.getVariable(); - LazyValue boundedLHS; - if (v1 instanceof ListValue || v1 instanceof MapValue) - { - ((AbstractListValue) v1).append(v2); - boundedLHS = (cc, tt)-> v1; - } - else - { - Value result = v1.add(v2).bindTo(varname); - boundedLHS = (cc, tt) -> result; - } - expression.setAnyVariable(c, varname, boundedLHS); - return boundedLHS; - }); - - expression.addLazyBinaryOperator("<>", precedence.get("assign=<>"), false, (c, t, lv1, lv2) -> - { - Value v1 = lv1.evalValue(c); - Value v2 = lv2.evalValue(c); - if (v1 instanceof ListValue.ListConstructorValue && v2 instanceof ListValue.ListConstructorValue) - { - List ll = ((ListValue)v1).getItems(); - List rl = ((ListValue)v2).getItems(); - if (ll.size() < rl.size()) throw new InternalExpressionException("Too many values to unpack"); - if (ll.size() > rl.size()) throw new InternalExpressionException("Too few values to unpack"); - for (Value v: ll) v.assertAssignable(); - for (Value v: rl) v.assertAssignable(); - Iterator li = ll.iterator(); - Iterator ri = rl.iterator(); - while(li.hasNext()) - { - Value lval = li.next(); - Value rval = ri.next(); - String lname = lval.getVariable(); - String rname = rval.getVariable(); - lval.reboundedTo(rname); - rval.reboundedTo(lname); - expression.setAnyVariable(c, lname, (cc, tt) -> rval); - expression.setAnyVariable(c, rname, (cc, tt) -> lval); - } - return (cc, tt) -> Value.TRUE; - } - v1.assertAssignable(); - v2.assertAssignable(); - String lvalvar = v1.getVariable(); - String rvalvar = v2.getVariable(); - Value lval = v2.reboundedTo(lvalvar); - Value rval = v1.reboundedTo(rvalvar); - expression.setAnyVariable(c, lvalvar, (cc, tt) -> lval); - expression.setAnyVariable(c, rvalvar, (cc, tt) -> rval); - return (cc, tt) -> lval; - }); - - expression.addUnaryOperator("-", false, (v) -> new NumericValue(-NumericValue.asNumber(v).getDouble())); - - expression.addUnaryOperator("+", false, (v) -> new NumericValue(NumericValue.asNumber(v).getDouble())); - - expression.addLazyUnaryOperator("!", precedence.get("unary+-!"), false, (c, t, lv)-> lv.evalValue(c, Context.BOOLEAN).getBoolean() ? (cc, tt)-> Value.FALSE : (cc, tt) -> Value.TRUE); // might need context boolean - - } - - private static void ArithmeticOperations(Expression expression) - { - expression.addLazyFunction("not", 1, (c, t, lv) -> lv.get(0).evalValue(c, Context.BOOLEAN).getBoolean() ? ((cc, tt) -> Value.FALSE) : ((cc, tt) -> Value.TRUE)); - - expression.addUnaryFunction("fact", (v) -> - { - long number = NumericValue.asNumber(v).getLong(); - long factorial = 1; - for (int i = 1; i <= number; i++) - { - factorial = factorial * i; - } - return new NumericValue(factorial); - }); - expression.addMathematicalUnaryFunction("sin", (d) -> Math.sin(Math.toRadians(d))); - expression.addMathematicalUnaryFunction("cos", (d) -> Math.cos(Math.toRadians(d))); - expression.addMathematicalUnaryFunction("tan", (d) -> Math.tan(Math.toRadians(d))); - expression.addMathematicalUnaryFunction("asin", (d) -> Math.toDegrees(Math.asin(d))); - expression.addMathematicalUnaryFunction("acos", (d) -> Math.toDegrees(Math.acos(d))); - expression.addMathematicalUnaryFunction("atan", (d) -> Math.toDegrees(Math.atan(d))); - expression.addMathematicalBinaryFunction("atan2", (d, d2) -> Math.toDegrees(Math.atan2(d, d2)) ); - expression.addMathematicalUnaryFunction("sinh", Math::sinh ); - expression.addMathematicalUnaryFunction("cosh", Math::cosh ); - expression.addMathematicalUnaryFunction("tanh", Math::tanh ); - expression.addMathematicalUnaryFunction("sec", (d) -> 1.0 / Math.cos(Math.toRadians(d)) ); // Formula: sec(x) = 1 / cos(x) - expression.addMathematicalUnaryFunction("csc", (d) -> 1.0 / Math.sin(Math.toRadians(d)) ); // Formula: csc(x) = 1 / sin(x) - expression.addMathematicalUnaryFunction("sech", (d) -> 1.0 / Math.cosh(d) ); // Formula: sech(x) = 1 / cosh(x) - expression.addMathematicalUnaryFunction("csch", (d) -> 1.0 / Math.sinh(d) ); // Formula: csch(x) = 1 / sinh(x) - expression.addMathematicalUnaryFunction("cot", (d) -> 1.0 / Math.tan(Math.toRadians(d)) ); // Formula: cot(x) = cos(x) / sin(x) = 1 / tan(x) - expression.addMathematicalUnaryFunction("acot", (d) -> Math.toDegrees(Math.atan(1.0 / d)) );// Formula: acot(x) = atan(1/x) - expression.addMathematicalUnaryFunction("coth", (d) -> 1.0 / Math.tanh(d) ); // Formula: coth(x) = 1 / tanh(x) - expression.addMathematicalUnaryFunction("asinh", (d) -> Math.log(d + (Math.sqrt(Math.pow(d, 2) + 1)))); // Formula: asinh(x) = ln(x + sqrt(x^2 + 1)) - expression.addMathematicalUnaryFunction("acosh", (d) -> Math.log(d + (Math.sqrt(Math.pow(d, 2) - 1)))); // Formula: acosh(x) = ln(x + sqrt(x^2 - 1)) - expression.addMathematicalUnaryFunction("atanh", (d) -> // Formula: atanh(x) = 0.5*ln((1 + x)/(1 - x)) - { - if (Math.abs(d) > 1 || Math.abs(d) == 1) - throw new InternalExpressionException("Number must be |x| < 1"); - return 0.5 * Math.log((1 + d) / (1 - d)); - }); - expression.addMathematicalUnaryFunction("rad", Math::toRadians); - expression.addMathematicalUnaryFunction("deg", Math::toDegrees); - expression.addMathematicalUnaryFunction("ln", Math::log); - expression.addMathematicalUnaryFunction("ln1p", Math::log1p); - expression.addMathematicalUnaryFunction("log10", Math::log10); - expression.addMathematicalUnaryFunction("log", a -> Math.log(a)/Math.log(2)); - expression.addMathematicalUnaryFunction("log1p", x -> Math.log1p(x)/Math.log(2)); - expression.addMathematicalUnaryFunction("sqrt", Math::sqrt); - expression.addMathematicalUnaryFunction("abs", Math::abs); - expression.addMathematicalUnaryFunction("round", (d) -> (double)Math.round(d)); - expression.addMathematicalUnaryFunction("floor", Math::floor); - expression.addMathematicalUnaryFunction("ceil", Math::ceil); - - expression.addLazyFunction("mandelbrot", 3, (c, t, lv) -> { - double a0 = NumericValue.asNumber(lv.get(0).evalValue(c)).getDouble(); - double b0 = NumericValue.asNumber(lv.get(1).evalValue(c)).getDouble(); - long maxiter = NumericValue.asNumber(lv.get(2).evalValue(c)).getLong(); - double a = 0.0D; - double b = 0.0D; - long iter = 0; - while(a*a+b*b<4 && iter < maxiter) - { - double temp = a*a-b*b+a0; - b = 2*a*b+b0; - a = temp; - iter++; - } - long iFinal = iter; - return (cc, tt) -> new NumericValue(iFinal); - }); - - expression.addFunction("max", (lv) -> - { - if (lv.size() == 0) - throw new InternalExpressionException("'max' requires at least one parameter"); - Value max = null; - if (lv.size()==1 && lv.get(0) instanceof ListValue) - lv = ((ListValue) lv.get(0)).getItems(); - for (Value parameter : lv) - { - if (max == null || parameter.compareTo(max) > 0) max = parameter; - } - return max; - }); - - expression.addFunction("min", (lv) -> - { - if (lv.size() == 0) - throw new InternalExpressionException("'min' requires at least one parameter"); - Value min = null; - if (lv.size()==1 && lv.get(0) instanceof ListValue) - lv = ((ListValue) lv.get(0)).getItems(); - for (Value parameter : lv) - { - if (min == null || parameter.compareTo(min) < 0) min = parameter; - } - return min; - }); - - expression.addUnaryFunction("relu", (v) -> v.compareTo(Value.ZERO) < 0 ? Value.ZERO : v); - } - - private static void LoopsAndHigherOrderFunctions(Expression expression) - { - // condition and expression will get a bound '_i' - // returns last successful expression or false - // while(cond, limit, expr) => ?? - expression.addFunction("break", lv -> { - if (lv.size()==0) throw new BreakStatement(null); - if (lv.size()==1) throw new BreakStatement(lv.get(0)); - throw new InternalExpressionException("'break' can only be called with zero or one argument"); - }); - - expression.addFunction("continue", lv -> { - if (lv.size()==0) throw new ContinueStatement(null); - if (lv.size()==1) throw new ContinueStatement(lv.get(0)); - throw new InternalExpressionException("'continue' can only be called with zero or one argument"); - }); - - expression.addLazyFunction("while", 3, (c, t, lv) -> - { - long limit = NumericValue.asNumber(lv.get(1).evalValue(c)).getLong(); - LazyValue condition = lv.get(0); - LazyValue expr = lv.get(2); - long i = 0; - Value lastOne = Value.NULL; - //scoping - LazyValue _val = c.getVariable("_"); - c.setVariable("_",(cc, tt) -> new NumericValue(0).bindTo("_")); - while (i new NumericValue(seriously).bindTo("_")); - } - //revering scope - c.setVariable("_", _val); - Value lastValueNoKidding = lastOne; - return (cc, tt) -> lastValueNoKidding; - }); - - // loop(Num, expr) => last_value - // expr receives bounded variable '_' indicating iteration - expression.addLazyFunction("loop", 2, (c, t, lv) -> - { - long limit = NumericValue.asNumber(lv.get(0).evalValue(c)).getLong(); - Value lastOne = Value.NULL; - LazyValue expr = lv.get(1); - //scoping - LazyValue _val = c.getVariable("_"); - for (long i=0; i < limit; i++) - { - long whyYouAsk = i; - c.setVariable("_", (cc, tt) -> new NumericValue(whyYouAsk).bindTo("_")); - try - { - lastOne = expr.evalValue(c, t); - } - catch (BreakStatement | ContinueStatement stmt) - { - if (stmt.retval != null) lastOne = stmt.retval; - if (stmt instanceof BreakStatement) break; - } - } - //revering scope - c.setVariable("_", _val); - Value trulyLastOne = lastOne; - return (cc, tt) -> trulyLastOne; - }); - - // map(list or Num, expr) => list_results - // receives bounded variable '_' with the expression - expression.addLazyFunction("map", 2, (c, t, lv) -> - { - Value rval= lv.get(0).evalValue(c); - if (!(rval instanceof AbstractListValue)) - throw new InternalExpressionException("First argument of 'map' function should be a list or iterator"); - Iterator iterator = ((AbstractListValue) rval).iterator(); - LazyValue expr = lv.get(1); - //scoping - LazyValue _val = c.getVariable("_"); - LazyValue _iter = c.getVariable("_i"); - List result = new ArrayList<>(); - for (int i=0; iterator.hasNext(); i++) - { - Value next = iterator.next(); - String var = next.boundVariable; - next.bindTo("_"); - int doYouReally = i; - c.setVariable("_", (cc, tt) -> next); - c.setVariable("_i", (cc, tt) -> new NumericValue(doYouReally).bindTo("_i")); - try - { - result.add(expr.evalValue(c)); - } - catch (BreakStatement | ContinueStatement stmt) - { - if (stmt.retval != null) result.add(stmt.retval); - if (stmt instanceof BreakStatement) - { - next.boundVariable = var; - break; - } - } - next.boundVariable = var; - } - ((AbstractListValue) rval).fatality(); - Value ret = ListValue.wrap(result); - //revering scope - c.setVariable("_", _val); - c.setVariable("_i", _iter); - return (cc, tt) -> ret; - }); - - // grep(list or num, expr) => list - // receives bounded variable '_' with the expression, and "_i" with index - // produces list of values for which the expression is true - expression.addLazyFunction("filter", 2, (c, t, lv) -> - { - Value rval= lv.get(0).evalValue(c); - if (!(rval instanceof AbstractListValue)) - throw new InternalExpressionException("First argument of 'filter' function should be a list or iterator"); - Iterator iterator = ((AbstractListValue) rval).iterator(); - LazyValue expr = lv.get(1); - //scoping - LazyValue _val = c.getVariable("_"); - LazyValue _iter = c.getVariable("_i"); - List result = new ArrayList<>(); - for (int i=0; iterator.hasNext(); i++) - { - Value next = iterator.next(); - String var = next.boundVariable; - next.bindTo("_"); - int seriously = i; - c.setVariable("_", (cc, tt) -> next); - c.setVariable("_i", (cc, tt) -> new NumericValue(seriously).bindTo("_i")); - try - { - if(expr.evalValue(c, Context.BOOLEAN).getBoolean()) - result.add(next); - } - catch (BreakStatement | ContinueStatement stmt) - { - if (stmt.retval != null && stmt.retval.getBoolean()) result.add(next); - if (stmt instanceof BreakStatement) - { - next.boundVariable = var; - break; - } - } - next.boundVariable = var; - } - ((AbstractListValue) rval).fatality(); - Value ret = ListValue.wrap(result); - //revering scope - c.setVariable("_", _val); - c.setVariable("_i", _iter); - return (cc, tt) -> ret; - }); - - // first(list, expr) => elem or null - // receives bounded variable '_' with the expression, and "_i" with index - // returns first element on the list for which the expr is true - expression.addLazyFunction("first", 2, (c, t, lv) -> - { - - Value rval= lv.get(0).evalValue(c); - if (!(rval instanceof AbstractListValue)) - throw new InternalExpressionException("First argument of 'first' function should be a list or iterator"); - Iterator iterator = ((AbstractListValue) rval).iterator(); - LazyValue expr = lv.get(1); - //scoping - LazyValue _val = c.getVariable("_"); - LazyValue _iter = c.getVariable("_i"); - Value result = Value.NULL; - for (int i=0; iterator.hasNext(); i++) - { - Value next = iterator.next(); - String var = next.boundVariable; - next.bindTo("_"); - int seriously = i; - c.setVariable("_", (cc, tt) -> next); - c.setVariable("_i", (cc, tt) -> new NumericValue(seriously).bindTo("_i")); - try - { - if(expr.evalValue(c, Context.BOOLEAN).getBoolean()) - { - result = next; - next.boundVariable = var; - break; - } - } - catch (BreakStatement stmt) - { - result = stmt.retval == null? next : stmt.retval; - next.boundVariable = var; - break; - } - catch (ContinueStatement ignored) - { - throw new InternalExpressionException("'continue' inside 'first' function has no sense"); - } - next.boundVariable = var; - } - //revering scope - ((AbstractListValue) rval).fatality(); - Value whyWontYouTrustMeJava = result; - c.setVariable("_", _val); - c.setVariable("_i", _iter); - return (cc, tt) -> whyWontYouTrustMeJava; - }); - - // all(list, expr) => boolean - // receives bounded variable '_' with the expression, and "_i" with index - // returns true if expr is true for all items - expression.addLazyFunction("all", 2, (c, t, lv) -> - { - Value rval= lv.get(0).evalValue(c); - if (!(rval instanceof AbstractListValue)) - throw new InternalExpressionException("First argument of 'all' function should be a list or iterator"); - Iterator iterator = ((AbstractListValue) rval).iterator(); - LazyValue expr = lv.get(1); - //scoping - LazyValue _val = c.getVariable("_"); - LazyValue _iter = c.getVariable("_i"); - LazyValue result = LazyValue.TRUE; - for (int i=0; iterator.hasNext(); i++) - { - Value next = iterator.next(); - String var = next.boundVariable; - next.bindTo("_"); - int seriously = i; - c.setVariable("_", (cc, tt) -> next); - c.setVariable("_i", (cc, tt) -> new NumericValue(seriously).bindTo("_i")); - if(!expr.evalValue(c, Context.BOOLEAN).getBoolean()) - { - result = LazyValue.FALSE; - next.boundVariable = var; - break; - } - next.boundVariable = var; - } - //revering scope - ((AbstractListValue) rval).fatality(); - c.setVariable("_", _val); - c.setVariable("_i", _iter); - return result; - }); - - // runs traditional for(init, condition, increment, body) tri-argument for loop with body in between - expression.addLazyFunction("c_for", 4, (c, t, lv) -> { - LazyValue initial = lv.get(0); - LazyValue condition = lv.get(1); - LazyValue increment = lv.get(2); - LazyValue body = lv.get(3); - int iterations = 0; - for (initial.evalValue(c, Context.VOID); condition.evalValue(c, Context.BOOLEAN).getBoolean(); increment.evalValue(c, Context.VOID)) - { - try - { - body.evalValue(c, Context.VOID); - } - catch (BreakStatement stmt) - { - break; - } - catch (ContinueStatement ignored) - { - } - iterations++; - } - int finalIterations = iterations; - return (cc, tt) -> new NumericValue(finalIterations); - }); - - // similar to map, but returns total number of successes - // for(list, expr) => success_count - // can be substituted for first and all, but first is more efficient and all doesn't require knowing list size - expression.addLazyFunction("for", 2, (c, t, lv) -> - { - Value rval= lv.get(0).evalValue(c); - if (!(rval instanceof AbstractListValue)) - throw new InternalExpressionException("First argument of 'for' function should be a list or iterator"); - Iterator iterator = ((AbstractListValue) rval).iterator(); - LazyValue expr = lv.get(1); - //scoping - LazyValue _val = c.getVariable("_"); - LazyValue _ite = c.getVariable("_i"); - int successCount = 0; - for (int i=0; iterator.hasNext(); i++) - { - Value next = iterator.next(); - String var = next.boundVariable; - next.bindTo("_"); - int seriously = i; - c.setVariable("_", (cc, tt) -> next); - c.setVariable("_i", (cc, tt) -> new NumericValue(seriously).bindTo("_i")); - Value result = Value.FALSE; - try - { - result = expr.evalValue(c, t); - } - catch (BreakStatement | ContinueStatement stmt) - { - if (stmt.retval != null) result = stmt.retval; - if (stmt instanceof BreakStatement) - { - next.boundVariable = var; - break; - } - } - if(t != Context.VOID && result.getBoolean()) - successCount++; - next.boundVariable = var; - } - //revering scope - ((AbstractListValue) rval).fatality(); - c.setVariable("_", _val); - c.setVariable("_i", _ite); - long promiseWontChange = successCount; - return (cc, tt) -> new NumericValue(promiseWontChange); - }); - - - // reduce(list, expr, ?acc) => value - // reduces values in the list with expression that gets accumulator - // each iteration expr receives acc - accumulator, and '_' - current list value - // returned value is substituted to the accumulator - expression.addLazyFunction("reduce", 3, (c, t, lv) -> - { - LazyValue expr = lv.get(1); - - Value acc = lv.get(2).evalValue(c); - Value rval= lv.get(0).evalValue(c); - if (!(rval instanceof AbstractListValue)) - throw new InternalExpressionException("First argument of 'reduce' should be a list or iterator"); - Iterator iterator = ((AbstractListValue) rval).iterator(); - - if (!iterator.hasNext()) - { - Value seriouslyWontChange = acc; - return (cc, tt) -> seriouslyWontChange; - } - - //scoping - LazyValue _val = c.getVariable("_"); - LazyValue _acc = c.getVariable("_a"); - LazyValue _ite = c.getVariable("_i"); - - for (int i=0; iterator.hasNext(); i++) - { - Value next = iterator.next(); - String var = next.boundVariable; - next.bindTo("_"); - Value promiseWontChangeYou = acc; - int seriously = i; - c.setVariable("_a", (cc, tt) -> promiseWontChangeYou.bindTo("_a")); - c.setVariable("_", (cc, tt) -> next); - c.setVariable("_i", (cc, tt) -> new NumericValue(seriously).bindTo("_i")); - try - { - acc = expr.evalValue(c, t); - } - catch (BreakStatement | ContinueStatement stmt) - { - if (stmt.retval != null) acc = stmt.retval; - if (stmt instanceof BreakStatement) - { - next.boundVariable = var; - break; - } - } - next.boundVariable = var; - } - //reverting scope - ((AbstractListValue) rval).fatality(); - c.setVariable("_a", _acc); - c.setVariable("_", _val); - c.setVariable("_i", _ite); - - Value hopeItsEnoughPromise = acc; - return (cc, tt) -> hopeItsEnoughPromise; - }); - } - - private static void BasicDataStructures(Expression expression) - { - expression.addFunction("l", lv -> - { - if (lv.size() == 1 && lv.get(0) instanceof LazyListValue) - return ListValue.wrap(((LazyListValue) lv.get(0)).unroll()); - return new ListValue.ListConstructorValue(lv); - }); - - expression.addFunction("join", (lv) -> - { - if (lv.size() < 2) - throw new InternalExpressionException("'join' takes at least 2 arguments"); - String delimiter = lv.get(0).getString(); - List toJoin; - if (lv.size()==2 && lv.get(1) instanceof LazyListValue) - { - toJoin = ((LazyListValue) lv.get(1)).unroll(); - - } - else if (lv.size() == 2 && lv.get(1) instanceof ListValue) - { - toJoin = new ArrayList<>(((ListValue)lv.get(1)).getItems()); - } - else - { - toJoin = lv.subList(1,lv.size()); - } - return new StringValue(toJoin.stream().map(Value::getString).collect(Collectors.joining(delimiter))); - }); - - expression.addBinaryFunction("split", (d, v) -> { - String delimiter = d.getString(); - String hwat = v.getString(); - return ListValue.wrap(Arrays.stream(hwat.split(delimiter)).map(StringValue::new).collect(Collectors.toList())); - }); - - expression.addFunction("slice", (lv) -> { - - if (lv.size() != 2 && lv.size() != 3) - throw new InternalExpressionException("'slice' takes 2 or 3 arguments"); - Value hwat = lv.get(0); - long from = NumericValue.asNumber(lv.get(1)).getLong(); - long to = -1; - if (lv.size()== 3) - to = NumericValue.asNumber(lv.get(2)).getLong(); - return hwat.slice(from, to); - }); - - expression.addFunction("sort", (lv) -> - { - List toSort = lv; - if (lv.size()==1 && lv.get(0) instanceof ListValue) - { - toSort = new ArrayList<>(((ListValue)lv.get(0)).getItems()); - } - Collections.sort(toSort); - return ListValue.wrap(toSort); - }); - - expression.addLazyFunction("sort_key", -1, (c, t, lv) -> //get working with iterators - { - if (lv.size() == 0) - throw new InternalExpressionException("First argument for 'sort_key' should be a List"); - Value v = lv.get(0).evalValue(c); - if (!(v instanceof ListValue)) - throw new InternalExpressionException("First argument for 'sort_key' should be a List"); - List toSort = new ArrayList<>(((ListValue) v).getItems()); - if (lv.size()==1) - { - Collections.shuffle(toSort); - Value ret = ListValue.wrap(toSort); - return (_c, _t) -> ret; - } - LazyValue sortKey = lv.get(1); - //scoping - LazyValue __ = c.getVariable("_"); - Collections.sort(toSort,(v1, v2) -> { - c.setVariable("_",(cc, tt) -> v1); - Value ev1 = sortKey.evalValue(c); - c.setVariable("_",(cc, tt) -> v2); - Value ev2 = sortKey.evalValue(c); - return ev1.compareTo(ev2); - }); - //revering scope - c.setVariable("_", __); - return (cc, tt) -> ListValue.wrap(toSort); - }); - - expression.addFunction("range", (lv) -> - { - long from = 0; - long to = 0; - long step = 1; - int argsize = lv.size(); - if (argsize == 0 || argsize > 3) - throw new InternalExpressionException("'range' accepts from 1 to 3 arguments, not "+argsize); - to = NumericValue.asNumber(lv.get(0)).getLong(); - if (lv.size() > 1) - { - from = to; - to = NumericValue.asNumber(lv.get(1)).getLong(); - if (lv.size() > 2) - { - step = NumericValue.asNumber(lv.get(2)).getLong(); - } - } - return LazyListValue.range(from, to, step); - }); - - expression.addLazyFunction("m", -1, (c, t, llv) -> - { - List lv = new ArrayList<>(); - for (LazyValue lazyParam : llv) { - lv.add(lazyParam.evalValue(c, Context.MAPDEF)); // none type default by design - } - Value ret; - if (lv.size() == 1 && lv.get(0) instanceof LazyListValue) - ret = new MapValue(((LazyListValue) lv.get(0)).unroll()); - else - ret = new MapValue(lv); - return (cc, tt) -> ret; - }); - - expression.addUnaryFunction("keys", v -> { - if (v instanceof MapValue) - return new ListValue(((MapValue) v).getMap().keySet()); - return Value.NULL; - }); - - expression.addUnaryFunction("values", v -> { - if (v instanceof MapValue) - return new ListValue(((MapValue) v).getMap().values()); - return Value.NULL; - }); - - expression.addUnaryFunction("pairs", v -> { - if (v instanceof MapValue) - return ListValue.wrap(((MapValue) v).getMap().entrySet().stream().map( - (p) -> ListValue.of(p.getKey(), p.getValue()) - ).collect(Collectors.toList())); - return Value.NULL; - }); - - expression.addLazyBinaryOperator(":", precedence.get("attribute~:"),true, (c, t, container_lv, key_lv) -> - { - Value container = container_lv.evalValue(c); - if (container instanceof LContainerValue) - { - ContainerValueInterface outerContainer = ((LContainerValue) container).getContainer(); - if (outerContainer == null) - { - Value innerLValue = new LContainerValue(null, null); - return (cc, tt) -> innerLValue; - } - Value innerContainer = outerContainer.get(((LContainerValue) container).getAddress()); - if (!(innerContainer instanceof ContainerValueInterface)) - { - Value innerLValue = new LContainerValue(null, null); - return (cc, tt) -> innerLValue; - } - Value innerLValue = new LContainerValue((ContainerValueInterface) innerContainer, key_lv.evalValue(c)); - return (cc, tt) -> innerLValue; - } - if (!(container instanceof ContainerValueInterface)) - if (t == Context.LVALUE) - return (cc, tt) -> new LContainerValue(null, null); - else - return (cc, tt) -> Value.NULL; - Value address = key_lv.evalValue(c); - if (t != Context.LVALUE) - { - Value retVal = ((ContainerValueInterface) container).get(address); - return (cc, ct) -> retVal; - } - Value retVal = new LContainerValue((ContainerValueInterface) container, address); - return (cc, ct) -> retVal; - }); - - expression.addLazyFunction("get", -1, (c, t, lv) -> - { - if (lv.size() == 0) - throw new InternalExpressionException("'get' requires parameters"); - if (lv.size() == 1) - { - Value v = lv.get(0).evalValue(c, Context.LVALUE); - if (!(v instanceof LContainerValue)) - return (cc, tt) -> Value.NULL; - ContainerValueInterface container = ((LContainerValue) v).getContainer(); - if (container == null) - return (cc, tt) -> Value.NULL; - Value ret = container.get(((LContainerValue) v).getAddress()); - return (cc, tt) -> ret; - } - Value container = lv.get(0).evalValue(c); - for (int i = 1; i < lv.size(); i++) - { - if (!(container instanceof ContainerValueInterface)) return (cc, tt) -> Value.NULL; - container = ((ContainerValueInterface) container).get(lv.get(i).evalValue(c)); - } - if (container == null) - return (cc, tt) -> Value.NULL; - Value finalContainer = container; - return (cc, tt) -> finalContainer; - }); - - expression.addLazyFunction("has", -1, (c, t, lv) -> - { - if (lv.size() == 0) - throw new InternalExpressionException("'has' requires parameters"); - if (lv.size() == 1) - { - Value v = lv.get(0).evalValue(c, Context.LVALUE); - if (!(v instanceof LContainerValue)) - return (cc, tt) -> Value.NULL; - ContainerValueInterface container = ((LContainerValue) v).getContainer(); - if (container == null) - return (cc, tt) -> Value.NULL; - Value ret = new NumericValue(container.has(((LContainerValue) v).getAddress())); - return (cc, tt) -> ret; - } - Value container = lv.get(0).evalValue(c); - for (int i = 1; i < lv.size()-1; i++) - { - if (!(container instanceof ContainerValueInterface)) return (cc, tt) -> Value.NULL; - container = ((ContainerValueInterface) container).get(lv.get(i).evalValue(c)); - } - if (!(container instanceof ContainerValueInterface)) - return (cc, tt) -> Value.NULL; - Value ret = new NumericValue(((ContainerValueInterface) container).has(lv.get(lv.size()-1).evalValue(c))); - return (cc, tt) -> ret; - }); - - expression.addLazyFunction("put", -1, (c, t, lv) -> - { - if(lv.size()<2) - { - throw new InternalExpressionException("'put' takes at least three arguments, a container, address, and values to insert at that index"); - } - Value container = lv.get(0).evalValue(c, Context.LVALUE); - if (container instanceof LContainerValue) - { - ContainerValueInterface internalContainer = ((LContainerValue) container).getContainer(); - if (internalContainer == null) - { - return (cc, tt) -> Value.NULL; - } - Value address = ((LContainerValue) container).getAddress(); - Value what = lv.get(1).evalValue(c); - Value retVal = new NumericValue( (lv.size() > 2) - ? internalContainer.put(address, what, lv.get(2).evalValue(c)) - : internalContainer.put(address, what)); - return (cc, tt) -> retVal; - - } - if(lv.size()<3) - { - throw new InternalExpressionException("'put' takes at least three arguments, a container, address, and values to insert at that index"); - } - if (!(container instanceof ContainerValueInterface)) - { - return (cc, tt) -> Value.NULL; - } - Value where = lv.get(1).evalValue(c); - Value what = lv.get(2).evalValue(c); - Value retVal = new NumericValue( (lv.size()>3) - ? ((ContainerValueInterface) container).put(where, what, lv.get(3).evalValue(c)) - : ((ContainerValueInterface) container).put(where, what)); - return (cc, tt) -> retVal; - }); - - expression.addLazyFunction("delete", -1, (c, t, lv) -> - { - if (lv.size() == 0) - throw new InternalExpressionException("'delete' requires parameters"); - if (lv.size() == 1) - { - Value v = lv.get(0).evalValue(c, Context.LVALUE); - if (!(v instanceof LContainerValue)) - return (cc, tt) -> Value.NULL; - ContainerValueInterface container = ((LContainerValue) v).getContainer(); - if (container == null) - return (cc, tt) -> Value.NULL; - Value ret = new NumericValue(container.delete(((LContainerValue) v).getAddress())); - return (cc, tt) -> ret; - } - Value container = lv.get(0).evalValue(c); - for (int i = 1; i < lv.size()-1; i++) - { - if (!(container instanceof ContainerValueInterface)) return (cc, tt) -> Value.NULL; - container = ((ContainerValueInterface) container).get(lv.get(i).evalValue(c)); - } - if (!(container instanceof ContainerValueInterface)) - return (cc, tt) -> Value.NULL; - Value ret = new NumericValue(((ContainerValueInterface) container).delete(lv.get(lv.size()-1).evalValue(c))); - return (cc, tt) -> ret; - }); - } - - private static void SystemFunctions(Expression expression) - { - expression.addUnaryFunction("hash_code", v -> new NumericValue(v.hashCode())); - - expression.addUnaryFunction("copy", Value::deepcopy); - - expression.addLazyFunction("bool", 1, (c, t, lv) -> { - Value v = lv.get(0).evalValue(c, Context.BOOLEAN); - if (v instanceof StringValue) - { - String str = v.getString(); - if ("false".equalsIgnoreCase(str) || "null".equalsIgnoreCase(str)) - { - return (cc, tt) -> Value.FALSE; - } - } - Value retval = new NumericValue(v.getBoolean()); - return (cc, tt) -> retval; - }); - expression.addUnaryFunction("number", v -> { - if (v instanceof NumericValue) - { - return v; - } - try - { - return new NumericValue(v.getString()); - } - catch (NumberFormatException format) - { - return Value.NULL; - } - }); - expression.addFunction("str", lv -> - { - if (lv.size() == 0) - throw new InternalExpressionException("'str' requires at least one argument"); - String format = lv.get(0).getString(); - if (lv.size() == 1) - return new StringValue(format); - int argIndex = 1; - if (lv.get(1) instanceof ListValue && lv.size() == 2) - { - lv = ((ListValue) lv.get(1)).getItems(); - argIndex = 0; - } - List args = new ArrayList<>(); - Matcher m = formatPattern.matcher(format); - - for (int i = 0, len = format.length(); i < len; ) { - if (m.find(i)) { - // Anything between the start of the string and the beginning - // of the format specifier is either fixed text or contains - // an invalid format string. - // [[scarpet]] but we skip it and let the String.format fail - char fmt = m.group(6).toLowerCase().charAt(0); - if (fmt == 's') - { - if (argIndex >= lv.size()) - throw new InternalExpressionException("Not enough arguments for "+m.group(0)); - args.add(lv.get(argIndex).getString()); - argIndex++; - } - else if (fmt == 'd' || fmt == 'o' || fmt == 'x') - { - if (argIndex >= lv.size()) - throw new InternalExpressionException("Not enough arguments for "+m.group(0)); - args.add(lv.get(argIndex).readInteger()); - argIndex++; - } - else if (fmt == 'a' || fmt == 'e' || fmt == 'f' || fmt == 'g') - { - if (argIndex >= lv.size()) - throw new InternalExpressionException("Not enough arguments for "+m.group(0)); - args.add(lv.get(argIndex).readDoubleNumber()); - argIndex++; - } - else if (fmt == 'b') - { - if (argIndex >= lv.size()) - throw new InternalExpressionException("Not enough arguments for "+m.group(0)); - args.add(lv.get(argIndex).getBoolean()); - argIndex++; - } - else if (fmt == '%') - { - //skip /%% - } - else - { - throw new InternalExpressionException("Format not supported: "+m.group(6)); - } - - i = m.end(); - } else { - // No more valid format specifiers. Check for possible invalid - // format specifiers. - // [[scarpet]] but we skip it and let the String.format fail - break; - } - } - try - { - return new StringValue(String.format(Locale.ROOT, format, args.toArray())); - } - catch (IllegalFormatException ife) - { - throw new InternalExpressionException("Illegal string format: "+ife.getMessage()); - } - }); - - expression.addUnaryFunction("lower", v -> new StringValue(v.getString().toLowerCase(Locale.ROOT))); - - expression.addUnaryFunction("upper", v -> new StringValue(v.getString().toUpperCase(Locale.ROOT))); - - expression.addUnaryFunction("title", v -> new StringValue(WordUtils.capitalizeFully(v.getString()))); - - expression.addFunction("replace", (lv) -> - { - if (lv.size() != 3 && lv.size() !=2) - throw new InternalExpressionException("'replace' expects string to read, pattern regex, and optional replacement string"); - String data = lv.get(0).getString(); - String regex = lv.get(1).getString(); - String replacement = ""; - if (lv.size() == 3) - replacement = lv.get(2).getString(); - return new StringValue(data.replaceAll(regex, replacement)); - }); - - expression.addFunction("replace_first", (lv) -> - { - if (lv.size() != 3 && lv.size() !=2) - throw new InternalExpressionException("'replace_first' expects string to read, pattern regex, and optional replacement string"); - String data = lv.get(0).getString(); - String regex = lv.get(1).getString(); - String replacement = ""; - if (lv.size() == 3) - replacement = lv.get(2).getString(); - return new StringValue(data.replaceFirst(regex, replacement)); - }); - - expression.addUnaryFunction("type", v -> new StringValue(v.getTypeString())); - - expression.addUnaryFunction("length", v -> new NumericValue(v.length())); - expression.addLazyFunction("rand", -1, (c, t, lv) -> { - int argsize = lv.size(); - Random randomizer = Expression.randomizer; - if (argsize != 1 && argsize != 2) - throw new InternalExpressionException("'rand' takes one (range) or two arguments (range and seed)"); - if (argsize == 2) randomizer = c.host.getRandom(NumericValue.asNumber(lv.get(1).evalValue(c)).getLong()); - Value argument = lv.get(0).evalValue(c); - if (argument instanceof ListValue) - { - List list = ((ListValue) argument).getItems(); - Value retval = list.get(randomizer.nextInt(list.size())); - return (cc, tt) -> retval; - } - if (t == Context.BOOLEAN) - { - double rv = NumericValue.asNumber(argument).getDouble()*randomizer.nextFloat(); - Value retval = rv<1.0D?Value.FALSE:Value.TRUE; - return (cc, tt) -> retval; - } - Value retval = new NumericValue(NumericValue.asNumber(argument).getDouble()*randomizer.nextDouble()); - return (cc, tt) -> retval; - }); - - expression.addLazyFunction("perlin", -1, (c, t, lv) -> { - PerlinNoiseSampler sampler; - Value x, y, z; - - if (lv.size() >= 4) - { - x = lv.get(0).evalValue(c); - y = lv.get(1).evalValue(c); - z = lv.get(2).evalValue(c); - sampler = PerlinNoiseSampler.getPerlin(NumericValue.asNumber(lv.get(3).evalValue(c)).getLong()); - } - else - { - sampler = PerlinNoiseSampler.instance; - y = Value.NULL; - z = Value.NULL; - if (lv.size() == 0 ) - throw new InternalExpressionException("'perlin' requires at least one dimension to sample from"); - x = NumericValue.asNumber(lv.get(0).evalValue(c)); - if (lv.size() > 1) - { - y = NumericValue.asNumber(lv.get(1).evalValue(c)); - if (lv.size() > 2) - z = NumericValue.asNumber(lv.get(2).evalValue(c)); - } - } - - double result; - - if (z instanceof NullValue) - if (y instanceof NullValue) - result = sampler.sample1d(NumericValue.asNumber(x).getDouble()); - else - result = sampler.sample2d(NumericValue.asNumber(x).getDouble(), NumericValue.asNumber(y).getDouble()); - else - result = sampler.sample3d( - NumericValue.asNumber(x).getDouble(), - NumericValue.asNumber(y).getDouble(), - NumericValue.asNumber(z).getDouble()); - Value ret = new NumericValue(result); - return (cc, tt) -> ret; - }); - - expression.addLazyFunction("simplex", -1, (c, t, lv) -> { - SimplexNoiseSampler sampler; - Value x, y, z; - - if (lv.size() >= 4) - { - x = lv.get(0).evalValue(c); - y = lv.get(1).evalValue(c); - z = lv.get(2).evalValue(c); - sampler = SimplexNoiseSampler.getSimplex(NumericValue.asNumber(lv.get(3).evalValue(c)).getLong()); - } - else - { - sampler = SimplexNoiseSampler.instance; - z = Value.NULL; - if (lv.size() < 2 ) - throw new InternalExpressionException("'simplex' requires at least two dimensions to sample from"); - x = NumericValue.asNumber(lv.get(0).evalValue(c)); - y = NumericValue.asNumber(lv.get(1).evalValue(c)); - if (lv.size() > 2) - z = NumericValue.asNumber(lv.get(2).evalValue(c)); - } - double result; - - if (z instanceof NullValue) - result = sampler.sample2d(NumericValue.asNumber(x).getDouble(), NumericValue.asNumber(y).getDouble()); - else - result = sampler.sample3d( - NumericValue.asNumber(x).getDouble(), - NumericValue.asNumber(y).getDouble(), - NumericValue.asNumber(z).getDouble()); - Value ret = new NumericValue(result); - return (cc, tt) -> ret; - }); - - expression.addUnaryFunction("print", (v) -> - { - System.out.println(v.getString()); - return v; // pass through for variables - }); - expression.addUnaryFunction("sleep", (v) -> - { - long time = NumericValue.asNumber(v).getLong(); - try - { - Thread.sleep(time); - Thread.yield(); - } - catch (InterruptedException ignored) { } - return v; // pass through for variables - }); - expression.addLazyFunction("time", 0, (c, t, lv) -> - { - Value time = new NumericValue((System.nanoTime() / 1000) / 1000.0); - return (cc, tt) -> time; - }); - - expression.addLazyFunction("unix_time", 0, (c, t, lv) -> - { - Value time = new NumericValue(System.currentTimeMillis()); - return (cc, tt) -> time; - }); - - expression.addFunction("convert_date", lv -> - { - int argsize = lv.size(); - if (lv.size() == 0) throw new InternalExpressionException("'convert_date' requires at least one parameter"); - Value value = lv.get(0); - if (argsize == 1 && !(value instanceof ListValue)) - { - Calendar cal = new GregorianCalendar(Locale.ROOT); - cal.setTimeInMillis(NumericValue.asNumber(value, "timestamp").getLong()); - int weekday = cal.get(Calendar.DAY_OF_WEEK)-1; - if (weekday == 0) weekday = 7; - Value retVal = ListValue.ofNums( - cal.get(Calendar.YEAR), - cal.get(Calendar.MONTH)+1, - cal.get(Calendar.DAY_OF_MONTH), - cal.get(Calendar.HOUR_OF_DAY), - cal.get(Calendar.MINUTE), - cal.get(Calendar.SECOND), - weekday, - cal.get(Calendar.DAY_OF_YEAR), - cal.get(Calendar.WEEK_OF_YEAR) - ); - return retVal; - } - else if(value instanceof ListValue) - { - lv = ((ListValue) value).getItems(); - argsize = lv.size(); - } - Calendar cal = new GregorianCalendar(0, 0, 0, 0, 0, 0); - - if (argsize == 3) - { - cal.set( - NumericValue.asNumber(lv.get(0)).getInt(), - NumericValue.asNumber(lv.get(1)).getInt()-1, - NumericValue.asNumber(lv.get(2)).getInt() - ); - } - else if (argsize == 6) - { - cal.set( - NumericValue.asNumber(lv.get(0)).getInt(), - NumericValue.asNumber(lv.get(1)).getInt()-1, - NumericValue.asNumber(lv.get(2)).getInt(), - NumericValue.asNumber(lv.get(3)).getInt(), - NumericValue.asNumber(lv.get(4)).getInt(), - NumericValue.asNumber(lv.get(5)).getInt() - ); - } - else throw new InternalExpressionException("Date conversion requires 3 arguments for Dates or 6 arguments, for time"); - return new NumericValue(cal.getTimeInMillis()); - }); - - expression.addLazyFunction("profile_expr", 1, (c, t, lv) -> - { - LazyValue lazy = lv.get(0); - long end = System.nanoTime()+50000000L; - long it = 0; - while (System.nanoTime() res; - }); - - expression.addLazyFunction("var", 1, (c, t, lv) -> { - String varname = lv.get(0).evalValue(c).getString(); - return expression.getOrSetAnyVariable(c, varname); - }); - - expression.addLazyFunction("undef", 1, (c, t, lv) -> - { - Value remove = lv.get(0).evalValue(c); - if (remove instanceof FunctionValue) - { - c.host.delFunction(expression.module, remove.getString()); - return (cc, tt) -> Value.NULL; - } - String varname = remove.getString(); - boolean isPrefix = varname.endsWith("*"); - if (isPrefix) - varname = varname.replaceAll("\\*+$", ""); - if (isPrefix) - { - c.host.delFunctionWithPrefix(expression.module, varname); - if (varname.startsWith("global_")) - { - c.host.delGlobalVariableWithPrefix(expression.module, varname); - } - else if (!varname.startsWith("_")) - { - c.removeVariablesMatching(varname); - } - } - else - { - c.host.delFunction(expression.module, varname); - if (varname.startsWith("global_")) - { - c.host.delGlobalVariable(expression.module, varname); - } - else if (!varname.startsWith("_")) - { - c.delVariable(varname); - } - } - return (cc, tt) -> Value.NULL; - }); - - //deprecate - expression.addLazyFunction("vars", 1, (c, t, lv) -> { - String prefix = lv.get(0).evalValue(c).getString(); - List values = new ArrayList<>(); - if (prefix.startsWith("global")) - { - c.host.globaVariableNames(expression.module, (s) -> s.startsWith(prefix)).forEach(s -> values.add(new StringValue(s))); - } - else - { - c.getAllVariableNames().stream().filter(s -> s.startsWith(prefix)).forEach(s -> values.add(new StringValue(s))); - } - Value retval = ListValue.wrap(values); - return (cc, tt) -> retval; - }); - - expression.addLazyFunctionWithDelegation("task", -1, (c, t, expr, tok, lv) -> { - if (lv.size() == 0) - throw new InternalExpressionException("'task' requires at least function to call as a parameter"); - FunctionArgument functionArgument = FunctionArgument.findIn(c, expression.module, lv, 0, true, false); - Value queue = Value.NULL; - if (lv.size() > functionArgument.offset) queue = lv.get(functionArgument.offset).evalValue(c); - ThreadValue thread = new ThreadValue(queue, functionArgument.function, expr, tok, c, functionArgument.args); - Thread.yield(); - return (cc, tt) -> thread; - }); - - expression.addFunction("task_count", (lv) -> - { - if (lv.size() > 0) - { - return new NumericValue(ThreadValue.taskCount(lv.get(0))); - } - return new NumericValue(ThreadValue.taskCount()); - }); - - expression.addUnaryFunction("task_value", (v) -> { - if (!(v instanceof ThreadValue)) - throw new InternalExpressionException("'task_value' could only be used with a task value"); - return ((ThreadValue) v).getValue(); - }); - - expression.addUnaryFunction("task_join", (v) -> { - if (!(v instanceof ThreadValue)) - throw new InternalExpressionException("'task_join' could only be used with a task value"); - return ((ThreadValue) v).join(); - }); - - expression.addLazyFunction("task_dock", 1, (c, t, lv) -> { - // pass through placeholder - // implmenetation should dock the task on the main thread. - return lv.get(0); - }); - - expression.addUnaryFunction("task_completed", (v) -> { - if (!(v instanceof ThreadValue)) - throw new InternalExpressionException("'task_completed' could only be used with a task value"); - return new NumericValue(((ThreadValue) v).isFinished()); - }); - - expression.addLazyFunction("synchronize", -1, (c, t, lv) -> - { - if (lv.size() == 0) throw new InternalExpressionException("'synchronize' require at least an expression to synchronize"); - Value lockValue = Value.NULL; - int ind = 0; - if (lv.size() == 2) - { - lockValue = lv.get(0).evalValue(c); - ind = 1; - } - synchronized (ThreadValue.getLock(lockValue)) - { - Value ret = lv.get(ind).evalValue(c, t); - return (_c, _t) -> ret; - } - }); - - expression.addLazyFunction("system_variable_get", -1, (c, t, lv) -> - { - if (lv.size() == 0) throw new InternalExpressionException("'system_variable_get' expects at least a key to be fetched"); - Value key = lv.get(0).evalValue(c); - if (lv.size() > 1) - { - ScriptHost.systemGlobals.computeIfAbsent(key, k -> lv.get(1).evalValue(c)); - } - Value res = ScriptHost.systemGlobals.get(key); - if (res!=null) return (cc, tt) -> res; - return (cc, tt) -> Value.NULL; - }); - - expression.addLazyFunction("system_variable_set", 2, (c, t, lv) -> - { - Value key = lv.get(0).evalValue(c); - Value value = lv.get(1).evalValue(c); - Value res = ScriptHost.systemGlobals.put(key, value); - if (res!=null) return (cc, tt) -> res; - return (cc, tt) -> Value.NULL; - }); - - - } - public void setAnyVariable(Context c, String name, LazyValue lv) { if (name.startsWith("global_")) @@ -2039,12 +402,13 @@ public class Expression public Expression(String expression) { this.expression = expression.trim().replaceAll("\\r\\n?", "\n").replaceAll("\\t"," "); - UserDefinedFunctionsAndControlFlow(this); - Operators(this); - ArithmeticOperations(this); - SystemFunctions(this); - LoopsAndHigherOrderFunctions(this); - BasicDataStructures(this); + Operators.apply(this); + ControlFlow.apply(this); + Functions.apply(this); + Arithmetic.apply(this); + Sys.apply(this); + Loops.apply(this); + DataStructures.apply(this); } diff --git a/src/main/java/carpet/script/ScriptHost.java b/src/main/java/carpet/script/ScriptHost.java index 385d07b3..abdaa8fd 100644 --- a/src/main/java/carpet/script/ScriptHost.java +++ b/src/main/java/carpet/script/ScriptHost.java @@ -3,6 +3,7 @@ package carpet.script; import carpet.script.bundled.Module; import carpet.script.exception.ExpressionException; import carpet.script.exception.InternalExpressionException; +import carpet.script.language.Arithmetic; import carpet.script.value.FunctionValue; import carpet.script.value.Value; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; @@ -20,7 +21,7 @@ import java.util.stream.Stream; public abstract class ScriptHost { public static Map systemGlobals = new ConcurrentHashMap<>(); - private static Map randomizers = new Long2ObjectOpenHashMap<>(); + private static final Map randomizers = new Long2ObjectOpenHashMap<>(); public Random getRandom(long aLong) { @@ -99,8 +100,8 @@ public abstract class ScriptHost void initializeModuleGlobals(ModuleData md) { - md.globalVariables.put("euler", (c, t) -> Expression.euler); - md.globalVariables.put("pi", (c, t) -> Expression.PI); + md.globalVariables.put("euler", (c, t) -> Arithmetic.euler); + md.globalVariables.put("pi", (c, t) -> Arithmetic.PI); md.globalVariables.put("null", (c, t) -> Value.NULL); md.globalVariables.put("true", (c, t) -> Value.TRUE); md.globalVariables.put("false", (c, t) -> Value.FALSE); diff --git a/src/main/java/carpet/script/api/Apps.java b/src/main/java/carpet/script/api/Apps.java new file mode 100644 index 00000000..75574737 --- /dev/null +++ b/src/main/java/carpet/script/api/Apps.java @@ -0,0 +1,30 @@ +package carpet.script.api; + +import carpet.script.CarpetScriptHost; +import carpet.script.Expression; +import carpet.script.value.MapValue; +import carpet.script.value.NumericValue; +import carpet.script.value.StringValue; +import carpet.script.value.Value; + +import java.util.stream.Collectors; + +public class Apps { + public static void apply(Expression expression) + { + //todo doc and test all + expression.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; + }); + + expression.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; + }); + + } +} diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java new file mode 100644 index 00000000..e4e62dc9 --- /dev/null +++ b/src/main/java/carpet/script/api/Auxiliary.java @@ -0,0 +1,936 @@ +package carpet.script.api; + +import carpet.CarpetServer; +import carpet.CarpetSettings; +import carpet.fakes.MinecraftServerInterface; +import carpet.fakes.StatTypeInterface; +import carpet.fakes.ThreadedAnvilChunkStorageInterface; +import carpet.helpers.FeatureGenerator; +import carpet.script.CarpetContext; +import carpet.script.CarpetScriptHost; +import carpet.script.Context; +import carpet.script.Expression; +import carpet.script.LazyValue; +import carpet.script.argument.BlockArgument; +import carpet.script.argument.FunctionArgument; +import carpet.script.argument.Vector3Argument; +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.ListValue; +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.script.value.ValueConversions; +import carpet.utils.Messenger; +import net.minecraft.block.BlockState; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.decoration.ArmorStandEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.StringTag; +import net.minecraft.nbt.Tag; +import net.minecraft.network.packet.s2c.play.PlaySoundIdS2CPacket; +import net.minecraft.particle.ParticleEffect; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.SoundCategory; +import net.minecraft.stat.Stat; +import net.minecraft.stat.StatType; +import net.minecraft.text.LiteralText; +import net.minecraft.util.Identifier; +import net.minecraft.util.InvalidIdentifierException; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.math.EulerAngle; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.registry.Registry; +import net.minecraft.util.registry.RegistryKey; +import net.minecraft.world.World; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.CompletionException; +import java.util.stream.Collectors; + +import static java.lang.Math.max; +import static java.lang.Math.min; + +public class Auxiliary { + public static final String MARKER_STRING = "__scarpet_marker"; + private static final Map mixerMap = Arrays.stream(SoundCategory.values()).collect(Collectors.toMap(SoundCategory::getName, k -> k)); + + 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; + } + + public static void apply(Expression expression) + { + expression.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); + }); + + expression.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; + }); + + expression.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; + }); + + expression.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 + expression.alias("particle_rect", "particle_box"); + + + expression.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; + }); + + expression.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()); + double yoffset; + if (targetBlock == null && name.isEmpty()) + { + yoffset = 0.0; + } + else if (!interactable && targetBlock == null) + { + yoffset = -0.41; + } + else + { + if (targetBlock==null) + { + yoffset = -armorstand.getHeight()-0.41; + } + else + { + yoffset = -armorstand.getHeight()+0.3; + } + } + armorstand.refreshPositionAndAngles( + pointLocator.vec.x, + //pointLocator.vec.y - ((!interactable && targetBlock == null)?0.41f:((targetBlock==null)?(armorstand.getHeight()+0.41):(armorstand.getHeight()-0.3))), + pointLocator.vec.y + yoffset, + 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; + }); + + expression.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().getEntitiesByType(EntityType.ARMOR_STAND, (as) -> as.getScoreboardTags().contains(markerName))) + { + total ++; + e.remove(); + } + int finalTotal = total; + return (_cc, _tt) -> new NumericValue(finalTotal); + }); + + expression.addLazyFunction("nbt", 1, (c, t, lv) -> { + Value ret = NBTSerializableValue.fromValue(lv.get(0).evalValue(c)); + return (cc, tt) -> ret; + }); + + expression.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; + }); + + expression.addLazyFunction("parse_nbt", 1, (c, t, lv) -> { + Value v = lv.get(0).evalValue(c); + if (v instanceof NBTSerializableValue) + { + Value parsed = ((NBTSerializableValue) v).toValue(); + return (cc, tt) -> parsed; + } + NBTSerializableValue ret = NBTSerializableValue.parseString(v.getString()); + if (ret == null) + return LazyValue.NULL; + Value parsed = ret.toValue(); + return (cc, tt) -> parsed; + }); + + expression.addLazyFunction("encode_nbt", -1, (c, t, lv) -> { + int argSize = lv.size(); + if (argSize==0 || argSize > 2) throw new InternalExpressionException("'encode_nbt' requires 1 or 2 parameters"); + Value v = lv.get(0).evalValue(c); + boolean force = (argSize > 1) && lv.get(1).evalValue(c).getBoolean(); + Tag tag; + try + { + tag = v.toTag(force); + } + catch (NBTSerializableValue.IncompatibleTypeException ignored) + { + throw new InternalExpressionException("cannot reliably encode to a tag the value of '"+ignored.val.getPrettyString()+"'"); + } + Value tagValue = new NBTSerializableValue(tag); + return (cc, tt) -> tagValue; + }); + + //"overridden" native call that prints to stderr + expression.addLazyFunction("print", -1, (c, t, lv) -> + { + if (lv.size() == 0 || lv.size() > 2) throw new InternalExpressionException("'print' takes one or two arguments"); + ServerCommandSource s = ((CarpetContext)c).s; + Value res = lv.get(0).evalValue(c); + if (lv.size() == 2) + { + ServerPlayerEntity player = EntityValue.getPlayerByValue(s.getMinecraftServer(), res); + s = player.getCommandSource(); + res = lv.get(1).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()); + } + } + Value finalRes = res; + return (_c, _t) -> finalRes; // pass through for variables + }); + + expression.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 + expression.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; + }); + + expression.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. + }); + + expression.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; + try + { + 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; + } + catch (Exception ignored) {} + return LazyValue.NULL; + }); + + expression.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; + }); + + expression.addLazyFunction("tick_time", 0, (c, t, lv) -> + { + Value time = new NumericValue(((CarpetContext) c).s.getMinecraftServer().getTicks()); + return (cc, tt) -> time; + }); + + expression.addLazyFunction("world_time", 0, (c, t, lv) -> + { + Value time = new NumericValue(((CarpetContext) c).s.getWorld().getTime()); + return (cc, tt) -> time; + }); + + expression.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);// setTimeOfDay(newTime); + } + return (cc, tt) -> time; + }); + + expression.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; + }); + + + expression.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; + }); + + expression.addLazyFunction("seed", -1, (c, t, lv) -> { + ServerCommandSource s = ((CarpetContext)c).s; + Value ret = new NumericValue(s.getWorld().getSeed()); + return (cc, tt) -> ret; + }); + + expression.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(); + ((ThreadedAnvilChunkStorageInterface) world.getChunkManager().threadedAnvilChunkStorage).relightChunk(new ChunkPos(pos)); + WorldAccess.forceChunkUpdate(pos, world); + return LazyValue.TRUE; + }); + + expression.addLazyFunction("current_dimension", 0, (c, t, lv) -> { + ServerCommandSource s = ((CarpetContext)c).s; + Value retval = ValueConversions.dimName(s.getWorld()); + return (cc, tt) -> retval; + }); + + expression.addLazyFunction("view_distance", 0, (c, t, lv) -> { + ServerCommandSource s = ((CarpetContext)c).s; + Value retval = new NumericValue(s.getMinecraftServer().getPlayerManager().getViewDistance()); + return (cc, tt) -> retval; + }); + + expression.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(World.NETHER)); //nether + break; + case "end": + case "the_end": + innerSource = outerSource.withWorld(outerSource.getMinecraftServer().getWorld(World.END)); //end + break; + case "overworld": + case "over_world": + innerSource = outerSource.withWorld(outerSource.getMinecraftServer().getWorld(World.OVERWORLD)); //ow + break; + default: + RegistryKey dim = null; + Identifier id = new Identifier(dimString); + // not using RegistryKey.of since that one creates on check + for (RegistryKey world : (outerSource.getMinecraftServer().getWorldRegistryKeys())) + { + if (id.equals(world.getValue())) + { + dim = world; + break; + } + } + if (dim == null) + throw new InternalExpressionException("Incorrect dimension string: "+dimString); + innerSource = outerSource.withWorld(outerSource.getMinecraftServer().getWorld(dim)); + } + }; + if (innerSource.getWorld() == outerSource.getWorld()) return lv.get(1); + Context newCtx = c.recreate(); + ((CarpetContext) newCtx).s = innerSource; + newCtx.variables = c.variables; + Value retval = lv.get(1).evalValue(newCtx); + return (cc, tt) -> retval; + }); + + expression.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.plop(what, ((CarpetContext) c).s.getWorld(), locator.block.getPos()); + + //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 + WorldAccess.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; + }); + + expression.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, expression.module, lv, 1, true, true); + + CarpetServer.scriptServer.events.scheduleCall( + (CarpetContext) c, + functionArgument.function, + functionArgument.resolveArgs(c, Context.NONE), + delay + ); + return (c_, t_) -> Value.TRUE; + }); + + expression.addLazyFunction("logger", -1, (c, t, lv) -> + { + //CarpetSettings.LOG.error("Standard Structures:"); + //CarpetSettings.LOG.error(Registry.STRUCTURE_FEATURE.getIds().stream().map(i -> "`'"+NBTSerializableValue.nameFromRegistryId(i)+"'`").sorted(). collect(Collectors.joining(", "))); + //CarpetSettings.LOG.error(""); + //CarpetSettings.LOG.error("Configured Structures:"); + //CarpetSettings.LOG.error(BuiltinRegistries.CONFIGURED_STRUCTURE_FEATURE.getIds().stream().map(i -> "`'"+NBTSerializableValue.nameFromRegistryId(i)+"'`").sorted().collect(Collectors.joining(", "))); + //CarpetSettings.LOG.error(""); + //CarpetSettings.LOG.error("Features:"); + //CarpetSettings.LOG.error(Registry.FEATURE.getIds().stream().map(i -> "`'"+NBTSerializableValue.nameFromRegistryId(i)+"'`").sorted().collect(Collectors.joining(", "))); + //CarpetSettings.LOG.error(""); + //CarpetSettings.LOG.error("Configured Features:"); + //CarpetSettings.LOG.error(BuiltinRegistries.CONFIGURED_FEATURE.getIds().stream().map(i -> "`'"+NBTSerializableValue.nameFromRegistryId(i)+"'`").sorted().collect(Collectors.joining(", "))); + //CarpetSettings.LOG.error(""); + //CarpetSettings.LOG.error("Custom:"); + //CarpetSettings.LOG.error(FeatureGenerator.featureMap.keySet().stream().map(i -> "`'"+i+"'`").sorted().collect(Collectors.joining(", "))); + + //CarpetSettings.LOG.error(Registry.ENTITY_TYPE.getIds().stream().sorted().map(ValueConversions::simplify).collect(Collectors.joining("`, `"))); + + Value res; + + if(lv.size()==1) + { + res = lv.get(0).evalValue(c); + CarpetSettings.LOG.info(res.getString()); + } + else if(lv.size()==2) + { + String level = lv.get(0).evalValue(c).getString().toLowerCase(Locale.ROOT); + res = lv.get(1).evalValue(c); + switch(level){ + case "error": CarpetSettings.LOG.error(res.getString()); break; + case "warn": CarpetSettings.LOG.warn(res.getString()); break; + case "debug": CarpetSettings.LOG.debug(res.getString()); break; + case "fatal": CarpetSettings.LOG.fatal(res.getString()); break; + case "info": CarpetSettings.LOG.info(res.getString()); break; + default: throw new InternalExpressionException("Unknown log level for 'logger': "+level); + } + } + else throw new InternalExpressionException("logger takes 1 or 2 arguments"); + + return (_c, _t) -> res; // pass through for variables + }); + + expression.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; + }); + + expression.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; + }); + + expression.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 + + expression.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; + }); + + expression.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; + }); + + expression.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 static 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); + } +} diff --git a/src/main/java/carpet/script/api/BlockIterators.java b/src/main/java/carpet/script/api/BlockIterators.java new file mode 100644 index 00000000..03582021 --- /dev/null +++ b/src/main/java/carpet/script/api/BlockIterators.java @@ -0,0 +1,513 @@ +package carpet.script.api; + +import carpet.script.CarpetContext; +import carpet.script.Context; +import carpet.script.Expression; +import carpet.script.LazyValue; +import carpet.script.argument.BlockArgument; +import carpet.script.argument.Vector3Argument; +import carpet.script.exception.BreakStatement; +import carpet.script.exception.ContinueStatement; +import carpet.script.exception.InternalExpressionException; +import carpet.script.value.BlockValue; +import carpet.script.value.LazyListValue; +import carpet.script.value.ListValue; +import carpet.script.value.NumericValue; +import carpet.script.value.Value; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3i; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import static java.lang.Math.abs; +import static java.lang.Math.max; +import static java.lang.Math.min; + +public class BlockIterators { + public static void apply(Expression expression) + { + expression.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); + }); + + expression.addLazyFunction("volume", -1, (c, t, lv) -> + { + CarpetContext cc = (CarpetContext)c; + + BlockArgument pos1Locator = BlockArgument.findIn(cc, lv, 0); + BlockArgument pos2Locator = BlockArgument.findIn(cc, lv, pos1Locator.offset); + BlockPos pos1 = pos1Locator.block.getPos(); + BlockPos pos2 = pos2Locator.block.getPos(); + + int x1 = pos1.getX(); + int y1 = pos1.getY(); + int z1 = pos1.getZ(); + int x2 = pos2.getX(); + int y2 = pos2.getY(); + int z2 = pos2.getZ(); + int minx = min(x1, x2); + int miny = min(y1, y2); + int minz = min(z1, z2); + int maxx = max(x1, x2); + int maxy = max(y1, y2); + int maxz = max(z1, z2); + LazyValue expr = lv.get(pos2Locator.offset); + + //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); + }); + + expression.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; + }); + + expression.addLazyFunction("rect", -1, (c, t, lv)-> + { + CarpetContext cc = (CarpetContext) c; + int cx, cy, cz; + int sminx, sminy, sminz; + int smaxx, smaxy, smaxz; + BlockArgument cposLocator = BlockArgument.findIn(cc, lv, 0); + BlockPos cpos = cposLocator.block.getPos(); + cx = cpos.getX(); + cy = cpos.getY(); + cz = cpos.getZ(); + if (lv.size() > cposLocator.offset) + { + Vector3Argument diffLocator = Vector3Argument.findIn(cc, lv, cposLocator.offset); + if (diffLocator.fromBlock) + { + sminx = MathHelper.floor(abs(diffLocator.vec.x - cx)); + sminy = MathHelper.floor(abs(diffLocator.vec.y - cx)); + sminz = MathHelper.floor(abs(diffLocator.vec.z - cx)); + } + else + { + sminx = MathHelper.floor(abs(diffLocator.vec.x)); + sminy = MathHelper.floor(abs(diffLocator.vec.y)); + sminz = MathHelper.floor(abs(diffLocator.vec.z)); + } + if (lv.size() > diffLocator.offset) + { + Vector3Argument posDiff = Vector3Argument.findIn(cc, lv, diffLocator.offset); + if (posDiff.fromBlock) + { + smaxx = MathHelper.floor(abs(posDiff.vec.x - cx)); + smaxy = MathHelper.floor(abs(posDiff.vec.y - cx)); + smaxz = MathHelper.floor(abs(posDiff.vec.z - cx)); + } + else + { + smaxx = MathHelper.floor(abs(posDiff.vec.x)); + smaxy = MathHelper.floor(abs(posDiff.vec.y)); + smaxz = MathHelper.floor(abs(posDiff.vec.z)); + } + } + else + { + smaxx = sminx; + smaxy = sminy; + smaxz = sminz; + } + } + else + { + sminx = 1; + sminy = 1; + sminz = 1; + smaxx = 1; + smaxy = 1; + smaxz = 1; + } + + 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); + } + }; + }); + + expression.addLazyFunction("diamond", -1, (c, t, lv)-> + { + CarpetContext cc = (CarpetContext)c; + + BlockArgument cposLocator=BlockArgument.findIn((CarpetContext)c,lv,0); + BlockPos cpos = cposLocator.block.getPos(); + + int cx; + int cy; + int cz; + int width; + int height; + try + { + cx = cpos.getX(); + cy = cpos.getY(); + cz = cpos.getZ(); + + if (lv.size()==cposLocator.offset) + { + 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()==1+cposLocator.offset) + { + width = (int) ((NumericValue) lv.get(cposLocator.offset).evalValue(c)).getLong(); + height = 0; + } + else if(lv.size()==2+cposLocator.offset) + { + width = (int) ((NumericValue) lv.get(cposLocator.offset).evalValue(c)).getLong(); + height = (int) ((NumericValue) lv.get(cposLocator.offset+1).evalValue(c)).getLong(); + } else{ + throw new InternalExpressionException("Incorrect number of arguments for 'diamond'"); + } + } + 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); + } + }; + } + }); + } +} diff --git a/src/main/java/carpet/script/api/Entities.java b/src/main/java/carpet/script/api/Entities.java new file mode 100644 index 00000000..252a4946 --- /dev/null +++ b/src/main/java/carpet/script/api/Entities.java @@ -0,0 +1,293 @@ +package carpet.script.api; + +import carpet.script.CarpetContext; +import carpet.script.Expression; +import carpet.script.LazyValue; +import carpet.script.argument.Vector3Argument; +import carpet.script.exception.InternalExpressionException; +import carpet.script.value.EntityValue; +import carpet.script.value.FunctionValue; +import carpet.script.value.ListValue; +import carpet.script.value.NBTSerializableValue; +import carpet.script.value.NullValue; +import carpet.script.value.NumericValue; +import carpet.script.value.Value; +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.SpawnReason; +import net.minecraft.entity.mob.MobEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.predicate.entity.EntityPredicates; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.registry.Registry; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public class Entities { + public static void apply(Expression expression) + { + expression.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; + }); + + expression.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()); + hasTag = true; + tag = v.getCompoundTag(); + + } + tag.putString("id", entityId.toString()); + Vec3d vec3d = position.vec; + + 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()), SpawnReason.COMMAND, null, null); + } + Value res = new EntityValue(entity_1); + return (_c, _t) -> res; + } + }); + + expression.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); + }); + + expression.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().getEntitiesByType(pair.getKey(), pair.getValue()); + Value retval = ListValue.wrap(entityList.stream().map(EntityValue::new).collect(Collectors.toList())); + return (_c, _t ) -> retval; + }); + + expression.addLazyFunction("entity_area", 7, (c, t, lv) -> { + Vec3d center = new Vec3d( + 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, 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().getEntitiesByType((EntityType) pair.getKey(), area, pair.getValue()); + Value retval = ListValue.wrap(entityList.stream().map(EntityValue::new).collect(Collectors.toList())); + return (_c, _t ) -> retval; + }); + + expression.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); + }); + + expression.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 + expression.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 + expression.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(expression.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; + }); + } +} diff --git a/src/main/java/carpet/script/api/Inventories.java b/src/main/java/carpet/script/api/Inventories.java new file mode 100644 index 00000000..37134c22 --- /dev/null +++ b/src/main/java/carpet/script/api/Inventories.java @@ -0,0 +1,427 @@ +package carpet.script.api; + +import carpet.fakes.IngredientInterface; +import carpet.fakes.RecipeManagerInterface; +import carpet.script.CarpetContext; +import carpet.script.Expression; +import carpet.script.LazyValue; +import carpet.script.exception.InternalExpressionException; +import carpet.script.value.ListValue; +import carpet.script.value.NBTSerializableValue; +import carpet.script.value.NullValue; +import carpet.script.value.NumericValue; +import carpet.script.value.StringValue; +import carpet.script.value.Value; +import com.google.common.collect.Sets; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.command.argument.ItemStackArgument; +import net.minecraft.entity.ItemEntity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket; +import net.minecraft.recipe.AbstractCookingRecipe; +import net.minecraft.recipe.CuttingRecipe; +import net.minecraft.recipe.Recipe; +import net.minecraft.recipe.RecipeType; +import net.minecraft.recipe.ShapedRecipe; +import net.minecraft.recipe.ShapelessRecipe; +import net.minecraft.recipe.SpecialCraftingRecipe; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Identifier; +import net.minecraft.util.InvalidIdentifierException; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.registry.Registry; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class Inventories { + public static void apply(Expression expression) + { + expression.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; + }); + + expression.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; + }); + + expression.addLazyFunction("recipe_data", -1, (c, t, lv) -> + { + CarpetContext cc = (CarpetContext)c; + if (lv.size() < 1) throw new InternalExpressionException("'recipe_data' requires at least one argument"); + String recipeName = lv.get(0).evalValue(c).getString(); + RecipeType type = RecipeType.CRAFTING; + if (lv.size() > 1) + { + String recipeType = lv.get(1).evalValue(c).getString(); + try + { + type = Registry.RECIPE_TYPE.get(new Identifier(recipeType)); + } + catch (InvalidIdentifierException ignored) + { + throw new InternalExpressionException("Unknown crafting category: " + recipeType); + } + } + List> recipes; + try + { + recipes = ((RecipeManagerInterface) cc.s.getMinecraftServer().getRecipeManager()).getAllMatching(type, new Identifier(recipeName)); + } + catch (InvalidIdentifierException ignored) + { + return LazyValue.NULL; + } + if (recipes.isEmpty()) + return LazyValue.NULL; + List recipesOutput = new ArrayList<>(); + for (Recipe recipe: recipes) + { + ItemStack result = recipe.getOutput(); + List ingredientValue = new ArrayList<>(); + recipe.getPreviewInputs().forEach( + ingredient -> + { + // I am flattening ingredient lists per slot. + // consider recipe_data('wooden_sword','crafting') and ('iron_nugget', 'blasting') and notice difference + // in depths of lists. + List> stacks = ((IngredientInterface) (Object) ingredient).getRecipeStacks(); + if (stacks.isEmpty()) + { + ingredientValue.add(Value.NULL); + } + else + { + List alternatives = new ArrayList<>(); + stacks.forEach(col -> col.stream().map(ListValue::fromItemStack).forEach(alternatives::add)); + ingredientValue.add(ListValue.wrap(alternatives)); + } + } + ); + Value recipeSpec; + if (recipe instanceof ShapedRecipe) + { + recipeSpec = ListValue.of( + new StringValue("shaped"), + new NumericValue(((ShapedRecipe) recipe).getWidth()), + new NumericValue(((ShapedRecipe) recipe).getHeight()) + ); + } + else if (recipe instanceof ShapelessRecipe) + { + recipeSpec = ListValue.of(new StringValue("shapeless")); + } + else if (recipe instanceof AbstractCookingRecipe) + { + recipeSpec = ListValue.of( + new StringValue("smelting"), + new NumericValue(((AbstractCookingRecipe) recipe).getCookTime()), + new NumericValue(((AbstractCookingRecipe) recipe).getExperience()) + ); + } + else if (recipe instanceof CuttingRecipe) + { + recipeSpec = ListValue.of(new StringValue("cutting")); + } + else if (recipe instanceof SpecialCraftingRecipe) + { + recipeSpec = ListValue.of(new StringValue("special")); + } + else + { + recipeSpec = ListValue.of(new StringValue("custom")); + } + + recipesOutput.add(ListValue.of(ListValue.fromItemStack(result), ListValue.wrap(ingredientValue), recipeSpec)); + } + Value ret = ListValue.wrap(recipesOutput); + return (_c, _t) -> ret; + }); + + expression.addLazyFunction("crafting_remaining_item", 1, (c, t, lv) -> + { + String itemStr = lv.get(0).evalValue(c).getString(); + Item item; + try + { + Identifier id = new Identifier(itemStr); + item = Registry.ITEM.get(id); + if (item == Items.AIR && !id.getPath().equalsIgnoreCase("air")) + throw new InvalidIdentifierException("boo"); + } + catch (InvalidIdentifierException ignored) + { + throw new InternalExpressionException("Incorrect item: "+itemStr); + } + if (!item.hasRecipeRemainder()) return LazyValue.NULL; + Value ret = new StringValue(NBTSerializableValue.nameFromRegistryId(Registry.ITEM.getId(item.getRecipeRemainder()))); + return (_c, _t ) -> ret; + }); + + + expression.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.size()); + return (_c, _t) -> res; + }); + + expression.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.isEmpty()); + return (_c, _t) -> res; + }); + + //inventory_get(, ) -> item_triple + expression.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.size(); i < maxi; i++) + { + fullInventory.add(ListValue.fromItemStack(inventoryLocator.inventory.getStack(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.size()) + return (_c, _t) -> Value.NULL; + Value res = ListValue.fromItemStack(inventoryLocator.inventory.getStack(slot)); + return (_c, _t) -> res; + }); + + //inventory_set(, , , , ) + expression.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.size()) + 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.removeStack(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.getStack(slot); + ItemStack newStack = previousStack.copy(); + newStack.setCount(count); + inventoryLocator.inventory.setStack(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.getStack(slot); + try + { + inventoryLocator.inventory.setStack(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 + expression.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.size(); i < maxi; i++) + { + ItemStack stack = inventoryLocator.inventory.getStack(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 + expression.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.containsAny(Sets.newHashSet(searchItem.getItem())))) + || (inventoryLocator.inventory.count(searchItem.getItem()) < amount)) + { + return (_c, _t) -> Value.FALSE; + } + for (int i = 0, maxi = inventoryLocator.inventory.size(); i < maxi; i++) + { + ItemStack stack = inventoryLocator.inventory.getStack(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.setStack(i, stack); + syncPlayerInventory(inventoryLocator, i); + return (_c, _t) -> Value.TRUE; + } + else + { + inventoryLocator.inventory.removeStack(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 + expression.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.size()) + 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.getStack(slot); + if (stack == null || stack.isEmpty()) + return (_c, _t) -> Value.ZERO; + if (amount == 0) + amount = stack.getCount(); + ItemStack droppedStack = inventoryLocator.inventory.removeStack(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 = Vec3d.ofCenter(inventoryLocator.position); //pos+0.5v + item = new ItemEntity(cc.s.getWorld(), point.x, point.y, point.z, droppedStack); + item.setToDefaultPickupDelay(); + cc.s.getWorld().spawnEntity(item); + } + Value res = new NumericValue(item.getStack().getCount()); + return (_c, _t) -> res; + }); + } + + private static void syncPlayerInventory(NBTSerializableValue.InventoryLocator inventory, int int_1) + { + if (inventory.owner instanceof ServerPlayerEntity && !inventory.isEnder) + { + ServerPlayerEntity player = (ServerPlayerEntity) inventory.owner; + player.networkHandler.sendPacket(new ScreenHandlerSlotUpdateS2CPacket( + -2, + int_1, + inventory.inventory.getStack(int_1) + )); + } + } +} diff --git a/src/main/java/carpet/script/api/Monitoring.java b/src/main/java/carpet/script/api/Monitoring.java new file mode 100644 index 00000000..5f7bd4bb --- /dev/null +++ b/src/main/java/carpet/script/api/Monitoring.java @@ -0,0 +1,63 @@ +package carpet.script.api; + +import carpet.script.CarpetContext; +import carpet.script.Expression; +import carpet.script.LazyValue; +import carpet.script.exception.InternalExpressionException; +import carpet.script.value.ListValue; +import carpet.script.value.MapValue; +import carpet.script.value.NumericValue; +import carpet.script.value.StringValue; +import carpet.script.value.Value; +import carpet.utils.SpawnReporter; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import net.minecraft.entity.SpawnGroup; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.world.SpawnHelper; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +public class Monitoring { + public static void apply(Expression expression) + { + // game processed snooper functions + expression.addLazyFunction("get_mob_counts", -1, (c, t, lv) -> + { + CarpetContext cc = (CarpetContext)c; + ServerWorld world = cc.s.getWorld(); + SpawnHelper.Info info = world.getChunkManager().getSpawnInfo(); + if (info == null) return LazyValue.NULL; + Object2IntMap mobcounts = info.getGroupToCount(); + int chunks = info.getSpawningChunkCount(); + Value retVal; + if (lv.size() == 0) + { + Map retDict = new HashMap<>(); + for (SpawnGroup category: mobcounts.keySet()) + { + int currentCap = (int)(category.getCapacity() * chunks / SpawnReporter.currentMagicNumber()); // MAGIC_NUMBER + retDict.put( + new StringValue(category.asString().toLowerCase(Locale.ROOT)), + ListValue.of( + new NumericValue(mobcounts.getInt(category)), + new NumericValue(currentCap)) + ); + } + retVal = MapValue.wrap(retDict); + } + else + { + String catString = lv.get(0).evalValue(c).getString(); + SpawnGroup cat = SpawnGroup.byName(catString.toLowerCase(Locale.ROOT)); + if (cat == null) throw new InternalExpressionException("Unreconized mob category: "+catString); + retVal = ListValue.of( + new NumericValue(mobcounts.getInt(cat)), + new NumericValue((int)(cat.getCapacity() * chunks / SpawnReporter.currentMagicNumber())) + ); + } + return (_c, _t) -> retVal; + }); + } +} diff --git a/src/main/java/carpet/script/api/Scoreboards.java b/src/main/java/carpet/script/api/Scoreboards.java new file mode 100644 index 00000000..4508daaf --- /dev/null +++ b/src/main/java/carpet/script/api/Scoreboards.java @@ -0,0 +1,149 @@ +package carpet.script.api; + +import carpet.script.CarpetContext; +import carpet.script.Expression; +import carpet.script.LazyValue; +import carpet.script.exception.InternalExpressionException; +import carpet.script.value.EntityValue; +import carpet.script.value.ListValue; +import carpet.script.value.NullValue; +import carpet.script.value.NumericValue; +import carpet.script.value.StringValue; +import carpet.script.value.Value; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.scoreboard.Scoreboard; +import net.minecraft.scoreboard.ScoreboardCriterion; +import net.minecraft.scoreboard.ScoreboardObjective; +import net.minecraft.scoreboard.ScoreboardPlayerScore; +import net.minecraft.text.LiteralText; + +import java.util.stream.Collectors; + +public class Scoreboards { + 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(); + } + } + + public static void apply(Expression expression) + { + // scoreboard(player,'objective') + // scoreboard(player, objective, newValue) + expression.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; + }); + + expression.addLazyFunction("scoreboard_remove", -1, (c, t, lv)-> { + if (lv.size()==0) throw new InternalExpressionException("'scoreboard_remove' requires at least one parameter"); + CarpetContext cc = (CarpetContext)c; + Scoreboard scoreboard = cc.s.getMinecraftServer().getScoreboard(); + String objectiveName = lv.get(0).evalValue(c).getString(); + ScoreboardObjective objective = scoreboard.getObjective(objectiveName); + if (objective == null) + return LazyValue.FALSE; + if (lv.size() == 1) + { + 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') + + expression.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; + }); + + expression.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); + }); + } +} diff --git a/src/main/java/carpet/script/api/WorldAccess.java b/src/main/java/carpet/script/api/WorldAccess.java new file mode 100644 index 00000000..1ab0a7ff --- /dev/null +++ b/src/main/java/carpet/script/api/WorldAccess.java @@ -0,0 +1,1385 @@ +package carpet.script.api; + +import carpet.CarpetServer; +import carpet.CarpetSettings; +import carpet.fakes.BiomeArrayInterface; +import carpet.fakes.ChunkGeneratorInterface; +import carpet.fakes.ChunkTicketManagerInterface; +import carpet.fakes.ServerChunkManagerInterface; +import carpet.fakes.SpawnHelperInnerInterface; +import carpet.fakes.ThreadedAnvilChunkStorageInterface; +import carpet.helpers.FeatureGenerator; +import carpet.mixins.PointOfInterest_scarpetMixin; +import carpet.script.CarpetContext; +import carpet.script.Context; +import carpet.script.Expression; +import carpet.script.Fluff; +import carpet.script.LazyValue; +import carpet.script.argument.BlockArgument; +import carpet.script.argument.Vector3Argument; +import carpet.script.exception.InternalExpressionException; +import carpet.script.utils.WorldTools; +import carpet.script.value.BlockValue; +import carpet.script.value.EntityValue; +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.Value; +import carpet.utils.BlockInfo; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongSet; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.block.CommandBlock; +import net.minecraft.block.JigsawBlock; +import net.minecraft.block.StructureBlock; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.command.argument.ItemStackArgument; +import net.minecraft.enchantment.EnchantmentHelper; +import net.minecraft.enchantment.Enchantments; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.FallingBlockEntity; +import net.minecraft.entity.ai.pathing.NavigationType; +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.nbt.CompoundTag; +import net.minecraft.nbt.Tag; +import net.minecraft.network.packet.s2c.play.ChunkDataS2CPacket; +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.state.StateManager; +import net.minecraft.state.property.Property; +import net.minecraft.structure.StructurePiece; +import net.minecraft.structure.StructureStart; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Clearable; +import net.minecraft.util.Identifier; +import net.minecraft.util.collection.SortedArraySet; +import net.minecraft.util.math.BlockBox; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.registry.Registry; +import net.minecraft.world.Heightmap; +import net.minecraft.world.LightType; +import net.minecraft.world.SpawnHelper; +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.gen.ChunkRandom; +import net.minecraft.world.gen.chunk.ChunkGenerator; +import net.minecraft.world.gen.feature.ConfiguredStructureFeature; +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 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.function.BiFunction; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static carpet.script.utils.WorldTools.canHasChunk; + +public class WorldAccess { + private static final Map DIRECTION_MAP = Arrays.stream(Direction.values()).collect(Collectors.toMap(Direction::getName, (direction) -> direction)); + private final static Map> ticketTypes = new HashMap>(){{ + put("portal", ChunkTicketType.PORTAL); + put("teleport", ChunkTicketType.POST_TELEPORT); + put("unknown", ChunkTicketType.UNKNOWN); // unknown + }}; + // dummy entity for dummy requirements in the loot tables (see snowball) + private static FallingBlockEntity DUMMY_ENTITY = new FallingBlockEntity(EntityType.FALLING_BLOCK, null); + + public static void forceChunkUpdate(BlockPos pos, ServerWorld world) + { + WorldChunk worldChunk = world.getChunkManager().getWorldChunk(pos.getX()>>4, pos.getZ()>>4, false); + if (worldChunk != null) + { + int vd = world.getServer().getPlayerManager().getViewDistance() * 16; + int vvd = vd * vd; + List nearbyPlayers = world.getPlayers(p -> pos.getSquaredDistance(p.getX(), pos.getY(), p.getZ(), true) < vvd); + if (!nearbyPlayers.isEmpty()) + { + ChunkDataS2CPacket packet = new ChunkDataS2CPacket(worldChunk, 65535); + ChunkPos chpos = new ChunkPos(pos); + nearbyPlayers.forEach(p -> p.networkHandler.sendPacket(packet)); + } + } + } + + private static 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 static 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 static LazyValue genericStateTest( + Context c, + String name, + List params, + Fluff.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 static > 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 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 would 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.isBlockBreakingRestricted(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 static void BooYah(ChunkGenerator generator) + { + synchronized (generator) + { + ((ChunkGeneratorInterface)generator).initStrongholds(); + } + } + + public static void apply(Expression expression) + { + expression.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; + }); + + expression.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?, column_mode?) + expression.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.getInCircle( + 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; + } + int radius = NumericValue.asNumber(lv.get(locator.offset+0).evalValue(c)).getInt(); + if (radius < 0) return ListValue.lazyEmpty(); + Predicate condition = PointOfInterestType.ALWAYS_TRUE; + PointOfInterestStorage.OccupationStatus status = PointOfInterestStorage.OccupationStatus.ANY; + boolean inColumn = false; + 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; + } + 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 if (!("any".equals(statusString))) + throw new InternalExpressionException( + "Incorrect POI occupation status "+status+ " use `any`, " + "`occupied` or `available`" + ); + if (locator.offset + 3 < lv.size()) + { + inColumn = lv.get(locator.offset+3).evalValue(c, Context.BOOLEAN).getBoolean(); + } + } + } + Stream pois = inColumn? + store.getInSquare(condition, pos, radius, status): + store.getInCircle(condition, pos, radius, status); + Value ret = ListValue.wrap(pois.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?, + expression.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.getInSquare((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; + }); + + + expression.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"); + } + }); + + expression.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; + }); + + expression.addLazyFunction("solid", -1, (c, t, lv) -> + genericStateTest(c, "solid", lv, (s, p, w) -> new NumericValue(s.isSolidBlock(w, p)))); // isSimpleFullBlock + + expression.addLazyFunction("air", -1, (c, t, lv) -> + booleanStateTest(c, "air", lv, (s, p) -> s.isAir())); + + expression.addLazyFunction("liquid", -1, (c, t, lv) -> + booleanStateTest(c, "liquid", lv, (s, p) -> !s.getFluidState().isEmpty())); + + expression.addLazyFunction("flammable", -1, (c, t, lv) -> + booleanStateTest(c, "flammable", lv, (s, p) -> s.getMaterial().isBurnable())); + + expression.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 + + expression.addLazyFunction("emitted_light", -1, (c, t, lv) -> + genericStateTest(c, "emitted_light", lv, (s, p, w) -> new NumericValue(s.getLuminance()))); + + expression.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))))); + + expression.addLazyFunction("block_light", -1, (c, t, lv) -> + genericStateTest(c, "block_light", lv, (s, p, w) -> new NumericValue(w.getLightLevel(LightType.BLOCK, p)))); + + expression.addLazyFunction("sky_light", -1, (c, t, lv) -> + genericStateTest(c, "sky_light", lv, (s, p, w) -> new NumericValue(w.getLightLevel(LightType.SKY, p)))); + + expression.addLazyFunction("see_sky", -1, (c, t, lv) -> + genericStateTest(c, "see_sky", lv, (s, p, w) -> new NumericValue(w.isSkyVisible(p)))); + + expression.addLazyFunction("brightness", -1, (c, t, lv) -> + genericStateTest(c, "brightness", lv, (s, p, w) -> new NumericValue(w.getBrightness(p)))); + + expression.addLazyFunction("hardness", -1, (c, t, lv) -> + genericStateTest(c, "hardness", lv, (s, p, w) -> new NumericValue(s.getHardness(w, p)))); + + expression.addLazyFunction("blast_resistance", -1, (c, t, lv) -> + genericStateTest(c, "blast_resistance", lv, (s, p, w) -> new NumericValue(s.getBlock().getBlastResistance()))); + + expression.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.getSlimeRandom( + chunkPos.x, chunkPos.z, + ((CarpetContext)c).s.getWorld().getSeed(), + 987234911L + ).nextInt(10) == 0); + return (_c, _t) -> ret; + }); + + + expression.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); + }); + + expression.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 + expression.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; + }); + + expression.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; + }); + + expression.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; + }); + + expression.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; + }); + + expression.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; + }); + + expression.addLazyFunction("suffocates", -1, (c, t, lv) -> + genericStateTest(c, "suffocates", lv, (s, p, w) -> new NumericValue(s.shouldSuffocate(w, p)))); // canSuffocate + + expression.addLazyFunction("power", -1, (c, t, lv) -> + genericStateTest(c, "power", lv, (s, p, w) -> new NumericValue(w.getReceivedRedstonePower(p)))); + + expression.addLazyFunction("ticks_randomly", -1, (c, t, lv) -> + booleanStateTest(c, "ticks_randomly", lv, (s, p) -> s.hasRandomTicks())); + + expression.addLazyFunction("update", -1, (c, t, lv) -> + booleanStateTest(c, "update", lv, (s, p) -> + { + ((CarpetContext) c).s.getWorld().updateNeighbor(p, s.getBlock(), p); + return true; + })); + + expression.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; + })); + + expression.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; + })); + + expression.addLazyFunction("without_updates", 1, (c, t, lv) -> + { + boolean previous = CarpetSettings.impendingFillSkipUpdates; + if (previous) return lv.get(0); + Value [] result = new Value[]{Value.NULL}; + ((CarpetContext)c).s.getMinecraftServer().submitAndJoin( () -> + { + try + { + CarpetSettings.impendingFillSkipUpdates = true; + result[0] = lv.get(0).evalValue(c, t); + } + finally + { + CarpetSettings.impendingFillSkipUpdates = previous; + } + }); + return (cc, tt) -> result[0]; + }); + + expression.addLazyFunction("set", -1, (c, t, lv) -> + { + CarpetContext cc = (CarpetContext)c; + ServerWorld world = cc.s.getWorld(); + 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()); + CompoundTag data = null; + if (lv.size() > sourceLocator.offset) + { + List args = new ArrayList<>(); + for (int i = sourceLocator.offset, m = lv.size(); i < m; i++) + { + args.add(lv.get(i).evalValue(c)); + } + if (args.get(0) instanceof ListValue) + { + if (args.size() == 2) + { + Value dataValue = NBTSerializableValue.fromValue( args.get(1)); + if (dataValue instanceof NBTSerializableValue) + { + data = ((NBTSerializableValue) dataValue).getCompoundTag(); + } + } + args = ((ListValue) args.get(0)).getItems(); + } + else + { + if ((args.size() & 1) == 1) + { + Value dataValue = NBTSerializableValue.fromValue( args.get(args.size()-1)); + if (dataValue instanceof NBTSerializableValue) + { + data = ((NBTSerializableValue) dataValue).getCompoundTag(); + } + } + } + StateManager states = sourceBlockState.getBlock().getStateManager(); + for (int i = 0; i < args.size()-1; i += 2) + { + String paramString = args.get(i).getString(); + Property property = states.getProperty(paramString); + if (property == null) + throw new InternalExpressionException("Property " + paramString + " doesn't apply to " + sourceLocator.block.getString()); + String paramValue = args.get(i + 1).getString(); + sourceBlockState = setProperty(property, paramString, paramValue, sourceBlockState); + } + } + + if (data == null) data = sourceLocator.block.getData(); + CompoundTag finalData = data; + + 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 (finalData != null) + { + BlockEntity be = world.getBlockEntity(targetPos); + if (be != null) + { + CompoundTag destTag = finalData.copy(); + destTag.putInt("x", targetPos.getX()); + destTag.putInt("y", targetPos.getY()); + destTag.putInt("z", targetPos.getZ()); + be.fromTag(finalSourceBlockState, destTag); + be.markDirty(); + } + } + }); + Value retval = new BlockValue(finalSourceBlockState, world, targetLocator.block.getPos()); + return (c_, t_) -> retval; + }); + + expression.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.syncWorldEvent(null, 2001, where, Block.getRawIdFromState(state)); + + boolean toolBroke = false; + boolean dropLoot = true; + if (playerBreak) + { + boolean isUsingEffectiveTool = !state.isToolRequired() || 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; + + }); + + expression.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 == Blocks.BEDROCK || block == Blocks.BARRIER) && player.interactionManager.isSurvivalLike())) + success = tryBreakBlock_copy_from_ServerPlayerInteractionManager(player, where); + if (success) + world.syncWorldEvent(null, 2001, where, Block.getRawIdFromState(state)); + return success ? LazyValue.TRUE : LazyValue.FALSE; + }); + + // TODO rename to use_item + expression.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; + }); + + expression.addLazyFunction("blocks_movement", -1, (c, t, lv) -> + booleanStateTest(c, "blocks_movement", lv, (s, p) -> + !s.canPathfindThrough(((CarpetContext) c).s.getWorld(), p, NavigationType.LAND))); + + expression.addLazyFunction("block_sound", -1, (c, t, lv) -> + stateStringQuery(c, "block_sound", lv, (s, p) -> + BlockInfo.soundName.get(s.getSoundGroup()))); + + expression.addLazyFunction("material", -1, (c, t, lv) -> + stateStringQuery(c, "material", lv, (s, p) -> + BlockInfo.materialName.get(s.getMaterial()))); + + expression.addLazyFunction("map_colour", -1, (c, t, lv) -> + stateStringQuery(c, "map_colour", lv, (s, p) -> + BlockInfo.mapColourName.get(s.getTopMaterialColor(((CarpetContext)c).s.getWorld(), p)))); + + expression.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; + }); + + expression.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; + }); + + expression.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); + // in locatebiome + Identifier biomeId = cc.s.getMinecraftServer().getRegistryManager().get(Registry.BIOME_KEY).getId(biome); + Value res = new StringValue(NBTSerializableValue.nameFromRegistryId(biomeId)); + return (_c, _t) -> res; + }); + + expression.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(); + // from locatebiome command code + Biome biome = cc.s.getMinecraftServer().getRegistryManager().get(Registry.BIOME_KEY).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; + }); + + expression.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; + }); + + expression.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, LongSet> references = world.getChunk(pos.getX() >> 4, pos.getZ() >> 4, ChunkStatus.STRUCTURE_REFERENCES).getStructureReferences(); + if (lv.size() == locator.offset) + { + //CarpetSettings.LOG.error(references.keySet().stream().collect(Collectors.joining(","))); + List referenceList = references.entrySet().stream(). + filter(e -> e.getValue()!= null && !e.getValue().isEmpty()). + map(e -> new StringValue(NBTSerializableValue.nameFromRegistryId(Registry.STRUCTURE_FEATURE.getId(e.getKey())))).collect(Collectors.toList()); + return (_c, _t ) -> ListValue.wrap(referenceList); + } + String simpleStructureName = lv.get(locator.offset).evalValue(c).getString().toLowerCase(Locale.ROOT); + //CarpetSettings.LOG.error(FeatureGenerator.featureToStructure.keySet().stream().collect(Collectors.joining(","))); + //CarpetSettings.LOG.error(FeatureGenerator.featureToStructure.values().stream().collect(Collectors.joining(","))); + StructureFeature structureName = Registry.STRUCTURE_FEATURE.get(new Identifier(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; + }); + + expression.addLazyFunction("structure_eligibility", -1, (c, t, lv) -> + {// TODO rename structureName to class + CarpetContext cc = (CarpetContext)c; + BlockArgument locator = BlockArgument.findIn(cc, lv, 0); + + ServerWorld world = cc.s.getWorld(); + + // well, because + BooYah(world.getChunkManager().getChunkGenerator()); + + 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(); + structure = Registry.STRUCTURE_FEATURE.get(new Identifier(reqString)); + if (structure == null) throw new InternalExpressionException("Unknown structure: " + reqString); + } + 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 : StructureFeature.STRUCTURES.values()) + { + StructureStart start = FeatureGenerator.shouldStructureStartAt(world, pos, str, needSize); + if (start == null) continue; + + Value key = new StringValue(NBTSerializableValue.nameFromRegistryId(Registry.STRUCTURE_FEATURE.getId(str))); + ret.put(key, (!needSize)?Value.NULL:structureToValue(start)); + } + Value retMap = MapValue.wrap(ret); + return (_c, _t) -> retMap; + }); + + expression.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, StructureStart> 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, StructureStart> entry : structures.entrySet()) + { + StructureStart start = entry.getValue(); + if (start == StructureStart.DEFAULT) + continue; + BlockBox box = start.getBoundingBox(); + structureList.put( + new StringValue(NBTSerializableValue.nameFromRegistryId(Registry.STRUCTURE_FEATURE.getId(entry.getKey()))), + 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(Registry.STRUCTURE_FEATURE.get(new Identifier(structureName))); + Value ret = structureToValue(start); + return (_c, _t) -> ret; + }); + + expression.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); + ConfiguredStructureFeature configuredStructure = FeatureGenerator.resolveConfiguredStructure(structureName, world, pos); + if (configuredStructure == 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, StructureStart> structures = world.getChunk(pos).getStructureStarts(); + if (lv.size() == locator.offset + 1) + { + Boolean res = FeatureGenerator.plopGrid(configuredStructure, ((CarpetContext) c).s.getWorld(), locator.block.getPos()); + //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 + { + StructureFeature structure = configuredStructure.feature; + if (!structures.containsKey(structure)) + { + return; + } + StructureStart start = structures.get(structure); + 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, LongSet> references = + world.getChunk(chpos.getStartPos()).getStructureReferences(); + if (references.containsKey(structure) && references.get(structure) != null) + references.get(structure).remove(structureChunkPos.toLong()); + } + } + structures.remove(structure); + 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; + }); + + expression.addLazyFunction("custom_dimension", -1, (c, t, lv) -> + { + if (lv.size() == 0) throw new InternalExpressionException("'custom_dimension' requires at least one argument"); + CarpetContext cc = (CarpetContext)c; + String worldKey = lv.get(0).evalValue(c).getString(); + + Long seed = null; + if (lv.size() > 1) + { + String seedKey = lv.get(1).evalValue(c).getString(); + try + { + seed = Long.parseLong(seedKey); + } + catch (NumberFormatException ignored) + { + throw new InternalExpressionException("Incorrect number format for seed: " + seedKey); + } + } + + boolean success = WorldTools.createWorld(cc.s.getMinecraftServer(), worldKey, seed); + if (!success) return LazyValue.FALSE; + CarpetServer.settingsManager.notifyPlayersCommandsChanged(); + return LazyValue.TRUE; + }); + + + expression.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.getStartPos(), 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]; + }); + + expression.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; + }); + + expression.addLazyFunction("spawn_potential", -1, (c, t, lv) -> + { + CarpetContext cc = (CarpetContext)c; + BlockArgument locator = BlockArgument.findIn(cc, lv, 0); + BlockPos pos = locator.block.getPos(); + double required_charge = 1; + if (lv.size() > locator.offset) + required_charge = NumericValue.asNumber(lv.get(locator.offset).evalValue(c)).getDouble(); + SpawnHelper.Info charger = cc.s.getWorld().getChunkManager().getSpawnInfo(); + if (charger == null) return LazyValue.NULL; + Value ret = new NumericValue( + ((SpawnHelperInnerInterface)charger).getPotentialCalculator(). + calculate(pos, required_charge ) + ); + return (_c, _t) -> ret; + }); + + expression.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) // portal + cc.s.getWorld().getChunkManager().addTicket(ChunkTicketType.PORTAL, target, radius, pos); + else if (ticket == ChunkTicketType.POST_TELEPORT) // 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; + }); + + } +} diff --git a/src/main/java/carpet/script/language/Arithmetic.java b/src/main/java/carpet/script/language/Arithmetic.java new file mode 100644 index 00000000..750b1ae7 --- /dev/null +++ b/src/main/java/carpet/script/language/Arithmetic.java @@ -0,0 +1,116 @@ +package carpet.script.language; + +import carpet.script.Context; +import carpet.script.Expression; +import carpet.script.exception.InternalExpressionException; +import carpet.script.value.ListValue; +import carpet.script.value.NumericValue; +import carpet.script.value.Value; + +public class Arithmetic { + public static final Value PI = new NumericValue( + "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679"); + public static final Value euler = new NumericValue( + "2.71828182845904523536028747135266249775724709369995957496696762772407663"); + + public static void apply(Expression expression) + { + expression.addLazyFunction("not", 1, (c, t, lv) -> lv.get(0).evalValue(c, Context.BOOLEAN).getBoolean() ? ((cc, tt) -> Value.FALSE) : ((cc, tt) -> Value.TRUE)); + + expression.addUnaryFunction("fact", (v) -> + { + long number = NumericValue.asNumber(v).getLong(); + long factorial = 1; + for (int i = 1; i <= number; i++) + { + factorial = factorial * i; + } + return new NumericValue(factorial); + }); + expression.addMathematicalUnaryFunction("sin", (d) -> Math.sin(Math.toRadians(d))); + expression.addMathematicalUnaryFunction("cos", (d) -> Math.cos(Math.toRadians(d))); + expression.addMathematicalUnaryFunction("tan", (d) -> Math.tan(Math.toRadians(d))); + expression.addMathematicalUnaryFunction("asin", (d) -> Math.toDegrees(Math.asin(d))); + expression.addMathematicalUnaryFunction("acos", (d) -> Math.toDegrees(Math.acos(d))); + expression.addMathematicalUnaryFunction("atan", (d) -> Math.toDegrees(Math.atan(d))); + expression.addMathematicalBinaryFunction("atan2", (d, d2) -> Math.toDegrees(Math.atan2(d, d2)) ); + expression.addMathematicalUnaryFunction("sinh", Math::sinh ); + expression.addMathematicalUnaryFunction("cosh", Math::cosh ); + expression.addMathematicalUnaryFunction("tanh", Math::tanh ); + expression.addMathematicalUnaryFunction("sec", (d) -> 1.0 / Math.cos(Math.toRadians(d)) ); // Formula: sec(x) = 1 / cos(x) + expression.addMathematicalUnaryFunction("csc", (d) -> 1.0 / Math.sin(Math.toRadians(d)) ); // Formula: csc(x) = 1 / sin(x) + expression.addMathematicalUnaryFunction("sech", (d) -> 1.0 / Math.cosh(d) ); // Formula: sech(x) = 1 / cosh(x) + expression.addMathematicalUnaryFunction("csch", (d) -> 1.0 / Math.sinh(d) ); // Formula: csch(x) = 1 / sinh(x) + expression.addMathematicalUnaryFunction("cot", (d) -> 1.0 / Math.tan(Math.toRadians(d)) ); // Formula: cot(x) = cos(x) / sin(x) = 1 / tan(x) + expression.addMathematicalUnaryFunction("acot", (d) -> Math.toDegrees(Math.atan(1.0 / d)) );// Formula: acot(x) = atan(1/x) + expression.addMathematicalUnaryFunction("coth", (d) -> 1.0 / Math.tanh(d) ); // Formula: coth(x) = 1 / tanh(x) + expression.addMathematicalUnaryFunction("asinh", (d) -> Math.log(d + (Math.sqrt(Math.pow(d, 2) + 1)))); // Formula: asinh(x) = ln(x + sqrt(x^2 + 1)) + expression.addMathematicalUnaryFunction("acosh", (d) -> Math.log(d + (Math.sqrt(Math.pow(d, 2) - 1)))); // Formula: acosh(x) = ln(x + sqrt(x^2 - 1)) + expression.addMathematicalUnaryFunction("atanh", (d) -> // Formula: atanh(x) = 0.5*ln((1 + x)/(1 - x)) + { + if (Math.abs(d) > 1 || Math.abs(d) == 1) + throw new InternalExpressionException("Number must be |x| < 1"); + return 0.5 * Math.log((1 + d) / (1 - d)); + }); + expression.addMathematicalUnaryFunction("rad", Math::toRadians); + expression.addMathematicalUnaryFunction("deg", Math::toDegrees); + expression.addMathematicalUnaryFunction("ln", Math::log); + expression.addMathematicalUnaryFunction("ln1p", Math::log1p); + expression.addMathematicalUnaryFunction("log10", Math::log10); + expression.addMathematicalUnaryFunction("log", a -> Math.log(a)/Math.log(2)); + expression.addMathematicalUnaryFunction("log1p", x -> Math.log1p(x)/Math.log(2)); + expression.addMathematicalUnaryFunction("sqrt", Math::sqrt); + expression.addMathematicalUnaryFunction("abs", Math::abs); + expression.addMathematicalUnaryFunction("round", (d) -> (double)Math.round(d)); + expression.addMathematicalUnaryFunction("floor", Math::floor); + expression.addMathematicalUnaryFunction("ceil", Math::ceil); + + expression.addLazyFunction("mandelbrot", 3, (c, t, lv) -> { + double a0 = NumericValue.asNumber(lv.get(0).evalValue(c)).getDouble(); + double b0 = NumericValue.asNumber(lv.get(1).evalValue(c)).getDouble(); + long maxiter = NumericValue.asNumber(lv.get(2).evalValue(c)).getLong(); + double a = 0.0D; + double b = 0.0D; + long iter = 0; + while(a*a+b*b<4 && iter < maxiter) + { + double temp = a*a-b*b+a0; + b = 2*a*b+b0; + a = temp; + iter++; + } + long iFinal = iter; + return (cc, tt) -> new NumericValue(iFinal); + }); + + expression.addFunction("max", (lv) -> + { + if (lv.size() == 0) + throw new InternalExpressionException("'max' requires at least one parameter"); + Value max = null; + if (lv.size()==1 && lv.get(0) instanceof ListValue) + lv = ((ListValue) lv.get(0)).getItems(); + for (Value parameter : lv) + { + if (max == null || parameter.compareTo(max) > 0) max = parameter; + } + return max; + }); + + expression.addFunction("min", (lv) -> + { + if (lv.size() == 0) + throw new InternalExpressionException("'min' requires at least one parameter"); + Value min = null; + if (lv.size()==1 && lv.get(0) instanceof ListValue) + lv = ((ListValue) lv.get(0)).getItems(); + for (Value parameter : lv) + { + if (min == null || parameter.compareTo(min) < 0) min = parameter; + } + return min; + }); + + expression.addUnaryFunction("relu", (v) -> v.compareTo(Value.ZERO) < 0 ? Value.ZERO : v); + } +} diff --git a/src/main/java/carpet/script/language/ControlFlow.java b/src/main/java/carpet/script/language/ControlFlow.java new file mode 100644 index 00000000..127db614 --- /dev/null +++ b/src/main/java/carpet/script/language/ControlFlow.java @@ -0,0 +1,70 @@ +package carpet.script.language; + +import carpet.script.Context; +import carpet.script.Expression; +import carpet.script.LazyValue; +import carpet.script.exception.ExitStatement; +import carpet.script.exception.InternalExpressionException; +import carpet.script.exception.ThrowStatement; +import carpet.script.value.Value; + +public class ControlFlow { + public static void apply(Expression expression) // public just to get the javadoc right + { + expression.addLazyBinaryOperator(";", Operators.precedence.get("nextop;"), true, (c, t, lv1, lv2) -> + { + lv1.evalValue(c, Context.VOID); + Value v2 = lv2.evalValue(c, t); + return (cc, tt) -> v2; + }); + + // if(cond1, expr1, cond2, expr2, ..., ?default) => value + expression.addLazyFunction("if", -1, (c, t, lv) -> + { + if ( lv.size() < 2 ) + throw new InternalExpressionException("'if' statement needs to have at least one condition and one case"); + for (int i=0; i ret; + } + } + if (lv.size()%2 == 1) + { + Value ret = lv.get(lv.size() - 1).evalValue(c, t); + return (cc, tt) -> ret; + } + return (cc, tt) -> Value.NULL; + }); + + expression.addFunction("exit", (lv) -> { throw new ExitStatement(lv.size()==0?Value.NULL:lv.get(0)); }); + + expression.addFunction("throw", (lv)-> {throw new ThrowStatement(lv.size()==0?Value.NULL:lv.get(0)); }); + + expression.addLazyFunction("try", -1, (c, t, lv) -> + { + if (lv.size()==0) + throw new InternalExpressionException("'try' needs at least an expression block"); + try + { + Value retval = lv.get(0).evalValue(c, t); + return (c_, t_) -> retval; + } + catch (ThrowStatement ret) + { + if (lv.size() == 1) + return (c_, t_) -> Value.NULL; + LazyValue __ = c.getVariable("_"); + c.setVariable("_", (__c, __t) -> ret.retval.reboundedTo("_")); + Value val = lv.get(1).evalValue(c, t); + c.setVariable("_",__); + return (c_, t_) -> val; + } + }); + + + } +} diff --git a/src/main/java/carpet/script/language/DataStructures.java b/src/main/java/carpet/script/language/DataStructures.java new file mode 100644 index 00000000..380b6af2 --- /dev/null +++ b/src/main/java/carpet/script/language/DataStructures.java @@ -0,0 +1,321 @@ +package carpet.script.language; + +import carpet.script.Context; +import carpet.script.Expression; +import carpet.script.LazyValue; +import carpet.script.exception.InternalExpressionException; +import carpet.script.value.ContainerValueInterface; +import carpet.script.value.LContainerValue; +import carpet.script.value.LazyListValue; +import carpet.script.value.ListValue; +import carpet.script.value.MapValue; +import carpet.script.value.NumericValue; +import carpet.script.value.StringValue; +import carpet.script.value.Value; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public class DataStructures { + public static void apply(Expression expression) + { + expression.addFunction("l", lv -> + { + if (lv.size() == 1 && lv.get(0) instanceof LazyListValue) + return ListValue.wrap(((LazyListValue) lv.get(0)).unroll()); + return new ListValue.ListConstructorValue(lv); + }); + + expression.addFunction("join", (lv) -> + { + if (lv.size() < 2) + throw new InternalExpressionException("'join' takes at least 2 arguments"); + String delimiter = lv.get(0).getString(); + List toJoin; + if (lv.size()==2 && lv.get(1) instanceof LazyListValue) + { + toJoin = ((LazyListValue) lv.get(1)).unroll(); + + } + else if (lv.size() == 2 && lv.get(1) instanceof ListValue) + { + toJoin = new ArrayList<>(((ListValue)lv.get(1)).getItems()); + } + else + { + toJoin = lv.subList(1,lv.size()); + } + return new StringValue(toJoin.stream().map(Value::getString).collect(Collectors.joining(delimiter))); + }); + + expression.addBinaryFunction("split", (d, v) -> { + String delimiter = d.getString(); + String hwat = v.getString(); + return ListValue.wrap(Arrays.stream(hwat.split(delimiter)).map(StringValue::new).collect(Collectors.toList())); + }); + + expression.addFunction("slice", (lv) -> { + + if (lv.size() != 2 && lv.size() != 3) + throw new InternalExpressionException("'slice' takes 2 or 3 arguments"); + Value hwat = lv.get(0); + long from = NumericValue.asNumber(lv.get(1)).getLong(); + long to = -1; + if (lv.size()== 3) + to = NumericValue.asNumber(lv.get(2)).getLong(); + return hwat.slice(from, to); + }); + + expression.addFunction("sort", (lv) -> + { + List toSort = lv; + if (lv.size()==1 && lv.get(0) instanceof ListValue) + { + toSort = new ArrayList<>(((ListValue)lv.get(0)).getItems()); + } + Collections.sort(toSort); + return ListValue.wrap(toSort); + }); + + expression.addLazyFunction("sort_key", -1, (c, t, lv) -> //get working with iterators + { + if (lv.size() == 0) + throw new InternalExpressionException("First argument for 'sort_key' should be a List"); + Value v = lv.get(0).evalValue(c); + if (!(v instanceof ListValue)) + throw new InternalExpressionException("First argument for 'sort_key' should be a List"); + List toSort = new ArrayList<>(((ListValue) v).getItems()); + if (lv.size()==1) + { + Collections.shuffle(toSort); + Value ret = ListValue.wrap(toSort); + return (_c, _t) -> ret; + } + LazyValue sortKey = lv.get(1); + //scoping + LazyValue __ = c.getVariable("_"); + Collections.sort(toSort,(v1, v2) -> { + c.setVariable("_",(cc, tt) -> v1); + Value ev1 = sortKey.evalValue(c); + c.setVariable("_",(cc, tt) -> v2); + Value ev2 = sortKey.evalValue(c); + return ev1.compareTo(ev2); + }); + //revering scope + c.setVariable("_", __); + return (cc, tt) -> ListValue.wrap(toSort); + }); + + expression.addFunction("range", (lv) -> + { + long from = 0; + long to = 0; + long step = 1; + int argsize = lv.size(); + if (argsize == 0 || argsize > 3) + throw new InternalExpressionException("'range' accepts from 1 to 3 arguments, not "+argsize); + to = NumericValue.asNumber(lv.get(0)).getLong(); + if (lv.size() > 1) + { + from = to; + to = NumericValue.asNumber(lv.get(1)).getLong(); + if (lv.size() > 2) + { + step = NumericValue.asNumber(lv.get(2)).getLong(); + } + } + return LazyListValue.range(from, to, step); + }); + + expression.addLazyFunction("m", -1, (c, t, llv) -> + { + List lv = new ArrayList<>(); + for (LazyValue lazyParam : llv) { + lv.add(lazyParam.evalValue(c, Context.MAPDEF)); // none type default by design + } + Value ret; + if (lv.size() == 1 && lv.get(0) instanceof LazyListValue) + ret = new MapValue(((LazyListValue) lv.get(0)).unroll()); + else + ret = new MapValue(lv); + return (cc, tt) -> ret; + }); + + expression.addUnaryFunction("keys", v -> { + if (v instanceof MapValue) + return new ListValue(((MapValue) v).getMap().keySet()); + return Value.NULL; + }); + + expression.addUnaryFunction("values", v -> { + if (v instanceof MapValue) + return new ListValue(((MapValue) v).getMap().values()); + return Value.NULL; + }); + + expression.addUnaryFunction("pairs", v -> { + if (v instanceof MapValue) + return ListValue.wrap(((MapValue) v).getMap().entrySet().stream().map( + (p) -> ListValue.of(p.getKey(), p.getValue()) + ).collect(Collectors.toList())); + return Value.NULL; + }); + + expression.addLazyBinaryOperator(":", Operators.precedence.get("attribute~:"),true, (c, t, container_lv, key_lv) -> + { + Value container = container_lv.evalValue(c); + if (container instanceof LContainerValue) + { + ContainerValueInterface outerContainer = ((LContainerValue) container).getContainer(); + if (outerContainer == null) + { + Value innerLValue = new LContainerValue(null, null); + return (cc, tt) -> innerLValue; + } + Value innerContainer = outerContainer.get(((LContainerValue) container).getAddress()); + if (!(innerContainer instanceof ContainerValueInterface)) + { + Value innerLValue = new LContainerValue(null, null); + return (cc, tt) -> innerLValue; + } + Value innerLValue = new LContainerValue((ContainerValueInterface) innerContainer, key_lv.evalValue(c)); + return (cc, tt) -> innerLValue; + } + if (!(container instanceof ContainerValueInterface)) + if (t == Context.LVALUE) + return (cc, tt) -> new LContainerValue(null, null); + else + return (cc, tt) -> Value.NULL; + Value address = key_lv.evalValue(c); + if (t != Context.LVALUE) + { + Value retVal = ((ContainerValueInterface) container).get(address); + return (cc, ct) -> retVal; + } + Value retVal = new LContainerValue((ContainerValueInterface) container, address); + return (cc, ct) -> retVal; + }); + + expression.addLazyFunction("get", -1, (c, t, lv) -> + { + if (lv.size() == 0) + throw new InternalExpressionException("'get' requires parameters"); + if (lv.size() == 1) + { + Value v = lv.get(0).evalValue(c, Context.LVALUE); + if (!(v instanceof LContainerValue)) + return (cc, tt) -> Value.NULL; + ContainerValueInterface container = ((LContainerValue) v).getContainer(); + if (container == null) + return (cc, tt) -> Value.NULL; + Value ret = container.get(((LContainerValue) v).getAddress()); + return (cc, tt) -> ret; + } + Value container = lv.get(0).evalValue(c); + for (int i = 1; i < lv.size(); i++) + { + if (!(container instanceof ContainerValueInterface)) return (cc, tt) -> Value.NULL; + container = ((ContainerValueInterface) container).get(lv.get(i).evalValue(c)); + } + if (container == null) + return (cc, tt) -> Value.NULL; + Value finalContainer = container; + return (cc, tt) -> finalContainer; + }); + + expression.addLazyFunction("has", -1, (c, t, lv) -> + { + if (lv.size() == 0) + throw new InternalExpressionException("'has' requires parameters"); + if (lv.size() == 1) + { + Value v = lv.get(0).evalValue(c, Context.LVALUE); + if (!(v instanceof LContainerValue)) + return (cc, tt) -> Value.NULL; + ContainerValueInterface container = ((LContainerValue) v).getContainer(); + if (container == null) + return (cc, tt) -> Value.NULL; + Value ret = new NumericValue(container.has(((LContainerValue) v).getAddress())); + return (cc, tt) -> ret; + } + Value container = lv.get(0).evalValue(c); + for (int i = 1; i < lv.size()-1; i++) + { + if (!(container instanceof ContainerValueInterface)) return (cc, tt) -> Value.NULL; + container = ((ContainerValueInterface) container).get(lv.get(i).evalValue(c)); + } + if (!(container instanceof ContainerValueInterface)) + return (cc, tt) -> Value.NULL; + Value ret = new NumericValue(((ContainerValueInterface) container).has(lv.get(lv.size()-1).evalValue(c))); + return (cc, tt) -> ret; + }); + + expression.addLazyFunction("put", -1, (c, t, lv) -> + { + if(lv.size()<2) + { + throw new InternalExpressionException("'put' takes at least three arguments, a container, address, and values to insert at that index"); + } + Value container = lv.get(0).evalValue(c, Context.LVALUE); + if (container instanceof LContainerValue) + { + ContainerValueInterface internalContainer = ((LContainerValue) container).getContainer(); + if (internalContainer == null) + { + return (cc, tt) -> Value.NULL; + } + Value address = ((LContainerValue) container).getAddress(); + Value what = lv.get(1).evalValue(c); + Value retVal = new NumericValue( (lv.size() > 2) + ? internalContainer.put(address, what, lv.get(2).evalValue(c)) + : internalContainer.put(address, what)); + return (cc, tt) -> retVal; + + } + if(lv.size()<3) + { + throw new InternalExpressionException("'put' takes at least three arguments, a container, address, and values to insert at that index"); + } + if (!(container instanceof ContainerValueInterface)) + { + return (cc, tt) -> Value.NULL; + } + Value where = lv.get(1).evalValue(c); + Value what = lv.get(2).evalValue(c); + Value retVal = new NumericValue( (lv.size()>3) + ? ((ContainerValueInterface) container).put(where, what, lv.get(3).evalValue(c)) + : ((ContainerValueInterface) container).put(where, what)); + return (cc, tt) -> retVal; + }); + + expression.addLazyFunction("delete", -1, (c, t, lv) -> + { + if (lv.size() == 0) + throw new InternalExpressionException("'delete' requires parameters"); + if (lv.size() == 1) + { + Value v = lv.get(0).evalValue(c, Context.LVALUE); + if (!(v instanceof LContainerValue)) + return (cc, tt) -> Value.NULL; + ContainerValueInterface container = ((LContainerValue) v).getContainer(); + if (container == null) + return (cc, tt) -> Value.NULL; + Value ret = new NumericValue(container.delete(((LContainerValue) v).getAddress())); + return (cc, tt) -> ret; + } + Value container = lv.get(0).evalValue(c); + for (int i = 1; i < lv.size()-1; i++) + { + if (!(container instanceof ContainerValueInterface)) return (cc, tt) -> Value.NULL; + container = ((ContainerValueInterface) container).get(lv.get(i).evalValue(c)); + } + if (!(container instanceof ContainerValueInterface)) + return (cc, tt) -> Value.NULL; + Value ret = new NumericValue(((ContainerValueInterface) container).delete(lv.get(lv.size()-1).evalValue(c))); + return (cc, tt) -> ret; + }); + } +} diff --git a/src/main/java/carpet/script/language/Functions.java b/src/main/java/carpet/script/language/Functions.java new file mode 100644 index 00000000..851dfb84 --- /dev/null +++ b/src/main/java/carpet/script/language/Functions.java @@ -0,0 +1,98 @@ +package carpet.script.language; + +import carpet.script.Context; +import carpet.script.Expression; +import carpet.script.LazyValue; +import carpet.script.argument.FunctionArgument; +import carpet.script.exception.InternalExpressionException; +import carpet.script.exception.ReturnStatement; +import carpet.script.value.FunctionSignatureValue; +import carpet.script.value.FunctionValue; +import carpet.script.value.GlobalValue; +import carpet.script.value.ListValue; +import carpet.script.value.StringValue; +import carpet.script.value.Value; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; + +public class Functions { + public static void apply(Expression expression) // public just to get the javadoc right + { + // artificial construct to handle user defined functions and function definitions + expression.addLazyFunction("import", -1, (c, t, lv) -> + { + if (lv.size() < 1) throw new InternalExpressionException("'import' needs at least a module name to import, and list of values to import"); + String moduleName = lv.get(0).evalValue(c).getString(); + c.host.importModule(c, moduleName); + moduleName = moduleName.toLowerCase(Locale.ROOT); + if (lv.size() > 1) + c.host.importNames(c, expression.module, moduleName, lv.subList(1, lv.size()).stream().map((l) -> l.evalValue(c).getString()).collect(Collectors.toList())); + if (t == Context.VOID) + return LazyValue.NULL; + ListValue list = ListValue.wrap(c.host.availableImports(moduleName).map(StringValue::new).collect(Collectors.toList())); + return (cc, tt) -> list; + }); + + expression.addLazyFunctionWithDelegation("call",-1, (c, t, expr, tok, lv) -> { // adjust based on c + if (lv.size() == 0) + throw new InternalExpressionException("'call' expects at least function name to call"); + //lv.remove(lv.size()-1); // aint gonna cut it // maybe it will because of the eager eval changes + if (t != Context.SIGNATURE) // just call the function + { + FunctionArgument functionArgument = FunctionArgument.findIn(c, expression.module, lv, 0, false, true); + FunctionValue fun = functionArgument.function; + Value retval = fun.callInContext(expr, c, t, fun.getExpression(), fun.getToken(), functionArgument.args).evalValue(c); + return (cc, tt) -> retval; ///!!!! dono might need to store expr and token in statics? (e? t?) + } + // gimme signature + String name = lv.get(0).evalValue(c).getString(); + List args = new ArrayList<>(); + List globals = new ArrayList<>(); + for (int i = 1; i < lv.size(); i++) + { + Value v = lv.get(i).evalValue(c, Context.LOCALIZATION); + if (!v.isBound()) + { + throw new InternalExpressionException("Only variables can be used in function signature, not " + v.getString()); + } + if (v instanceof GlobalValue) + { + globals.add(v.boundVariable); + } + else + { + args.add(v.boundVariable); + } + } + Value retval = new FunctionSignatureValue(name, args, globals); + return (cc, tt) -> retval; + }); + expression.addLazyFunction("outer", 1, (c, t, lv) -> { + if (t != Context.LOCALIZATION) + throw new InternalExpressionException("Outer scoping of variables is only possible in function signatures"); + return (cc, tt) -> new GlobalValue(lv.get(0).evalValue(c)); + }); + + //assigns const procedure to the lhs, returning its previous value + expression.addLazyBinaryOperatorWithDelegation("->", Operators.precedence.get("def->"), false, (c, type, e, t, lv1, lv2) -> + { + if (type == Context.MAPDEF) + { + Value result = ListValue.of(lv1.evalValue(c), lv2.evalValue(c)); + return (cc, tt) -> result; + } + Value v1 = lv1.evalValue(c, Context.SIGNATURE); + if (!(v1 instanceof FunctionSignatureValue)) + throw new InternalExpressionException("'->' operator requires a function signature on the LHS"); + FunctionSignatureValue sign = (FunctionSignatureValue) v1; + Value result = expression.addContextFunction(c, sign.getName(), e, t, sign.getArgs(), sign.getGlobals(), lv2); + return (cc, tt) -> result; + }); + + expression.addFunction("return", (lv) -> { throw new ReturnStatement(lv.size()==0?Value.NULL:lv.get(0));} ); + + } +} diff --git a/src/main/java/carpet/script/language/Loops.java b/src/main/java/carpet/script/language/Loops.java new file mode 100644 index 00000000..8d103bdc --- /dev/null +++ b/src/main/java/carpet/script/language/Loops.java @@ -0,0 +1,410 @@ +package carpet.script.language; + +import carpet.script.Context; +import carpet.script.Expression; +import carpet.script.LazyValue; +import carpet.script.exception.BreakStatement; +import carpet.script.exception.ContinueStatement; +import carpet.script.exception.InternalExpressionException; +import carpet.script.value.AbstractListValue; +import carpet.script.value.ListValue; +import carpet.script.value.NumericValue; +import carpet.script.value.Value; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class Loops { + public static void apply(Expression expression) + { + // condition and expression will get a bound '_i' + // returns last successful expression or false + // while(cond, limit, expr) => ?? + expression.addFunction("break", lv -> { + if (lv.size()==0) throw new BreakStatement(null); + if (lv.size()==1) throw new BreakStatement(lv.get(0)); + throw new InternalExpressionException("'break' can only be called with zero or one argument"); + }); + + expression.addFunction("continue", lv -> { + if (lv.size()==0) throw new ContinueStatement(null); + if (lv.size()==1) throw new ContinueStatement(lv.get(0)); + throw new InternalExpressionException("'continue' can only be called with zero or one argument"); + }); + + expression.addLazyFunction("while", 3, (c, t, lv) -> + { + long limit = NumericValue.asNumber(lv.get(1).evalValue(c)).getLong(); + LazyValue condition = lv.get(0); + LazyValue expr = lv.get(2); + long i = 0; + Value lastOne = Value.NULL; + //scoping + LazyValue _val = c.getVariable("_"); + c.setVariable("_",(cc, tt) -> new NumericValue(0).bindTo("_")); + while (i new NumericValue(seriously).bindTo("_")); + } + //revering scope + c.setVariable("_", _val); + Value lastValueNoKidding = lastOne; + return (cc, tt) -> lastValueNoKidding; + }); + + // loop(Num, expr) => last_value + // expr receives bounded variable '_' indicating iteration + expression.addLazyFunction("loop", 2, (c, t, lv) -> + { + long limit = NumericValue.asNumber(lv.get(0).evalValue(c)).getLong(); + Value lastOne = Value.NULL; + LazyValue expr = lv.get(1); + //scoping + LazyValue _val = c.getVariable("_"); + for (long i=0; i < limit; i++) + { + long whyYouAsk = i; + c.setVariable("_", (cc, tt) -> new NumericValue(whyYouAsk).bindTo("_")); + try + { + lastOne = expr.evalValue(c, t); + } + catch (BreakStatement | ContinueStatement stmt) + { + if (stmt.retval != null) lastOne = stmt.retval; + if (stmt instanceof BreakStatement) break; + } + } + //revering scope + c.setVariable("_", _val); + Value trulyLastOne = lastOne; + return (cc, tt) -> trulyLastOne; + }); + + // map(list or Num, expr) => list_results + // receives bounded variable '_' with the expression + expression.addLazyFunction("map", 2, (c, t, lv) -> + { + Value rval= lv.get(0).evalValue(c); + if (!(rval instanceof AbstractListValue)) + throw new InternalExpressionException("First argument of 'map' function should be a list or iterator"); + Iterator iterator = ((AbstractListValue) rval).iterator(); + LazyValue expr = lv.get(1); + //scoping + LazyValue _val = c.getVariable("_"); + LazyValue _iter = c.getVariable("_i"); + List result = new ArrayList<>(); + for (int i=0; iterator.hasNext(); i++) + { + Value next = iterator.next(); + String var = next.boundVariable; + next.bindTo("_"); + int doYouReally = i; + c.setVariable("_", (cc, tt) -> next); + c.setVariable("_i", (cc, tt) -> new NumericValue(doYouReally).bindTo("_i")); + try + { + result.add(expr.evalValue(c)); + } + catch (BreakStatement | ContinueStatement stmt) + { + if (stmt.retval != null) result.add(stmt.retval); + if (stmt instanceof BreakStatement) + { + next.boundVariable = var; + break; + } + } + next.boundVariable = var; + } + ((AbstractListValue) rval).fatality(); + Value ret = ListValue.wrap(result); + //revering scope + c.setVariable("_", _val); + c.setVariable("_i", _iter); + return (cc, tt) -> ret; + }); + + // grep(list or num, expr) => list + // receives bounded variable '_' with the expression, and "_i" with index + // produces list of values for which the expression is true + expression.addLazyFunction("filter", 2, (c, t, lv) -> + { + Value rval= lv.get(0).evalValue(c); + if (!(rval instanceof AbstractListValue)) + throw new InternalExpressionException("First argument of 'filter' function should be a list or iterator"); + Iterator iterator = ((AbstractListValue) rval).iterator(); + LazyValue expr = lv.get(1); + //scoping + LazyValue _val = c.getVariable("_"); + LazyValue _iter = c.getVariable("_i"); + List result = new ArrayList<>(); + for (int i=0; iterator.hasNext(); i++) + { + Value next = iterator.next(); + String var = next.boundVariable; + next.bindTo("_"); + int seriously = i; + c.setVariable("_", (cc, tt) -> next); + c.setVariable("_i", (cc, tt) -> new NumericValue(seriously).bindTo("_i")); + try + { + if(expr.evalValue(c, Context.BOOLEAN).getBoolean()) + result.add(next); + } + catch (BreakStatement | ContinueStatement stmt) + { + if (stmt.retval != null && stmt.retval.getBoolean()) result.add(next); + if (stmt instanceof BreakStatement) + { + next.boundVariable = var; + break; + } + } + next.boundVariable = var; + } + ((AbstractListValue) rval).fatality(); + Value ret = ListValue.wrap(result); + //revering scope + c.setVariable("_", _val); + c.setVariable("_i", _iter); + return (cc, tt) -> ret; + }); + + // first(list, expr) => elem or null + // receives bounded variable '_' with the expression, and "_i" with index + // returns first element on the list for which the expr is true + expression.addLazyFunction("first", 2, (c, t, lv) -> + { + + Value rval= lv.get(0).evalValue(c); + if (!(rval instanceof AbstractListValue)) + throw new InternalExpressionException("First argument of 'first' function should be a list or iterator"); + Iterator iterator = ((AbstractListValue) rval).iterator(); + LazyValue expr = lv.get(1); + //scoping + LazyValue _val = c.getVariable("_"); + LazyValue _iter = c.getVariable("_i"); + Value result = Value.NULL; + for (int i=0; iterator.hasNext(); i++) + { + Value next = iterator.next(); + String var = next.boundVariable; + next.bindTo("_"); + int seriously = i; + c.setVariable("_", (cc, tt) -> next); + c.setVariable("_i", (cc, tt) -> new NumericValue(seriously).bindTo("_i")); + try + { + if(expr.evalValue(c, Context.BOOLEAN).getBoolean()) + { + result = next; + next.boundVariable = var; + break; + } + } + catch (BreakStatement stmt) + { + result = stmt.retval == null? next : stmt.retval; + next.boundVariable = var; + break; + } + catch (ContinueStatement ignored) + { + throw new InternalExpressionException("'continue' inside 'first' function has no sense"); + } + next.boundVariable = var; + } + //revering scope + ((AbstractListValue) rval).fatality(); + Value whyWontYouTrustMeJava = result; + c.setVariable("_", _val); + c.setVariable("_i", _iter); + return (cc, tt) -> whyWontYouTrustMeJava; + }); + + // all(list, expr) => boolean + // receives bounded variable '_' with the expression, and "_i" with index + // returns true if expr is true for all items + expression.addLazyFunction("all", 2, (c, t, lv) -> + { + Value rval= lv.get(0).evalValue(c); + if (!(rval instanceof AbstractListValue)) + throw new InternalExpressionException("First argument of 'all' function should be a list or iterator"); + Iterator iterator = ((AbstractListValue) rval).iterator(); + LazyValue expr = lv.get(1); + //scoping + LazyValue _val = c.getVariable("_"); + LazyValue _iter = c.getVariable("_i"); + LazyValue result = LazyValue.TRUE; + for (int i=0; iterator.hasNext(); i++) + { + Value next = iterator.next(); + String var = next.boundVariable; + next.bindTo("_"); + int seriously = i; + c.setVariable("_", (cc, tt) -> next); + c.setVariable("_i", (cc, tt) -> new NumericValue(seriously).bindTo("_i")); + if(!expr.evalValue(c, Context.BOOLEAN).getBoolean()) + { + result = LazyValue.FALSE; + next.boundVariable = var; + break; + } + next.boundVariable = var; + } + //revering scope + ((AbstractListValue) rval).fatality(); + c.setVariable("_", _val); + c.setVariable("_i", _iter); + return result; + }); + + // runs traditional for(init, condition, increment, body) tri-argument for loop with body in between + expression.addLazyFunction("c_for", 4, (c, t, lv) -> { + LazyValue initial = lv.get(0); + LazyValue condition = lv.get(1); + LazyValue increment = lv.get(2); + LazyValue body = lv.get(3); + int iterations = 0; + for (initial.evalValue(c, Context.VOID); condition.evalValue(c, Context.BOOLEAN).getBoolean(); increment.evalValue(c, Context.VOID)) + { + try + { + body.evalValue(c, Context.VOID); + } + catch (BreakStatement stmt) + { + break; + } + catch (ContinueStatement ignored) + { + } + iterations++; + } + int finalIterations = iterations; + return (cc, tt) -> new NumericValue(finalIterations); + }); + + // similar to map, but returns total number of successes + // for(list, expr) => success_count + // can be substituted for first and all, but first is more efficient and all doesn't require knowing list size + expression.addLazyFunction("for", 2, (c, t, lv) -> + { + Value rval= lv.get(0).evalValue(c); + if (!(rval instanceof AbstractListValue)) + throw new InternalExpressionException("First argument of 'for' function should be a list or iterator"); + Iterator iterator = ((AbstractListValue) rval).iterator(); + LazyValue expr = lv.get(1); + //scoping + LazyValue _val = c.getVariable("_"); + LazyValue _ite = c.getVariable("_i"); + int successCount = 0; + for (int i=0; iterator.hasNext(); i++) + { + Value next = iterator.next(); + String var = next.boundVariable; + next.bindTo("_"); + int seriously = i; + c.setVariable("_", (cc, tt) -> next); + c.setVariable("_i", (cc, tt) -> new NumericValue(seriously).bindTo("_i")); + Value result = Value.FALSE; + try + { + result = expr.evalValue(c, t); + } + catch (BreakStatement | ContinueStatement stmt) + { + if (stmt.retval != null) result = stmt.retval; + if (stmt instanceof BreakStatement) + { + next.boundVariable = var; + break; + } + } + if(t != Context.VOID && result.getBoolean()) + successCount++; + next.boundVariable = var; + } + //revering scope + ((AbstractListValue) rval).fatality(); + c.setVariable("_", _val); + c.setVariable("_i", _ite); + long promiseWontChange = successCount; + return (cc, tt) -> new NumericValue(promiseWontChange); + }); + + + // reduce(list, expr, ?acc) => value + // reduces values in the list with expression that gets accumulator + // each iteration expr receives acc - accumulator, and '_' - current list value + // returned value is substituted to the accumulator + expression.addLazyFunction("reduce", 3, (c, t, lv) -> + { + LazyValue expr = lv.get(1); + + Value acc = lv.get(2).evalValue(c); + Value rval= lv.get(0).evalValue(c); + if (!(rval instanceof AbstractListValue)) + throw new InternalExpressionException("First argument of 'reduce' should be a list or iterator"); + Iterator iterator = ((AbstractListValue) rval).iterator(); + + if (!iterator.hasNext()) + { + Value seriouslyWontChange = acc; + return (cc, tt) -> seriouslyWontChange; + } + + //scoping + LazyValue _val = c.getVariable("_"); + LazyValue _acc = c.getVariable("_a"); + LazyValue _ite = c.getVariable("_i"); + + for (int i=0; iterator.hasNext(); i++) + { + Value next = iterator.next(); + String var = next.boundVariable; + next.bindTo("_"); + Value promiseWontChangeYou = acc; + int seriously = i; + c.setVariable("_a", (cc, tt) -> promiseWontChangeYou.bindTo("_a")); + c.setVariable("_", (cc, tt) -> next); + c.setVariable("_i", (cc, tt) -> new NumericValue(seriously).bindTo("_i")); + try + { + acc = expr.evalValue(c, t); + } + catch (BreakStatement | ContinueStatement stmt) + { + if (stmt.retval != null) acc = stmt.retval; + if (stmt instanceof BreakStatement) + { + next.boundVariable = var; + break; + } + } + next.boundVariable = var; + } + //reverting scope + ((AbstractListValue) rval).fatality(); + c.setVariable("_a", _acc); + c.setVariable("_", _val); + c.setVariable("_i", _ite); + + Value hopeItsEnoughPromise = acc; + return (cc, tt) -> hopeItsEnoughPromise; + }); + } +} diff --git a/src/main/java/carpet/script/language/Operators.java b/src/main/java/carpet/script/language/Operators.java new file mode 100644 index 00000000..a5f5a146 --- /dev/null +++ b/src/main/java/carpet/script/language/Operators.java @@ -0,0 +1,219 @@ +package carpet.script.language; + +import carpet.script.Context; +import carpet.script.Expression; +import carpet.script.LazyValue; +import carpet.script.exception.InternalExpressionException; +import carpet.script.value.AbstractListValue; +import carpet.script.value.ContainerValueInterface; +import carpet.script.value.LContainerValue; +import carpet.script.value.ListValue; +import carpet.script.value.MapValue; +import carpet.script.value.NumericValue; +import carpet.script.value.Value; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public class Operators { + public static final Map precedence = new HashMap() {{ + put("attribute~:", 80); + put("unary+-!", 60); + put("exponent^", 40); + put("multiplication*/%", 30); + put("addition+-", 20); + put("compare>=><=<", 10); + put("equal==!=", 7); + put("and&&", 5); + put("or||", 4); + put("assign=<>", 3); + put("def->", 2); + put("nextop;", 1); + }}; + + public static void apply(Expression expression) + { + expression.addBinaryOperator("+", precedence.get("addition+-"), true, Value::add); + expression.addBinaryOperator("-", precedence.get("addition+-"), true, Value::subtract); + expression.addBinaryOperator("*", precedence.get("multiplication*/%"), true, Value::multiply); + expression.addBinaryOperator("/", precedence.get("multiplication*/%"), true, Value::divide); + expression.addBinaryOperator("%", precedence.get("multiplication*/%"), true, (v1, v2) -> + new NumericValue(NumericValue.asNumber(v1).getDouble() % NumericValue.asNumber(v2).getDouble())); + expression.addBinaryOperator("^", precedence.get("exponent^"), false, (v1, v2) -> + new NumericValue(java.lang.Math.pow(NumericValue.asNumber(v1).getDouble(), NumericValue.asNumber(v2).getDouble()))); + + expression.addLazyBinaryOperator("&&", precedence.get("and&&"), false, (c, t, lv1, lv2) -> + { + Value v1 = lv1.evalValue(c, Context.BOOLEAN); + if (!v1.getBoolean()) return (cc, tt) -> v1; + return lv2; + }); + + expression.addLazyBinaryOperator("||", precedence.get("or||"), false, (c, t, lv1, lv2) -> + { + Value v1 = lv1.evalValue(c, Context.BOOLEAN); + if (v1.getBoolean()) return (cc, tt) -> v1; + return lv2; + }); + + expression.addBinaryOperator("~", precedence.get("attribute~:"), true, Value::in); + + expression.addBinaryOperator(">", precedence.get("compare>=><=<"), false, (v1, v2) -> + v1.compareTo(v2) > 0 ? Value.TRUE : Value.FALSE); + expression.addBinaryOperator(">=", precedence.get("compare>=><=<"), false, (v1, v2) -> + v1.compareTo(v2) >= 0 ? Value.TRUE : Value.FALSE); + expression.addBinaryOperator("<", precedence.get("compare>=><=<"), false, (v1, v2) -> + v1.compareTo(v2) < 0 ? Value.TRUE : Value.FALSE); + expression.addBinaryOperator("<=", precedence.get("compare>=><=<"), false, (v1, v2) -> + v1.compareTo(v2) <= 0 ? Value.TRUE : Value.FALSE); + expression.addBinaryOperator("==", precedence.get("equal==!="), false, (v1, v2) -> + v1.equals(v2) ? Value.TRUE : Value.FALSE); + expression.addBinaryOperator("!=", precedence.get("equal==!="), false, (v1, v2) -> + v1.equals(v2) ? Value.FALSE : Value.TRUE); + + expression.addLazyBinaryOperator("=", precedence.get("assign=<>"), false, (c, t, lv1, lv2) -> + { + Value v1 = lv1.evalValue(c, Context.LVALUE); + Value v2 = lv2.evalValue(c); + if (v1 instanceof ListValue.ListConstructorValue && v2 instanceof ListValue) + { + List ll = ((ListValue)v1).getItems(); + List rl = ((ListValue)v2).getItems(); + if (ll.size() < rl.size()) throw new InternalExpressionException("Too many values to unpack"); + if (ll.size() > rl.size()) throw new InternalExpressionException("Too few values to unpack"); + for (Value v: ll) v.assertAssignable(); + Iterator li = ll.iterator(); + Iterator ri = rl.iterator(); + while(li.hasNext()) + { + String lname = li.next().getVariable(); + Value vval = ri.next().reboundedTo(lname); + expression.setAnyVariable(c, lname, (cc, tt) -> vval); + } + return (cc, tt) -> Value.TRUE; + } + if (v1 instanceof LContainerValue) + { + ContainerValueInterface container = ((LContainerValue) v1).getContainer(); + if (container == null) + return (cc, tt) -> Value.NULL; + Value address = ((LContainerValue) v1).getAddress(); + if (!(container.put(address, v2))) return (cc, tt) -> Value.NULL; + return (cc, tt) -> v2; + } + v1.assertAssignable(); + String varname = v1.getVariable(); + Value copy = v2.reboundedTo(varname); + LazyValue boundedLHS = (cc, tt) -> copy; + expression.setAnyVariable(c, varname, boundedLHS); + return boundedLHS; + }); + + expression.addLazyBinaryOperator("+=", precedence.get("assign=<>"), false, (c, t, lv1, lv2) -> + { + Value v1 = lv1.evalValue(c, Context.LVALUE); + Value v2 = lv2.evalValue(c); + if (v1 instanceof ListValue.ListConstructorValue && v2 instanceof ListValue) + { + List ll = ((ListValue)v1).getItems(); + List rl = ((ListValue)v2).getItems(); + if (ll.size() < rl.size()) throw new InternalExpressionException("Too many values to unpack"); + if (ll.size() > rl.size()) throw new InternalExpressionException("Too few values to unpack"); + for (Value v: ll) v.assertAssignable(); + Iterator li = ll.iterator(); + Iterator ri = rl.iterator(); + while(li.hasNext()) + { + Value lval = li.next(); + String lname = lval.getVariable(); + Value result = lval.add(ri.next()).bindTo(lname); + expression.setAnyVariable(c, lname, (cc, tt) -> result); + } + return (cc, tt) -> Value.TRUE; + } + if (v1 instanceof LContainerValue) + { + ContainerValueInterface cvi = ((LContainerValue) v1).getContainer(); + if (cvi == null) + { + throw new InternalExpressionException("Failed to resolve left hand side of the += operation"); + } + Value key = ((LContainerValue) v1).getAddress(); + Value value = cvi.get(key); + if (value instanceof ListValue || value instanceof MapValue) + { + ((AbstractListValue) value).append(v2); + return (cc, tt) -> value; + } + else + { + Value res = value.add(v2); + cvi.put(key, res); + return (cc, tt) -> res; + } + } + v1.assertAssignable(); + String varname = v1.getVariable(); + LazyValue boundedLHS; + if (v1 instanceof ListValue || v1 instanceof MapValue) + { + ((AbstractListValue) v1).append(v2); + boundedLHS = (cc, tt)-> v1; + } + else + { + Value result = v1.add(v2).bindTo(varname); + boundedLHS = (cc, tt) -> result; + } + expression.setAnyVariable(c, varname, boundedLHS); + return boundedLHS; + }); + + expression.addLazyBinaryOperator("<>", precedence.get("assign=<>"), false, (c, t, lv1, lv2) -> + { + Value v1 = lv1.evalValue(c); + Value v2 = lv2.evalValue(c); + if (v1 instanceof ListValue.ListConstructorValue && v2 instanceof ListValue.ListConstructorValue) + { + List ll = ((ListValue)v1).getItems(); + List rl = ((ListValue)v2).getItems(); + if (ll.size() < rl.size()) throw new InternalExpressionException("Too many values to unpack"); + if (ll.size() > rl.size()) throw new InternalExpressionException("Too few values to unpack"); + for (Value v: ll) v.assertAssignable(); + for (Value v: rl) v.assertAssignable(); + Iterator li = ll.iterator(); + Iterator ri = rl.iterator(); + while(li.hasNext()) + { + Value lval = li.next(); + Value rval = ri.next(); + String lname = lval.getVariable(); + String rname = rval.getVariable(); + lval.reboundedTo(rname); + rval.reboundedTo(lname); + expression.setAnyVariable(c, lname, (cc, tt) -> rval); + expression.setAnyVariable(c, rname, (cc, tt) -> lval); + } + return (cc, tt) -> Value.TRUE; + } + v1.assertAssignable(); + v2.assertAssignable(); + String lvalvar = v1.getVariable(); + String rvalvar = v2.getVariable(); + Value lval = v2.reboundedTo(lvalvar); + Value rval = v1.reboundedTo(rvalvar); + expression.setAnyVariable(c, lvalvar, (cc, tt) -> lval); + expression.setAnyVariable(c, rvalvar, (cc, tt) -> rval); + return (cc, tt) -> lval; + }); + + expression.addUnaryOperator("-", false, (v) -> new NumericValue(-NumericValue.asNumber(v).getDouble())); + + expression.addUnaryOperator("+", false, (v) -> new NumericValue(NumericValue.asNumber(v).getDouble())); + + expression.addLazyUnaryOperator("!", precedence.get("unary+-!"), false, (c, t, lv)-> lv.evalValue(c, Context.BOOLEAN).getBoolean() ? (cc, tt)-> Value.FALSE : (cc, tt) -> Value.TRUE); // might need context boolean + + } +} diff --git a/src/main/java/carpet/script/language/Sys.java b/src/main/java/carpet/script/language/Sys.java new file mode 100644 index 00000000..227f5f3c --- /dev/null +++ b/src/main/java/carpet/script/language/Sys.java @@ -0,0 +1,521 @@ +package carpet.script.language; + +import carpet.script.Context; +import carpet.script.Expression; +import carpet.script.LazyValue; +import carpet.script.ScriptHost; +import carpet.script.argument.FunctionArgument; +import carpet.script.exception.InternalExpressionException; +import carpet.script.utils.PerlinNoiseSampler; +import carpet.script.utils.SimplexNoiseSampler; +import carpet.script.value.FunctionValue; +import carpet.script.value.ListValue; +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 org.apache.commons.lang3.text.WordUtils; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.IllegalFormatException; +import java.util.List; +import java.util.Locale; +import java.util.Random; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Sys { + public static final Random randomizer = new Random(); + // %[argument_index$][flags][width][.precision][t]conversion + private static final Pattern formatPattern = Pattern.compile("%(\\d+\\$)?([-#+ 0,(<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])"); + + public static void apply(Expression expression) + { + expression.addUnaryFunction("hash_code", v -> new NumericValue(v.hashCode())); + + expression.addUnaryFunction("copy", Value::deepcopy); + + expression.addLazyFunction("bool", 1, (c, t, lv) -> { + Value v = lv.get(0).evalValue(c, Context.BOOLEAN); + if (v instanceof StringValue) + { + String str = v.getString(); + if ("false".equalsIgnoreCase(str) || "null".equalsIgnoreCase(str)) + { + return (cc, tt) -> Value.FALSE; + } + } + Value retval = new NumericValue(v.getBoolean()); + return (cc, tt) -> retval; + }); + expression.addUnaryFunction("number", v -> { + if (v instanceof NumericValue) + { + return v; + } + try + { + return new NumericValue(v.getString()); + } + catch (NumberFormatException format) + { + return Value.NULL; + } + }); + expression.addFunction("str", lv -> + { + if (lv.size() == 0) + throw new InternalExpressionException("'str' requires at least one argument"); + String format = lv.get(0).getString(); + if (lv.size() == 1) + return new StringValue(format); + int argIndex = 1; + if (lv.get(1) instanceof ListValue && lv.size() == 2) + { + lv = ((ListValue) lv.get(1)).getItems(); + argIndex = 0; + } + List args = new ArrayList<>(); + Matcher m = formatPattern.matcher(format); + + for (int i = 0, len = format.length(); i < len; ) { + if (m.find(i)) { + // Anything between the start of the string and the beginning + // of the format specifier is either fixed text or contains + // an invalid format string. + // [[scarpet]] but we skip it and let the String.format fail + char fmt = m.group(6).toLowerCase().charAt(0); + if (fmt == 's') + { + if (argIndex >= lv.size()) + throw new InternalExpressionException("Not enough arguments for "+m.group(0)); + args.add(lv.get(argIndex).getString()); + argIndex++; + } + else if (fmt == 'd' || fmt == 'o' || fmt == 'x') + { + if (argIndex >= lv.size()) + throw new InternalExpressionException("Not enough arguments for "+m.group(0)); + args.add(lv.get(argIndex).readInteger()); + argIndex++; + } + else if (fmt == 'a' || fmt == 'e' || fmt == 'f' || fmt == 'g') + { + if (argIndex >= lv.size()) + throw new InternalExpressionException("Not enough arguments for "+m.group(0)); + args.add(lv.get(argIndex).readDoubleNumber()); + argIndex++; + } + else if (fmt == 'b') + { + if (argIndex >= lv.size()) + throw new InternalExpressionException("Not enough arguments for "+m.group(0)); + args.add(lv.get(argIndex).getBoolean()); + argIndex++; + } + else if (fmt == '%') + { + //skip /%% + } + else + { + throw new InternalExpressionException("Format not supported: "+m.group(6)); + } + + i = m.end(); + } else { + // No more valid format specifiers. Check for possible invalid + // format specifiers. + // [[scarpet]] but we skip it and let the String.format fail + break; + } + } + try + { + return new StringValue(String.format(Locale.ROOT, format, args.toArray())); + } + catch (IllegalFormatException ife) + { + throw new InternalExpressionException("Illegal string format: "+ife.getMessage()); + } + }); + + expression.addUnaryFunction("lower", v -> new StringValue(v.getString().toLowerCase(Locale.ROOT))); + + expression.addUnaryFunction("upper", v -> new StringValue(v.getString().toUpperCase(Locale.ROOT))); + + expression.addUnaryFunction("title", v -> new StringValue(WordUtils.capitalizeFully(v.getString()))); + + expression.addFunction("replace", (lv) -> + { + if (lv.size() != 3 && lv.size() !=2) + throw new InternalExpressionException("'replace' expects string to read, pattern regex, and optional replacement string"); + String data = lv.get(0).getString(); + String regex = lv.get(1).getString(); + String replacement = ""; + if (lv.size() == 3) + replacement = lv.get(2).getString(); + return new StringValue(data.replaceAll(regex, replacement)); + }); + + expression.addFunction("replace_first", (lv) -> + { + if (lv.size() != 3 && lv.size() !=2) + throw new InternalExpressionException("'replace_first' expects string to read, pattern regex, and optional replacement string"); + String data = lv.get(0).getString(); + String regex = lv.get(1).getString(); + String replacement = ""; + if (lv.size() == 3) + replacement = lv.get(2).getString(); + return new StringValue(data.replaceFirst(regex, replacement)); + }); + + expression.addUnaryFunction("type", v -> new StringValue(v.getTypeString())); + + expression.addUnaryFunction("length", v -> new NumericValue(v.length())); + expression.addLazyFunction("rand", -1, (c, t, lv) -> { + int argsize = lv.size(); + Random randomizer = Sys.randomizer; + if (argsize != 1 && argsize != 2) + throw new InternalExpressionException("'rand' takes one (range) or two arguments (range and seed)"); + if (argsize == 2) randomizer = c.host.getRandom(NumericValue.asNumber(lv.get(1).evalValue(c)).getLong()); + Value argument = lv.get(0).evalValue(c); + if (argument instanceof ListValue) + { + List list = ((ListValue) argument).getItems(); + Value retval = list.get(randomizer.nextInt(list.size())); + return (cc, tt) -> retval; + } + if (t == Context.BOOLEAN) + { + double rv = NumericValue.asNumber(argument).getDouble()*randomizer.nextFloat(); + Value retval = rv<1.0D?Value.FALSE:Value.TRUE; + return (cc, tt) -> retval; + } + Value retval = new NumericValue(NumericValue.asNumber(argument).getDouble()*randomizer.nextDouble()); + return (cc, tt) -> retval; + }); + + expression.addLazyFunction("perlin", -1, (c, t, lv) -> { + PerlinNoiseSampler sampler; + Value x, y, z; + + if (lv.size() >= 4) + { + x = lv.get(0).evalValue(c); + y = lv.get(1).evalValue(c); + z = lv.get(2).evalValue(c); + sampler = PerlinNoiseSampler.getPerlin(NumericValue.asNumber(lv.get(3).evalValue(c)).getLong()); + } + else + { + sampler = PerlinNoiseSampler.instance; + y = Value.NULL; + z = Value.NULL; + if (lv.size() == 0 ) + throw new InternalExpressionException("'perlin' requires at least one dimension to sample from"); + x = NumericValue.asNumber(lv.get(0).evalValue(c)); + if (lv.size() > 1) + { + y = NumericValue.asNumber(lv.get(1).evalValue(c)); + if (lv.size() > 2) + z = NumericValue.asNumber(lv.get(2).evalValue(c)); + } + } + + double result; + + if (z instanceof NullValue) + if (y instanceof NullValue) + result = sampler.sample1d(NumericValue.asNumber(x).getDouble()); + else + result = sampler.sample2d(NumericValue.asNumber(x).getDouble(), NumericValue.asNumber(y).getDouble()); + else + result = sampler.sample3d( + NumericValue.asNumber(x).getDouble(), + NumericValue.asNumber(y).getDouble(), + NumericValue.asNumber(z).getDouble()); + Value ret = new NumericValue(result); + return (cc, tt) -> ret; + }); + + expression.addLazyFunction("simplex", -1, (c, t, lv) -> { + SimplexNoiseSampler sampler; + Value x, y, z; + + if (lv.size() >= 4) + { + x = lv.get(0).evalValue(c); + y = lv.get(1).evalValue(c); + z = lv.get(2).evalValue(c); + sampler = SimplexNoiseSampler.getSimplex(NumericValue.asNumber(lv.get(3).evalValue(c)).getLong()); + } + else + { + sampler = SimplexNoiseSampler.instance; + z = Value.NULL; + if (lv.size() < 2 ) + throw new InternalExpressionException("'simplex' requires at least two dimensions to sample from"); + x = NumericValue.asNumber(lv.get(0).evalValue(c)); + y = NumericValue.asNumber(lv.get(1).evalValue(c)); + if (lv.size() > 2) + z = NumericValue.asNumber(lv.get(2).evalValue(c)); + } + double result; + + if (z instanceof NullValue) + result = sampler.sample2d(NumericValue.asNumber(x).getDouble(), NumericValue.asNumber(y).getDouble()); + else + result = sampler.sample3d( + NumericValue.asNumber(x).getDouble(), + NumericValue.asNumber(y).getDouble(), + NumericValue.asNumber(z).getDouble()); + Value ret = new NumericValue(result); + return (cc, tt) -> ret; + }); + + expression.addUnaryFunction("print", (v) -> + { + System.out.println(v.getString()); + return v; // pass through for variables + }); + expression.addUnaryFunction("sleep", (v) -> + { + long time = NumericValue.asNumber(v).getLong(); + try + { + Thread.sleep(time); + Thread.yield(); + } + catch (InterruptedException ignored) { } + return v; // pass through for variables + }); + expression.addLazyFunction("time", 0, (c, t, lv) -> + { + Value time = new NumericValue((System.nanoTime() / 1000) / 1000.0); + return (cc, tt) -> time; + }); + + expression.addLazyFunction("unix_time", 0, (c, t, lv) -> + { + Value time = new NumericValue(System.currentTimeMillis()); + return (cc, tt) -> time; + }); + + expression.addFunction("convert_date", lv -> + { + int argsize = lv.size(); + if (lv.size() == 0) throw new InternalExpressionException("'convert_date' requires at least one parameter"); + Value value = lv.get(0); + if (argsize == 1 && !(value instanceof ListValue)) + { + Calendar cal = new GregorianCalendar(Locale.ROOT); + cal.setTimeInMillis(NumericValue.asNumber(value, "timestamp").getLong()); + int weekday = cal.get(Calendar.DAY_OF_WEEK)-1; + if (weekday == 0) weekday = 7; + Value retVal = ListValue.ofNums( + cal.get(Calendar.YEAR), + cal.get(Calendar.MONTH)+1, + cal.get(Calendar.DAY_OF_MONTH), + cal.get(Calendar.HOUR_OF_DAY), + cal.get(Calendar.MINUTE), + cal.get(Calendar.SECOND), + weekday, + cal.get(Calendar.DAY_OF_YEAR), + cal.get(Calendar.WEEK_OF_YEAR) + ); + return retVal; + } + else if(value instanceof ListValue) + { + lv = ((ListValue) value).getItems(); + argsize = lv.size(); + } + Calendar cal = new GregorianCalendar(0, 0, 0, 0, 0, 0); + + if (argsize == 3) + { + cal.set( + NumericValue.asNumber(lv.get(0)).getInt(), + NumericValue.asNumber(lv.get(1)).getInt()-1, + NumericValue.asNumber(lv.get(2)).getInt() + ); + } + else if (argsize == 6) + { + cal.set( + NumericValue.asNumber(lv.get(0)).getInt(), + NumericValue.asNumber(lv.get(1)).getInt()-1, + NumericValue.asNumber(lv.get(2)).getInt(), + NumericValue.asNumber(lv.get(3)).getInt(), + NumericValue.asNumber(lv.get(4)).getInt(), + NumericValue.asNumber(lv.get(5)).getInt() + ); + } + else throw new InternalExpressionException("Date conversion requires 3 arguments for Dates or 6 arguments, for time"); + return new NumericValue(cal.getTimeInMillis()); + }); + + expression.addLazyFunction("profile_expr", 1, (c, t, lv) -> + { + LazyValue lazy = lv.get(0); + long end = System.nanoTime()+50000000L; + long it = 0; + while (System.nanoTime() res; + }); + + expression.addLazyFunction("var", 1, (c, t, lv) -> { + String varname = lv.get(0).evalValue(c).getString(); + return expression.getOrSetAnyVariable(c, varname); + }); + + expression.addLazyFunction("undef", 1, (c, t, lv) -> + { + Value remove = lv.get(0).evalValue(c); + if (remove instanceof FunctionValue) + { + c.host.delFunction(expression.module, remove.getString()); + return (cc, tt) -> Value.NULL; + } + String varname = remove.getString(); + boolean isPrefix = varname.endsWith("*"); + if (isPrefix) + varname = varname.replaceAll("\\*+$", ""); + if (isPrefix) + { + c.host.delFunctionWithPrefix(expression.module, varname); + if (varname.startsWith("global_")) + { + c.host.delGlobalVariableWithPrefix(expression.module, varname); + } + else if (!varname.startsWith("_")) + { + c.removeVariablesMatching(varname); + } + } + else + { + c.host.delFunction(expression.module, varname); + if (varname.startsWith("global_")) + { + c.host.delGlobalVariable(expression.module, varname); + } + else if (!varname.startsWith("_")) + { + c.delVariable(varname); + } + } + return (cc, tt) -> Value.NULL; + }); + + //deprecate + expression.addLazyFunction("vars", 1, (c, t, lv) -> { + String prefix = lv.get(0).evalValue(c).getString(); + List values = new ArrayList<>(); + if (prefix.startsWith("global")) + { + c.host.globaVariableNames(expression.module, (s) -> s.startsWith(prefix)).forEach(s -> values.add(new StringValue(s))); + } + else + { + c.getAllVariableNames().stream().filter(s -> s.startsWith(prefix)).forEach(s -> values.add(new StringValue(s))); + } + Value retval = ListValue.wrap(values); + return (cc, tt) -> retval; + }); + + expression.addLazyFunctionWithDelegation("task", -1, (c, t, expr, tok, lv) -> { + if (lv.size() == 0) + throw new InternalExpressionException("'task' requires at least function to call as a parameter"); + FunctionArgument functionArgument = FunctionArgument.findIn(c, expression.module, lv, 0, true, false); + Value queue = Value.NULL; + if (lv.size() > functionArgument.offset) queue = lv.get(functionArgument.offset).evalValue(c); + ThreadValue thread = new ThreadValue(queue, functionArgument.function, expr, tok, c, functionArgument.args); + Thread.yield(); + return (cc, tt) -> thread; + }); + + expression.addFunction("task_count", (lv) -> + { + if (lv.size() > 0) + { + return new NumericValue(ThreadValue.taskCount(lv.get(0))); + } + return new NumericValue(ThreadValue.taskCount()); + }); + + expression.addUnaryFunction("task_value", (v) -> { + if (!(v instanceof ThreadValue)) + throw new InternalExpressionException("'task_value' could only be used with a task value"); + return ((ThreadValue) v).getValue(); + }); + + expression.addUnaryFunction("task_join", (v) -> { + if (!(v instanceof ThreadValue)) + throw new InternalExpressionException("'task_join' could only be used with a task value"); + return ((ThreadValue) v).join(); + }); + + expression.addLazyFunction("task_dock", 1, (c, t, lv) -> { + // pass through placeholder + // implmenetation should dock the task on the main thread. + return lv.get(0); + }); + + expression.addUnaryFunction("task_completed", (v) -> { + if (!(v instanceof ThreadValue)) + throw new InternalExpressionException("'task_completed' could only be used with a task value"); + return new NumericValue(((ThreadValue) v).isFinished()); + }); + + expression.addLazyFunction("synchronize", -1, (c, t, lv) -> + { + if (lv.size() == 0) throw new InternalExpressionException("'synchronize' require at least an expression to synchronize"); + Value lockValue = Value.NULL; + int ind = 0; + if (lv.size() == 2) + { + lockValue = lv.get(0).evalValue(c); + ind = 1; + } + synchronized (ThreadValue.getLock(lockValue)) + { + Value ret = lv.get(ind).evalValue(c, t); + return (_c, _t) -> ret; + } + }); + + expression.addLazyFunction("system_variable_get", -1, (c, t, lv) -> + { + if (lv.size() == 0) throw new InternalExpressionException("'system_variable_get' expects at least a key to be fetched"); + Value key = lv.get(0).evalValue(c); + if (lv.size() > 1) + { + ScriptHost.systemGlobals.computeIfAbsent(key, k -> lv.get(1).evalValue(c)); + } + Value res = ScriptHost.systemGlobals.get(key); + if (res!=null) return (cc, tt) -> res; + return (cc, tt) -> Value.NULL; + }); + + expression.addLazyFunction("system_variable_set", 2, (c, t, lv) -> + { + Value key = lv.get(0).evalValue(c); + Value value = lv.get(1).evalValue(c); + Value res = ScriptHost.systemGlobals.put(key, value); + if (res!=null) return (cc, tt) -> res; + return (cc, tt) -> Value.NULL; + }); + + + } +} diff --git a/src/main/java/carpet/script/utils/ShapeDispatcher.java b/src/main/java/carpet/script/utils/ShapeDispatcher.java index 43b09746..761b8420 100644 --- a/src/main/java/carpet/script/utils/ShapeDispatcher.java +++ b/src/main/java/carpet/script/utils/ShapeDispatcher.java @@ -3,8 +3,8 @@ package carpet.script.utils; import carpet.CarpetSettings; import carpet.network.ServerNetworkHandler; import carpet.script.CarpetContext; -import carpet.script.Expression; import carpet.script.exception.InternalExpressionException; +import carpet.script.language.Sys; import carpet.script.value.BlockValue; import carpet.script.value.EntityValue; import carpet.script.value.FormattedTextValue; @@ -1310,7 +1310,7 @@ public class ShapeDispatcher for (Vec3d delta = new Vec3d(0.0,0.0,0.0); delta.lengthSquared()