package carpet.script.api; import carpet.CarpetServer; import carpet.CarpetSettings; import carpet.fakes.ChunkGeneratorInterface; import carpet.fakes.ChunkTicketManagerInterface; import carpet.fakes.NoiseColumnSamplerInterface; import carpet.fakes.ServerChunkManagerInterface; import carpet.fakes.ServerWorldInterface; import carpet.fakes.SpawnHelperInnerInterface; import carpet.fakes.ThreadedAnvilChunkStorageInterface; import carpet.helpers.FeatureGenerator; import carpet.mixins.PoiRecord_scarpetMixin; import carpet.script.CarpetContext; import carpet.script.Context; import carpet.script.Expression; import carpet.script.Fluff; import carpet.script.annotation.Locator; import carpet.script.annotation.ScarpetFunction; import carpet.script.argument.BlockArgument; import carpet.script.argument.Vector3Argument; import carpet.script.exception.InternalExpressionException; import carpet.script.exception.ThrowStatement; import carpet.script.exception.Throwables; import carpet.script.utils.BiomeInfo; import carpet.script.utils.InputValidator; import carpet.script.utils.WorldTools; import carpet.script.value.BlockValue; import carpet.script.value.BooleanValue; 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.script.value.ValueConversions; 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 org.jetbrains.annotations.Nullable; 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.BiPredicate; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; import net.minecraft.commands.arguments.item.ItemInput; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.QuartPos; import net.minecraft.core.Registry; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; import net.minecraft.network.protocol.game.ClientboundExplodePacket; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.Ticket; import net.minecraft.server.level.TicketType; import net.minecraft.sounds.SoundSource; import net.minecraft.tags.TagContainer; import net.minecraft.util.Mth; import net.minecraft.util.SortedArraySet; import net.minecraft.world.Clearable; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.ai.village.poi.PoiManager; import net.minecraft.world.entity.ai.village.poi.PoiRecord; import net.minecraft.world.entity.ai.village.poi.PoiType; import net.minecraft.world.entity.item.FallingBlockEntity; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.DiggerItem; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.item.ShearsItem; import net.minecraft.world.item.SwordItem; import net.minecraft.world.item.TridentItem; import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.item.enchantment.Enchantments; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.Explosion; import net.minecraft.world.level.Level; import net.minecraft.world.level.LightLayer; import net.minecraft.world.level.NaturalSpawner; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.BiomeSource; import net.minecraft.world.level.biome.Climate; import net.minecraft.world.level.biome.MultiNoiseBiomeSource; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.SoundType; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.block.state.properties.Property; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.levelgen.WorldgenRandom; import net.minecraft.world.level.levelgen.feature.ConfiguredStructureFeature; import net.minecraft.world.level.levelgen.feature.StructureFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.StructureStart; import net.minecraft.world.level.pathfinder.PathComputationType; import net.minecraft.world.level.storage.ServerLevelData; import net.minecraft.world.phys.Vec3; 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)); static { DIRECTION_MAP.put("y", Direction.UP); DIRECTION_MAP.put("z", Direction.SOUTH); DIRECTION_MAP.put("x", Direction.EAST); } private final static Map> ticketTypes = new HashMap>(){{ put("portal", TicketType.PORTAL); put("teleport", TicketType.POST_TELEPORT); put("unknown", TicketType.UNKNOWN); // unknown }}; // dummy entity for dummy requirements in the loot tables (see snowball) private static FallingBlockEntity DUMMY_ENTITY = null; private static Value booleanStateTest( Context c, String name, List params, BiPredicate test ) { CarpetContext cc = (CarpetContext) c; if (params.size() == 0) throw new InternalExpressionException("'" + name + "' requires at least one parameter"); Value v0 = params.get(0); if (v0 instanceof BlockValue) return BooleanValue.of(test.test(((BlockValue) v0).getBlockState(), ((BlockValue) v0).getPos())); BlockValue block = BlockArgument.findIn(cc, params, 0).block; return BooleanValue.of(test.test(block.getBlockState(), block.getPos())); } private static Value 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); if (v0 instanceof BlockValue) { String strVal = test.apply( ((BlockValue) v0).getBlockState(), ((BlockValue) v0).getPos()); return StringValue.of(strVal); } BlockValue block = BlockArgument.findIn(cc, params, 0).block; return StringValue.of(test.apply(block.getBlockState(), block.getPos())); } private static Value 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); if (v0 instanceof BlockValue) { try { return test.apply(((BlockValue) v0).getBlockState(), ((BlockValue) v0).getPos(), cc.s.getLevel()); } 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; return test.apply(block.getBlockState(), block.getPos(), cc.s.getLevel()); } private static > BlockState setProperty(Property property, String name, String value, BlockState bs) { Optional optional = property.getValue(value); if (optional.isPresent()) { bs = bs.setValue(property, optional.get()); } else { throw new InternalExpressionException(value + " is not a valid value for property " + name); } return bs; } private static void nullCheck(Value v, String name) { if (v.isNull()) { throw new IllegalArgumentException(name + " cannot be null"); } } private static float numberGetOrThrow(Value v) { double num = v.readDoubleNumber(); if (Double.isNaN(num)) { throw new IllegalArgumentException(v.getString() + " needs to be a numeric value"); } return (float) num; } private static void BooYah(ChunkGenerator generator) { synchronized (generator) { ((ChunkGeneratorInterface)generator).initStrongholds(); } } public static void apply(Expression expression) { expression.addContextFunction("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 retval; }); expression.addContextFunction("block_data", -1, (c, t, lv) -> { if (lv.size() == 0) throw new InternalExpressionException("Block requires at least one parameter"); CompoundTag tag = BlockArgument.findIn( (CarpetContext) c, lv, 0, true).block.getData(); return NBTSerializableValue.of(tag); }); // poi_get(pos, radius?, type?, occupation?, column_mode?) expression.addContextFunction("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(); PoiManager store = cc.s.getLevel().getPoiManager(); if (lv.size() == locator.offset) { PoiType poiType = store.getType(pos).orElse(null); if (poiType == null) return Value.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. PoiRecord poi = store.getInRange( poiType.getPredicate(), pos, 1, PoiManager.Occupancy.ANY ).filter(p -> p.getPos().equals(pos)).findFirst().orElse(null); if (poi == null) return Value.NULL; return ListValue.of( new StringValue(poi.getPoiType().toString()), new NumericValue(poiType.getMaxTickets() - ((PoiRecord_scarpetMixin)poi).getFreeTickets()) ); } int radius = NumericValue.asNumber(lv.get(locator.offset+0)).getInt(); if (radius < 0) return ListValue.of(); Predicate condition = PoiType.ALL; PoiManager.Occupancy status = PoiManager.Occupancy.ANY; boolean inColumn = false; if (locator.offset + 1 < lv.size()) { String poiType = lv.get(locator.offset+1).getString().toLowerCase(Locale.ROOT); if (!"any".equals(poiType)) { PoiType type = Registry.POINT_OF_INTEREST_TYPE.getOptional(InputValidator.identifierOf(poiType)) .orElseThrow(() -> new ThrowStatement(poiType, Throwables.UNKNOWN_POI)); condition = (tt) -> tt == type; } if (locator.offset + 2 < lv.size()) { String statusString = lv.get(locator.offset+2).getString().toLowerCase(Locale.ROOT); if ("occupied".equals(statusString)) status = PoiManager.Occupancy.IS_OCCUPIED; else if ("available".equals(statusString)) status = PoiManager.Occupancy.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).getBoolean(); } } } Stream pois = inColumn? store.getInSquare(condition, pos, radius, status): store.getInRange(condition, pos, radius, status); return ListValue.wrap(pois.sorted(Comparator.comparingDouble(p -> p.getPos().distSqr(pos))).map(p -> ListValue.of( new StringValue(p.getPoiType().toString()), new NumericValue(p.getPoiType().getMaxTickets() - ((PoiRecord_scarpetMixin)p).getFreeTickets()), ListValue.of(new NumericValue(p.getPos().getX()), new NumericValue(p.getPos().getY()), new NumericValue(p.getPos().getZ())) ) ).collect(Collectors.toList())); }); //poi_set(pos, null) poi_set(pos, type, occupied?, expression.addContextFunction("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); PoiManager store = cc.s.getLevel().getPoiManager(); if (poi.isNull()) { // clear poi information if (!store.getType(pos).isPresent()) return Value.FALSE; store.remove(pos); return Value.TRUE; } String poiTypeString = poi.getString().toLowerCase(Locale.ROOT); PoiType type = Registry.POINT_OF_INTEREST_TYPE.getOptional(InputValidator.identifierOf(poiTypeString)) .orElseThrow(() -> new ThrowStatement(poiTypeString, Throwables.UNKNOWN_POI)); int occupancy = 0; if (locator.offset + 1 < lv.size()) { occupancy = (int)NumericValue.asNumber(lv.get(locator.offset + 1)).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, PoiManager.Occupancy.ANY ).filter(p -> p.getPos().equals(pos)).findFirst().ifPresent(p -> { for (int i=0; i < finalO; i++) ((PoiRecord_scarpetMixin)p).callAcquireTicket(); }); } return Value.TRUE; }); expression.addContextFunction("weather",-1,(c, t, lv) -> { ServerLevel world = ((CarpetContext) c).s.getLevel(); if(lv.size()==0)//cos it can thunder when raining or when clear. return new StringValue(world.isThundering() ? "thunder" : (world.isRaining() ? "rain" : "clear")); Value weather = lv.get(0); ServerLevelData worldProperties = ((ServerWorldInterface) world).getWorldPropertiesCM(); if(lv.size()==1) { int ticks; switch (weather.getString().toLowerCase(Locale.ROOT)) { case "clear": ticks = worldProperties.getClearWeatherTime(); break; case "rain": ticks = world.isRaining()? worldProperties.getRainTime():0;//cos if not it gives 1 for some reason break; case "thunder": ticks = world.isThundering()? worldProperties.getThunderTime():0;//same dealio here break; default: throw new InternalExpressionException("Weather can only be 'clear', 'rain' or 'thunder'"); } return new NumericValue(ticks); } if(lv.size()==2) { int ticks = NumericValue.asNumber(lv.get(1), "tick_time in 'weather'").getInt(); switch (weather.getString().toLowerCase(Locale.ROOT)) { case "clear": world.setWeatherParameters(ticks,0,false,false); break; case "rain": world.setWeatherParameters(0,ticks,true,false); break; case "thunder": world.setWeatherParameters( 0, ticks,//this is used to set thunder time, idk why... true, true ); break; default: throw new InternalExpressionException("Weather can only be 'clear', 'rain' or 'thunder'"); } return NumericValue.of(ticks); } throw new InternalExpressionException("'weather' requires 0, 1 or 2 arguments"); }); expression.addUnaryFunction("pos", v -> { if (v instanceof BlockValue) { BlockPos pos = ((BlockValue) v).getPos(); if (pos == null) throw new InternalExpressionException("Cannot fetch position of an unrealized block"); return ListValue.of(new NumericValue(pos.getX()), new NumericValue(pos.getY()), new NumericValue(pos.getZ())); } else if (v instanceof EntityValue) { Entity e = ((EntityValue) v).getEntity(); if (e == null) throw new InternalExpressionException("Null entity"); return ListValue.of(new NumericValue(e.getX()), new NumericValue(e.getY()), new NumericValue(e.getZ())); } else { throw new InternalExpressionException("'pos' works only with a block or an entity type"); } }); expression.addContextFunction("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).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)).getLong(); BlockPos retpos = pos.relative(dir, howMuch); return ListValue.of(new NumericValue(retpos.getX()), new NumericValue(retpos.getY()), new NumericValue(retpos.getZ())); }); expression.addContextFunction("solid", -1, (c, t, lv) -> genericStateTest(c, "solid", lv, (s, p, w) -> BooleanValue.of(s.isRedstoneConductor(w, p)))); // isSimpleFullBlock expression.addContextFunction("air", -1, (c, t, lv) -> booleanStateTest(c, "air", lv, (s, p) -> s.isAir())); expression.addContextFunction("liquid", -1, (c, t, lv) -> booleanStateTest(c, "liquid", lv, (s, p) -> !s.getFluidState().isEmpty())); expression.addContextFunction("flammable", -1, (c, t, lv) -> booleanStateTest(c, "flammable", lv, (s, p) -> s.getMaterial().isFlammable())); expression.addContextFunction("transparent", -1, (c, t, lv) -> booleanStateTest(c, "transparent", lv, (s, p) -> !s.getMaterial().isSolid())); /*this.expr.addContextFunction("opacity", -1, (c, t, lv) -> genericStateTest(c, "opacity", lv, (s, p, w) -> new NumericValue(s.getOpacity(w, p)))); this.expr.addContextFunction("blocks_daylight", -1, (c, t, lv) -> genericStateTest(c, "blocks_daylight", lv, (s, p, w) -> new NumericValue(s.propagatesSkylightDown(w, p))));*/ // investigate expression.addContextFunction("emitted_light", -1, (c, t, lv) -> genericStateTest(c, "emitted_light", lv, (s, p, w) -> new NumericValue(s.getLightEmission()))); expression.addContextFunction("light", -1, (c, t, lv) -> genericStateTest(c, "light", lv, (s, p, w) -> new NumericValue(Math.max(w.getBrightness(LightLayer.BLOCK, p), w.getBrightness(LightLayer.SKY, p))))); expression.addContextFunction("block_light", -1, (c, t, lv) -> genericStateTest(c, "block_light", lv, (s, p, w) -> new NumericValue(w.getBrightness(LightLayer.BLOCK, p)))); expression.addContextFunction("sky_light", -1, (c, t, lv) -> genericStateTest(c, "sky_light", lv, (s, p, w) -> new NumericValue(w.getBrightness(LightLayer.SKY, p)))); expression.addContextFunction("see_sky", -1, (c, t, lv) -> genericStateTest(c, "see_sky", lv, (s, p, w) -> BooleanValue.of(w.canSeeSky(p)))); expression.addContextFunction("brightness", -1, (c, t, lv) -> genericStateTest(c, "brightness", lv, (s, p, w) -> new NumericValue(w.getBrightness(p)))); expression.addContextFunction("hardness", -1, (c, t, lv) -> genericStateTest(c, "hardness", lv, (s, p, w) -> new NumericValue(s.getDestroySpeed(w, p)))); expression.addContextFunction("blast_resistance", -1, (c, t, lv) -> genericStateTest(c, "blast_resistance", lv, (s, p, w) -> new NumericValue(s.getBlock().getExplosionResistance()))); expression.addContextFunction("in_slime_chunk", -1, (c, t, lv) -> { BlockPos pos = BlockArgument.findIn((CarpetContext)c, lv, 0).block.getPos(); ChunkPos chunkPos = new ChunkPos(pos); return BooleanValue.of(WorldgenRandom.seedSlimeChunk( chunkPos.x, chunkPos.z, ((CarpetContext)c).s.getLevel().getSeed(), 987234911L ).nextInt(10) == 0); }); expression.addContextFunction("top", -1, (c, t, lv) -> { String type = lv.get(0).getString().toLowerCase(Locale.ROOT); Heightmap.Types htype; switch (type) { //case "light": htype = Heightmap.Type.LIGHT_BLOCKING; break; //investigate case "motion": htype = Heightmap.Types.MOTION_BLOCKING; break; case "terrain": htype = Heightmap.Types.MOTION_BLOCKING_NO_LEAVES; break; case "ocean_floor": htype = Heightmap.Types.OCEAN_FLOOR; break; case "surface": htype = Heightmap.Types.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(); return new NumericValue(((CarpetContext)c).s.getLevel().getChunk(x >> 4, z >> 4).getHeight(htype, x & 15, z & 15) + 1); }); expression.addContextFunction("loaded", -1, (c, t, lv) -> BooleanValue.of((((CarpetContext) c).s.getLevel().hasChunkAt(BlockArgument.findIn((CarpetContext) c, lv, 0).block.getPos())))); // Deprecated, use loaded_status as more indicative expression.addContextFunction("loaded_ep", -1, (c, t, lv) -> { c.host.issueDeprecation("loaded_ep(...)"); BlockPos pos = BlockArgument.findIn((CarpetContext)c, lv, 0).block.getPos(); return BooleanValue.of(((CarpetContext)c).s.getLevel().isPositionEntityTicking(pos));// 1.17pre1 getChunkManager().shouldTickChunk(new ChunkPos(pos))); }); expression.addContextFunction("loaded_status", -1, (c, t, lv) -> { BlockPos pos = BlockArgument.findIn((CarpetContext)c, lv, 0).block.getPos(); LevelChunk chunk = ((CarpetContext)c).s.getLevel().getChunkSource().getChunk(pos.getX()>>4, pos.getZ()>>4, false); if (chunk == null) return Value.ZERO; return new NumericValue(chunk.getFullStatus().ordinal()); }); expression.addContextFunction("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).getBoolean(); return BooleanValue.of(canHasChunk(((CarpetContext)c).s.getLevel(), new ChunkPos(pos), null, force)); }); expression.addContextFunction("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).getBoolean(); ChunkAccess chunk = ((CarpetContext)c).s.getLevel().getChunk(pos.getX()>>4, pos.getZ()>>4, ChunkStatus.EMPTY, forceLoad); if (chunk == null) return Value.NULL; return new StringValue(chunk.getStatus().getName()); }); expression.addContextFunction("chunk_tickets", -1, (c, t, lv) -> { ServerLevel world = ((CarpetContext) c).s.getLevel(); Long2ObjectOpenHashMap>> levelTickets = ( (ChunkTicketManagerInterface) ((ServerChunkManagerInterface) world.getChunkSource()) .getCMTicketManager() ).getTicketsByPosition(); List res = new ArrayList<>(); if (lv.size() == 0) { for (long key : levelTickets.keySet()) { ChunkPos chpos = new ChunkPos(key); for (Ticket ticket : levelTickets.get(key)) { res.add(ListValue.of( new StringValue(ticket.getType().toString()), new NumericValue(33 - ticket.getTicketLevel()), 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 (Ticket ticket : tickets) { res.add(ListValue.of( new StringValue(ticket.getType().toString()), new NumericValue(33 - ticket.getTicketLevel()) )); } } } res.sort(Comparator.comparing(e -> ((ListValue) e).getItems().get(1)).reversed()); return ListValue.wrap(res); }); expression.addContextFunction("suffocates", -1, (c, t, lv) -> genericStateTest(c, "suffocates", lv, (s, p, w) -> BooleanValue.of(s.isSuffocating(w, p)))); // canSuffocate expression.addContextFunction("power", -1, (c, t, lv) -> genericStateTest(c, "power", lv, (s, p, w) -> new NumericValue(w.getBestNeighborSignal(p)))); expression.addContextFunction("ticks_randomly", -1, (c, t, lv) -> booleanStateTest(c, "ticks_randomly", lv, (s, p) -> s.isRandomlyTicking())); expression.addContextFunction("update", -1, (c, t, lv) -> booleanStateTest(c, "update", lv, (s, p) -> { ((CarpetContext) c).s.getLevel().neighborChanged(p, s.getBlock(), p); return true; })); expression.addContextFunction("block_tick", -1, (c, t, lv) -> booleanStateTest(c, "block_tick", lv, (s, p) -> { ServerLevel w = ((CarpetContext)c).s.getLevel(); s.randomTick(w, p, w.random); return true; })); expression.addContextFunction("random_tick", -1, (c, t, lv) -> booleanStateTest(c, "random_tick", lv, (s, p) -> { ServerLevel w = ((CarpetContext)c).s.getLevel(); if (s.isRandomlyTicking() || s.getFluidState().isRandomlyTicking()) s.randomTick(w, p, w.random); return true; })); // lazy cause its parked execution expression.addLazyFunction("without_updates", 1, (c, t, lv) -> { boolean previous = CarpetSettings.impendingFillSkipUpdates.get(); if (previous) return lv.get(0); Value [] result = new Value[]{Value.NULL}; ((CarpetContext)c).s.getServer().executeBlocking( () -> { try { CarpetSettings.impendingFillSkipUpdates.set(true); result[0] = lv.get(0).evalValue(c, t); } finally { CarpetSettings.impendingFillSkipUpdates.set(previous); } }); return (cc, tt) -> result[0]; }); expression.addContextFunction("set", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; ServerLevel world = cc.s.getLevel(); 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)); } 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.get(0) instanceof MapValue) { if (args.size() == 2) { Value dataValue = NBTSerializableValue.fromValue( args.get(1)); if (dataValue instanceof NBTSerializableValue) { data = ((NBTSerializableValue) dataValue).getCompoundTag(); } } Map state = ((MapValue) args.get(0)).getMap(); List mapargs = new ArrayList<>(); state.forEach( (k, v) -> {mapargs.add(k); mapargs.add(v);}); args = mapargs; } else { if ((args.size() & 1) == 1) { Value dataValue = NBTSerializableValue.fromValue( args.get(args.size()-1)); if (dataValue instanceof NBTSerializableValue) { data = ((NBTSerializableValue) dataValue).getCompoundTag(); } } } StateDefinition states = sourceBlockState.getBlock().getStateDefinition(); 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 Value.FALSE; BlockState finalSourceBlockState = sourceBlockState; BlockPos targetPos = targetLocator.block.getPos(); Boolean[] result = new Boolean[]{true}; cc.s.getServer().executeBlocking( () -> { Clearable.tryClear(world.getBlockEntity(targetPos)); boolean success = world.setBlock(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.load(destTag); be.setChanged(); success = true; } } result[0] = success; }); if (!result[0]) return Value.FALSE; return new BlockValue(finalSourceBlockState, world, targetLocator.block.getPos()); }); expression.addContextFunction("destroy", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; ServerLevel world = cc.s.getLevel(); BlockArgument locator = BlockArgument.findIn(cc, lv, 0); BlockState state = locator.block.getBlockState(); if (state.isAir()) return 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); if (val instanceof NumericValue) { how = ((NumericValue) val).getLong(); } else { playerBreak = true; String itemString = val.getString(); item = Registry.ITEM.getOptional(InputValidator.identifierOf(itemString)) .orElseThrow(() -> new ThrowStatement(itemString, Throwables.UNKNOWN_ITEM)); } } 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); if (tagValue.isNull()) tag = null; else if (tagValue instanceof NBTSerializableValue) tag = ((NBTSerializableValue) tagValue).getCompoundTag(); else { NBTSerializableValue readTag = NBTSerializableValue.parseString(tagValue.getString(), true); tag = readTag.getCompoundTag(); } } ItemStack tool = new ItemStack(item, 1); if (tag != null) tool.setTag(tag); if (playerBreak && state.getDestroySpeed(world, where) < 0.0) return Value.FALSE; boolean removed = world.removeBlock(where, false); if (!removed) return Value.FALSE; world.levelEvent(null, 2001, where, Block.getId(state)); boolean toolBroke = false; boolean dropLoot = true; if (playerBreak) { boolean isUsingEffectiveTool = !state.requiresCorrectToolForDrops() || tool.isCorrectToolForDrops(state); //postMine() durability from item classes float hardness = state.getDestroySpeed(world, where); int damageAmount = 0; if ((item instanceof DiggerItem && hardness > 0.0) || item instanceof ShearsItem) { damageAmount = 1; } else if (item instanceof TridentItem || item instanceof SwordItem) { damageAmount = 2; } toolBroke = damageAmount>0 && tool.hurt(damageAmount, world.getRandom(), null); if (!isUsingEffectiveTool) dropLoot = false; } if (dropLoot) { if (how < 0 || (tag != null && EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) > 0)) { Block.popResource(world, where, new ItemStack(state.getBlock())); } else { if (how > 0) tool.enchant(Enchantments.BLOCK_FORTUNE, (int) how); if (DUMMY_ENTITY == null) DUMMY_ENTITY = new FallingBlockEntity(EntityType.FALLING_BLOCK, null); Block.dropResources(state, world, where, be, DUMMY_ENTITY, tool); } } if (!playerBreak) // no tool info - block brokwn return Value.TRUE; if (toolBroke) return Value.NULL; Tag outtag = tool.getTag(); if (outtag == null) return Value.TRUE; return new NBTSerializableValue(() -> outtag); }); expression.addContextFunction("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; Level world = cc.s.getLevel(); Value entityValue = lv.get(0); if (!(entityValue instanceof EntityValue)) return Value.FALSE; Entity e = ((EntityValue) entityValue).getEntity(); if (!(e instanceof ServerPlayer)) return Value.FALSE; ServerPlayer player = (ServerPlayer)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.gameMode.isSurvival())) success = player.gameMode.destroyBlock(where); if (success) world.levelEvent(null, 2001, where, Block.getId(state)); return BooleanValue.of(success); }); expression.addContextFunction("create_explosion", -1, (c, t, lv) -> { if (lv.isEmpty()) throw new InternalExpressionException("'create_explosion' requires at least a position to explode"); CarpetContext cc = (CarpetContext)c; float powah = 4.0f; Explosion.BlockInteraction mode = Explosion.BlockInteraction.BREAK; boolean createFire = false; Entity source = null; LivingEntity attacker = null; Vector3Argument location = Vector3Argument.findIn(lv, 0, false, true); Vec3 pos = location.vec; if (lv.size() > location.offset) { powah = NumericValue.asNumber(lv.get(location.offset), "explosion power").getFloat(); if (powah < 0) throw new InternalExpressionException("Explosion power cannot be negative"); if (lv.size() > location.offset+1) { String strval = lv.get(location.offset+1).getString(); try { mode = Explosion.BlockInteraction.valueOf(strval.toUpperCase(Locale.ROOT)); } catch (IllegalArgumentException ile) { throw new InternalExpressionException("Illegal explosions block behaviour: "+strval); } if (lv.size() > location.offset+2) { createFire = lv.get(location.offset+2).getBoolean(); if (lv.size() > location.offset+3) { Value enVal= lv.get(location.offset+3); if (enVal.isNull()) {} // is null already else if (enVal instanceof EntityValue) { source = ((EntityValue) enVal).getEntity(); } else { throw new InternalExpressionException("Fourth parameter of the explosion has to be an entity, not "+enVal.getTypeString()); } if (lv.size() > location.offset+4) { enVal = lv.get(location.offset+4); if (enVal.isNull()) {} // is null already else if (enVal instanceof EntityValue) { Entity attackingEntity = ((EntityValue) enVal).getEntity(); if (attackingEntity instanceof LivingEntity) { attacker = (LivingEntity) attackingEntity; } else throw new InternalExpressionException("Attacking entity needs to be a living thing, "+ ValueConversions.of(Registry.ENTITY_TYPE.getKey(attackingEntity.getType())).getString() +" ain't it."); } else { throw new InternalExpressionException("Fifth parameter of the explosion has to be a living entity, not "+enVal.getTypeString()); } } } } } } LivingEntity theAttacker = attacker; float thePowah = powah; // copy of ServerWorld.createExplosion #TRACK# Explosion explosion = new Explosion(cc.s.getLevel(), source, null, null, pos.x, pos.y, pos.z, powah, createFire, mode){ @Override public @Nullable LivingEntity getSourceMob() { return theAttacker; } }; explosion.explode(); explosion.finalizeExplosion(false); if (mode == Explosion.BlockInteraction.NONE) explosion.clearToBlow(); cc.s.getLevel().players().forEach(spe -> { if (spe.distanceToSqr(pos) < 4096.0D) spe.connection.send(new ClientboundExplodePacket(pos.x, pos.y, pos.z, thePowah, explosion.getToBlow(), explosion.getHitPlayers().get(spe))); }); return Value.TRUE; }); // TODO rename to use_item expression.addContextFunction("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).getString(); Vector3Argument locator = Vector3Argument.findIn(lv, 1); ItemInput stackArg = NBTSerializableValue.parseItem(itemString); BlockPos where = new BlockPos(locator.vec); String facing = "up"; if (lv.size() > locator.offset) facing = lv.get(locator.offset).getString(); boolean sneakPlace = false; if (lv.size() > locator.offset+1) sneakPlace = lv.get(locator.offset+1).getBoolean(); BlockValue.PlacementContext ctx; try { ctx = BlockValue.PlacementContext.from(cc.s.getLevel(), where, facing, sneakPlace, stackArg.createItemStack(1, false)); } catch (CommandSyntaxException e) { throw new InternalExpressionException(e.getMessage()); } if (!(stackArg.getItem() instanceof BlockItem)) { InteractionResult useResult = ctx.getItemInHand().useOn(ctx); if (useResult == InteractionResult.CONSUME || useResult == InteractionResult.SUCCESS) { return Value.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 Value.FALSE; BlockState placementState = blockItem.getBlock().getStateForPlacement(ctx); if (placementState != null) { if (placementState.canSurvive(cc.s.getLevel(), where)) { cc.s.getLevel().setBlock(where, placementState, 2); SoundType blockSoundGroup = placementState.getSoundType(); cc.s.getLevel().playSound(null, where, blockSoundGroup.getPlaceSound(), SoundSource.BLOCKS, (blockSoundGroup.getVolume() + 1.0F) / 2.0F, blockSoundGroup.getPitch() * 0.8F); return Value.TRUE; } } } return Value.FALSE; }); expression.addContextFunction("blocks_movement", -1, (c, t, lv) -> booleanStateTest(c, "blocks_movement", lv, (s, p) -> !s.isPathfindable(((CarpetContext) c).s.getLevel(), p, PathComputationType.LAND))); expression.addContextFunction("block_sound", -1, (c, t, lv) -> stateStringQuery(c, "block_sound", lv, (s, p) -> BlockInfo.soundName.get(s.getSoundType()))); expression.addContextFunction("material", -1, (c, t, lv) -> stateStringQuery(c, "material", lv, (s, p) -> BlockInfo.materialName.get(s.getMaterial()))); expression.addContextFunction("map_colour", -1, (c, t, lv) -> stateStringQuery(c, "map_colour", lv, (s, p) -> BlockInfo.mapColourName.get(s.getMapColor(((CarpetContext)c).s.getLevel(), p)))); // Deprecated for block_state() expression.addContextFunction("property", -1, (c, t, lv) -> { c.host.issueDeprecation("property(...)"); 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).getString(); StateDefinition states = state.getBlock().getStateDefinition(); Property property = states.getProperty(tag); if (property == null) return Value.NULL; return new StringValue(state.getValue(property).toString().toLowerCase(Locale.ROOT)); }); // Deprecated for block_state() expression.addContextFunction("block_properties", -1, (c, t, lv) -> { c.host.issueDeprecation("block_properties(...)"); BlockArgument locator = BlockArgument.findIn((CarpetContext) c, lv, 0); BlockState state = locator.block.getBlockState(); StateDefinition states = state.getBlock().getStateDefinition(); return ListValue.wrap(states.getProperties().stream().map( p -> new StringValue(p.getName())).collect(Collectors.toList()) ); }); // block_state(block) // block_state(block, property) expression.addContextFunction("block_state", -1, (c, t, lv) -> { BlockArgument locator = BlockArgument.findIn((CarpetContext) c, lv, 0, true); BlockState state = locator.block.getBlockState(); StateDefinition states = state.getBlock().getStateDefinition(); if (locator.offset == lv.size()) { Map properties = new HashMap<>(); for(Property p : states.getProperties()) { properties.put(StringValue.of(p.getName()), ValueConversions.fromProperty(state, p));// ValueConversions.fromObject(state.get(p), false)); } return MapValue.wrap(properties); } String tag = lv.get(locator.offset).getString(); Property property = states.getProperty(tag); if (property == null) return Value.NULL; return ValueConversions.fromProperty(state, property); }); expression.addContextFunction("block_list", -1, (c, t, lv) -> { if (lv.size() == 0) return ListValue.wrap(Registry.BLOCK.keySet().stream().map(ValueConversions::of).collect(Collectors.toList())); CarpetContext cc = (CarpetContext)c; TagContainer tagManager = cc.s.getServer().getTags(); String tag = lv.get(0).getString(); net.minecraft.tags.Tag blockTag = tagManager.getOrEmpty(Registry.BLOCK_REGISTRY).getTag(InputValidator.identifierOf(tag)); if (blockTag == null) return Value.NULL; return ListValue.wrap(blockTag.getValues().stream().map(b -> ValueConversions.of(Registry.BLOCK.getKey(b))).collect(Collectors.toList())); }); expression.addContextFunction("block_tags", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; TagContainer tagManager = cc.s.getServer().getTags(); if (lv.size() == 0) return ListValue.wrap(tagManager.getOrEmpty(Registry.BLOCK_REGISTRY).getAvailableTags().stream().map(ValueConversions::of).collect(Collectors.toList())); BlockArgument blockLocator = BlockArgument.findIn(cc, lv, 0, true); if (blockLocator.offset == lv.size()) { Block target = blockLocator.block.getBlockState().getBlock(); return ListValue.wrap(tagManager.getOrEmpty(Registry.BLOCK_REGISTRY).getAllTags().entrySet().stream().filter(e -> e.getValue().contains(target)).map(e -> ValueConversions.of(e.getKey())).collect(Collectors.toList())); } String tag = lv.get(blockLocator.offset).getString(); net.minecraft.tags.Tag blockTag = tagManager.getOrEmpty(Registry.BLOCK_REGISTRY).getTag(InputValidator.identifierOf(tag)); if (blockTag == null) return Value.NULL; return BooleanValue.of(blockLocator.block.getBlockState().is(blockTag)); }); expression.addContextFunction("biome", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext) c; ServerLevel world = cc.s.getLevel(); if (lv.size() == 0) return ListValue.wrap(world.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY).keySet().stream().map(ValueConversions::of)); Biome biome; BiomeSource biomeSource = world.getChunkSource().getGenerator().getBiomeSource(); if ( lv.size() == 1 && lv.get(0) instanceof MapValue map && biomeSource instanceof MultiNoiseBiomeSource mnbs ) { Value temperature = map.get(new StringValue("temperature")); nullCheck(temperature, "temperature"); Value humidity = map.get(new StringValue("humidity")); nullCheck(humidity, "humidity"); Value continentalness = map.get(new StringValue("continentalness")); nullCheck(continentalness, "continentalness"); Value erosion = map.get(new StringValue("erosion")); nullCheck(erosion, "erosion"); Value depth = map.get(new StringValue("depth")); nullCheck(depth, "depth"); Value weirdness = map.get(new StringValue("weirdness")); nullCheck(weirdness, "weirdness"); Climate.TargetPoint point = new Climate.TargetPoint( Climate.quantizeCoord(numberGetOrThrow(temperature)), Climate.quantizeCoord(numberGetOrThrow(humidity)), Climate.quantizeCoord(numberGetOrThrow(continentalness)), Climate.quantizeCoord(numberGetOrThrow(erosion)), Climate.quantizeCoord(numberGetOrThrow(depth)), Climate.quantizeCoord(numberGetOrThrow(weirdness)) ); biome = mnbs.getNoiseBiome(point); ResourceLocation biomeId = cc.s.getServer().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY).getKey(biome); return new StringValue(NBTSerializableValue.nameFromRegistryId(biomeId)); } BlockArgument locator = BlockArgument.findIn(cc, lv, 0, false, false, true); if (locator.replacement != null) { biome = world.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY).get(InputValidator.identifierOf(locator.replacement)); if (biome == null) throw new ThrowStatement(locator.replacement, Throwables.UNKNOWN_BIOME); } else { BlockPos pos = locator.block.getPos(); biome = world.getBiome(pos); } // in locatebiome if (locator.offset == lv.size()) { ResourceLocation biomeId = cc.s.getServer().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY).getKey(biome); return new StringValue(NBTSerializableValue.nameFromRegistryId(biomeId)); } String biomeFeature = lv.get(locator.offset).getString(); BiFunction featureProvider = BiomeInfo.biomeFeatures.get(biomeFeature); if (featureProvider == null) throw new InternalExpressionException("Unknown biome feature: " + biomeFeature); return featureProvider.apply(world, biome); }); expression.addContextFunction("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).getString(); // from locatebiome command code Biome biome = cc.s.getServer().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY).getOptional(InputValidator.identifierOf(biomeName)) .orElseThrow(() -> new ThrowStatement(biomeName, Throwables.UNKNOWN_BIOME)); boolean doImmediateUpdate = true; if (lv.size() > locator.offset+1) { doImmediateUpdate = lv.get(locator.offset+1).getBoolean(); } ServerLevel world = cc.s.getLevel(); BlockPos pos = locator.block.getPos(); ChunkAccess chunk = world.getChunk(pos.getX() >> 4, pos.getZ() >> 4, ChunkStatus.BIOMES); if (chunk instanceof LevelChunk) return Value.FALSE; // questinoalble, but it makes sense int biomeX = QuartPos.fromBlock(pos.getX()); int biomeY = QuartPos.fromBlock(pos.getY()); int biomeZ = QuartPos.fromBlock(pos.getZ()); try { int i = QuartPos.fromBlock(chunk.getMinBuildHeight()); int j = i + QuartPos.fromBlock(chunk.getHeight()) - 1; int k = Mth.clamp(biomeY, i, j); int l = chunk.getSectionIndex(QuartPos.toBlock(k)); chunk.getSection(l).getBiomes().set(biomeX & 3, k & 3, biomeZ & 3, biome); } catch (Throwable var8) { return Value.FALSE; } if (doImmediateUpdate) WorldTools.forceChunkUpdate(pos, world); return Value.TRUE; }); expression.addContextFunction("reload_chunk", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; BlockPos pos = BlockArgument.findIn(cc, lv, 0).block.getPos(); ServerLevel world = cc.s.getLevel(); cc.s.getServer().executeBlocking( () -> WorldTools.forceChunkUpdate(pos, world)); return Value.TRUE; }); expression.addContextFunction("structure_references", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; BlockArgument locator = BlockArgument.findIn(cc, lv, 0); ServerLevel world = cc.s.getLevel(); BlockPos pos = locator.block.getPos(); Map, LongSet> references = world.getChunk(pos.getX() >> 4, pos.getZ() >> 4, ChunkStatus.STRUCTURE_REFERENCES).getAllReferences(); if (lv.size() == locator.offset) return ListValue.wrap(references.entrySet().stream(). filter(e -> e.getValue()!= null && !e.getValue().isEmpty()). map(e -> new StringValue(NBTSerializableValue.nameFromRegistryId(Registry.STRUCTURE_FEATURE.getKey(e.getKey())))).collect(Collectors.toList()) ); String simpleStructureName = lv.get(locator.offset).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(InputValidator.identifierOf(simpleStructureName)); if (structureName == null) return Value.NULL; LongSet structureReferences = references.get(structureName); if (structureReferences == null || structureReferences.isEmpty()) return ListValue.of(); return ListValue.wrap(structureReferences.stream().map(l -> ListValue.of( new NumericValue(16*ChunkPos.getX(l)), Value.ZERO, new NumericValue(16*ChunkPos.getZ(l)))).collect(Collectors.toList())); }); expression.addContextFunction("structure_eligibility", -1, (c, t, lv) -> {// TODO rename structureName to class CarpetContext cc = (CarpetContext)c; BlockArgument locator = BlockArgument.findIn(cc, lv, 0); ServerLevel world = cc.s.getLevel(); // well, because BooYah(world.getChunkSource().getGenerator()); BlockPos pos = locator.block.getPos(); StructureFeature structure = null; boolean needSize = false; if (lv.size() > locator.offset) { Value requested = lv.get(locator.offset+0); if (!requested.isNull()) { String reqString = requested.getString(); structure = Registry.STRUCTURE_FEATURE.getOptional(InputValidator.identifierOf(reqString)) .orElseThrow(() -> new ThrowStatement(reqString, Throwables.UNKNOWN_STRUCTURE)); } if (lv.size() > locator.offset+1) { needSize = lv.get(locator.offset+1).getBoolean(); } } if (structure != null) { StructureStart start = FeatureGenerator.shouldStructureStartAt(world, pos, structure, needSize); if (start == null) return Value.NULL; if (!needSize) return Value.TRUE; return ValueConversions.of(start); } Map ret = new HashMap<>(); for(StructureFeature str : StructureFeature.STRUCTURES_REGISTRY.values()) { StructureStart start; try { start = FeatureGenerator.shouldStructureStartAt(world, pos, str, needSize); } catch (NullPointerException npe) { CarpetSettings.LOG.error("Failed to detect structure: "+str.getFeatureName()); start = null; } if (start == null) continue; Value key = new StringValue(NBTSerializableValue.nameFromRegistryId(Registry.STRUCTURE_FEATURE.getKey(str))); ret.put(key, (!needSize)?Value.NULL: ValueConversions.of(start)); } return MapValue.wrap(ret); }); expression.addContextFunction("structures", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; BlockArgument locator = BlockArgument.findIn(cc, lv, 0); ServerLevel world = cc.s.getLevel(); BlockPos pos = locator.block.getPos(); Map, StructureStart> structures = world.getChunk(pos.getX() >> 4, pos.getZ() >> 4, ChunkStatus.STRUCTURE_STARTS).getAllStarts(); if (lv.size() == locator.offset) { Map structureList = new HashMap<>(); for (Map.Entry, StructureStart> entry : structures.entrySet()) { StructureStart start = entry.getValue(); if (start == StructureStart.INVALID_START) continue; BoundingBox box = start.getBoundingBox(); structureList.put( new StringValue(NBTSerializableValue.nameFromRegistryId(Registry.STRUCTURE_FEATURE.getKey(entry.getKey()))), ListValue.of(ListValue.fromTriple(box.minX(), box.minY(), box.minZ()), ListValue.fromTriple(box.maxX(), box.maxY(), box.maxZ())) ); } return MapValue.wrap(structureList); } String structureName = lv.get(locator.offset).getString().toLowerCase(Locale.ROOT); return ValueConversions.of(structures.get(Registry.STRUCTURE_FEATURE.get(InputValidator.identifierOf(structureName)))); }); expression.addContextFunction("set_structure", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; BlockArgument locator = BlockArgument.findIn(cc, lv, 0); ServerLevel world = cc.s.getLevel(); 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).getString().toLowerCase(Locale.ROOT); ConfiguredStructureFeature configuredStructure = FeatureGenerator.resolveConfiguredStructure(structureName, world, pos); if (configuredStructure == null) throw new ThrowStatement(structureName, Throwables.UNKNOWN_STRUCTURE); // 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.getServer().executeBlocking(() -> { Map, StructureStart> structures = world.getChunk(pos).getAllStarts(); if (lv.size() == locator.offset + 1) { Boolean res = FeatureGenerator.plopGrid(configuredStructure, ((CarpetContext) c).s.getLevel(), 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); if (newValue.isNull()) // remove structure { StructureFeature structure = configuredStructure.feature; if (!structures.containsKey(structure)) { return; } StructureStart start = structures.get(structure); ChunkPos structureChunkPos = start.getChunkPos(); // new ChunkPos(start.getChunkX(), start.getChunkZ()); BoundingBox box = start.getBoundingBox(); for (int chx = box.minX() / 16; chx <= box.maxX() / 16; chx++) // minx maxx { for (int chz = box.minZ() / 16; chz <= box.maxZ() / 16; chz++) //minZ maxZ { ChunkPos chpos = new ChunkPos(chx, chz); // getting a chunk will convert it to full, allowing to modify references Map, LongSet> references = world.getChunk(chpos.getWorldPosition()).getAllReferences(); if (references.containsKey(structure) && references.get(structure) != null) references.get(structure).remove(structureChunkPos.toLong()); } } structures.remove(structure); result[0] = Value.TRUE; } }); return result[0]; // preventing from lazy evaluating of the result in case a future completes later }); expression.addContextFunction("custom_dimension", -1, (c, t, lv) -> { if (lv.size() == 0) throw new InternalExpressionException("'custom_dimension' requires at least one argument"); CarpetContext cc = (CarpetContext)c; cc.host.issueDeprecation("custom_dimension()"); String worldKey = lv.get(0).getString(); Long seed = null; if (lv.size() > 1) { String seedKey = lv.get(1).getString(); try { seed = Long.parseLong(seedKey); } catch (NumberFormatException ignored) { throw new InternalExpressionException("Incorrect number format for seed: " + seedKey); } } boolean success = WorldTools.createWorld(cc.s.getServer(), worldKey, seed); if (!success) return Value.FALSE; CarpetServer.settingsManager.notifyPlayersCommandsChanged(); return Value.TRUE; }); // todo maybe enable chunk blending? expression.addContextFunction("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); if (first instanceof ListValue) { List listVal = ((ListValue) first).getItems(); int offset = 0; BlockArgument locator = BlockArgument.findIn(cc, listVal, 0); requestedChunks.add(new ChunkPos(locator.block.getPos())); while (listVal.size() > locator.offset) { locator = BlockArgument.findIn(cc, listVal, locator.offset); requestedChunks.add(new ChunkPos(locator.block.getPos())); } } else { BlockArgument locator = BlockArgument.findIn(cc, Collections.singletonList(first), 0); 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)); } } else { requestedChunks.add(from); } } ServerLevel world = cc.s.getLevel(); Value [] result = new Value[]{Value.NULL}; ((CarpetContext)c).s.getServer().executeBlocking( () -> { Map report = ((ThreadedAnvilChunkStorageInterface) world.getChunkSource().chunkMap).regenerateChunkRegion(requestedChunks); /*for (ChunkPos chpos: requestedChunks) // needed in 1.16 only { if (world.getChunk(chpos.x, chpos.z, ChunkStatus.FULL, false) != null) { WorldTools.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 result[0]; }); expression.addContextFunction("inhabited_time", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; BlockArgument locator = BlockArgument.findIn(cc, lv, 0); BlockPos pos = locator.block.getPos(); return new NumericValue(cc.s.getLevel().getChunk(pos).getInhabitedTime()); }); expression.addContextFunction("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)).getDouble(); NaturalSpawner.SpawnState charger = cc.s.getLevel().getChunkSource().getLastSpawnState(); if (charger == null) return Value.NULL; return new NumericValue( ((SpawnHelperInnerInterface)charger).getPotentialCalculator(). getPotentialEnergyChange(pos, required_charge ) ); }); expression.addContextFunction("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).getString(); TicketType 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)).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 == TicketType.PORTAL) // portal cc.s.getLevel().getChunkSource().addRegionTicket(TicketType.PORTAL, target, radius, pos); else if (ticket == TicketType.POST_TELEPORT) // post teleport cc.s.getLevel().getChunkSource().addRegionTicket(TicketType.POST_TELEPORT, target, radius, 1); else cc.s.getLevel().getChunkSource().addRegionTicket(TicketType.UNKNOWN, target, radius, target); return new NumericValue(ticket.timeout()); }); } @ScarpetFunction(maxParams = -1) public Value sample_noise(Context c, @Locator.Block BlockPos pos, String... noiseQueries) { int mappedX = QuartPos.fromBlock(pos.getX()); int mappedY = QuartPos.fromBlock(pos.getY()); int mappedZ = QuartPos.fromBlock(pos.getZ()); Climate.Sampler mns = ((CarpetContext) c).s.getLevel().getChunkSource().getGenerator().climateSampler(); Map ret = new HashMap<>(); if (noiseQueries.length == 0) { noiseQueries = new String[]{"continentalness", "erosion", "weirdness", "temperature", "humidity", "depth"}; } for (String noise : noiseQueries) { double noiseValue = ((NoiseColumnSamplerInterface) mns).getNoiseSample(noise, mappedX, mappedY, mappedZ); ret.put(new StringValue(noise), new NumericValue(noiseValue)); } return MapValue.wrap(ret); } }