diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index cede1808..64b07dfd 100644 --- a/docs/scarpet/api/Auxiliary.md +++ b/docs/scarpet/api/Auxiliary.md @@ -385,6 +385,83 @@ Returns `true` if the file was saved successfully, `false` otherwise. Uses the same file structure for exclusive app data, and shared data folder as `load_app_data`. +### `create_datapack(name, data)` + +Creates and loads custom datapack. The data has to be a map representing the file structure and the content of the +json files of the target pack. + +Returns `null` if the pack with this name already exists or is loaded, meaning no change has been made. +Returns `false` if adding of the datapack wasn't successful. +Returns `true` if creation and loading of the datapack was successful. Loading of a datapack results in +reloading of all other datapacks (vanilla restrictions, identical to /datapack enable), however unlike with `/reload` +command, scarpet apps will not be reloaded by adding a datapack using `create_datapack`. + +Currently, only json files are supported in the packs. `'pack.mcmeta'` file is added automatically. + +Reloading of datapacks that define new dimensions is not implemented in vanilla. Vanilla game only loads +dimension information on server start. `create_datapack` is therefore a direct replacement of manually ploping of the specified +file structure in a datapack file and calling `/datapack enable` on the new datapack with all its quirks and sideeffects +(like no worldgen changes, reloading all other datapacks, etc.). To enable newly added custom dimensions, call much more +experimental `check_hidden_dimensions()` after adding a datapack if needed. + +Synopsis: +
+script run create_datapack('foo', 
+{
+    'foo' -> {
+        'bar.json' -> {
+            'c' -> true,
+            'd' -> false,
+            'e' -> {'foo' -> [1,2,3]},
+            'a' -> 'foobar',
+            'b' -> 5
+        }
+    }
+})
+
+script run create_datapack('funky_world',  {
+    'data' -> {
+        'minecraft' -> {
+            'dimension' -> {
+                'custom_ow.json' -> { 
+                    'type' -> 'minecraft:the_end',
+                    'generator' -> {
+                        'biome_source' -> {
+                             'seed' -> 0,
+                             'large_biomes' -> false,
+                             'type' -> 'minecraft:vanilla_layered'
+                        },
+                        'seed' -> 0,
+                        'settings' -> 'minecraft:nether',
+                        'type' -> 'minecraft:noise'
+                    }
+                }
+            }
+        }
+    }
+});
+check_hidden_dimensions();  => ['funky_world']
+        
+
+ +### `enable_hidden_dimensions()` + +The function reads current datapack settings detecting new dimensions defined by these datapacks that have not yet been added +to the list of current dimensions and adds them so that they can be used and accessed right away. It doesn't matter how the +datapacks have been added to the game, either with 'create_datapack()' or manually by dropping a datapack file and calling +`/datapack enable` on it. Returns the list of valid dimension names / identifiers that has been added in the process. + +Fine print: The function should be +considered experimental. There 'should not be' (famous last words) any side-effects if no worlds are added. Already connected +clients will not see suggestions for commands that use dimensions `/execute in ` (vanilla client limitation) +but all commands should work just fine with +the new dimensions. Existing worlds that have gotten modified settings by the datapacks will not be reloaded or replaced. +The usability of the dimensions added this way has not been fully tested, but it seems it works just fine. Generator settings +for the new dimensions will not be added to `'level.dat'` but it will be added there automatically next time the game restarts by +vanilla. One could have said to use this method with caution, and the authors take no responsibility of any losses incurred due to +mis-handlilng of the temporary added dimensions, yet the feature itself (custom dimensions) is clearly experimental for Mojang +themselves, so that's about it. + ### `tick_time()` Returns server tick counter. Can be used to run certain operations every n-th ticks, or to count in-game time. @@ -502,6 +579,7 @@ Available options in the scarpet app space: Relevant world related properties * `world_name` - name of the world * `world_seed` - a numeric seed of the world + * `world_dimensions` - a list of dimensions in the world * `world_path` - full path to the world saves folder * `world_folder` - name of the direct folder in the saves that holds world files * `world_carpet_rules` - returns all Carpet rules in a map form (`rule`->`value`). Note that the values are always returned as strings, so you can't do boolean comparisons directly. Includes rules from extensions with their namespace (`namespace:rule`->`value`). You can later listen to rule changes with the `on_carpet_rule_changes(rule, newValue)` event. diff --git a/docs/scarpet/api/BlocksAndWorldAccess.md b/docs/scarpet/api/BlocksAndWorldAccess.md index 41d1ef8c..bb791fe0 100644 --- a/docs/scarpet/api/BlocksAndWorldAccess.md +++ b/docs/scarpet/api/BlocksAndWorldAccess.md @@ -779,7 +779,9 @@ All generated structures will retain their properties, like mob spawning, howeve itself has certain rules to spawn mobs, like plopping a nether fortress in the overworld will not spawn nether mobs, because nether mobs can spawn only in the nether, but plopped in the nether - will behave like a valid nether fortress. -### `custom_dimension(name, seed?)` +### (deprecated) `custom_dimension(name, seed?)` + +Deprecated by `create_datapack()` which can be used to setup custom dimensions Ensures the dimension with the given `'name'` is available and configured with the given seed. It merely sets the world generator settings to the overworld, and the optional custom seed (or using current world seed, if not provided). diff --git a/docs/scarpet/resources/editors/idea/2.txt b/docs/scarpet/resources/editors/idea/2.txt index 4186fa25..1c3251b3 100644 --- a/docs/scarpet/resources/editors/idea/2.txt +++ b/docs/scarpet/resources/editors/idea/2.txt @@ -17,9 +17,9 @@ brightness chunk_tickets convert_date crafting_remaining_item +create_datapack create_marker current_dimension -custom_dimension day_time delete_file destroy @@ -28,6 +28,7 @@ display_title draw_shape drop_item emitted_light +enable_hidden_dimensions encode_nbt entity_area entity_event diff --git a/docs/scarpet/resources/editors/npp/scarpet.xml b/docs/scarpet/resources/editors/npp/scarpet.xml index b7735dc6..5ae29a0f 100644 --- a/docs/scarpet/resources/editors/npp/scarpet.xml +++ b/docs/scarpet/resources/editors/npp/scarpet.xml @@ -55,7 +55,7 @@ set without_updates blocks_movement block_sound material map_colour block_state block_list block_tags block_data poi set_poi place_item set_biome biome loaded_status generation_status inhabited_time spawn_potential chunk_tickets - structure_eligibility structures structure_references set_structure reset_chunk reload_chunk custom_dimension + structure_eligibility structures structure_references set_structure reset_chunk reload_chunk create_datapack enable_hidden_dimensions add_chunk_ticket convert_date unix_time diff --git a/src/main/java/carpet/fakes/MinecraftServerInterface.java b/src/main/java/carpet/fakes/MinecraftServerInterface.java index 08fa24ae..a4422592 100644 --- a/src/main/java/carpet/fakes/MinecraftServerInterface.java +++ b/src/main/java/carpet/fakes/MinecraftServerInterface.java @@ -1,5 +1,6 @@ package carpet.fakes; +import net.minecraft.resource.ServerResourceManager; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.registry.RegistryKey; import net.minecraft.world.World; @@ -13,4 +14,5 @@ public interface MinecraftServerInterface void forceTick(BooleanSupplier sup); LevelStorage.Session getCMSession(); Map, ServerWorld> getCMWorlds(); + ServerResourceManager getResourceManager(); } diff --git a/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java b/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java index a4934a7f..1b71dfa9 100644 --- a/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java +++ b/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java @@ -2,6 +2,7 @@ package carpet.mixins; import carpet.fakes.MinecraftServerInterface; import carpet.helpers.TickSpeed; +import net.minecraft.resource.ServerResourceManager; import net.minecraft.server.MinecraftServer; import net.minecraft.server.ServerTask; import net.minecraft.server.world.ServerWorld; @@ -44,6 +45,8 @@ public abstract class MinecraftServer_scarpetMixin extends ReentrantThreadExecut @Shadow @Final private Map, ServerWorld> worlds; + @Shadow private ServerResourceManager serverResourceManager; + @Override public void forceTick(BooleanSupplier isAhead) { @@ -58,6 +61,11 @@ public abstract class MinecraftServer_scarpetMixin extends ReentrantThreadExecut return session; } + @Override + public ServerResourceManager getResourceManager() { + return serverResourceManager; + } + @Override public Map, ServerWorld> getCMWorlds() { diff --git a/src/main/java/carpet/mixins/PlayerManager_coreMixin.java b/src/main/java/carpet/mixins/PlayerManager_coreMixin.java index a272f7bd..de999990 100644 --- a/src/main/java/carpet/mixins/PlayerManager_coreMixin.java +++ b/src/main/java/carpet/mixins/PlayerManager_coreMixin.java @@ -25,10 +25,4 @@ public class PlayerManager_coreMixin CarpetServer.onPlayerLoggedIn(player); PLAYER_CONNECTS.onPlayerEvent(player); } - - @Inject(method = "onDataPacksReloaded", at = @At("HEAD")) - private void onResourcesRefreshedReloadCommand(CallbackInfo ci) - { - CarpetServer.onReload(server); - } } diff --git a/src/main/java/carpet/mixins/ReloadCommand_reloadAppsMixin.java b/src/main/java/carpet/mixins/ReloadCommand_reloadAppsMixin.java new file mode 100644 index 00000000..bf460603 --- /dev/null +++ b/src/main/java/carpet/mixins/ReloadCommand_reloadAppsMixin.java @@ -0,0 +1,23 @@ +package carpet.mixins; + +import carpet.CarpetServer; +import com.mojang.brigadier.context.CommandContext; +import net.minecraft.server.command.ReloadCommand; +import net.minecraft.server.command.ServerCommandSource; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ReloadCommand.class) +public class ReloadCommand_reloadAppsMixin { + //method_13530(Lcom/mojang/brigadier/context/CommandContext;)I + // internal of register. + @SuppressWarnings("UnresolvedMixinReference") + @Inject(method = "method_13530", at = @At("TAIL")) + private static void onReload(CommandContext context, CallbackInfoReturnable cir) + { + // can't fetch here the reference to the server + CarpetServer.onReload(context.getSource().getMinecraftServer()); + } +} diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java index 2fcfee47..653c80e4 100644 --- a/src/main/java/carpet/script/api/Auxiliary.java +++ b/src/main/java/carpet/script/api/Auxiliary.java @@ -20,6 +20,7 @@ import carpet.script.argument.Vector3Argument; import carpet.script.exception.ExitStatement; import carpet.script.exception.InternalExpressionException; import carpet.script.utils.FixedCommandSource; +import carpet.script.utils.InputValidator; import carpet.script.utils.ScarpetJsonDeserializer; import carpet.script.utils.ShapeDispatcher; import carpet.script.utils.WorldTools; @@ -35,6 +36,10 @@ import carpet.script.value.StringValue; import carpet.script.value.Value; import carpet.script.value.ValueConversions; import carpet.utils.Messenger; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import net.minecraft.SharedConstants; import net.minecraft.block.BlockState; import net.minecraft.command.DataCommandStorage; import net.minecraft.entity.Entity; @@ -45,6 +50,7 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtHelper; +import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.StringTag; import net.minecraft.nbt.Tag; import net.minecraft.network.Packet; @@ -57,6 +63,11 @@ import net.minecraft.network.packet.s2c.play.SubtitleS2CPacket; import net.minecraft.network.packet.s2c.play.TitleFadeS2CPacket; import net.minecraft.network.packet.s2c.play.TitleS2CPacket; import net.minecraft.particle.ParticleEffect; +import net.minecraft.resource.DataPackSettings; +import net.minecraft.resource.ReloadableResourceManagerImpl; +import net.minecraft.resource.ResourcePackManager; +import net.minecraft.resource.ResourcePackProfile; +import net.minecraft.resource.ServerResourceManager; import net.minecraft.server.MinecraftServer; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.network.ServerPlayerEntity; @@ -68,13 +79,27 @@ import net.minecraft.text.LiteralText; import net.minecraft.text.Text; import net.minecraft.util.Identifier; import net.minecraft.util.InvalidIdentifierException; +import net.minecraft.util.Util; +import net.minecraft.util.WorldSavePath; +import net.minecraft.util.dynamic.RegistryOps; 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.DynamicRegistryManager; import net.minecraft.util.registry.Registry; +import net.minecraft.util.registry.RegistryKey; +import net.minecraft.world.SaveProperties; import net.minecraft.world.World; +import net.minecraft.world.biome.source.BiomeAccess; +import net.minecraft.world.border.WorldBorderListener; +import net.minecraft.world.dimension.DimensionOptions; +import net.minecraft.world.dimension.DimensionType; +import net.minecraft.world.gen.GeneratorOptions; +import net.minecraft.world.gen.chunk.ChunkGenerator; +import net.minecraft.world.level.UnmodifiableLevelProperties; +import net.minecraft.world.level.storage.LevelStorage; +import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Triple; @@ -83,6 +108,13 @@ import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import com.google.gson.JsonParseException; +import java.io.BufferedWriter; +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -102,7 +134,7 @@ 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 final Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().registerTypeAdapter(Value.class, new ScarpetJsonDeserializer()).create(); + public static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().registerTypeAdapter(Value.class, new ScarpetJsonDeserializer()).create(); public static String recognizeResource(Value value, boolean isFloder) { @@ -862,7 +894,7 @@ public class Auxiliary { exception = e.getCause(); throw new InternalExpressionException("Failed to read JSON file: "+exception.getMessage()); } - Value parsedJson = gson.fromJson(json, Value.class); + Value parsedJson = GSON.fromJson(json, Value.class); if (parsedJson == null) retVal = Value.NULL; else @@ -901,7 +933,7 @@ public class Auxiliary { } else if (fdesc.getMiddle().equals("json")) { - List data = Collections.singletonList(gson.toJson(lv.get(2).evalValue(c).toJson())); + List data = Collections.singletonList(GSON.toJson(lv.get(2).evalValue(c).toJson())); ((CarpetScriptHost) c.host).removeResourceFile(fdesc.getLeft(), fdesc.getRight(), fdesc.getMiddle()); success = ((CarpetScriptHost) c.host).appendLogFile(fdesc.getLeft(), fdesc.getRight(), fdesc.getMiddle(), data); } @@ -1072,8 +1104,179 @@ public class Auxiliary { if (old_nbt == null) return LazyValue.NULL; return (_c, _t) -> new NBTSerializableValue(old_nbt); }); + + // script run create_datapack('foo', {'foo' -> {'bar.json' -> {'c' -> true,'d' -> false,'e' -> {'foo' -> [1,2,3]},'a' -> 'foobar','b' -> 5}}}) + expression.addLazyFunction("create_datapack", 2, (c, t, lv) -> { + CarpetContext cc = (CarpetContext)c; + String origName = lv.get(0).evalValue(c).getString(); + String name = InputValidator.validateSimpleString(origName, true); + MinecraftServer server = cc.s.getMinecraftServer(); + for (String dpName : server.getDataPackManager().getNames()) + { + if (dpName.equalsIgnoreCase("file/"+name+".zip") || + dpName.equalsIgnoreCase("file/"+name)) + return LazyValue.NULL; + + } + Value dpdata = lv.get(1).evalValue(c); + if (!(dpdata instanceof MapValue)) + throw new InternalExpressionException("datapack data needs to be a valid map type"); + ResourcePackManager packManager = server.getDataPackManager(); + Path dbFloder = server.getSavePath(WorldSavePath.DATAPACKS); + Path packFloder = dbFloder.resolve(name+".zip"); + if (Files.exists(packFloder) || Files.exists(dbFloder.resolve(name))) return LazyValue.NULL; + Boolean [] successful = new Boolean[]{true}; + server.submitAndJoin( () -> + { + try { + //Files.createDirectory(packFloder); + try (FileSystem zipfs = FileSystems.newFileSystem(URI.create("jar:" + packFloder.toUri().toString()), ImmutableMap.of("create", "true"))) { + Path zipRoot = zipfs.getPath("/"); + zipValueToJson(zipRoot.resolve("pack.mcmeta"), MapValue.wrap( + ImmutableMap.of(StringValue.of("pack"), MapValue.wrap(ImmutableMap.of( + StringValue.of("pack_format"), new NumericValue(SharedConstants.getGameVersion().getPackVersion()), + StringValue.of("description"), StringValue.of(name), + StringValue.of("source"), StringValue.of("scarpet") + ))) + )); + walkTheDPMap((MapValue) dpdata, zipRoot); + } + packManager.scanPacks(); + ResourcePackProfile resourcePackProfile = packManager.getProfile("file/" + name + ".zip"); + if (resourcePackProfile == null || packManager.getEnabledProfiles().contains(resourcePackProfile)) { + throw new IOException(); + } + List list = Lists.newArrayList(packManager.getEnabledProfiles()); + resourcePackProfile.getInitialPosition().insert(list, resourcePackProfile, p -> p, false); + + + server.reloadResources(list.stream().map(ResourcePackProfile::getName).collect(Collectors.toList())). + exceptionally(exc -> { + successful[0] = false; + return null; + }).join(); + if (!successful[0]) { + throw new IOException(); + } + } catch (IOException e) + { + successful[0] = false; + try { + FileUtils.forceDelete(packFloder.toFile()); + } catch (IOException ignored) { + throw new InternalExpressionException("Failed to install a datapack and failed to clean up after it"); + } + + } + }); + return successful[0]?LazyValue.TRUE:LazyValue.FALSE; + }); + + expression.addLazyFunction("enable_hidden_dimensions", 0, (c, t, lv) -> { + CarpetContext cc = (CarpetContext)c; + // from minecraft.server.Main.main + MinecraftServer server = cc.s.getMinecraftServer(); + LevelStorage.Session session = ((MinecraftServerInterface)server).getCMSession(); + DataPackSettings dataPackSettings = session.getDataPackSettings(); + ResourcePackManager resourcePackManager = server.getDataPackManager(); + DataPackSettings dataPackSettings2 = MinecraftServer.loadDataPacks(resourcePackManager, dataPackSettings == null ? DataPackSettings.SAFE_MODE : dataPackSettings, false); + ServerResourceManager serverRM = ((MinecraftServerInterface)server).getResourceManager(); + ReloadableResourceManagerImpl resourceManager = (ReloadableResourceManagerImpl) serverRM.getResourceManager(); + + //believe the other one will fillup based on the datapacks only. + resourceManager.close(); + resourcePackManager.createResourcePacks().forEach(resourceManager::addPack); + + //not sure its needed, but doesn't seem to have a negative effect and might be used in some custom shtuff + serverRM.loadRegistryTags(); + + RegistryOps registryOps = RegistryOps.of(NbtOps.INSTANCE, serverRM.getResourceManager(), (DynamicRegistryManager.Impl) server.getRegistryManager()); + SaveProperties saveProperties = session.readLevelProperties(registryOps, dataPackSettings2); + if (saveProperties == null) return LazyValue.NULL; + //session.backupLevelDataFile(server.getRegistryManager(), saveProperties); // no need + + // MinecraftServer.createWorlds + // save properties should now contain dimension settings + GeneratorOptions generatorOptions = saveProperties.getGeneratorOptions(); + boolean bl = generatorOptions.isDebugWorld(); + long l = generatorOptions.getSeed(); + long m = BiomeAccess.hashSeed(l); + Map, ServerWorld> existing_worlds = ((MinecraftServerInterface)server).getCMWorlds(); + List addeds = new ArrayList<>(); + for (Map.Entry, DimensionOptions> entry : generatorOptions.getDimensions().getEntries()) { + RegistryKey registryKey = entry.getKey(); + if (!existing_worlds.containsKey(registryKey)) + { + addeds.add(ValueConversions.of(registryKey.getValue())); + RegistryKey registryKey2 = RegistryKey.of(Registry.DIMENSION, registryKey.getValue()); + DimensionType dimensionType3 = entry.getValue().getDimensionType(); + ChunkGenerator chunkGenerator3 = entry.getValue().getChunkGenerator(); + UnmodifiableLevelProperties unmodifiableLevelProperties = new UnmodifiableLevelProperties(saveProperties, saveProperties.getMainWorldProperties()); + ServerWorld serverWorld2 = new ServerWorld(server, Util.getMainWorkerExecutor(), session, unmodifiableLevelProperties, registryKey2, dimensionType3, WorldTools.NOOP_LISTENER, chunkGenerator3, bl, m, ImmutableList.of(), false); + server.getOverworld().getWorldBorder().addListener(new WorldBorderListener.WorldBorderSyncer(serverWorld2.getWorldBorder())); + existing_worlds.put(registryKey2, serverWorld2); + } + } + Value ret = ListValue.wrap(addeds); + return (_c, _t) -> ret; + }); } + private static void zipValueToJson(Path path, Value output) throws IOException + { + JsonElement element = output.toJson(); + if (element == null) + throw new InternalExpressionException("Cannot interpret "+output.getPrettyString()+" as a json object"); + String string = GSON.toJson(element); + Files.createDirectories(path.getParent()); + BufferedWriter bufferedWriter = Files.newBufferedWriter(path); + Throwable incident = null; + try + { + bufferedWriter.write(string); + } + catch (Throwable shitHappened) + { + incident = shitHappened; + throw shitHappened; + } + finally + { + if (incident != null) { + try { + bufferedWriter.close(); + } catch (Throwable otherShitHappened) { + incident.addSuppressed(otherShitHappened); + } + } else { + bufferedWriter.close(); + } + } + } + + private static void walkTheDPMap(MapValue node, Path path) throws IOException + { + Map items = node.getMap(); + for (Map.Entry entry : items.entrySet()) + { + Value val = entry.getValue(); + String strkey = entry.getKey().getString(); + Path child = path.resolve(strkey); + if (strkey.endsWith(".json")) + { + zipValueToJson(child, val); + } + else + { + if (!(val instanceof MapValue)) throw new InternalExpressionException("Value of "+strkey+" should be a map"); + Files.createDirectory(child); + walkTheDPMap((MapValue) val, child); + } + } + } + + + private static Stat getStat(StatType type, Identifier id) { T key = type.getRegistry().get(id); diff --git a/src/main/java/carpet/script/api/Inventories.java b/src/main/java/carpet/script/api/Inventories.java index 6cdb86ec..313ea9f9 100644 --- a/src/main/java/carpet/script/api/Inventories.java +++ b/src/main/java/carpet/script/api/Inventories.java @@ -91,7 +91,7 @@ public class Inventories { Item item = NBTSerializableValue.parseItem(lv.get(0).evalValue(c).getString()).getItem(); if (lv.size() == 1) { - Value ret = ListValue.wrap(tagManager.getOrCreateTagGroup(Registry.ITEM_KEY).getTagsFor(item).stream().map(ValueConversions::of).collect(Collectors.toList())); + Value ret = ListValue.wrap(tagManager.getOrCreateTagGroup(Registry.ITEM_KEY).getTags().entrySet().stream().filter(e -> e.getValue().contains(item)).map(e -> ValueConversions.of(e.getKey())).collect(Collectors.toList())); return (_c, _t) -> ret; } String tag = lv.get(1).evalValue(c).getString(); diff --git a/src/main/java/carpet/script/api/WorldAccess.java b/src/main/java/carpet/script/api/WorldAccess.java index e190bb3a..eeb9a080 100644 --- a/src/main/java/carpet/script/api/WorldAccess.java +++ b/src/main/java/carpet/script/api/WorldAccess.java @@ -1127,7 +1127,8 @@ public class WorldAccess { BlockArgument blockLocator = BlockArgument.findIn(cc, lv, 0, true); if (blockLocator.offset == lv.size()) { - Value ret = ListValue.wrap(tagManager.getOrCreateTagGroup(Registry.BLOCK_KEY).getTagsFor(blockLocator.block.getBlockState().getBlock()).stream().map(ValueConversions::of).collect(Collectors.toList())); + Block target = blockLocator.block.getBlockState().getBlock(); + Value ret = ListValue.wrap(tagManager.getOrCreateTagGroup(Registry.BLOCK_KEY).getTags().entrySet().stream().filter(e -> e.getValue().contains(target)).map(e -> ValueConversions.of(e.getKey())).collect(Collectors.toList())); return (_c, _t) -> ret; } String tag = lv.get(blockLocator.offset).evalValue(c).getString(); @@ -1382,6 +1383,7 @@ public class WorldAccess { { 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).evalValue(c).getString(); Long seed = null; diff --git a/src/main/java/carpet/script/utils/InputValidator.java b/src/main/java/carpet/script/utils/InputValidator.java new file mode 100644 index 00000000..67405ab3 --- /dev/null +++ b/src/main/java/carpet/script/utils/InputValidator.java @@ -0,0 +1,16 @@ +package carpet.script.utils; + +import carpet.script.exception.InternalExpressionException; + +import java.util.Locale; + +public class InputValidator { + public static String validateSimpleString(String input, boolean strict) + { + String simplified = input.toLowerCase(Locale.ROOT).replaceAll("[^A-Za-z0-9+_]", ""); + if (simplified.isEmpty() || (strict && !simplified.equals(input))) + throw new InternalExpressionException("simple name can only contain numbers, letter and _"); + return simplified; + } + +} diff --git a/src/main/java/carpet/script/utils/SystemInfo.java b/src/main/java/carpet/script/utils/SystemInfo.java index 230fbac5..79dd6b1a 100644 --- a/src/main/java/carpet/script/utils/SystemInfo.java +++ b/src/main/java/carpet/script/utils/SystemInfo.java @@ -9,6 +9,7 @@ import carpet.script.value.MapValue; import carpet.script.value.NumericValue; import carpet.script.value.StringValue; import carpet.script.value.Value; +import carpet.script.value.ValueConversions; import carpet.settings.ParsedRule; import carpet.settings.SettingsManager; import com.sun.management.OperatingSystemMXBean; @@ -49,6 +50,7 @@ public class SystemInfo { String tlf = serverPath.getName(nodeCount-2).toString(); return StringValue.of(tlf); }); + put("world_dimensions", c -> ListValue.wrap(c.s.getMinecraftServer().getWorldRegistryKeys().stream().map(k -> ValueConversions.of(k.getValue())).collect(Collectors.toList()))); put("game_difficulty", c -> StringValue.of(c.s.getMinecraftServer().getSaveProperties().getDifficulty().getName())); put("game_hardcore", c -> new NumericValue(c.s.getMinecraftServer().getSaveProperties().isHardcore())); put("game_storage_format", c -> StringValue.of(c.s.getMinecraftServer().getSaveProperties().getFormatName(c.s.getMinecraftServer().getSaveProperties().getVersion()))); diff --git a/src/main/java/carpet/script/utils/WorldTools.java b/src/main/java/carpet/script/utils/WorldTools.java index ca3c3354..4ae4559b 100644 --- a/src/main/java/carpet/script/utils/WorldTools.java +++ b/src/main/java/carpet/script/utils/WorldTools.java @@ -157,5 +157,5 @@ public class WorldTools @Override public void stop() { } } - private static final WorldGenerationProgressListener NOOP_LISTENER = new NoopWorldGenerationProgressListener(); + public static final WorldGenerationProgressListener NOOP_LISTENER = new NoopWorldGenerationProgressListener(); } diff --git a/src/main/java/carpet/script/value/EntityValue.java b/src/main/java/carpet/script/value/EntityValue.java index fd26fdaa..89dd8ca4 100644 --- a/src/main/java/carpet/script/value/EntityValue.java +++ b/src/main/java/carpet/script/value/EntityValue.java @@ -444,7 +444,7 @@ public class EntityValue extends Value put("tags", (e, a) -> ListValue.wrap(e.getScoreboardTags().stream().map(StringValue::new).collect(Collectors.toList()))); put("scoreboard_tags", (e, a) -> ListValue.wrap(e.getScoreboardTags().stream().map(StringValue::new).collect(Collectors.toList()))); - put("entity_tags", (e, a) -> ListValue.wrap(e.getServer().getTagManager().getOrCreateTagGroup(Registry.ENTITY_TYPE_KEY).getTagsFor(e.getType()).stream().map(ValueConversions::of).collect(Collectors.toList()))); + put("entity_tags", (e, a) -> ListValue.wrap(e.getServer().getTagManager().getOrCreateTagGroup(Registry.ENTITY_TYPE_KEY).getTags().entrySet().stream().filter(entry -> entry.getValue().contains(e.getType())).map(entry -> ValueConversions.of(entry.getKey())).collect(Collectors.toList()))); // deprecated put("has_tag", (e, a) -> new NumericValue(e.getScoreboardTags().contains(a.getString()))); diff --git a/src/main/java/carpet/script/value/MapValue.java b/src/main/java/carpet/script/value/MapValue.java index 81375bdc..10d11f24 100644 --- a/src/main/java/carpet/script/value/MapValue.java +++ b/src/main/java/carpet/script/value/MapValue.java @@ -7,6 +7,7 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -16,7 +17,7 @@ import java.util.stream.Collectors; public class MapValue extends AbstractListValue implements ContainerValueInterface { - private Map map; + private final Map map; private MapValue() { @@ -268,7 +269,9 @@ public class MapValue extends AbstractListValue implements ContainerValueInterfa public JsonElement toJson() { JsonObject jsonMap = new JsonObject(); - map.forEach((k, v) -> jsonMap.add(k.getString(), v.toJson())); + List keys = new ArrayList<>(map.keySet()); + Collections.sort(keys); + keys.forEach( k -> jsonMap.add(k.getString(), map.get(k).toJson())); return jsonMap; } } diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json index 8298432d..7e4e2089 100644 --- a/src/main/resources/carpet.mixins.json +++ b/src/main/resources/carpet.mixins.json @@ -178,6 +178,7 @@ "PointOfInterest_scarpetMixin", "ServerWorld_scarpetMixin", "HungerManagerMixin_scarpetEventsMixin", + "ReloadCommand_reloadAppsMixin", "RecipeManager_scarpetMixin", "Ingredient_scarpetMixin",